Conversation
Both providers shipped a near-identical ~200-line app-log poller with the same acquisition rollback, polling state machine, tail-overlap algorithm, finish memoization and disposal mechanics. Extract the shared lifecycle into capture-kit (ADR 0019 §5) behind a narrow reader input, leaving each provider a thin adapter that supplies backend identity and its own cleanup wording. Cancellation is characterized, not unified. Doublespeed's reader forwards the poller's abort signal; Limrun's WebSocket-backed read stays uncancelable. The shared bounded read forwards the signal and always settles on its own timeout, so neither provider's guarantee changes and abort never implies the request ended. Provider poller source drops 395 -> 276 lines; the tail algorithm and lifecycle now live once. No provider name leaks into shared policy. Issue: #2616 (stacked on #2112)
Size Report
npm unpacked components
Startup median (7 runs, lower is better):
Top changed chunks: no changes in the largest emitted chunks. Top changed packed files
|
|
Reviewed at e5f4096. This is already the smaller design: two ~200-line copies become one shared module and two ~20-line adapters, for -37 net production lines, and the extraction matches both providers by code comparison. One question: could the Limrun adapter use Compatibility & Provenance fails in the daemon wire-compat gate on files the #2112 base stack touches ( Next: #2112 lands, then this retargets to |
|
Answering the review at e5f4096 and the CI status.
CI —
I checked the two ways to green it now and both mean doing #2112's own rebase (merge Net: everything else is green on this head (Lint, Typecheck & Package, Repo Guards, Coverage, Integration, all Smoke, Bundle). This matches the plan in your note — retarget to The two follow-ups ( |
|
Thanks, that answers the question. Compatibility & Provenance still fails on wire files from the #2112 base, which this diff does not touch. Next: #2112 lands, then this retargets to |
Summary
Extracts the shared bounded provider app-log poller into
@agent-device/capture-kit(issue #2616), so Limrun and Doublespeed stop maintaining two ~200-line copies of the same lifecycle.Both
provider-limrun/src/app-log-poller.tsandprovider-doublespeed/src/app-log-poller.tscarried the identical acquisition rollback, polling state machine (active/recovering/failed/ended), tail-overlap (KMP suffix/prefix) algorithm, finish memoization, and disposal mechanics. They now share one implementation incapture-kit/src/app-log-polling.tsbehind a narrow input — a disposable reader (readLogs(appBundleId, lineLimit, signal?)+ async dispose), backend identity, app bundle id, output path, the host clock/output ports, and the cleanup-failure message. Each provider keeps only an adapter.No user/API/CLI behavior changes. Polling interval (1000 ms), read timeout (5000 ms), line limit (1000), tail budget (256 KiB), the
[agent-device][mark]filter, backend selection, append/newline behavior, and every user-visible error string are preserved.Base / dependency (read first)
Stacked on #2112.
provider-doublespeedexists only on #2112's branch (an external-fork PR, currently conflicting withmain), and #2616 is explicitly blocked by it. #2112's base lives in a sibling fork that the network parent cannot head a PR into, so this PR's base isstack/2616-on-2112— a mirror of #2112's head commit (8dad650) pushed here purely to keep the review diff isolated to this change. Retarget tomainonce #2112 merges; the Doublespeed hunks are then pure edits, not additions. Do not merge towardmainindependently.Kept provider-specific (deliberate, characterized — not unified)
ios-simulator/androidfromreader.platform; Doublespeed is the constantios-simulator. Both flow through the sharedbackendinput; shared policy names no provider.AbortSignal; Limrun's WebSocket-backed reader takes no signal and cannot cancel. The shared bounded read forwards the signal and always settles on its own timeout (settleOnAbort), so neither guarantee changes and abort never implies the provider request ended. Limrun's priorawaitLimrunOperation(...)(called with no drain callback) is behaviorally identical to the sharedsettleOnAborton every reachable path.Validation
vitest runfor the shared + both provider poller suites: 19 green. Common tests moved with their code; added characterizations: streaming tail dedup across successful reads,endedstate, empty-delta no-write, trailing-newline normalization, disposal order (reader → output), thefinally-abort of the read timer, timeout with a signal-ignoring reader (would hang without the settle wrap), a signal-honoring reader rejecting on abort, finish-during-in-flight (stop before write), late rejection, repeated finish, and reader/output disposal failure.pnpm check:layering,fallow audit(unused types: 0),tsc -bfor the three packages, andeager-closure-budgets(302) all pass.settleOnAbort,previous = read.text, theif (stopped) returnshort-circuit, the adopted-flag, and thefinallyabort each fail a named assertion).capture-kit/index32→33 andsession-teardown60→61: the shared poller is one new façade module, consistent with the sibling durable-capture exports.provider-integration/Coverage/Apple-device lanes are GitHub-authoritative and were not run against a live Doublespeed/Limrun session locally. The previously-seen android-snapshot / daemon-entrypoint / Apple provider-integration failures pass in isolation and are host contention.Tradeoffs / follow-ups
disposeAllduplicates a cleanup-settlement pattern already inapp-log-pid-process.ts;MARK_PREFIXstill duplicates the daemon writer's marker literal across a package boundary.Size: 8 files touched (2 new shared files, 2 adapters, 3 tests, 1 budget pin). Scope stayed within the app-log capture path. Docs/skills unchanged (no CLI/help surface change).