@sentropic/h2a 0.85.24 → 0.86.0
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/.claude-plugin/plugin.json +2 -3
- package/.codex-plugin/plugin.json +1 -1
- package/dist/bin.js +51 -2
- package/dist/bin.js.map +1 -1
- package/dist/cli-command-map.d.ts +119 -0
- package/dist/cli-command-map.d.ts.map +1 -0
- package/dist/cli-command-map.js +396 -0
- package/dist/cli-command-map.js.map +1 -0
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli-contract.js +38 -0
- package/dist/cli-contract.js.map +1 -1
- package/dist/cli.d.ts +25 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +356 -7
- package/dist/cli.js.map +1 -1
- package/dist/identity.d.ts +37 -3
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -0
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/enrollment/ceremony.d.ts +542 -0
- package/dist/runtime/enrollment/ceremony.d.ts.map +1 -0
- package/dist/runtime/enrollment/ceremony.js +777 -0
- package/dist/runtime/enrollment/ceremony.js.map +1 -0
- package/dist/runtime/enrollment/index.d.ts +16 -0
- package/dist/runtime/enrollment/index.d.ts.map +1 -0
- package/dist/runtime/enrollment/index.js +16 -0
- package/dist/runtime/enrollment/index.js.map +1 -0
- package/dist/runtime/feed/descriptors.d.ts +261 -0
- package/dist/runtime/feed/descriptors.d.ts.map +1 -0
- package/dist/runtime/feed/descriptors.js +425 -0
- package/dist/runtime/feed/descriptors.js.map +1 -0
- package/dist/runtime/feed/index.d.ts +12 -0
- package/dist/runtime/feed/index.d.ts.map +1 -0
- package/dist/runtime/feed/index.js +12 -0
- package/dist/runtime/feed/index.js.map +1 -0
- package/dist/runtime/identity/index.d.ts +3 -3
- package/dist/runtime/identity/index.d.ts.map +1 -1
- package/dist/runtime/identity/index.js +2 -2
- package/dist/runtime/identity/index.js.map +1 -1
- package/dist/runtime/identity/live.d.ts +68 -0
- package/dist/runtime/identity/live.d.ts.map +1 -1
- package/dist/runtime/identity/live.js +78 -2
- package/dist/runtime/identity/live.js.map +1 -1
- package/dist/runtime/identity/readers.d.ts +89 -5
- package/dist/runtime/identity/readers.d.ts.map +1 -1
- package/dist/runtime/identity/readers.js +242 -33
- package/dist/runtime/identity/readers.js.map +1 -1
- package/dist/runtime/local-files/presence.d.ts +16 -1
- package/dist/runtime/local-files/presence.d.ts.map +1 -1
- package/dist/runtime/local-files/presence.js +32 -20
- package/dist/runtime/local-files/presence.js.map +1 -1
- package/dist/runtime/loop/index.d.ts +5 -0
- package/dist/runtime/loop/index.d.ts.map +1 -1
- package/dist/runtime/loop/index.js +48 -5
- package/dist/runtime/loop/index.js.map +1 -1
- package/dist/runtime/mcp/agent-launch.d.ts +15 -1
- package/dist/runtime/mcp/agent-launch.d.ts.map +1 -1
- package/dist/runtime/mcp/agent-launch.js +45 -4
- package/dist/runtime/mcp/agent-launch.js.map +1 -1
- package/dist/runtime/mcp/index.d.ts +1 -1
- package/dist/runtime/mcp/index.d.ts.map +1 -1
- package/dist/runtime/mcp/index.js +1 -1
- package/dist/runtime/mcp/index.js.map +1 -1
- package/dist/runtime/mcp/server.d.ts +3 -1
- package/dist/runtime/mcp/server.d.ts.map +1 -1
- package/dist/runtime/mcp/server.js +7 -2
- package/dist/runtime/mcp/server.js.map +1 -1
- package/dist/runtime/mcp/sessions.d.ts +19 -0
- package/dist/runtime/mcp/sessions.d.ts.map +1 -1
- package/dist/runtime/mcp/sessions.js +44 -2
- package/dist/runtime/mcp/sessions.js.map +1 -1
- package/dist/runtime/mcp/stdio.d.ts +8 -0
- package/dist/runtime/mcp/stdio.d.ts.map +1 -1
- package/dist/runtime/mcp/stdio.js +42 -0
- package/dist/runtime/mcp/stdio.js.map +1 -1
- package/dist/runtime/mcp-registry/broker.d.ts +175 -0
- package/dist/runtime/mcp-registry/broker.d.ts.map +1 -0
- package/dist/runtime/mcp-registry/broker.js +203 -0
- package/dist/runtime/mcp-registry/broker.js.map +1 -0
- package/dist/runtime/mcp-registry/broker.typecheck.d.ts +2 -0
- package/dist/runtime/mcp-registry/broker.typecheck.d.ts.map +1 -0
- package/dist/runtime/mcp-registry/broker.typecheck.js +48 -0
- package/dist/runtime/mcp-registry/broker.typecheck.js.map +1 -0
- package/dist/runtime/mirror/accept.d.ts +43 -5
- package/dist/runtime/mirror/accept.d.ts.map +1 -1
- package/dist/runtime/mirror/accept.js +55 -7
- package/dist/runtime/mirror/accept.js.map +1 -1
- package/dist/runtime/mirror/build.d.ts +23 -4
- package/dist/runtime/mirror/build.d.ts.map +1 -1
- package/dist/runtime/mirror/build.js +34 -4
- package/dist/runtime/mirror/build.js.map +1 -1
- package/dist/runtime/mirror/index.d.ts +3 -0
- package/dist/runtime/mirror/index.d.ts.map +1 -1
- package/dist/runtime/mirror/index.js +3 -0
- package/dist/runtime/mirror/index.js.map +1 -1
- package/dist/runtime/mirror/ingest.d.ts +109 -0
- package/dist/runtime/mirror/ingest.d.ts.map +1 -0
- package/dist/runtime/mirror/ingest.js +117 -0
- package/dist/runtime/mirror/ingest.js.map +1 -0
- package/dist/runtime/mirror/push-daemon.d.ts +376 -0
- package/dist/runtime/mirror/push-daemon.d.ts.map +1 -0
- package/dist/runtime/mirror/push-daemon.js +644 -0
- package/dist/runtime/mirror/push-daemon.js.map +1 -0
- package/dist/runtime/mirror/sanitize.d.ts +477 -0
- package/dist/runtime/mirror/sanitize.d.ts.map +1 -0
- package/dist/runtime/mirror/sanitize.js +625 -0
- package/dist/runtime/mirror/sanitize.js.map +1 -0
- package/dist/runtime/mirror/serve.d.ts.map +1 -1
- package/dist/runtime/mirror/serve.js +104 -24
- package/dist/runtime/mirror/serve.js.map +1 -1
- package/dist/runtime/reporting/report-ai.d.ts.map +1 -1
- package/dist/runtime/reporting/report-ai.js +1 -0
- package/dist/runtime/reporting/report-ai.js.map +1 -1
- package/dist/runtime/reporting/stdin.d.ts +2 -0
- package/dist/runtime/reporting/stdin.d.ts.map +1 -0
- package/dist/runtime/reporting/stdin.js +8 -0
- package/dist/runtime/reporting/stdin.js.map +1 -0
- package/dist/status-surface.d.ts +167 -0
- package/dist/status-surface.d.ts.map +1 -0
- package/dist/status-surface.js +670 -0
- package/dist/status-surface.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -1
- package/focus-app/client/_app/immutable/assets/{0.BdzHFb9q.css → 0.CgEVz9YW.css} +1 -1
- package/focus-app/client/_app/immutable/assets/0.CgEVz9YW.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/0.CgEVz9YW.css.gz +0 -0
- package/focus-app/client/_app/immutable/assets/2.DaTmt6o7.css +1 -0
- package/focus-app/client/_app/immutable/assets/2.DaTmt6o7.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/2.DaTmt6o7.css.gz +0 -0
- package/focus-app/client/_app/immutable/assets/3.vWQUAHjz.css +1 -0
- package/focus-app/client/_app/immutable/assets/3.vWQUAHjz.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/3.vWQUAHjz.css.gz +0 -0
- package/focus-app/client/_app/immutable/assets/AppShell.CDPwj3zU.css +1 -0
- package/focus-app/client/_app/immutable/assets/AppShell.CDPwj3zU.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/AppShell.CDPwj3zU.css.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/{Cj6wo28J.js → 1NK_OeRJ.js} +1 -1
- package/focus-app/client/_app/immutable/chunks/1NK_OeRJ.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/1NK_OeRJ.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/{Bs3f1UbD.js → B1veta3r.js} +2 -2
- package/focus-app/client/_app/immutable/chunks/B1veta3r.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/B1veta3r.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/{DEmooz0s.js → BZ4lcC0v.js} +1 -1
- package/focus-app/client/_app/immutable/chunks/BZ4lcC0v.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/BZ4lcC0v.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/Bu7F9NOv.js +2 -0
- package/focus-app/client/_app/immutable/chunks/Bu7F9NOv.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/Bu7F9NOv.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/C6KhNEX3.js +1 -0
- package/focus-app/client/_app/immutable/chunks/C6KhNEX3.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/C6KhNEX3.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/DIljKPJN.js +1 -0
- package/focus-app/client/_app/immutable/chunks/DIljKPJN.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/DIljKPJN.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/iXQate3J.js +2 -0
- package/focus-app/client/_app/immutable/chunks/iXQate3J.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/iXQate3J.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/{ZxFklh9T.js → kCE9wz0g.js} +1 -1
- package/focus-app/client/_app/immutable/chunks/kCE9wz0g.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/kCE9wz0g.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/x-hq5fDG.js +1 -0
- package/focus-app/client/_app/immutable/chunks/x-hq5fDG.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/x-hq5fDG.js.gz +0 -0
- package/focus-app/client/_app/immutable/entry/app.C1sDdCg5.js +2 -0
- package/focus-app/client/_app/immutable/entry/app.C1sDdCg5.js.br +0 -0
- package/focus-app/client/_app/immutable/entry/app.C1sDdCg5.js.gz +0 -0
- package/focus-app/client/_app/immutable/entry/start.X3j_A5RN.js +1 -0
- package/focus-app/client/_app/immutable/entry/start.X3j_A5RN.js.br +2 -0
- package/focus-app/client/_app/immutable/entry/start.X3j_A5RN.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/0.gLl44WzG.js +5 -0
- package/focus-app/client/_app/immutable/nodes/0.gLl44WzG.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/0.gLl44WzG.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/1.DUrghq2x.js +1 -0
- package/focus-app/client/_app/immutable/nodes/1.DUrghq2x.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/1.DUrghq2x.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/2.Bp7JE7Pz.js +1 -0
- package/focus-app/client/_app/immutable/nodes/2.Bp7JE7Pz.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/2.Bp7JE7Pz.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/3.Cml8enOG.js +12 -0
- package/focus-app/client/_app/immutable/nodes/3.Cml8enOG.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/3.Cml8enOG.js.gz +0 -0
- package/focus-app/client/_app/version.json +1 -1
- package/focus-app/client/_app/version.json.br +0 -0
- package/focus-app/client/_app/version.json.gz +0 -0
- package/focus-app/h2a-focus.json +108 -80
- package/focus-app/handler.js +7 -7
- package/focus-app/index.js +7 -7
- package/focus-app/server/chunks/chunks/AppShell.js-6Pi87dO0.js +1353 -0
- package/focus-app/server/chunks/chunks/AppShell.js-6Pi87dO0.js.map +1 -0
- package/focus-app/server/chunks/chunks/agent-memory-dossier.js-nmFG_rgx.js +1036 -0
- package/focus-app/server/chunks/chunks/agent-memory-dossier.js-nmFG_rgx.js.map +1 -0
- package/focus-app/server/chunks/chunks/{exports.js-DUTkELZq.js → exports.js-CdqPbjm1.js} +3 -3
- package/focus-app/server/chunks/chunks/{exports.js-DUTkELZq.js.map → exports.js-CdqPbjm1.js.map} +1 -1
- package/focus-app/server/chunks/chunks/h2a-bus.js-bLEJ_Adi.js +233 -0
- package/focus-app/server/chunks/chunks/h2a-bus.js-bLEJ_Adi.js.map +1 -0
- package/focus-app/server/chunks/chunks/{index.js-Di21TltP.js → index.js-laGHLarB.js} +5 -2
- package/focus-app/server/chunks/chunks/index.js-laGHLarB.js.map +1 -0
- package/focus-app/server/chunks/chunks/{internal.js-33vbd70h.js → internal.js-C6T8nHkl.js} +4 -4
- package/focus-app/server/chunks/chunks/{internal.js-33vbd70h.js.map → internal.js-C6T8nHkl.js.map} +1 -1
- package/focus-app/server/chunks/chunks/{root.js-DO1m-gJA.js → root.js-CtVQOgDw.js} +2 -2
- package/focus-app/server/chunks/chunks/{root.js-DO1m-gJA.js.map → root.js-CtVQOgDw.js.map} +1 -1
- package/focus-app/server/chunks/entries/endpoints/api/decisions/inject/{_server.ts.js-j0NNcOSZ.js → _server.ts.js-hrbMnsV0.js} +13 -42
- package/focus-app/server/chunks/entries/endpoints/api/decisions/inject/_server.ts.js-hrbMnsV0.js.map +1 -0
- package/focus-app/server/chunks/entries/endpoints/api/dossiers/agent-memory/include/_server.ts.js-Dw4odGdV.js +138 -0
- package/focus-app/server/chunks/entries/endpoints/api/dossiers/agent-memory/include/_server.ts.js-Dw4odGdV.js.map +1 -0
- package/focus-app/server/chunks/entries/endpoints/api/h2a/targets/_server.ts.js-BLjYv7vg.js +41 -0
- package/focus-app/server/chunks/entries/endpoints/api/h2a/targets/_server.ts.js-BLjYv7vg.js.map +1 -0
- package/focus-app/server/chunks/entries/fallbacks/{error.svelte.js-Dj3sETSX.js → error.svelte.js-RbeaNF5a.js} +4 -4
- package/focus-app/server/chunks/entries/fallbacks/{error.svelte.js-Dj3sETSX.js.map → error.svelte.js-RbeaNF5a.js.map} +1 -1
- package/focus-app/server/chunks/entries/pages/{_layout.svelte.js-BoLsbuC2.js → _layout.svelte.js-e1E_I04z.js} +2 -2
- package/focus-app/server/chunks/entries/pages/{_layout.svelte.js-BoLsbuC2.js.map → _layout.svelte.js-e1E_I04z.js.map} +1 -1
- package/focus-app/server/chunks/entries/pages/{_page.svelte.js-QDldMYhk.js → _page.svelte.js-DbaEG23x.js} +31 -1373
- package/focus-app/server/chunks/entries/pages/_page.svelte.js-DbaEG23x.js.map +1 -0
- package/focus-app/server/chunks/entries/pages/dossier/agent-memory/_page.server.ts.js-CFC-wn6U.js +88 -0
- package/focus-app/server/chunks/entries/pages/dossier/agent-memory/_page.server.ts.js-CFC-wn6U.js.map +1 -0
- package/focus-app/server/chunks/entries/pages/dossier/agent-memory/_page.svelte.js-BpR26NMa.js +1574 -0
- package/focus-app/server/chunks/entries/pages/dossier/agent-memory/_page.svelte.js-BpR26NMa.js.map +1 -0
- package/focus-app/server/chunks/{handler-Cp_5hLyY.js → handler-Be3dyNi0.js} +3 -3
- package/focus-app/server/chunks/{handler-Cp_5hLyY.js.map → handler-Be3dyNi0.js.map} +1 -1
- package/focus-app/server/chunks/{index.js-CBh7Wjqp.js → index.js-siUxMMnK.js} +3 -3
- package/focus-app/server/chunks/{index.js-CBh7Wjqp.js.map → index.js-siUxMMnK.js.map} +1 -1
- package/focus-app/server/chunks/manifest.js-CXJqSHHn.js +78 -0
- package/focus-app/server/chunks/manifest.js-CXJqSHHn.js.map +1 -0
- package/focus-app/server/chunks/nodes/0.js-leU6sfRR.js +9 -0
- package/focus-app/server/chunks/nodes/{0.js-4wlmdsk8.js.map → 0.js-leU6sfRR.js.map} +1 -1
- package/focus-app/server/chunks/nodes/1.js-os6QXm6B.js +9 -0
- package/focus-app/server/chunks/nodes/{1.js-B-LpY-lC.js.map → 1.js-os6QXm6B.js.map} +1 -1
- package/focus-app/server/chunks/nodes/2.js-DAaLjh93.js +19 -0
- package/focus-app/server/chunks/nodes/2.js-DAaLjh93.js.map +1 -0
- package/focus-app/server/chunks/nodes/3.js-Cmgi6Uy0.js +18 -0
- package/focus-app/server/chunks/nodes/3.js-Cmgi6Uy0.js.map +1 -0
- package/package.json +2 -2
- package/skills/h2a-run/SKILL.md +100 -0
- package/focus-app/client/_app/immutable/assets/0.BdzHFb9q.css.br +0 -2
- package/focus-app/client/_app/immutable/assets/0.BdzHFb9q.css.gz +0 -0
- package/focus-app/client/_app/immutable/assets/2.Ddxq02y4.css +0 -1
- package/focus-app/client/_app/immutable/assets/2.Ddxq02y4.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/2.Ddxq02y4.css.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/Bs3f1UbD.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/Bs3f1UbD.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/Bv8OL8ib.js +0 -1
- package/focus-app/client/_app/immutable/chunks/Bv8OL8ib.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/Bv8OL8ib.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/CVq_IFFr.js +0 -2
- package/focus-app/client/_app/immutable/chunks/CVq_IFFr.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/CVq_IFFr.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/Cj6wo28J.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/Cj6wo28J.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/DEmooz0s.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/DEmooz0s.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/DQIRXgdC.js +0 -2
- package/focus-app/client/_app/immutable/chunks/DQIRXgdC.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/DQIRXgdC.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/ZxFklh9T.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/ZxFklh9T.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/_wRuVGPs.js +0 -1
- package/focus-app/client/_app/immutable/chunks/_wRuVGPs.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/_wRuVGPs.js.gz +0 -0
- package/focus-app/client/_app/immutable/entry/app.6DKouWRt.js +0 -2
- package/focus-app/client/_app/immutable/entry/app.6DKouWRt.js.br +0 -0
- package/focus-app/client/_app/immutable/entry/app.6DKouWRt.js.gz +0 -0
- package/focus-app/client/_app/immutable/entry/start.DlLmkUb7.js +0 -1
- package/focus-app/client/_app/immutable/entry/start.DlLmkUb7.js.br +0 -2
- package/focus-app/client/_app/immutable/entry/start.DlLmkUb7.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/0.CuSOF0Of.js +0 -5
- package/focus-app/client/_app/immutable/nodes/0.CuSOF0Of.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/0.CuSOF0Of.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/1.D39jdnqm.js +0 -1
- package/focus-app/client/_app/immutable/nodes/1.D39jdnqm.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/1.D39jdnqm.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/2.CnU8ifRl.js +0 -1
- package/focus-app/client/_app/immutable/nodes/2.CnU8ifRl.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/2.CnU8ifRl.js.gz +0 -0
- package/focus-app/server/chunks/chunks/index.js-Di21TltP.js.map +0 -1
- package/focus-app/server/chunks/entries/endpoints/api/decisions/inject/_server.ts.js-j0NNcOSZ.js.map +0 -1
- package/focus-app/server/chunks/entries/pages/_page.svelte.js-QDldMYhk.js.map +0 -1
- package/focus-app/server/chunks/manifest.js-Cm6cC7mC.js +0 -56
- package/focus-app/server/chunks/manifest.js-Cm6cC7mC.js.map +0 -1
- package/focus-app/server/chunks/nodes/0.js-4wlmdsk8.js +0 -9
- package/focus-app/server/chunks/nodes/1.js-B-LpY-lC.js +0 -9
- package/focus-app/server/chunks/nodes/2.js-F6jp5eD9.js +0 -19
- package/focus-app/server/chunks/nodes/2.js-F6jp5eD9.js.map +0 -1
|
@@ -9,6 +9,61 @@ import { decideLegacyAdoption, legacyAliasAlreadyAdopted, recordIdentityAlias }
|
|
|
9
9
|
import { defaultProviderSessionReaders, readHostSessionName } from "./readers.js";
|
|
10
10
|
import { resolveProviderSession } from "./resolver.js";
|
|
11
11
|
import { durableWorkspaceId } from "./workspace-id.js";
|
|
12
|
+
/**
|
|
13
|
+
* The CLOSED vocabulary an agent may DECLARE at registration.
|
|
14
|
+
*
|
|
15
|
+
* DISPLAY ONLY, and NON-AUTHORITATIVE. Self-reported by the agent, and it MUST
|
|
16
|
+
* NEVER be an input to any authorization decision, anywhere — not in h2a, not in
|
|
17
|
+
* a gateway, not in a UI. Authorization is the principal binding plus
|
|
18
|
+
* server-side scoping; a capability string proves nothing. (Binding condition #3
|
|
19
|
+
* of the session-exposure feed contract ratified 2026-07-24,
|
|
20
|
+
* docs/superpowers/specs/2026-07-24-h2a-feed-contract-for-sentropic.md.)
|
|
21
|
+
*
|
|
22
|
+
* These values are written to `H2AActorRegistration.declaredCapabilities` and
|
|
23
|
+
* NEVER to `capabilities` — the latter is the authority-bearing rights list read
|
|
24
|
+
* by the subagent ceiling (`subagents.ts` `capabilities-exceed-parent`) and by
|
|
25
|
+
* `canAttestComprehension`. Writing display vocabulary there would widen a
|
|
26
|
+
* privilege ceiling as a side effect of a display feature, which is exactly the
|
|
27
|
+
* defect the architect's 2026-07-25 split ruling removes. The two fields must
|
|
28
|
+
* never be merged.
|
|
29
|
+
*
|
|
30
|
+
* Note why "no vocabulary member may ever equal a right string" is NOT a
|
|
31
|
+
* sufficient guard, and is not relied upon here: the subagent ceiling is a
|
|
32
|
+
* SUBSET check over the whole field, not a lookup of specific right strings — so
|
|
33
|
+
* that invariant holds for these three values and the ceiling still widens.
|
|
34
|
+
* Separation of fields is the mitigation; string choice is not.
|
|
35
|
+
*
|
|
36
|
+
* Closed on purpose: an unknown string is DROPPED rather than stored, so the
|
|
37
|
+
* set a consumer can ever render stays enumerable and reviewable.
|
|
38
|
+
*/
|
|
39
|
+
export const H2A_DECLARED_CAPABILITIES = [
|
|
40
|
+
/** The agent can open/hold h2a sessions (presence + heartbeat). */
|
|
41
|
+
"h2a.session",
|
|
42
|
+
/** The agent is reachable over an MCP channel. */
|
|
43
|
+
"h2a.mcp",
|
|
44
|
+
/** The agent can fan work out to subagents. */
|
|
45
|
+
"h2a.subagents"
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* What the CLI's own registration path declares. Narrower than the vocabulary
|
|
49
|
+
* on purpose: these two are true of every h2a CLI agent by construction, while
|
|
50
|
+
* `h2a.subagents` is host-specific and not knowable at this call site — an
|
|
51
|
+
* over-claim would be a lie in a browser panel, so it is left to a host plugin
|
|
52
|
+
* that actually knows.
|
|
53
|
+
*/
|
|
54
|
+
export const H2A_CLI_DECLARED_CAPABILITIES = [
|
|
55
|
+
"h2a.session",
|
|
56
|
+
"h2a.mcp"
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Keep only members of the closed vocabulary, de-duplicated and in vocabulary
|
|
60
|
+
* order, so what lands in the registry is never caller-shaped free text.
|
|
61
|
+
*/
|
|
62
|
+
export function sanitizeDeclaredCapabilities(capabilities) {
|
|
63
|
+
if (!capabilities || capabilities.length === 0)
|
|
64
|
+
return [];
|
|
65
|
+
return H2A_DECLARED_CAPABILITIES.filter((known) => capabilities.includes(known));
|
|
66
|
+
}
|
|
12
67
|
function safeKeyName(instance) {
|
|
13
68
|
return instance.replace(/[:/]/g, "-");
|
|
14
69
|
}
|
|
@@ -43,7 +98,14 @@ function realWorkspacePath(cwd) {
|
|
|
43
98
|
function labelFromCwd(cwd) {
|
|
44
99
|
return basename(cwd) || "workspace";
|
|
45
100
|
}
|
|
46
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Short, stable, NON-SECRET fingerprint of a public key: the first 16 hex chars
|
|
103
|
+
* of its sha256. Used by the reclaim proof's nonce and by the enrollment
|
|
104
|
+
* ceremony's owner-facing summary, so the owner can eyeball WHICH key was
|
|
105
|
+
* proved without ever reading a PEM. It is a label, never an authority: nothing
|
|
106
|
+
* may accept a fingerprint where it should verify a signature.
|
|
107
|
+
*/
|
|
108
|
+
export function publicKeyFingerprint(publicKeyPem) {
|
|
47
109
|
return createHash("sha256").update(publicKeyPem, "utf8").digest("hex").slice(0, 16);
|
|
48
110
|
}
|
|
49
111
|
function remoteBridgeInstance(providerSessionId) {
|
|
@@ -116,6 +178,9 @@ function ensureRegistered(input) {
|
|
|
116
178
|
instance: input.instance,
|
|
117
179
|
roles: ["AGENTS"],
|
|
118
180
|
scopes: [...input.scopes],
|
|
181
|
+
// AUTHORITY-BEARING and intentionally left EMPTY, exactly as before this
|
|
182
|
+
// workstream: it is the subagent ceiling and the attestation right. A
|
|
183
|
+
// display list must never be written here (architect ruling, 2026-07-25).
|
|
119
184
|
capabilities: [],
|
|
120
185
|
endpoints: [{ kind: "local-files", uri: `file://${input.root}` }],
|
|
121
186
|
publicKeys: [input.publicKeyPem],
|
|
@@ -123,7 +188,11 @@ function ensureRegistered(input) {
|
|
|
123
188
|
agentUuid: input.agentUuid,
|
|
124
189
|
workspace: input.workspace,
|
|
125
190
|
name: input.name,
|
|
126
|
-
createdAt: new Date(input.now()).toISOString()
|
|
191
|
+
createdAt: new Date(input.now()).toISOString(),
|
|
192
|
+
// The declared DISPLAY list, kept structurally apart from `capabilities`.
|
|
193
|
+
...(input.declaredCapabilities.length > 0
|
|
194
|
+
? { declaredCapabilities: [...input.declaredCapabilities] }
|
|
195
|
+
: {})
|
|
127
196
|
};
|
|
128
197
|
store.registerInstance(registration);
|
|
129
198
|
return;
|
|
@@ -206,6 +275,10 @@ export function resolveLiveIdentity(input) {
|
|
|
206
275
|
name,
|
|
207
276
|
publicKeyPem: keypair.publicKeyPem,
|
|
208
277
|
scopes,
|
|
278
|
+
// Declared at mint only: an already-registered instance keeps whatever it
|
|
279
|
+
// declared then. Nothing downstream may treat this list as authority, so a
|
|
280
|
+
// narrow/empty list is a display gap, never a permission gap.
|
|
281
|
+
declaredCapabilities: sanitizeDeclaredCapabilities(input.declaredCapabilities),
|
|
209
282
|
now
|
|
210
283
|
});
|
|
211
284
|
recordIdentityAlias(input.root, {
|
|
@@ -222,6 +295,9 @@ export function resolveLiveIdentity(input) {
|
|
|
222
295
|
legacyInstance,
|
|
223
296
|
action: result.action,
|
|
224
297
|
providerSessionSource: provider.source,
|
|
298
|
+
...(provider.providerSessionId !== undefined
|
|
299
|
+
? { providerSessionId: provider.providerSessionId }
|
|
300
|
+
: {}),
|
|
225
301
|
privateKeyPath: keypair.privateKeyPath,
|
|
226
302
|
publicKeyPath: keypair.publicKeyPath,
|
|
227
303
|
migrationNotice: result.action === "mint" || !existingBinding
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.js","sourceRoot":"","sources":["../../../src/runtime/identity/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EACL,YAAY,EACZ,UAAU,EACV,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,aAAa,EACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,aAAa,EAGd,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EACL,oBAAoB,EACpB,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,6BAA6B,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAA+B,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"live.js","sourceRoot":"","sources":["../../../src/runtime/identity/live.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,25 +8,79 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { ProviderSessionReaders } from "./resolver.js";
|
|
10
10
|
export declare const defaultProviderSessionReaders: ProviderSessionReaders;
|
|
11
|
+
/**
|
|
12
|
+
* How much of the tail of a Claude transcript to scan for the current title.
|
|
13
|
+
*
|
|
14
|
+
* A transcript is append-only, and `customTitle` is carried by dedicated
|
|
15
|
+
* **rename-event records** (`type: "custom-title"`) — NOT by ordinary records.
|
|
16
|
+
* Measured 2026-07-25 over all 8078 local transcripts: title-bearing records are
|
|
17
|
+
* 0.06% of tail records (5.1% within the one heavily-renamed transcript that
|
|
18
|
+
* motivated this fix). The newest such record is therefore near the END, and a
|
|
19
|
+
* 64 KiB window reaches it: of the 45 transcripts that carry a title at all,
|
|
20
|
+
* **all 45** are resolved from this window — 0 missed, 0 wrong values.
|
|
21
|
+
*
|
|
22
|
+
* Bounded rather than whole-file because this now runs on the heartbeat and live
|
|
23
|
+
* transcripts reach hundreds of MB (largest local: 233 MB; cost 0.30 ms/call).
|
|
24
|
+
*/
|
|
25
|
+
export declare const CLAUDE_TITLE_TAIL_BYTES: number;
|
|
26
|
+
/**
|
|
27
|
+
* How much of the TAIL of `~/.codex/session_index.jsonl` to read. Codex resolves
|
|
28
|
+
* last-match-wins, so the tail is what matters; the file grows monotonically and
|
|
29
|
+
* this also runs on the heartbeat (48 KiB today, so today's whole file fits).
|
|
30
|
+
* A session whose entry has aged out of the window simply yields `undefined`,
|
|
31
|
+
* which the caller treats as "keep the name you have".
|
|
32
|
+
*/
|
|
33
|
+
export declare const CODEX_INDEX_TAIL_BYTES: number;
|
|
34
|
+
/**
|
|
35
|
+
* Upper bound on a display name written into presence. A host title is
|
|
36
|
+
* user-controlled and unbounded; `isH2ASession` only checks `typeof`, so an
|
|
37
|
+
* absurd title would otherwise land in every peer's presence read. Truncated
|
|
38
|
+
* rather than rejected so the name stays findable by the substring match that
|
|
39
|
+
* `discover_sessions(name:)` performs.
|
|
40
|
+
*/
|
|
41
|
+
export declare const MAX_DISPLAY_NAME_CHARS = 200;
|
|
42
|
+
/** Initial re-scan delay after a transcript lookup MISSES (negative cache). */
|
|
43
|
+
export declare const TRANSCRIPT_MISS_BACKOFF_MS = 60000;
|
|
44
|
+
/** Ceiling for the negative-cache backoff. */
|
|
45
|
+
export declare const TRANSCRIPT_MISS_BACKOFF_MAX_MS = 300000;
|
|
11
46
|
/**
|
|
12
47
|
* Injectable FS readers for `readHostSessionName` (test-friendly).
|
|
13
48
|
* Only reading is required; real-FS defaults are `defaultHostNameReaders`.
|
|
14
49
|
*/
|
|
15
50
|
export interface HostNameReaders {
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Read the LAST `maxBytes` of a file as newline-delimited lines, dropping a
|
|
53
|
+
* leading partial record when the file was truncated. Returns [] on any error.
|
|
54
|
+
*
|
|
55
|
+
* Deliberately not `readLines(path, maxLines)` (the pre-fix head reader): a
|
|
56
|
+
* head read of an append-only transcript returns the title as of session
|
|
57
|
+
* START and can never observe a later rename. An external implementor of this
|
|
58
|
+
* interface gets a compile error rather than silently-stale names.
|
|
59
|
+
*/
|
|
60
|
+
readTailLines(path: string, maxBytes: number): string[];
|
|
61
|
+
/**
|
|
62
|
+
* List newline-delimited JSONL entries from the TAIL of
|
|
63
|
+
* `~/.codex/session_index.jsonl` (see `CODEX_INDEX_TAIL_BYTES`).
|
|
64
|
+
*/
|
|
19
65
|
readCodexSessionIndex(): string[];
|
|
20
66
|
/** Whether the home dir root is known (for path construction). */
|
|
21
67
|
homedir(): string;
|
|
68
|
+
/**
|
|
69
|
+
* Locate the transcript file for a Claude session id. Optional: defaults to
|
|
70
|
+
* scanning `~/.claude/projects`. Injectable so a test can COUNT the scans and
|
|
71
|
+
* pin the negative-cache behaviour — that walk is 87 directories / 14345 files
|
|
72
|
+
* and it sits on a 5-second timer, so a repeated miss must not re-walk.
|
|
73
|
+
*/
|
|
74
|
+
findClaudeTranscript?(cwd: string, sessionId: string, home: string): string | undefined;
|
|
22
75
|
}
|
|
23
76
|
export declare const defaultHostNameReaders: HostNameReaders;
|
|
24
77
|
/**
|
|
25
78
|
* Read the host-native session name for the given session.
|
|
26
79
|
*
|
|
27
80
|
* - **claude**: reads the transcript JSONL (located via the CLAUDE_CODE_SESSION_ID
|
|
28
|
-
* resolver); scans the
|
|
29
|
-
* then falls back to
|
|
81
|
+
* resolver); scans the TAIL for the LAST `customTitle` (the current user
|
|
82
|
+
* rename — the transcript is append-only), then falls back to the first
|
|
83
|
+
* `agentName` seen. Never returns `aiTitle`.
|
|
30
84
|
* - **codex**: reads `~/.codex/session_index.jsonl`; returns `thread_name` for
|
|
31
85
|
* the entry whose `id === sessionId` (last-match wins).
|
|
32
86
|
* - Other hosts: returns undefined.
|
|
@@ -39,4 +93,34 @@ export declare function readHostSessionName(opts: {
|
|
|
39
93
|
sessionId?: string;
|
|
40
94
|
readers?: HostNameReaders;
|
|
41
95
|
}): string | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Build a re-callable resolver for this session's host-native display name, for
|
|
98
|
+
* the heartbeat refresh path (spec 2026-07-25-h2a-lane-addressing §D1b).
|
|
99
|
+
*
|
|
100
|
+
* `readHostSessionName` is a one-shot: it re-scans every project directory to
|
|
101
|
+
* locate the transcript. This factory memoizes the transcript path (the session
|
|
102
|
+
* id is fixed for the life of the server process) so a steady-state heartbeat
|
|
103
|
+
* costs one bounded tail read.
|
|
104
|
+
*
|
|
105
|
+
* **Negative caching (required, not an optimization).** A session whose
|
|
106
|
+
* transcript never appears is a real, observed state — it is the RC-3 case in the
|
|
107
|
+
* spec, where `CLAUDE_CODE_SESSION_ID` names a session with no transcript at all.
|
|
108
|
+
* Memoizing only on success meant that session re-walked
|
|
109
|
+
* `~/.claude/projects` — 87 directories, 14345 files, 8.73 ms — on EVERY
|
|
110
|
+
* heartbeat, i.e. every 5 seconds, forever. So a miss is cached too, with
|
|
111
|
+
* exponential backoff from `TRANSCRIPT_MISS_BACKOFF_MS` to
|
|
112
|
+
* `TRANSCRIPT_MISS_BACKOFF_MAX_MS`. A transcript that appears later is still
|
|
113
|
+
* picked up, just not instantly — bounded by the current backoff.
|
|
114
|
+
*
|
|
115
|
+
* Returns `undefined` whenever the title cannot be read — the caller must treat
|
|
116
|
+
* that as "keep the name you have", never as "fall back to the cwd basename".
|
|
117
|
+
*/
|
|
118
|
+
export declare function createHostSessionNameRefresher(opts: {
|
|
119
|
+
host: string;
|
|
120
|
+
cwd: string;
|
|
121
|
+
sessionId?: string;
|
|
122
|
+
readers?: HostNameReaders;
|
|
123
|
+
/** Injectable clock, for testing the negative cache without real waiting. */
|
|
124
|
+
now?: () => number;
|
|
125
|
+
}): () => string | undefined;
|
|
42
126
|
//# sourceMappingURL=readers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readers.d.ts","sourceRoot":"","sources":["../../../src/runtime/identity/readers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"readers.d.ts","sourceRoot":"","sources":["../../../src/runtime/identity/readers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAcH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AA2F5D,eAAO,MAAM,6BAA6B,EAAE,sBAK3C,CAAC;AAIF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,QAAY,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,QAAa,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,QAAS,CAAC;AACjD,8CAA8C;AAC9C,eAAO,MAAM,8BAA8B,SAAU,CAAC;AAEtD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;;OAQG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACxD;;;OAGG;IACH,qBAAqB,IAAI,MAAM,EAAE,CAAC;IAClC,kEAAkE;IAClE,OAAO,IAAI,MAAM,CAAC;IAClB;;;;;OAKG;IACH,oBAAoB,CAAC,CACnB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAAC;CACvB;AAED,eAAO,MAAM,sBAAsB,EAAE,eAiDpC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B,GAAG,MAAM,GAAG,SAAS,CAcrB;AAmKD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,GAAG,MAAM,MAAM,GAAG,SAAS,CAgC3B"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* never blocks connect). The transcript layouts were live-verified 2026-05-30
|
|
7
7
|
* (gemini's path layout drifts across versions — we match on `.project_root`).
|
|
8
8
|
*/
|
|
9
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
9
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
/** First newline-delimited line of a (possibly large) file, decoded best-effort. */
|
|
@@ -111,36 +111,97 @@ export const defaultProviderSessionReaders = {
|
|
|
111
111
|
geminiSessionForCwd,
|
|
112
112
|
agyConversationForCwd
|
|
113
113
|
};
|
|
114
|
+
// ── WP-6: host-native session name reading ────────────────────────────────
|
|
115
|
+
/**
|
|
116
|
+
* How much of the tail of a Claude transcript to scan for the current title.
|
|
117
|
+
*
|
|
118
|
+
* A transcript is append-only, and `customTitle` is carried by dedicated
|
|
119
|
+
* **rename-event records** (`type: "custom-title"`) — NOT by ordinary records.
|
|
120
|
+
* Measured 2026-07-25 over all 8078 local transcripts: title-bearing records are
|
|
121
|
+
* 0.06% of tail records (5.1% within the one heavily-renamed transcript that
|
|
122
|
+
* motivated this fix). The newest such record is therefore near the END, and a
|
|
123
|
+
* 64 KiB window reaches it: of the 45 transcripts that carry a title at all,
|
|
124
|
+
* **all 45** are resolved from this window — 0 missed, 0 wrong values.
|
|
125
|
+
*
|
|
126
|
+
* Bounded rather than whole-file because this now runs on the heartbeat and live
|
|
127
|
+
* transcripts reach hundreds of MB (largest local: 233 MB; cost 0.30 ms/call).
|
|
128
|
+
*/
|
|
129
|
+
export const CLAUDE_TITLE_TAIL_BYTES = 64 * 1024;
|
|
130
|
+
/**
|
|
131
|
+
* How much of the TAIL of `~/.codex/session_index.jsonl` to read. Codex resolves
|
|
132
|
+
* last-match-wins, so the tail is what matters; the file grows monotonically and
|
|
133
|
+
* this also runs on the heartbeat (48 KiB today, so today's whole file fits).
|
|
134
|
+
* A session whose entry has aged out of the window simply yields `undefined`,
|
|
135
|
+
* which the caller treats as "keep the name you have".
|
|
136
|
+
*/
|
|
137
|
+
export const CODEX_INDEX_TAIL_BYTES = 256 * 1024;
|
|
138
|
+
/**
|
|
139
|
+
* Upper bound on a display name written into presence. A host title is
|
|
140
|
+
* user-controlled and unbounded; `isH2ASession` only checks `typeof`, so an
|
|
141
|
+
* absurd title would otherwise land in every peer's presence read. Truncated
|
|
142
|
+
* rather than rejected so the name stays findable by the substring match that
|
|
143
|
+
* `discover_sessions(name:)` performs.
|
|
144
|
+
*/
|
|
145
|
+
export const MAX_DISPLAY_NAME_CHARS = 200;
|
|
146
|
+
/** Initial re-scan delay after a transcript lookup MISSES (negative cache). */
|
|
147
|
+
export const TRANSCRIPT_MISS_BACKOFF_MS = 60_000;
|
|
148
|
+
/** Ceiling for the negative-cache backoff. */
|
|
149
|
+
export const TRANSCRIPT_MISS_BACKOFF_MAX_MS = 300_000;
|
|
114
150
|
export const defaultHostNameReaders = {
|
|
115
|
-
|
|
151
|
+
readTailLines(path, maxBytes) {
|
|
152
|
+
let fd;
|
|
116
153
|
try {
|
|
117
|
-
const
|
|
118
|
-
|
|
154
|
+
const size = statSync(path).size;
|
|
155
|
+
const length = Math.min(size, Math.max(0, maxBytes));
|
|
156
|
+
const start = size - length;
|
|
157
|
+
const buf = Buffer.allocUnsafe(length);
|
|
158
|
+
fd = openSync(path, "r");
|
|
159
|
+
let read = 0;
|
|
160
|
+
while (read < length) {
|
|
161
|
+
const n = readSync(fd, buf, read, length - read, start + read);
|
|
162
|
+
if (n <= 0)
|
|
163
|
+
break;
|
|
164
|
+
read += n;
|
|
165
|
+
}
|
|
166
|
+
const lines = buf.subarray(0, read).toString("utf8").split("\n");
|
|
167
|
+
// When we started mid-file the first element is a partial record; drop it
|
|
168
|
+
// rather than letting JSON.parse fail on a truncated object.
|
|
169
|
+
if (start > 0)
|
|
170
|
+
lines.shift();
|
|
171
|
+
return lines;
|
|
119
172
|
}
|
|
120
173
|
catch {
|
|
121
174
|
return [];
|
|
122
175
|
}
|
|
176
|
+
finally {
|
|
177
|
+
if (fd !== undefined) {
|
|
178
|
+
try {
|
|
179
|
+
closeSync(fd);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// best-effort
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
123
186
|
},
|
|
124
187
|
readCodexSessionIndex() {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const raw = readFileSync(f, "utf8");
|
|
128
|
-
return raw.split("\n");
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
188
|
+
// Tail-bounded: this runs on the heartbeat and the index grows monotonically.
|
|
189
|
+
return defaultHostNameReaders.readTailLines(join(homedir(), ".codex", "session_index.jsonl"), CODEX_INDEX_TAIL_BYTES);
|
|
133
190
|
},
|
|
134
191
|
homedir() {
|
|
135
192
|
return homedir();
|
|
193
|
+
},
|
|
194
|
+
findClaudeTranscript(cwd, sessionId, home) {
|
|
195
|
+
return findClaudeTranscript(cwd, sessionId, home);
|
|
136
196
|
}
|
|
137
197
|
};
|
|
138
198
|
/**
|
|
139
199
|
* Read the host-native session name for the given session.
|
|
140
200
|
*
|
|
141
201
|
* - **claude**: reads the transcript JSONL (located via the CLAUDE_CODE_SESSION_ID
|
|
142
|
-
* resolver); scans the
|
|
143
|
-
* then falls back to
|
|
202
|
+
* resolver); scans the TAIL for the LAST `customTitle` (the current user
|
|
203
|
+
* rename — the transcript is append-only), then falls back to the first
|
|
204
|
+
* `agentName` seen. Never returns `aiTitle`.
|
|
144
205
|
* - **codex**: reads `~/.codex/session_index.jsonl`; returns `thread_name` for
|
|
145
206
|
* the entry whose `id === sessionId` (last-match wins).
|
|
146
207
|
* - Other hosts: returns undefined.
|
|
@@ -195,34 +256,176 @@ function findClaudeTranscript(cwd, sessionId, home = homedir()) {
|
|
|
195
256
|
return undefined;
|
|
196
257
|
}
|
|
197
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* C0/C1 control characters and Unicode bidi overrides/isolates.
|
|
261
|
+
*
|
|
262
|
+
* A display name is host-controlled free text that D3's list-and-ask presents to
|
|
263
|
+
* a HUMAN choosing a message recipient. A bidi override (U+202E) can make
|
|
264
|
+
* `auth<U+202E>gnitnuocca` render as `authaccounting`, so two candidates in a
|
|
265
|
+
* disambiguation list can be made to look alike; a C0 control can truncate or
|
|
266
|
+
* corrupt a terminal line. Neither is ever legitimate in a display name, so both
|
|
267
|
+
* are removed at INGEST rather than left to every consumer to re-discover.
|
|
268
|
+
*
|
|
269
|
+
* This is defence in depth, not a substitute for escaping: a renderer must still
|
|
270
|
+
* escape what it prints (spec §10.6), and candidate choice must never rest on
|
|
271
|
+
* title text alone.
|
|
272
|
+
*/
|
|
273
|
+
// eslint-disable-next-line no-control-regex
|
|
274
|
+
const UNSAFE_DISPLAY_CHARS = /[\u0000-\u001F\u007F-\u009F\u200E\u200F\u202A-\u202E\u2066-\u2069]/g;
|
|
275
|
+
/**
|
|
276
|
+
* Normalize a candidate display name: strip unsafe characters, trim, reject
|
|
277
|
+
* whitespace-only, and cap the length. Without the trim a title of `" "` is
|
|
278
|
+
* truthy and would be written to presence verbatim; without the cap an unbounded
|
|
279
|
+
* user-controlled string lands in every peer's presence read (`isH2ASession` only
|
|
280
|
+
* checks `typeof`).
|
|
281
|
+
*
|
|
282
|
+
* EVERY host reader must route its title through this function. Claude reaches it
|
|
283
|
+
* via `jsonField`; Codex calls it directly (`readCodexSessionName`). A reader that
|
|
284
|
+
* bypasses it writes untrimmed, unbounded, control-bearing text into presence.
|
|
285
|
+
*/
|
|
286
|
+
function normalizeTitle(value) {
|
|
287
|
+
if (typeof value !== "string")
|
|
288
|
+
return undefined;
|
|
289
|
+
const trimmed = value.replace(UNSAFE_DISPLAY_CHARS, "").trim();
|
|
290
|
+
if (trimmed.length === 0)
|
|
291
|
+
return undefined;
|
|
292
|
+
return trimmed.length > MAX_DISPLAY_NAME_CHARS
|
|
293
|
+
? trimmed.slice(0, MAX_DISPLAY_NAME_CHARS)
|
|
294
|
+
: trimmed;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Read one display-name field out of a JSONL record. Total (undefined on junk).
|
|
298
|
+
*
|
|
299
|
+
* `requireCustomTitleType` applies the same policy as
|
|
300
|
+
* `h2a-runtime/src/restore.ts` — only a dedicated rename-event record
|
|
301
|
+
* (`type: "custom-title"`) carries an authoritative title. See
|
|
302
|
+
* `titleFromClaudeTranscript` for the measurement behind that choice.
|
|
303
|
+
*/
|
|
304
|
+
function jsonField(line, key, requireCustomTitleType = false) {
|
|
305
|
+
const trimmed = line?.trim();
|
|
306
|
+
if (!trimmed)
|
|
307
|
+
return undefined;
|
|
308
|
+
try {
|
|
309
|
+
const obj = JSON.parse(trimmed);
|
|
310
|
+
if (requireCustomTitleType && obj.type !== "custom-title")
|
|
311
|
+
return undefined;
|
|
312
|
+
return normalizeTitle(obj[key]);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return undefined; // skip malformed lines
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Extract the CURRENT display title from a Claude transcript.
|
|
320
|
+
*
|
|
321
|
+
* A transcript is **append-only**, and a title lives on a dedicated rename-event
|
|
322
|
+
* record (`type: "custom-title"`), so a rename appends the NEW title at the END.
|
|
323
|
+
* We therefore scan **backwards** and take the LAST one.
|
|
324
|
+
*
|
|
325
|
+
* The pre-fix reader took the FIRST `customTitle` out of the first 40 lines,
|
|
326
|
+
* which is the title as of session start — it could never observe a rename, so
|
|
327
|
+
* `presence.name` stayed pinned to a stale title for the life of the session
|
|
328
|
+
* (measured live: 1022 records saying `39etc` then 65 saying `auth`; the reader
|
|
329
|
+
* returned `39etc` while the human saw `auth`).
|
|
330
|
+
*
|
|
331
|
+
* **Policy note (single source of truth).** `h2a-runtime/src/restore.ts:187-200`
|
|
332
|
+
* reads the same field and requires `type === "custom-title"`. This reader now
|
|
333
|
+
* applies the identical predicate, so the two agree by construction. That is not
|
|
334
|
+
* a guess: measured 2026-07-25 across all 8078 local transcripts, comparing both
|
|
335
|
+
* policies on the same window, there were **0 divergences**, and **0** of the 89
|
|
336
|
+
* title-bearing records carried the field on any other record type. The stricter
|
|
337
|
+
* predicate is chosen because a misread produces a WRONG name (bad routing) while
|
|
338
|
+
* a missed read produces NO name (the caller keeps the name it has).
|
|
339
|
+
*
|
|
340
|
+
* `agentName` keeps first-seen-wins — it is not user-mutable, so recency is
|
|
341
|
+
* meaningless for it, and it is not a rename event so the type check must not
|
|
342
|
+
* apply to it. `aiTitle` is never read, hence never returned.
|
|
343
|
+
*/
|
|
344
|
+
function titleFromClaudeTranscript(transcriptPath, readers) {
|
|
345
|
+
const lines = readers.readTailLines(transcriptPath, CLAUDE_TITLE_TAIL_BYTES);
|
|
346
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
347
|
+
// Cheap pre-filter before JSON.parse, mirroring restore.ts.
|
|
348
|
+
if (!lines[i]?.includes('"custom-title"'))
|
|
349
|
+
continue;
|
|
350
|
+
const customTitle = jsonField(lines[i], "customTitle", true);
|
|
351
|
+
if (customTitle)
|
|
352
|
+
return customTitle;
|
|
353
|
+
}
|
|
354
|
+
for (const line of lines) {
|
|
355
|
+
const agentName = jsonField(line, "agentName");
|
|
356
|
+
if (agentName)
|
|
357
|
+
return agentName;
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
/** Locate a transcript, preferring an injected finder (see `HostNameReaders`). */
|
|
362
|
+
function locateClaudeTranscript(cwd, sessionId, readers) {
|
|
363
|
+
return readers.findClaudeTranscript
|
|
364
|
+
? readers.findClaudeTranscript(cwd, sessionId, readers.homedir())
|
|
365
|
+
: findClaudeTranscript(cwd, sessionId, readers.homedir());
|
|
366
|
+
}
|
|
198
367
|
function readClaudeSessionName(cwd, sessionId, readers) {
|
|
199
368
|
if (!sessionId)
|
|
200
369
|
return undefined;
|
|
201
|
-
const transcriptPath =
|
|
370
|
+
const transcriptPath = locateClaudeTranscript(cwd, sessionId, readers);
|
|
202
371
|
if (!transcriptPath)
|
|
203
372
|
return undefined;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
373
|
+
return titleFromClaudeTranscript(transcriptPath, readers);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Build a re-callable resolver for this session's host-native display name, for
|
|
377
|
+
* the heartbeat refresh path (spec 2026-07-25-h2a-lane-addressing §D1b).
|
|
378
|
+
*
|
|
379
|
+
* `readHostSessionName` is a one-shot: it re-scans every project directory to
|
|
380
|
+
* locate the transcript. This factory memoizes the transcript path (the session
|
|
381
|
+
* id is fixed for the life of the server process) so a steady-state heartbeat
|
|
382
|
+
* costs one bounded tail read.
|
|
383
|
+
*
|
|
384
|
+
* **Negative caching (required, not an optimization).** A session whose
|
|
385
|
+
* transcript never appears is a real, observed state — it is the RC-3 case in the
|
|
386
|
+
* spec, where `CLAUDE_CODE_SESSION_ID` names a session with no transcript at all.
|
|
387
|
+
* Memoizing only on success meant that session re-walked
|
|
388
|
+
* `~/.claude/projects` — 87 directories, 14345 files, 8.73 ms — on EVERY
|
|
389
|
+
* heartbeat, i.e. every 5 seconds, forever. So a miss is cached too, with
|
|
390
|
+
* exponential backoff from `TRANSCRIPT_MISS_BACKOFF_MS` to
|
|
391
|
+
* `TRANSCRIPT_MISS_BACKOFF_MAX_MS`. A transcript that appears later is still
|
|
392
|
+
* picked up, just not instantly — bounded by the current backoff.
|
|
393
|
+
*
|
|
394
|
+
* Returns `undefined` whenever the title cannot be read — the caller must treat
|
|
395
|
+
* that as "keep the name you have", never as "fall back to the cwd basename".
|
|
396
|
+
*/
|
|
397
|
+
export function createHostSessionNameRefresher(opts) {
|
|
398
|
+
const readers = opts.readers ?? defaultHostNameReaders;
|
|
399
|
+
const now = opts.now ?? Date.now;
|
|
400
|
+
let cachedTranscript;
|
|
401
|
+
let nextScanAllowedAt = 0;
|
|
402
|
+
let missBackoffMs = TRANSCRIPT_MISS_BACKOFF_MS;
|
|
403
|
+
return () => {
|
|
211
404
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
customTitle = obj.customTitle;
|
|
215
|
-
break; // prefer first customTitle found
|
|
405
|
+
if (opts.host === "codex") {
|
|
406
|
+
return readCodexSessionName(opts.sessionId, readers);
|
|
216
407
|
}
|
|
217
|
-
|
|
218
|
-
|
|
408
|
+
const sessionId = opts.sessionId;
|
|
409
|
+
if (opts.host !== "claude" || !sessionId)
|
|
410
|
+
return undefined;
|
|
411
|
+
if (cachedTranscript === undefined) {
|
|
412
|
+
const at = now();
|
|
413
|
+
// Negative-cache hit: do NOT walk the filesystem.
|
|
414
|
+
if (at < nextScanAllowedAt)
|
|
415
|
+
return undefined;
|
|
416
|
+
cachedTranscript = locateClaudeTranscript(opts.cwd, sessionId, readers);
|
|
417
|
+
if (cachedTranscript === undefined) {
|
|
418
|
+
nextScanAllowedAt = at + missBackoffMs;
|
|
419
|
+
missBackoffMs = Math.min(missBackoffMs * 2, TRANSCRIPT_MISS_BACKOFF_MAX_MS);
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
219
422
|
}
|
|
423
|
+
return titleFromClaudeTranscript(cachedTranscript, readers);
|
|
220
424
|
}
|
|
221
425
|
catch {
|
|
222
|
-
//
|
|
426
|
+
return undefined; // a naming bug must never break the heartbeat
|
|
223
427
|
}
|
|
224
|
-
}
|
|
225
|
-
return customTitle ?? agentName;
|
|
428
|
+
};
|
|
226
429
|
}
|
|
227
430
|
function readCodexSessionName(sessionId, readers) {
|
|
228
431
|
if (!sessionId)
|
|
@@ -235,8 +438,14 @@ function readCodexSessionName(sessionId, readers) {
|
|
|
235
438
|
continue;
|
|
236
439
|
try {
|
|
237
440
|
const obj = JSON.parse(trimmed);
|
|
238
|
-
if (obj.id === sessionId
|
|
239
|
-
|
|
441
|
+
if (obj.id === sessionId) {
|
|
442
|
+
// Route through the SAME normalization as Claude. Previously this branch
|
|
443
|
+
// assigned `obj.thread_name` raw, so a Codex thread name was written to
|
|
444
|
+
// presence untrimmed, whitespace-only-accepted and UNBOUNDED — the one
|
|
445
|
+
// host-controlled presence input the length cap did not actually cover.
|
|
446
|
+
const normalized = normalizeTitle(obj.thread_name);
|
|
447
|
+
if (normalized !== undefined)
|
|
448
|
+
lastMatch = normalized;
|
|
240
449
|
}
|
|
241
450
|
}
|
|
242
451
|
catch {
|