otacon 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/dist/cli/browser.js +1 -1
- package/dist/cli/browser.js.map +1 -1
- package/dist/cli/client.js +3 -3
- package/dist/cli/client.js.map +1 -1
- package/dist/cli/commands/answer.js +1 -1
- package/dist/cli/commands/answer.js.map +1 -1
- package/dist/cli/commands/ask.js +3 -3
- package/dist/cli/commands/ask.js.map +1 -1
- package/dist/cli/commands/clean.js +14 -16
- package/dist/cli/commands/clean.js.map +1 -1
- package/dist/cli/commands/config.js +1 -1
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/doctor.js +4 -4
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/expose.js +7 -7
- package/dist/cli/commands/expose.js.map +1 -1
- package/dist/cli/commands/implement-done.js +1 -1
- package/dist/cli/commands/implement-done.js.map +1 -1
- package/dist/cli/commands/install.js +16 -10
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/open.js +31 -7
- package/dist/cli/commands/open.js.map +1 -1
- package/dist/cli/commands/progress.js +1 -1
- package/dist/cli/commands/progress.js.map +1 -1
- package/dist/cli/commands/resume.js +67 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/start.js +18 -4
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.js +15 -4
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/submit.js +5 -5
- package/dist/cli/commands/submit.js.map +1 -1
- package/dist/cli/commands/update.js +24 -17
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/wait.js +1 -1
- package/dist/cli/commands/wait.js.map +1 -1
- package/dist/cli/install/assets.js +123 -48
- package/dist/cli/install/assets.js.map +1 -1
- package/dist/cli/install/locations.js +1 -1
- package/dist/cli/install/locations.js.map +1 -1
- package/dist/cli/install/tailscale.js +2 -2
- package/dist/cli/install/tailscale.js.map +1 -1
- package/dist/cli/install/wrapper.js +202 -0
- package/dist/cli/install/wrapper.js.map +1 -0
- package/dist/cli/main.js +5 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output.js +2 -2
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/session.js +18 -7
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/update.js +79 -38
- package/dist/cli/update.js.map +1 -1
- package/dist/daemon/activity.js +1 -1
- package/dist/daemon/activity.js.map +1 -1
- package/dist/daemon/anchor.js +1 -1
- package/dist/daemon/anchor.js.map +1 -1
- package/dist/daemon/app.js +370 -82
- package/dist/daemon/app.js.map +1 -1
- package/dist/daemon/approve.js +3 -3
- package/dist/daemon/approve.js.map +1 -1
- package/dist/daemon/capture/adapter.js +23 -0
- package/dist/daemon/capture/adapter.js.map +1 -0
- package/dist/daemon/capture/claude.js +274 -0
- package/dist/daemon/capture/claude.js.map +1 -0
- package/dist/daemon/capture/codex.js +413 -0
- package/dist/daemon/capture/codex.js.map +1 -0
- package/dist/daemon/capture/normalize.js +89 -0
- package/dist/daemon/capture/normalize.js.map +1 -0
- package/dist/daemon/capture/opencode.js +330 -0
- package/dist/daemon/capture/opencode.js.map +1 -0
- package/dist/daemon/capture/registry.js +33 -0
- package/dist/daemon/capture/registry.js.map +1 -0
- package/dist/daemon/capture/stream-store.js +121 -0
- package/dist/daemon/capture/stream-store.js.map +1 -0
- package/dist/daemon/capture/tailer.js +108 -0
- package/dist/daemon/capture/tailer.js.map +1 -0
- package/dist/daemon/desktop-notify.js +11 -5
- package/dist/daemon/desktop-notify.js.map +1 -1
- package/dist/daemon/diagrams.js +90 -0
- package/dist/daemon/diagrams.js.map +1 -0
- package/dist/daemon/diff.js +1 -2
- package/dist/daemon/diff.js.map +1 -1
- package/dist/daemon/linter/parse.js +45 -12
- package/dist/daemon/linter/parse.js.map +1 -1
- package/dist/daemon/linter/rules.js +20 -17
- package/dist/daemon/linter/rules.js.map +1 -1
- package/dist/daemon/main.js +1 -1
- package/dist/daemon/main.js.map +1 -1
- package/dist/daemon/notify.js +1 -1
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/presence.js +1 -1
- package/dist/daemon/presence.js.map +1 -1
- package/dist/daemon/queue.js +7 -7
- package/dist/daemon/queue.js.map +1 -1
- package/dist/daemon/store.js +66 -79
- package/dist/daemon/store.js.map +1 -1
- package/dist/daemon/threads.js +111 -25
- package/dist/daemon/threads.js.map +1 -1
- package/dist/daemon/transcript.js +1 -1
- package/dist/daemon/transcript.js.map +1 -1
- package/dist/daemon/ui.js +8 -6
- package/dist/daemon/ui.js.map +1 -1
- package/dist/daemon/viewers.js +37 -0
- package/dist/daemon/viewers.js.map +1 -0
- package/dist/shared/config.js +42 -8
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/gwt.js +1 -1
- package/dist/shared/gwt.js.map +1 -1
- package/dist/shared/paths.js +63 -36
- package/dist/shared/paths.js.map +1 -1
- package/dist/shared/question-spec.js +1 -1
- package/dist/shared/question-spec.js.map +1 -1
- package/dist/shared/types.js +7 -3
- package/dist/shared/types.js.map +1 -1
- package/dist/shared/version.js +1 -1
- package/dist/skills/otacon/SKILL.md +250 -0
- package/dist/ui/assets/{arc-Cp3sPd_U.js → arc-BUR2DxNA.js} +1 -1
- package/dist/ui/assets/architecture-7EHR7CIX-TTokq2IO.js +1 -0
- package/dist/ui/assets/{architectureDiagram-3BPJPVTR-C4F3heWP.js → architectureDiagram-3BPJPVTR-unLnkDyM.js} +1 -1
- package/dist/ui/assets/{blockDiagram-GPEHLZMM-DmJoG8ky.js → blockDiagram-GPEHLZMM-DHx8lNeL.js} +1 -1
- package/dist/ui/assets/{c4Diagram-AAUBKEIU-BDEf52Jp.js → c4Diagram-AAUBKEIU-BU9T562l.js} +1 -1
- package/dist/ui/assets/channel-BA6ChrT3.js +1 -0
- package/dist/ui/assets/{chunk-2J33WTMH-Dh_UFriv.js → chunk-2J33WTMH-BEb0myVl.js} +1 -1
- package/dist/ui/assets/{chunk-3OPIFGDE-c66RlAN8.js → chunk-3OPIFGDE-DESBG_RB.js} +1 -1
- package/dist/ui/assets/{chunk-4BX2VUAB-CrGJaCCg.js → chunk-4BX2VUAB-dt3F_E_5.js} +1 -1
- package/dist/ui/assets/{chunk-55IACEB6-BvwqEeyq.js → chunk-55IACEB6-BcyuZM7U.js} +1 -1
- package/dist/ui/assets/{chunk-5ZQYHXKU-BfF2IfQe.js → chunk-5ZQYHXKU-DqdwSJlO.js} +1 -1
- package/dist/ui/assets/{chunk-727SXJPM-DOEUwc9I.js → chunk-727SXJPM-B04SqNWj.js} +1 -1
- package/dist/ui/assets/{chunk-AQP2D5EJ-CVf2xV2Z.js → chunk-AQP2D5EJ-DpjCPBWN.js} +1 -1
- package/dist/ui/assets/{chunk-BSJP7CBP-D_EbTWTC.js → chunk-BSJP7CBP-BNOU3k5G.js} +1 -1
- package/dist/ui/assets/{chunk-CSCIHK7Q-CkxTSMAM.js → chunk-CSCIHK7Q-iWOtNZm_.js} +1 -1
- package/dist/ui/assets/{chunk-FMBD7UC4-DVShhFc7.js → chunk-FMBD7UC4-BHH_etky.js} +1 -1
- package/dist/ui/assets/{chunk-KSCS5N6A-DnHEEYpq.js → chunk-KSCS5N6A-DbRuazP3.js} +1 -1
- package/dist/ui/assets/{chunk-L5ZTLDWV-B_gjyajS.js → chunk-L5ZTLDWV-B2ZZFONi.js} +1 -1
- package/dist/ui/assets/{chunk-LZXEDZCA-Bsf7basG.js → chunk-LZXEDZCA-DryVpwAh.js} +2 -2
- package/dist/ui/assets/{chunk-ND2GUHAM-DGepGb8_.js → chunk-ND2GUHAM-2Bb1izqg.js} +1 -1
- package/dist/ui/assets/{chunk-NZK2D7GU-BJIlh-gB.js → chunk-NZK2D7GU-2DhvLbqL.js} +1 -1
- package/dist/ui/assets/{chunk-O5CBEL6O-cdVbOOCs.js → chunk-O5CBEL6O-B5oigO7D.js} +1 -1
- package/dist/ui/assets/chunk-QZHKN3VN-BDHgdxoT.js +1 -0
- package/dist/ui/assets/chunk-WU5MYG2G-FDJTP_wT.js +1 -0
- package/dist/ui/assets/{chunk-XPW4576I-DPW39hNO.js → chunk-XPW4576I-Dmq-O7bc.js} +1 -1
- package/dist/ui/assets/classDiagram-4FO5ZUOK-B5kZsiIt.js +1 -0
- package/dist/ui/assets/classDiagram-v2-Q7XG4LA2-B5kZsiIt.js +1 -0
- package/dist/ui/assets/{cose-bilkent-S5V4N54A-DmMsC6om.js → cose-bilkent-S5V4N54A-oTsqU1DY.js} +1 -1
- package/dist/ui/assets/{dagre-BM42HDAG-BmVr7T7Z.js → dagre-BM42HDAG-CZykCU6B.js} +1 -1
- package/dist/ui/assets/{diagram-2AECGRRQ-e0VnXEyj.js → diagram-2AECGRRQ-BY7clIlO.js} +1 -1
- package/dist/ui/assets/{diagram-5GNKFQAL-SCJGYE6m.js → diagram-5GNKFQAL-BWdq4hV0.js} +1 -1
- package/dist/ui/assets/{diagram-KO2AKTUF-DDUZO1Mj.js → diagram-KO2AKTUF-CCGEaiZg.js} +1 -1
- package/dist/ui/assets/{diagram-LMA3HP47-5x1ypf6a.js → diagram-LMA3HP47-CVkepFYU.js} +1 -1
- package/dist/ui/assets/{diagram-OG6HWLK6-3nDhrQ20.js → diagram-OG6HWLK6-f-NjsfLG.js} +1 -1
- package/dist/ui/assets/{dist-NEinnePC.js → dist-ZqsueX9_.js} +1 -1
- package/dist/ui/assets/{erDiagram-TEJ5UH35-BNvwSDNZ.js → erDiagram-TEJ5UH35-D8Wn6QP3.js} +1 -1
- package/dist/ui/assets/eventmodeling-FCH6USID-BODDoY6e.js +1 -0
- package/dist/ui/assets/{flowDiagram-I6XJVG4X-Bt_wDUhb.js → flowDiagram-I6XJVG4X-BYnhla9k.js} +1 -1
- package/dist/ui/assets/{ganttDiagram-6RSMTGT7-DVcJ0rNX.js → ganttDiagram-6RSMTGT7-Dpu52ZRF.js} +1 -1
- package/dist/ui/assets/{gitGraph-WXDBUCRP-CO_SyAgP.js → gitGraph-WXDBUCRP-ou8xzQe1.js} +1 -1
- package/dist/ui/assets/{gitGraphDiagram-PVQCEYII-oDyO3lWI.js → gitGraphDiagram-PVQCEYII-BnTuFH7F.js} +1 -1
- package/dist/ui/assets/index-B2mL0c61.js +11 -0
- package/dist/ui/assets/index-sZ1TgAvb.css +1 -0
- package/dist/ui/assets/{info-J43DQDTF-Bn17NS7h.js → info-J43DQDTF-xgUHa7k0.js} +1 -1
- package/dist/ui/assets/{infoDiagram-5YYISTIA-HG1opLLT.js → infoDiagram-5YYISTIA-DJBudrwD.js} +1 -1
- package/dist/ui/assets/{ishikawaDiagram-YF4QCWOH-Di_yQwi8.js → ishikawaDiagram-YF4QCWOH-DpFHgERI.js} +1 -1
- package/dist/ui/assets/{journeyDiagram-JHISSGLW-DZtHvLeE.js → journeyDiagram-JHISSGLW-Cd32hdpY.js} +1 -1
- package/dist/ui/assets/{kanban-definition-UN3LZRKU-B_RCx3Km.js → kanban-definition-UN3LZRKU-Dm1V8lr0.js} +1 -1
- package/dist/ui/assets/{line-DenX-zXQ.js → line-CZu6_PMX.js} +1 -1
- package/dist/ui/assets/{linear-dly_ngoq.js → linear-2tkTX_U2.js} +1 -1
- package/dist/ui/assets/{mermaid-parser.core-CRmtm0s9.js → mermaid-parser.core-C4m04cRe.js} +2 -2
- package/dist/ui/assets/{mermaid.core-C_3KVfpx.js → mermaid.core-BPeg1ewg.js} +3 -3
- package/dist/ui/assets/{mindmap-definition-RKZ34NQL-wdzSyYO6.js → mindmap-definition-RKZ34NQL-BENqEkIK.js} +1 -1
- package/dist/ui/assets/{packet-YPE3B663-tUyFmR11.js → packet-YPE3B663-DNO0oBLH.js} +1 -1
- package/dist/ui/assets/{pie-LRSECV5Y-Cel48VVp.js → pie-LRSECV5Y-CWoz31oB.js} +1 -1
- package/dist/ui/assets/{pieDiagram-4H26LBE5-B55ypWtu.js → pieDiagram-4H26LBE5-CtmxhUGI.js} +1 -1
- package/dist/ui/assets/{plan-view-CMoo3_gE.js → plan-view-bZtdFbit.js} +4 -4
- package/dist/ui/assets/{quadrantDiagram-W4KKPZXB-9WcKQV0a.js → quadrantDiagram-W4KKPZXB-BO3IZNbx.js} +1 -1
- package/dist/ui/assets/{radar-GUYGQ44K-w5pk53Vr.js → radar-GUYGQ44K-AEfROz99.js} +1 -1
- package/dist/ui/assets/{requirementDiagram-4Y6WPE33-CzE82fXz.js → requirementDiagram-4Y6WPE33-D-wQ1szT.js} +1 -1
- package/dist/ui/assets/{sankeyDiagram-5OEKKPKP-DSGO39je.js → sankeyDiagram-5OEKKPKP-Bj4kD_AJ.js} +1 -1
- package/dist/ui/assets/{sequenceDiagram-3UESZ5HK-BKSDDr0S.js → sequenceDiagram-3UESZ5HK-M8QVVH74.js} +1 -1
- package/dist/ui/assets/{src-JXBGgRt-.js → src-DvptJAGq.js} +1 -1
- package/dist/ui/assets/{stateDiagram-AJRCARHV-DZHYA9aj.js → stateDiagram-AJRCARHV-BiPRs9rN.js} +1 -1
- package/dist/ui/assets/stateDiagram-v2-BHNVJYJU-BYjicZlC.js +1 -0
- package/dist/ui/assets/{timeline-definition-PNZ67QCA-WqJFw7aE.js → timeline-definition-PNZ67QCA-DS0AGRKw.js} +1 -1
- package/dist/ui/assets/{treeView-BLDUP644-DoIQYMiz.js → treeView-BLDUP644-DJakvUbF.js} +1 -1
- package/dist/ui/assets/{treemap-LRROVOQU-BA9si_Mo.js → treemap-LRROVOQU-QXfc2Vwr.js} +1 -1
- package/dist/ui/assets/{vennDiagram-CIIHVFJN-BvWRUfFr.js → vennDiagram-CIIHVFJN-CBdJA0hv.js} +1 -1
- package/dist/ui/assets/{wardley-L42UT6IY-Cf9PU44t.js → wardley-L42UT6IY-DI9SdW45.js} +1 -1
- package/dist/ui/assets/{wardleyDiagram-YWT4CUSO-CGuxl7AU.js → wardleyDiagram-YWT4CUSO-RMmsKLRe.js} +1 -1
- package/dist/ui/assets/{xychartDiagram-2RQKCTM6-CNteNXpe.js → xychartDiagram-2RQKCTM6-DFXccP8B.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +7 -5
- package/dist/ui/assets/architecture-7EHR7CIX-BbwstElO.js +0 -1
- package/dist/ui/assets/channel-BMK3JFRf.js +0 -1
- package/dist/ui/assets/chunk-QZHKN3VN-DLNUTn7U.js +0 -1
- package/dist/ui/assets/chunk-WU5MYG2G-ipH3YPcA.js +0 -1
- package/dist/ui/assets/classDiagram-4FO5ZUOK-CvIMLlSD.js +0 -1
- package/dist/ui/assets/classDiagram-v2-Q7XG4LA2-CvIMLlSD.js +0 -1
- package/dist/ui/assets/eventmodeling-FCH6USID-BeFWDbla.js +0 -1
- package/dist/ui/assets/index-CJJIQ0dr.css +0 -1
- package/dist/ui/assets/index-Dh5CsH24.js +0 -11
- package/dist/ui/assets/stateDiagram-v2-BHNVJYJU-DnAfDlvZ.js +0 -1
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// The OpenCode transcript adapter. Unlike Claude/Codex — each one JSONL file —
|
|
2
|
+
// OpenCode persists its sessions in a local SQLite database
|
|
3
|
+
// (`$XDG_DATA_HOME/opencode/opencode.db`, default `~/.local/share/opencode/`),
|
|
4
|
+
// with one row per session/message/part. We read that DB *read-only* with Node's
|
|
5
|
+
// built-in `node:sqlite` (zero new npm deps), which keeps the same file-based,
|
|
6
|
+
// no-extra-process model as the other adapters — no `opencode serve` to spawn.
|
|
7
|
+
//
|
|
8
|
+
// Schema we rely on (confirmed against a real install, storage migration 2):
|
|
9
|
+
// - `session(id, directory, time_created, time_updated, …)` — `directory` is
|
|
10
|
+
// the session's recorded cwd; that is the authoritative repo key.
|
|
11
|
+
// - `message(id, session_id, time_created, data)` — `data` JSON has a `role`.
|
|
12
|
+
// - `part(id, message_id, session_id, time_created, time_updated, data)` —
|
|
13
|
+
// `data` JSON's `type` is one of `text` / `reasoning` / `tool` / … We map
|
|
14
|
+
// `text` → text, `reasoning` → thinking, and `tool` → a `running` event plus,
|
|
15
|
+
// once its `state.status` settles, a SEPARATE `ok`/`error` outcome event.
|
|
16
|
+
//
|
|
17
|
+
// Because the source is a DB (rows, not a byte stream), the `Cursor`'s `offset`
|
|
18
|
+
// is unused — incrementality lives in the opaque carry: a high-water
|
|
19
|
+
// `time_created` watermark plus the set of part ids already emitted *at exactly*
|
|
20
|
+
// that watermark (so a same-millisecond insert is neither re-emitted nor
|
|
21
|
+
// dropped). The daemon round-trips the carry untouched, exactly as it does the
|
|
22
|
+
// byte offset for the JSONL adapters.
|
|
23
|
+
//
|
|
24
|
+
// Everything is fail-soft: a missing `node:sqlite` runtime (Node < 22), a locked
|
|
25
|
+
// or vanished DB, a torn `data` JSON, or any query error is swallowed — at worst
|
|
26
|
+
// the session runs on the `otacon progress` floor.
|
|
27
|
+
import { createRequire } from "node:module";
|
|
28
|
+
import { homedir } from "node:os";
|
|
29
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
30
|
+
const AGENT = "opencode";
|
|
31
|
+
// The daemon ships as an ESM bundle (`"type": "module"`), where the bare global
|
|
32
|
+
// `require` is undefined — so we mint a CommonJS-style `require` bound to this
|
|
33
|
+
// module via `createRequire`. That is the only way to load a built-in
|
|
34
|
+
// SYNCHRONOUSLY from ESM (`locate`/`parse` are sync per the adapter contract, so
|
|
35
|
+
// `await import()` is not an option). On Node < 22 or under bun, `node:sqlite`
|
|
36
|
+
// is absent and this `require` throws — caught below, degrading to the floor.
|
|
37
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
38
|
+
/** Opens `path` read-only via `node:sqlite`, or null when unavailable/unreadable. */
|
|
39
|
+
function defaultOpen(path) {
|
|
40
|
+
let mod;
|
|
41
|
+
try {
|
|
42
|
+
// `node:sqlite` is built in on Node >= 22. require() so a missing module on
|
|
43
|
+
// an older runtime throws here and is swallowed (floor), not at import time.
|
|
44
|
+
mod = nodeRequire("node:sqlite");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return new mod.DatabaseSync(path, { readonly: true });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null; // DB missing, locked exclusively, or corrupt — degrade to floor
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Test seam: the daemon uses the real `node:sqlite`; a test injects a fake. */
|
|
57
|
+
let openDb = defaultOpen;
|
|
58
|
+
/** @internal — swap the SQLite opener (tests only). */
|
|
59
|
+
export function __setOpenDb(fn) {
|
|
60
|
+
openDb = fn;
|
|
61
|
+
}
|
|
62
|
+
/** `$XDG_DATA_HOME/opencode` (if set) else `~/.local/share/opencode`. */
|
|
63
|
+
function dataRoot() {
|
|
64
|
+
const xdg = process.env.XDG_DATA_HOME;
|
|
65
|
+
if (typeof xdg === "string" && xdg !== "")
|
|
66
|
+
return join(xdg, "opencode");
|
|
67
|
+
// Prefer $HOME (the standard override; what a test or custom-home user
|
|
68
|
+
// expects) and fall back to os.homedir() — Bun's homedir() ignores $HOME.
|
|
69
|
+
const home = process.env.HOME && process.env.HOME !== "" ? process.env.HOME : homedir();
|
|
70
|
+
return join(home, ".local", "share", "opencode");
|
|
71
|
+
}
|
|
72
|
+
/** The OpenCode SQLite DB path: `<dataRoot>/opencode.db`. */
|
|
73
|
+
function dbPath() {
|
|
74
|
+
return join(dataRoot(), "opencode.db");
|
|
75
|
+
}
|
|
76
|
+
/** Run a query fail-soft, always closing the handle; [] on any error. */
|
|
77
|
+
function query(path, sql, params) {
|
|
78
|
+
const db = openDb(path);
|
|
79
|
+
if (!db)
|
|
80
|
+
return [];
|
|
81
|
+
try {
|
|
82
|
+
return db.prepare(sql).all(...params);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return []; // bad SQL/schema drift/locked read — degrade to floor
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
try {
|
|
89
|
+
db.close();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// best-effort close — never throw out of a fail-soft path
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The freshest session whose recorded `directory` equals `repoRoot`. We let
|
|
98
|
+
* SQLite do the filter + newest-first ordering and take the first row. The
|
|
99
|
+
* returned handle's `path` is the DB file; the resolved session id rides on the
|
|
100
|
+
* carry (stashed by `parse`, keyed off this same query). Null when the DB is
|
|
101
|
+
* missing/unreadable or no session matches. Never throws.
|
|
102
|
+
*/
|
|
103
|
+
function locate(repoRoot) {
|
|
104
|
+
const path = dbPath();
|
|
105
|
+
const rows = query(path, "SELECT id FROM session WHERE directory = ? ORDER BY time_updated DESC, time_created DESC LIMIT 1", [repoRoot]);
|
|
106
|
+
const id = rows[0] && typeof rows[0] === "object" ? rows[0].id : undefined;
|
|
107
|
+
if (typeof id !== "string" || id === "")
|
|
108
|
+
return null;
|
|
109
|
+
// Encode the session id into the handle path (`<db>#<sessionId>`) so `parse`
|
|
110
|
+
// knows which session to scan without re-running the directory match — the
|
|
111
|
+
// TranscriptHandle has no field for it, and the cwd→session mapping is fixed
|
|
112
|
+
// for a located handle's lifetime.
|
|
113
|
+
return { agent: AGENT, path: `${path}#${id}` };
|
|
114
|
+
}
|
|
115
|
+
/** Split a `<db>#<sessionId>` handle path back into its parts. */
|
|
116
|
+
function splitHandle(handlePath) {
|
|
117
|
+
const hash = handlePath.lastIndexOf("#");
|
|
118
|
+
if (hash === -1)
|
|
119
|
+
return { dbFile: handlePath, sessionId: "" };
|
|
120
|
+
return { dbFile: handlePath.slice(0, hash), sessionId: handlePath.slice(hash + 1) };
|
|
121
|
+
}
|
|
122
|
+
const TOOL_LABEL_MAX = 80;
|
|
123
|
+
/** First line of `text`, trimmed; the one-line label for a text/reasoning body. */
|
|
124
|
+
function firstLine(text) {
|
|
125
|
+
const nl = text.indexOf("\n");
|
|
126
|
+
return (nl === -1 ? text : text.slice(0, nl)).trim();
|
|
127
|
+
}
|
|
128
|
+
/** Make `p` repo-relative when it sits under `repoRoot`; else return it as-is. */
|
|
129
|
+
function relPath(p, repoRoot) {
|
|
130
|
+
if (typeof p !== "string" || p === "")
|
|
131
|
+
return String(p ?? "");
|
|
132
|
+
if (!isAbsolute(p))
|
|
133
|
+
return p;
|
|
134
|
+
const rel = relative(repoRoot, p);
|
|
135
|
+
// `relative` of a path outside the repo starts with ".." or is absolute on a
|
|
136
|
+
// different drive — keep the absolute path then (it is genuinely elsewhere).
|
|
137
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel))
|
|
138
|
+
return p;
|
|
139
|
+
return rel.split(sep).join("/");
|
|
140
|
+
}
|
|
141
|
+
/** Cap a one-line command/string to the label width with a trailing ellipsis. */
|
|
142
|
+
function clamp(text) {
|
|
143
|
+
const oneLine = firstLine(text);
|
|
144
|
+
return oneLine.length > TOOL_LABEL_MAX ? `${oneLine.slice(0, TOOL_LABEL_MAX - 1)}…` : oneLine;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Concise label for a tool part, e.g. "Bash: bun test" / "Read src/x.ts". Tool
|
|
148
|
+
* names are lowercase in OpenCode; we render the same verbs as the Claude
|
|
149
|
+
* adapter. Falls back to the tool's title (OpenCode computes a useful one) or
|
|
150
|
+
* the bare tool name.
|
|
151
|
+
*/
|
|
152
|
+
function toolLabel(tool, input, title, repoRoot) {
|
|
153
|
+
const file = (k) => relPath(String(input[k] ?? ""), repoRoot);
|
|
154
|
+
switch (tool) {
|
|
155
|
+
case "bash": {
|
|
156
|
+
const cmd = String(input.command ?? "");
|
|
157
|
+
return cmd === "" ? "Bash" : `Bash: ${clamp(cmd)}`;
|
|
158
|
+
}
|
|
159
|
+
case "read":
|
|
160
|
+
return `Read ${file("filePath")}`;
|
|
161
|
+
case "edit":
|
|
162
|
+
return `Edit ${file("filePath")}`;
|
|
163
|
+
case "write":
|
|
164
|
+
return `Write ${file("filePath")}`;
|
|
165
|
+
case "grep":
|
|
166
|
+
return `Grep ${String(input.pattern ?? "")}`;
|
|
167
|
+
case "glob":
|
|
168
|
+
return `Glob ${String(input.pattern ?? "")}`;
|
|
169
|
+
case "webfetch":
|
|
170
|
+
return `Fetch ${String(input.url ?? "")}`;
|
|
171
|
+
case "websearch":
|
|
172
|
+
return `Search: ${String(input.query ?? "")}`;
|
|
173
|
+
case "task":
|
|
174
|
+
return `Task: ${String(input.description ?? input.prompt ?? "")}`;
|
|
175
|
+
default:
|
|
176
|
+
// A meaningful title (OpenCode often sets one, e.g. a glob pattern) beats
|
|
177
|
+
// the bare lowercase tool name; otherwise show the name.
|
|
178
|
+
return title !== "" ? `${tool}: ${clamp(title)}` : tool;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Coerce a tool `state.output`/`error` to a one-line-safe detail string. */
|
|
182
|
+
function detailText(v) {
|
|
183
|
+
if (typeof v === "string")
|
|
184
|
+
return v;
|
|
185
|
+
if (v === undefined || v === null)
|
|
186
|
+
return "";
|
|
187
|
+
try {
|
|
188
|
+
return JSON.stringify(v);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return "";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Map one part's parsed `data` to the events it produces — possibly TWO for a
|
|
196
|
+
* settled tool (the `running` event and its `ok`/`error` outcome, appended
|
|
197
|
+
* separately so the store never upserts). Unrecognized types yield nothing.
|
|
198
|
+
*/
|
|
199
|
+
function partToEvents(data, repoRoot) {
|
|
200
|
+
switch (data.type) {
|
|
201
|
+
case "text": {
|
|
202
|
+
const text = typeof data.text === "string" ? data.text : "";
|
|
203
|
+
if (text.trim() === "")
|
|
204
|
+
return [];
|
|
205
|
+
return [{ kind: "text", label: clamp(text) || "text", detail: text }];
|
|
206
|
+
}
|
|
207
|
+
case "reasoning": {
|
|
208
|
+
const text = typeof data.text === "string" ? data.text : "";
|
|
209
|
+
if (text.trim() === "")
|
|
210
|
+
return [];
|
|
211
|
+
return [{ kind: "thinking", label: "thinking…", detail: text }];
|
|
212
|
+
}
|
|
213
|
+
case "tool": {
|
|
214
|
+
const tool = typeof data.tool === "string" ? data.tool : "tool";
|
|
215
|
+
const state = data.state && typeof data.state === "object" ? data.state : {};
|
|
216
|
+
const input = state.input && typeof state.input === "object" ? state.input : {};
|
|
217
|
+
const title = typeof state.title === "string" ? state.title : "";
|
|
218
|
+
const rawInput = (() => {
|
|
219
|
+
try {
|
|
220
|
+
return JSON.stringify(input);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return "";
|
|
224
|
+
}
|
|
225
|
+
})();
|
|
226
|
+
const events = [
|
|
227
|
+
{
|
|
228
|
+
kind: "tool",
|
|
229
|
+
tool,
|
|
230
|
+
label: toolLabel(tool, input, title, repoRoot),
|
|
231
|
+
detail: rawInput === "" || rawInput === "{}" ? undefined : rawInput,
|
|
232
|
+
status: "running",
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
// A settled tool carries its outcome on the same part (OpenCode mutates the
|
|
236
|
+
// part in place as the tool runs). We still emit the outcome as its OWN
|
|
237
|
+
// event to keep the append-only "running then ok/error" shape; a part that
|
|
238
|
+
// is still `pending`/`running` only yields the running event for now and
|
|
239
|
+
// its outcome arrives when the part's `time_updated` bumps it back to us.
|
|
240
|
+
const status = state.status;
|
|
241
|
+
if (status === "completed") {
|
|
242
|
+
const detail = detailText(state.output);
|
|
243
|
+
events.push({ kind: "tool", status: "ok", label: "→ ok", detail: detail === "" ? undefined : detail });
|
|
244
|
+
}
|
|
245
|
+
else if (status === "error") {
|
|
246
|
+
const detail = detailText(state.error) || detailText(state.output);
|
|
247
|
+
events.push({ kind: "tool", status: "error", label: "→ error", detail: detail === "" ? undefined : detail });
|
|
248
|
+
}
|
|
249
|
+
return events;
|
|
250
|
+
}
|
|
251
|
+
default:
|
|
252
|
+
return []; // step-start/step-finish/patch/file/compaction/… are not activity
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Scan the located session's parts newer than the cursor watermark, in
|
|
257
|
+
* chronological order, and emit their events. Incrementality lives in the carry:
|
|
258
|
+
* we ask for `time_created >= watermark` (so a part inserted in the SAME
|
|
259
|
+
* millisecond as the last frontier is not missed), then skip any id already
|
|
260
|
+
* emitted at exactly that watermark. After emitting, the watermark advances to
|
|
261
|
+
* the newest `time_created` we saw and the tie set is reset to just the ids at
|
|
262
|
+
* that new frontier — bounded, and enough to dedupe the next same-ms insert.
|
|
263
|
+
* Fail-soft throughout: a missing DB, a torn `data` JSON, or a query error
|
|
264
|
+
* yields no events and leaves the cursor put.
|
|
265
|
+
*/
|
|
266
|
+
function parse(handle, cursor) {
|
|
267
|
+
const { dbFile, sessionId } = splitHandle(handle.path);
|
|
268
|
+
if (sessionId === "")
|
|
269
|
+
return { events: [], cursor };
|
|
270
|
+
const carry = cursor;
|
|
271
|
+
const watermark = typeof carry.watermark === "number" && carry.watermark >= 0 ? carry.watermark : 0;
|
|
272
|
+
const emitted = Array.isArray(carry.emittedAtWatermark) ? new Set(carry.emittedAtWatermark) : new Set();
|
|
273
|
+
// Resolve repoRoot once (from the session row) and stash it on the carry.
|
|
274
|
+
let repoRoot = typeof carry.repoRoot === "string" ? carry.repoRoot : undefined;
|
|
275
|
+
if (repoRoot === undefined) {
|
|
276
|
+
const sess = query(dbFile, "SELECT directory FROM session WHERE id = ? LIMIT 1", [sessionId]);
|
|
277
|
+
const dir = sess[0] && typeof sess[0] === "object" ? sess[0].directory : undefined;
|
|
278
|
+
repoRoot = typeof dir === "string" ? dir : "";
|
|
279
|
+
}
|
|
280
|
+
const rows = query(dbFile, "SELECT id, time_created, data FROM part WHERE session_id = ? AND time_created >= ? ORDER BY time_created ASC, id ASC", [sessionId, watermark]);
|
|
281
|
+
const events = [];
|
|
282
|
+
let maxTime = watermark;
|
|
283
|
+
const idsAtMax = emitted.size > 0 ? [...emitted] : []; // carry forward the current frontier's tie set
|
|
284
|
+
for (const row of rows) {
|
|
285
|
+
const id = typeof row.id === "string" ? row.id : undefined;
|
|
286
|
+
const tCreated = typeof row.time_created === "number" ? row.time_created : 0;
|
|
287
|
+
if (id === undefined)
|
|
288
|
+
continue;
|
|
289
|
+
if (emitted.has(id))
|
|
290
|
+
continue; // already emitted at the prior watermark — skip
|
|
291
|
+
let data;
|
|
292
|
+
try {
|
|
293
|
+
const raw = typeof row.data === "string" ? row.data : "";
|
|
294
|
+
const parsed = raw === "" ? {} : JSON.parse(raw);
|
|
295
|
+
data = parsed && typeof parsed === "object" ? parsed : {};
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
continue; // torn `data` JSON — skip this part, keep going
|
|
299
|
+
}
|
|
300
|
+
for (const event of partToEvents(data, repoRoot))
|
|
301
|
+
events.push(event);
|
|
302
|
+
// Advance the frontier. A part AT the current max joins its tie set; a newer
|
|
303
|
+
// one resets the tie set to just itself.
|
|
304
|
+
if (tCreated > maxTime) {
|
|
305
|
+
maxTime = tCreated;
|
|
306
|
+
idsAtMax.length = 0;
|
|
307
|
+
idsAtMax.push(id);
|
|
308
|
+
}
|
|
309
|
+
else if (tCreated === maxTime) {
|
|
310
|
+
idsAtMax.push(id);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const nextCursor = {
|
|
314
|
+
...cursor,
|
|
315
|
+
offset: 0, // unused for a DB source; kept for the opaque-cursor contract
|
|
316
|
+
repoRoot,
|
|
317
|
+
watermark: maxTime,
|
|
318
|
+
emittedAtWatermark: idsAtMax,
|
|
319
|
+
};
|
|
320
|
+
return { events, cursor: nextCursor };
|
|
321
|
+
}
|
|
322
|
+
/** The OpenCode SQLite adapter (read-only, fail-soft). */
|
|
323
|
+
export const opencodeAdapter = {
|
|
324
|
+
agent: AGENT,
|
|
325
|
+
locate,
|
|
326
|
+
parse,
|
|
327
|
+
};
|
|
328
|
+
// Test-only exports — the pure mappers + path helpers are worth covering directly.
|
|
329
|
+
export const __test = { toolLabel, relPath, detailText, partToEvents, splitHandle, dbPath, dataRoot };
|
|
330
|
+
//# sourceMappingURL=opencode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../../src/daemon/capture/opencode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The adapter registry: an ordered list of transcript adapters and the lookup
|
|
2
|
+
// the tailer uses to bind a session's repo to the agent that's actually working
|
|
3
|
+
// there. `findAdapter(repoRoot)` returns the first adapter whose `locate` finds
|
|
4
|
+
// a transcript, or null when none matches — and null is the WHOLE point: a repo
|
|
5
|
+
// whose agent has no adapter attaches no tailer and runs on the `otacon
|
|
6
|
+
// progress` floor (the graceful-degradation guarantee). Adding an agent (Phase
|
|
7
|
+
// 5/6: Codex, OpenCode, …) is one line here plus its adapter module.
|
|
8
|
+
import { claudeAdapter } from "./claude.js";
|
|
9
|
+
import { codexAdapter } from "./codex.js";
|
|
10
|
+
import { opencodeAdapter } from "./opencode.js";
|
|
11
|
+
/** Ordered adapters; the first whose `locate` matches wins. */
|
|
12
|
+
export const ADAPTERS = [claudeAdapter, codexAdapter, opencodeAdapter];
|
|
13
|
+
/**
|
|
14
|
+
* The first adapter with a located transcript for `repoRoot`, or null when no
|
|
15
|
+
* adapter matches (floor only). Fail-soft: a throwing `locate` is treated as no
|
|
16
|
+
* match, never propagated — one misbehaving adapter must not deny every other
|
|
17
|
+
* agent its stream.
|
|
18
|
+
*/
|
|
19
|
+
export function findAdapter(repoRoot, adapters = ADAPTERS) {
|
|
20
|
+
for (const adapter of adapters) {
|
|
21
|
+
let handle = null;
|
|
22
|
+
try {
|
|
23
|
+
handle = adapter.locate(repoRoot);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
handle = null;
|
|
27
|
+
}
|
|
28
|
+
if (handle)
|
|
29
|
+
return { adapter, handle };
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/daemon/capture/registry.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,wEAAwE;AACxE,+EAA+E;AAC/E,qEAAqE;AAGrE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,+DAA+D;AAC/D,MAAM,CAAC,MAAM,QAAQ,GAAiC,CAAC,aAAa,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;AAErG;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,QAAgB,EAChB,WAAyC,QAAQ;IAEjD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,MAAM,GAA4B,IAAI,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,MAAM;YAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IACzC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Per-session normalized live-activity stream: .otacon/<id>/stream.jsonl holds
|
|
2
|
+
// the daemon's append-only, capped record of captured agent activity plus
|
|
3
|
+
// `otacon progress` highlights — the high-frequency telemetry the review UI
|
|
4
|
+
// watches while the agent works. JSONL (one StreamEvent per line) so a
|
|
5
|
+
// frequent capture source pays a cheap append, not a whole-file rewrite, on the
|
|
6
|
+
// common path; the file is rewritten only when it grows past the cap (keeping
|
|
7
|
+
// the newest N). Same storage posture as activity.ts and the queue/transcript
|
|
8
|
+
// readers: atomic rewrites, corrupt lines skipped (never quarantine the whole
|
|
9
|
+
// file — a JSONL stream's value is the lines that DID parse), never fatal.
|
|
10
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
11
|
+
import { dirname } from "node:path";
|
|
12
|
+
import { writeFileAtomic } from "../store.js";
|
|
13
|
+
const KINDS = ["tool", "text", "thinking", "highlight"];
|
|
14
|
+
/**
|
|
15
|
+
* One JSONL line → a StreamEvent, or undefined for a blank/corrupt/incomplete
|
|
16
|
+
* line (a torn final append, a hand-edit). Validates every field, not just the
|
|
17
|
+
* envelope (same argument as activity.ts): a bad event would otherwise flow a
|
|
18
|
+
* non-event into the stream snapshot and the SSE frame.
|
|
19
|
+
*/
|
|
20
|
+
function parseLine(line) {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
if (trimmed === "")
|
|
23
|
+
return undefined;
|
|
24
|
+
let raw;
|
|
25
|
+
try {
|
|
26
|
+
raw = JSON.parse(trimmed);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const e = raw;
|
|
32
|
+
const valid = typeof e === "object" &&
|
|
33
|
+
e !== null &&
|
|
34
|
+
typeof e.seq === "number" &&
|
|
35
|
+
Number.isInteger(e.seq) &&
|
|
36
|
+
typeof e.at === "string" &&
|
|
37
|
+
KINDS.includes(e.kind) &&
|
|
38
|
+
typeof e.label === "string" &&
|
|
39
|
+
(e.detail === undefined || typeof e.detail === "string") &&
|
|
40
|
+
(e.tool === undefined || typeof e.tool === "string") &&
|
|
41
|
+
(e.status === undefined ||
|
|
42
|
+
e.status === "running" ||
|
|
43
|
+
e.status === "ok" ||
|
|
44
|
+
e.status === "error");
|
|
45
|
+
return valid ? e : undefined;
|
|
46
|
+
}
|
|
47
|
+
/** Every parseable event, oldest first; missing file or all-corrupt = []. Never throws. */
|
|
48
|
+
function readAll(path) {
|
|
49
|
+
if (!existsSync(path))
|
|
50
|
+
return [];
|
|
51
|
+
let body;
|
|
52
|
+
try {
|
|
53
|
+
body = readFileSync(path, "utf8");
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const events = [];
|
|
59
|
+
for (const line of body.split("\n")) {
|
|
60
|
+
const event = parseLine(line);
|
|
61
|
+
if (event)
|
|
62
|
+
events.push(event);
|
|
63
|
+
}
|
|
64
|
+
return events;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The stream's events, oldest first (newest last). `limit` returns only the
|
|
68
|
+
* newest N — what the per-session SSE snapshot serves. Corrupt lines are
|
|
69
|
+
* skipped; a missing file reads as empty.
|
|
70
|
+
*/
|
|
71
|
+
export function readStream(path, limit) {
|
|
72
|
+
const events = readAll(path);
|
|
73
|
+
if (limit !== undefined && limit > 0 && events.length > limit) {
|
|
74
|
+
return events.slice(events.length - limit);
|
|
75
|
+
}
|
|
76
|
+
return events;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Durably append events as JSONL lines, then cap: when the file holds more than
|
|
80
|
+
* `cap`, it is rewritten atomically to the newest `cap` (older lines drop off
|
|
81
|
+
* the front). The append is the cheap common path; the rewrite is the rare
|
|
82
|
+
* trim. Returns the events as appended (unchanged). A `cap` of 0 or less keeps
|
|
83
|
+
* everything (no trim).
|
|
84
|
+
*/
|
|
85
|
+
export function appendStreamEvents(path, events, cap) {
|
|
86
|
+
if (events.length === 0)
|
|
87
|
+
return events;
|
|
88
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
89
|
+
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
90
|
+
appendFileSync(path, lines);
|
|
91
|
+
if (cap > 0) {
|
|
92
|
+
const all = readAll(path);
|
|
93
|
+
if (all.length > cap) {
|
|
94
|
+
const kept = all.slice(all.length - cap);
|
|
95
|
+
writeFileAtomic(path, kept.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return events;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Monotonic per-session seq source for the daemon. Seeds lazily from the
|
|
102
|
+
* stream's max seq on first use (so a daemon restart never re-mints a live
|
|
103
|
+
* seq), then increments in memory. One instance per session id; the daemon owns
|
|
104
|
+
* the single writer, so no locking is needed (DECISIONS.md "One daemon process
|
|
105
|
+
* owns all state").
|
|
106
|
+
*/
|
|
107
|
+
export class StreamSeq {
|
|
108
|
+
last;
|
|
109
|
+
/** Mint the next seq, seeding from the file's max on first call. */
|
|
110
|
+
next(path) {
|
|
111
|
+
if (this.last === undefined) {
|
|
112
|
+
let max = 0;
|
|
113
|
+
for (const event of readAll(path))
|
|
114
|
+
max = Math.max(max, event.seq);
|
|
115
|
+
this.last = max;
|
|
116
|
+
}
|
|
117
|
+
this.last += 1;
|
|
118
|
+
return this.last;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=stream-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-store.js","sourceRoot":"","sources":["../../../src/daemon/capture/stream-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// The per-session transcript tailer: the daemon-side loop that watches a coding
|
|
2
|
+
// agent's live transcript and feeds new activity into the Phase 1 stream
|
|
3
|
+
// pipeline. Bound to a session's lifetime — `start` when the session goes
|
|
4
|
+
// active, `stop` on terminal/removal — it (1) finds the agent's transcript via
|
|
5
|
+
// the registry, (2) polls it for new bytes, (3) `parse`s them into
|
|
6
|
+
// `RawStreamEvent`s, (4) `normalize`s each (the daemon stamps seq + `at`,
|
|
7
|
+
// redaction + truncation happen there), (5) appends them as ONE batch, and (6)
|
|
8
|
+
// publishes ONE coalesced `stream` frame per batch.
|
|
9
|
+
//
|
|
10
|
+
// Design choices (favoring reliability over cleverness):
|
|
11
|
+
// - A plain poll loop, not `fs.watch`. `fs.watch` is famously
|
|
12
|
+
// platform-flaky (missed events, double-fires, no support on some network
|
|
13
|
+
// FSes); a short interval poll is boring and always works. A burst between
|
|
14
|
+
// two polls is naturally coalesced into one parse → one append → one frame.
|
|
15
|
+
// - Re-locate while no transcript is found yet. A session can be created a
|
|
16
|
+
// beat before the agent's transcript file appears; we keep retrying
|
|
17
|
+
// `findAdapter` on each tick until one matches (bounded only by `stop`).
|
|
18
|
+
// - Fail-soft throughout: a parse/store/publish error on one tick is
|
|
19
|
+
// swallowed; the next tick tries again. The tailer never throws into the
|
|
20
|
+
// daemon, and at worst the session runs on the `otacon progress` floor.
|
|
21
|
+
import { INITIAL_CURSOR } from "./adapter.js";
|
|
22
|
+
import { normalize } from "./normalize.js";
|
|
23
|
+
import { findAdapter as defaultFindAdapter } from "./registry.js";
|
|
24
|
+
/** Default poll cadence — coalesces a burst of writes into one batch per tick. */
|
|
25
|
+
export const DEFAULT_POLL_MS = 150;
|
|
26
|
+
/**
|
|
27
|
+
* One session's tailer. Construct, `start()`, and `stop()`; both are idempotent.
|
|
28
|
+
* A test can skip the timer entirely and drive `tick()` by hand.
|
|
29
|
+
*/
|
|
30
|
+
export class Tailer {
|
|
31
|
+
deps;
|
|
32
|
+
findAdapter;
|
|
33
|
+
pollMs;
|
|
34
|
+
setIntervalFn;
|
|
35
|
+
clearIntervalFn;
|
|
36
|
+
timer;
|
|
37
|
+
adapter;
|
|
38
|
+
handle;
|
|
39
|
+
cursor = { ...INITIAL_CURSOR };
|
|
40
|
+
running = false;
|
|
41
|
+
constructor(deps) {
|
|
42
|
+
this.deps = deps;
|
|
43
|
+
this.findAdapter = deps.findAdapter ?? defaultFindAdapter;
|
|
44
|
+
this.pollMs = deps.pollMs ?? DEFAULT_POLL_MS;
|
|
45
|
+
this.setIntervalFn = deps.setInterval ?? ((cb, ms) => setInterval(cb, ms));
|
|
46
|
+
this.clearIntervalFn = deps.clearInterval ?? ((h) => clearInterval(h));
|
|
47
|
+
}
|
|
48
|
+
/** Begin polling. Idempotent — a second `start` is a no-op. */
|
|
49
|
+
start() {
|
|
50
|
+
if (this.running)
|
|
51
|
+
return;
|
|
52
|
+
this.running = true;
|
|
53
|
+
// Try to bind immediately so a transcript that already exists streams on the
|
|
54
|
+
// first tick; if none is found yet, `tick` keeps re-locating.
|
|
55
|
+
this.locate();
|
|
56
|
+
const timer = this.setIntervalFn(() => this.tick(), this.pollMs);
|
|
57
|
+
// Don't let the poll timer keep the daemon process alive on its own.
|
|
58
|
+
timer.unref?.();
|
|
59
|
+
this.timer = timer;
|
|
60
|
+
}
|
|
61
|
+
/** Tear down the poller. Idempotent. */
|
|
62
|
+
stop() {
|
|
63
|
+
this.running = false;
|
|
64
|
+
if (this.timer !== undefined) {
|
|
65
|
+
this.clearIntervalFn(this.timer);
|
|
66
|
+
this.timer = undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* One poll: re-locate if still unbound, then parse any new bytes and flush a
|
|
71
|
+
* single batch. Public so a test can drive it without a real timer. Fully
|
|
72
|
+
* fail-soft — any error is swallowed and the next tick retries.
|
|
73
|
+
*/
|
|
74
|
+
tick() {
|
|
75
|
+
try {
|
|
76
|
+
if (this.adapter === undefined || this.handle === undefined) {
|
|
77
|
+
this.locate();
|
|
78
|
+
if (this.adapter === undefined || this.handle === undefined)
|
|
79
|
+
return; // still no transcript
|
|
80
|
+
}
|
|
81
|
+
const { events: raw, cursor } = this.adapter.parse(this.handle, this.cursor);
|
|
82
|
+
this.cursor = cursor;
|
|
83
|
+
if (raw.length === 0)
|
|
84
|
+
return;
|
|
85
|
+
const cfg = this.deps.config();
|
|
86
|
+
const at = new Date().toISOString();
|
|
87
|
+
const normalized = raw.map((e) => normalize(e, cfg, this.deps.nextSeq(), at));
|
|
88
|
+
this.deps.append(normalized);
|
|
89
|
+
this.deps.publish(normalized);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// A parse/store/publish hiccup on one tick must not kill the loop — the
|
|
93
|
+
// session falls back to the floor for this tick and we retry next time.
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Resolve the adapter+handle for this repo (no-op when one is already bound). */
|
|
97
|
+
locate() {
|
|
98
|
+
if (this.adapter !== undefined && this.handle !== undefined)
|
|
99
|
+
return;
|
|
100
|
+
const found = this.findAdapter(this.deps.repoRoot);
|
|
101
|
+
if (!found)
|
|
102
|
+
return; // floor: no adapter for this repo's agent (yet)
|
|
103
|
+
this.adapter = found.adapter;
|
|
104
|
+
this.handle = found.handle;
|
|
105
|
+
this.cursor = { ...INITIAL_CURSOR };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=tailer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tailer.js","sourceRoot":"","sources":["../../../src/daemon/capture/tailer.ts"],"names":[],"mappings":"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"}
|