jg-agent-hq 0.2.0 → 0.4.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +7 -4
  3. package/api/README.md +35 -17
  4. package/dist/actions/live/live.d.ts +4 -1
  5. package/dist/actions/live/live.js +22 -6
  6. package/dist/actions/reads.d.ts +1 -1
  7. package/dist/actions/reads.js +4 -2
  8. package/dist/contracts/actions/types.d.ts +6 -6
  9. package/dist/contracts/actions/types.js +1 -1
  10. package/dist/contracts/browser/index.d.ts +4 -0
  11. package/dist/contracts/browser/index.js +6 -0
  12. package/dist/contracts/browser/types.d.ts +2475 -0
  13. package/dist/contracts/browser/types.js +109 -0
  14. package/dist/contracts/handoffs/types.d.ts +5 -5
  15. package/dist/contracts/index.d.ts +4 -0
  16. package/dist/contracts/index.js +6 -0
  17. package/dist/contracts/plans/types.d.ts +25 -25
  18. package/dist/contracts/plans/types.js +9 -10
  19. package/dist/contracts/types.d.ts +96 -30
  20. package/dist/contracts/types.js +10 -2
  21. package/dist/handoffs/types.d.ts +6 -6
  22. package/dist/plans/admission.d.ts +2 -2
  23. package/dist/plans/admission.js +24 -16
  24. package/dist/plans/authority.d.ts +6 -0
  25. package/dist/plans/authority.js +39 -8
  26. package/dist/plans/finalization.d.ts +1 -1
  27. package/dist/plans/finalization.js +4 -3
  28. package/dist/plans/plans.d.ts +3 -3
  29. package/dist/plans/plans.js +9 -11
  30. package/dist/plans/reconciliation.js +3 -3
  31. package/dist/plans/revisions.d.ts +2 -2
  32. package/dist/plans/revisions.js +21 -3
  33. package/dist/plans/types.d.ts +4 -4
  34. package/dist/providers/claude/control/control.d.ts +1 -1
  35. package/dist/providers/claude/history/types.d.ts +39 -11
  36. package/dist/runtime/browser/browser.d.ts +8 -0
  37. package/dist/runtime/browser/browser.js +55 -0
  38. package/dist/runtime/browser/index.d.ts +1 -0
  39. package/dist/runtime/browser/index.js +2 -0
  40. package/dist/runtime/runtime.js +12 -1
  41. package/dist/runtime/types.d.ts +19 -1
  42. package/dist/store/browser/browser.model.d.ts +24 -0
  43. package/dist/store/browser/browser.model.js +75 -0
  44. package/dist/store/browser/index.d.ts +1 -0
  45. package/dist/store/browser/index.js +2 -0
  46. package/dist/store/control/types.d.ts +6 -6
  47. package/dist/store/plans/plans.model.d.ts +2 -2
  48. package/dist/store/plans/plans.model.js +1 -1
  49. package/dist/store/schema.sql +15 -2
  50. package/dist/store/store.js +4 -1
  51. package/dist/store/store.worker.js +11 -0
  52. package/dist/store/types.d.ts +1545 -510
  53. package/dist/store/types.js +17 -1
  54. package/errors/README.md +11 -1
  55. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Add closed browser host commands and bounded session/repository/plan read contracts, without exposing private handlers through standalone IPC.
6
+ - Persist operator-bound host action receipts before effects, reject changed replays, retain uncertainty across restart and join exact server-owned native child receipts.
7
+ - Add conditional live-window reads so unchanged SSE polling does not repeatedly copy or serialize conversation bodies.
8
+
9
+ ## 0.3.0
10
+
11
+ - Publish the consumer-tested package through the configured GitHub Actions trusted publisher before deploying the website. Verify release ordering and archive integrity, and safely skip identical published versions.
12
+ - Support whole-plan approval, execution, completion and revision when no milestone breakdown is needed. Preserve an absent milestone file and require explicit reconciliation when removing a sequence.
13
+ - Keep native thread execution scope on ordinary follow-ups, enforcing revocation and the single implementation owner for whole-plan execution. Preserve explicitly requested, revision-guarded feedback assessment without restoring execution authority. Keep existing-turn permission replies and session renaming separate from new execution admission.
14
+
3
15
  ## 0.2.0
4
16
 
5
17
  - Preserve coordinator-owned, revision-bound plan comparison evidence through plan operations. Expose its bounded count in status without copying review bodies into responses.
package/README.md CHANGED
@@ -21,7 +21,7 @@ This public package provides a local controller and browser-safe contracts. Appl
21
21
 
22
22
  Use pnpm 12.4.0. Run `pnpm install`, then `pnpm check:package` to build, exercise the built API and packed consumer, typecheck and check formatting. `pnpm test:unit` and `pnpm test:integration` select their own categories after a build. The private website workspace has separate checks; install Chromium with `pnpm --dir website exec playwright install chromium`, then run `pnpm --dir website check`. `pnpm check` runs both boundaries.
23
23
 
24
- Work on `development`; reviewed changes reach `main` through a pull request. npm releases are manual and use the exact tested tarball.
24
+ Work on `development`; reviewed changes reach `main` through a pull request. After both Node checks pass on `main`, `check.yml` publishes the exact consumer-tested tarball through npm's configured trusted publisher, then deploys the website. No local npm login or token is needed. An identical published version is skipped; changed contents require a new version.
25
25
 
26
26
  ## Lock ownership
27
27
 
@@ -45,7 +45,9 @@ Use `--provider claude` for Claude history and usage; the default is `codex`. Cl
45
45
 
46
46
  Explicit actions create, name, adopt, resume and close threads; send idle messages; steer an exact running turn; queue the next message; interrupt; and answer native questions or permissions. Actions have stable UUIDs and durable receipts. Repeating the same request returns its receipt; changing that UUID's payload conflicts. A lost acknowledgement stays uncertain until the exact native response arrives or the operator makes an explicit recovery choice. HQ never resends uncertain input.
47
47
 
48
- Native execution settings, instructions, skills and tool permissions remain with each CLI. Claude uses an owned structured process per controlled session and reports immediate steering as unavailable. Plan approval grants no native tool permission. Close other CLI controllers before confirming adoption, resume or recovery. HQ coordinates sessions sharing one state directory; it cannot lock independently running native CLIs. Durable state includes `action.key`, which must stay private and accompany its SQLite database. Queued undelivered text is saved; ordinary message bodies and question answers are not.
48
+ Native execution settings, instructions, skills and tool permissions remain with each CLI. Claude uses an owned structured process per controlled session and reports immediate steering as unavailable. Plan approval grants no native tool permission. Close other CLI controllers before confirming adoption, resume or recovery. HQ coordinates sessions sharing one state directory; it cannot lock independently running native CLIs. Integrating browser hosts can use durable host-action admission and operator-scoped reconciliation through the programmatic API; see the [browser host contract](api/README.md#browser-host-integration). The standalone CLI advertises only commands it implements.
49
+
50
+ Durable state includes `action.key`, which must stay private and accompany its SQLite database. Queued undelivered text is saved; ordinary message bodies and question answers are not.
49
51
 
50
52
  Use `suggest-thread-name --topic "Build usage" --state-root <directory>` for an editable label and `managed-thread-name --name build-usage --state-root <directory>` to resolve it. Creation adds a short ordinal suffix when a name is occupied. Actions and relationships retain immutable IDs.
51
53
 
@@ -57,11 +59,11 @@ Use `jg-agent-hq request --state-root <directory>` with a closed HQ command on s
57
59
 
58
60
  `plan-status --directory <path>` and `plan-milestones --directory <path>` inspect a selected ordinary planning directory. No project registry is required. Mutations use the existing `request` JSON command with `kind: 'plan'`; see the [plan command reference](api/README.md#plan-coordination).
59
61
 
60
- Finalization assigns milestone UUIDs and returns the complete canonical sequence. Plan approval, sequence approval and scoped execution authorization are separate explicit operator actions. None sends native input or answers tool permissions. `coordination.json` holds current records beside the plan. SQLite retains write replay identities and a replaceable, paged milestone projection. Keep both when recovering interrupted writes. A pending write blocks further mutation until its original action is reconciled.
62
+ Finalization assigns milestone UUIDs and returns the complete canonical sequence when a breakdown exists. A plan without a breakdown remains one unit: finalization returns null content, plan approval suffices, and whole-plan execution uses an empty milestone selection with a null milestone ID. No milestone file is created merely to make a small plan executable. Plan approval, sequence approval and scoped execution authorization are separate explicit operator actions. None sends native input or answers tool permissions. `coordination.json` holds current records beside the plan. SQLite retains write replay identities and a replaceable, paged milestone projection. Keep both when recovering interrupted writes. A pending write blocks further mutation until its original action is reconciled.
61
63
 
62
64
  Coordinators can retain revision-bound compatibility reviews in that same file. Core operations preserve the records without assessing them or granting authority. The integrating coordinator owns review freshness and conflict gates.
63
65
 
64
- Begin a revision before native planning. HQ stops bound implementation threads and their owned background work, captures partial-work evidence, and keeps execution paused while the CLI revises the plan and regenerates milestones. The finalizer carries exact matches automatically and asks for one confirmation covering every unmatched completed or partial milestone. Equivalent rewording preserves the original completion evidence. Changed requirements remain pending. Present and approve the canonical sequence, then explicitly authorize the new execution scope and mode. Neither approval nor restart resumes implementation. See the [revision recovery contract](api/README.md#revision-and-progress-recovery).
66
+ Begin a revision before native planning. HQ stops bound implementation threads and their owned background work, captures partial-work evidence, and keeps execution paused while the CLI revises the plan and regenerates milestones. The finalizer carries exact matches automatically and asks for one confirmation covering every unmatched completed or partial milestone. Equivalent rewording preserves the original completion evidence. Changed requirements remain pending. Present and approve the canonical sequence, then explicitly authorize the new execution scope and mode. Neither approval nor restart resumes implementation. Whole-plan checkpoints and completion evidence also work without a milestone file. Removing a sequence requires explicit confirmation covering its retained milestone evidence before whole-plan execution can begin. See the [revision recovery contract](api/README.md#revision-and-progress-recovery).
65
67
 
66
68
  ## Cross-provider feedback
67
69
 
@@ -87,6 +89,7 @@ Collect one completed reviewer message by its exact native turn/item reference.
87
89
  - `src/store/`: SQLite discovery metadata and the action/control ledger in one worker.
88
90
  - `src/contracts/`: browser-safe command and response contracts.
89
91
  - `scripts/package/`: build-output cleanup and packed-consumer checks.
92
+ - `scripts/release/`: registry version/integrity checks and trusted publication of the tested archive.
90
93
  - `scripts/test-support/`: disposable external CLI fixtures.
91
94
  - `vitest/`: separate unit and integration selection.
92
95
  - `api/README.md`: canonical public API reference.
package/api/README.md CHANGED
@@ -124,17 +124,17 @@ Trusted runtime construction also accepts `codexExtraSkillRoots?: string[]` and
124
124
 
125
125
  ## Control reads and reconnect
126
126
 
127
- | Command | Fields | Result |
128
- | --------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
129
- | `managed-threads` | `workspaceRoot`, optional `limit`, `cursor` | Creation/UUID seek page of HQ records, including inactive and uncertain states. |
130
- | `managed-thread` | `threadId` | One HQ record or null. |
131
- | `managed-thread-name` | `name` | One HQ record or null, with case-insensitive lookup and the same workspace authorization. |
132
- | `suggest-thread-name` | `topic` | An editable lowercase label, with a currently free suffix when needed. This read does not reserve it. |
133
- | `receipt` | `actionId` | One receipt or null. |
134
- | `actions` | `threadId`, optional `limit`, `cursor` | Creation/UUID seek page of action metadata without queued bodies. |
135
- | `events` | `threadId`, optional `after` (default 0), `limit` | Ascending durable sequence page and `hasMore`; continue after the final sequence. |
136
- | `approvals` | `threadId` | Current in-memory native prompts, including exact scope and question metadata. |
137
- | `live-items` | `threadId` | Active/awaiting-flush item window and `hasGap`. |
127
+ | Command | Fields | Result |
128
+ | --------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
129
+ | `managed-threads` | `workspaceRoot`, optional `limit`, `cursor` | Creation/UUID seek page of HQ records, including inactive and uncertain states. |
130
+ | `managed-thread` | `threadId` | One HQ record or null. |
131
+ | `managed-thread-name` | `name` | One HQ record or null, with case-insensitive lookup and the same workspace authorization. |
132
+ | `suggest-thread-name` | `topic` | An editable lowercase label, with a currently free suffix when needed. This read does not reserve it. |
133
+ | `receipt` | `actionId` | One receipt or null. |
134
+ | `actions` | `threadId`, optional `limit`, `cursor` | Creation/UUID seek page of action metadata without queued bodies. |
135
+ | `events` | `threadId`, optional `after` (default 0), `limit` | Ascending durable sequence page and `hasMore`; continue after the final sequence. |
136
+ | `approvals` | `threadId` | Current in-memory native prompts, including exact scope and question metadata. |
137
+ | `live-items` | `threadId` | Active/awaiting-flush item window, `hasGap`, `revision` and `unchanged`. Optional `afterRevision` omits bodies for an unchanged window. |
138
138
 
139
139
  The installed CLI exposes `managed-thread-name --name <name>` and `suggest-thread-name --topic <topic>`. Suggestions normalize punctuation to hyphens, keep at most 32 characters, and use `session` when the topic contains no ASCII letters or digits. Creation checks availability again inside its admission transaction. All action targets, histories and relationships continue to use immutable HQ/native identities. Name lookup never adopts, resumes or starts a native session.
140
140
 
@@ -179,18 +179,20 @@ Send `{ kind: 'plan', command }` through the existing local request API. Every c
179
179
  | `reconciliation` | `target`, `collection: 'sources' \| 'drafts' \| 'evidence' \| 'correspondences'`, optional `limit` (32/128) and `cursor`. Returns retained evidence by indexed seek page. |
180
180
  | `approve` | `target`, `action`, `expected`, `scope: 'plan' \| 'sequence'`, `presentationHash`. The hash must match the exact presented plan or finalized sequence. Sequence approval also requires current plan approval. |
181
181
  | `revoke` | `target`, `action`, `expected`, `scope: 'plan' \| 'sequence' \| 'execution' \| 'all'`. Revoking a plan also revokes sequence and execution authority; revoking a sequence also revokes execution authority. |
182
- | `authorize` | `target`, `action`, `expected`, `mode: 'supervised' \| 'auto'`, selected unique `milestoneIds`. Records permission for this exact execution scope without starting an agent. |
183
- | `check-execution` | `target`, `expected`, `authorizationId`, `milestoneId`, `mode`. Rejects missing, revoked, stale, wrong-mode or out-of-scope authority. It sends no native input. |
182
+ | `authorize` | `target`, `action`, `expected`, `mode: 'supervised' \| 'auto'`, selected unique `milestoneIds` (empty only for a plan without a breakdown). Records permission for this exact execution scope without starting an agent. |
183
+ | `check-execution` | `target`, `expected`, `authorizationId`, nullable `milestoneId`, `mode`. Rejects missing, revoked, stale, wrong-mode or out-of-scope authority. It sends no native input. |
184
184
 
185
185
  `expected` is the exact `{ planDigest, breakdownDigest }` returned by inspection. Each mutating `action` has a UUID `id`, `operatorId` and `source: { kind: 'user-action' | 'user-message', reference }`. These are trusted local operator inputs. The module never infers approval from natural-language claims, an agent response or imported feedback. Its owner-only local IPC and files do not isolate hostile code executing as the same OS user. Native permissions remain separate action kinds with their own exact native request identities.
186
186
 
187
+ Milestones are optional. Finalizing a plan with no milestone file returns `content: null` and does not create a file. That unit needs plan approval and explicit execution authorization, with `milestoneIds: []`; execution checks, completion and native execution scopes select it with `milestoneId: null`. A real breakdown requires a nonempty selection of its stable IDs and its separate sequence approval. Null cannot select a milestone or bypass sequence approval. Whole-plan completion retains its exact plan contract/digest, actual check references and checkout evidence, with null shared-sequence digest. Completed work cannot be authorized again under that unchanged contract.
188
+
187
189
  A first-use sequence has one `**Milestone ID:** <uuid>` immediately below each `**Status:**` line. Blank lines may separate the heading from its status; no intervening requirement prose is allowed. The initial finalizer rejects duplicate, malformed or misplaced IDs and inconsistent framing. It refreshes existing Overview status rows and returns the complete canonical file for presentation before approval. A changed finalized contract goes through checkpoint reconciliation. A raw or changed sequence cannot inherit finalized status or sequence authority. A done marker remains visible but blocks execution without completion evidence.
188
190
 
189
191
  The milestone contract includes its stable UUID, title without its ordinal, all requirement prose, shared sequence requirements and the approval statement. Status and identity marker lines are excluded from the prose; only the derived Overview table is excluded from shared text. Prose next to that table remains contractual. Sequence order is significant. Renumbering preserves execution identity; changed requirements, dependencies, scope, reservations or order require applicable approval. Full-file hashes preserve exact UTF-8 bytes and are distinct from normalized execution revisions. Administrative frontmatter excluded by the trusted contract parser, session labels, status advancement and coordination receipts do not change execution digests.
190
192
 
191
193
  Plan and milestone artifacts are bounded to 1 MiB each; coordination evidence is bounded to 8 MiB and a sequence to 1,024 milestones. Reads do not scan planning trees. Milestone pages use ordinal seek ordering with unique ascending ordinals. Cursors bind the selected directory, page size, execution revisions and exact milestone inspection hash. Any changed artifact requires a fresh page; renumbering or status-only edits still preserve approval. A SQLite projection is rebuilt only when the selected milestone file's full hash changes. Every read verifies its source file; the projection is replaceable and never grants authority.
192
194
 
193
- Mutations hold the shared metadata lock at `<coordinationRoot>/.runtime/hq.lock` (`coordinationRoot` defaults to `stateRoot`), retain a durable action identity and intended write before changing files, sync temporary files and atomically replace each artifact. Use `serve --coordination-root <directory>` when the canonical coordination lock is separate from runtime storage. Repeating an action UUID with the identical payload never reapproves revoked work. A changed payload conflicts. An interrupted write remains blocked and can be retried with its original action only when files match the recorded baseline or exact intended output. Conflicting edits are preserved. Completed write bodies are removed from SQLite while replay identity remains. The current finalization receipt retains the operator event, execution revision, canonical file hash and timestamp independently of write recovery bodies. Status reads expose `pendingActionId` for unresolved writes; they never approve or resume work.
195
+ Mutations hold the shared metadata lock at `<coordinationRoot>/.runtime/hq.lock` (`coordinationRoot` defaults to `stateRoot`), retain a durable action identity and intended write before changing files, sync temporary files and atomically replace each artifact. Use `serve --coordination-root <directory>` when the canonical coordination lock is separate from runtime storage. Repeating an action UUID with the identical payload never reapproves revoked work. A changed payload conflicts. An interrupted write remains blocked and can be retried with its original action only when files match the recorded baseline or exact intended output. Conflicting edits are preserved. Completed write bodies are removed from SQLite while replay identity remains. The current finalization receipt retains the operator event, execution revision, nullable canonical file hash and timestamp independently of write recovery bodies. Status reads expose `pendingActionId` for unresolved writes; they never approve or resume work.
194
196
 
195
197
  `coordination.json` includes required `comparisons`, an array bounded to 1,024 records validated by `PlanComparisonSchema`. Each record binds `sourceRevision`, `targetDirectory`, `targetRevision`, a `compatible` or `contradiction` verdict, `reason`, `reviewedAt` and reviewer provenance (`provider`, `cliVersion`, nullable observed `model`, and native message `reference`). A trusted coordinator writes these records while holding the same metadata lock. Core operations preserve them; status exposes only `comparisonCount`. They never change execution digests or grant authority. The coordinator owns relevance, freshness, semantic judgments, bounded review reads and any execution gate, including contradictions. The standalone core does not assess compatibility.
196
198
 
@@ -202,11 +204,13 @@ Begin revision before invoking a planning skill. Execution admission closes unde
202
204
 
203
205
  The local host supplies checkout evidence as `{ repository, directory, claimReference, paths }`. Paths are exact relative task paths. The core streams their contents, records link targets without following them, includes missing paths as deletions, and captures the current Git branch/HEAD. It does not stage or reset files and rejects traversal, parent-link escapes, `.git` and excluded archive/backup directories. Check commands and references are trusted operator evidence; this primitive does not execute arbitrary submitted commands or treat an imported agent claim as verification.
204
206
 
205
- After native `jg-plan` removes milestones and native `jg-breakdown` regenerates them, close the planning controller before finalization. Only unique exact normalized title and complete-contract matches retain identity automatically. Every unmatched checkpoint source, including done and partial work, appears in one saved proposal. Confirmation decisions use `sourceIds`, `targetIds`, `relation` (`equivalent`, `requirements-changed`, `omitted`, `reopened`) and explicit one-to-one `retainedPairs`. One source ID cannot be assigned to multiple targets. Grouped decisions retain split/merged evidence. A complete equivalent mapping preserves verified completion or partial status; rewording does not create new checks. A changed requirement gets fresh pending identity. Explicit reopening preserves evidence while invalidating its completion credit. Unverified done markers cannot become verified through an equivalent mapping.
207
+ After native `jg-plan` revises the contract and an explicitly requested `jg-breakdown` regenerates a needed sequence, close the planning controller before finalization. Only unique exact normalized title and complete-contract matches retain identity automatically. Every unmatched checkpoint source, including done and partial work, appears in one saved proposal. Confirmation decisions use `sourceIds`, `targetIds`, `relation` (`equivalent`, `requirements-changed`, `omitted`, `reopened`) and explicit one-to-one `retainedPairs`. One source ID cannot be assigned to multiple targets. Grouped decisions retain split/merged evidence. A complete equivalent mapping preserves verified completion or partial status; rewording does not create new checks. A changed requirement gets fresh pending identity. Explicit reopening preserves evidence while invalidating its completion credit. Unverified done markers cannot become verified through an equivalent mapping.
208
+
209
+ A whole-plan checkpoint retains null milestones and an empty milestone inventory. If a revision removes a sequence because the remaining scope no longer needs one, every unmatched checkpoint milestone still requires explicit omission confirmation. Its verified evidence remains available; absence alone never resets progress or authorizes whole-plan execution.
206
210
 
207
211
  The proposal and assigned identities are persisted before the canonical file is replaced. Retry the same action and payload after a partial write; do not regenerate UUIDs or confirm a different proposal. Present the complete canonical output before sequence approval. Applicable plan and sequence approvals are required again after digest changes. Approval does not resume work: send a fresh `authorize` with the intended mode and scope. Execution authority is also bound to the current runtime epoch.
208
212
 
209
- Guided native input adds `plan: { kind: 'execution', authorizationId, milestoneId, mode, revision }` or `{ kind: 'planning', revisionId, invocationId }` to its action envelope. The controller binds this context atomically to the managed thread. Planning starts with the exact authorized native skill. A live thread cannot switch scopes; close it before an explicit resume under a different scope. Queued input retains its original scope and cannot run under stale approval or during revision. Ordinary input on a bound thread remains subject to its current gate. Native permissions are still separate exact-request replies.
213
+ Guided native input adds `plan: { kind: 'execution', authorizationId, milestoneId, mode, revision }` or `{ kind: 'planning', revisionId, invocationId }` to its action envelope. The controller binds this context atomically to the managed thread. Planning starts with the exact authorized native skill. A live thread cannot switch scopes; close it before an explicit resume under a different scope. Queued input retains its original scope and cannot run under stale approval or during revision. Ordinary input on a bound thread retains that exact scope and remains subject to its current gate, including after revocation or completion. It cannot silently become unscoped input. Renaming and explicit replies to an existing native permission/question remain separate, revision-guarded controls; neither starts a new implementation turn or restores execution authority. Native permissions still require the exact pending request.
210
214
 
211
215
  Selected status/admission/queue observations detect external contract edits or milestone deletion, persist a closed gate and request exact bound-thread interruption. These observations grant no operator authority and report uncertain in-flight effects. The finalized baseline retains identities and completion evidence for explicit reconciliation of direct CLI edits. Invalid artifacts fail explicitly. No directory scan or filesystem watcher is used. Status projects Revising, approval/breakdown requirements, Ready, In progress, Completed and Blocked independently of provider process state.
212
216
 
@@ -246,8 +250,22 @@ Creation may still be pending when its response arrives. Read `show` until its c
246
250
 
247
251
  Collection reads exactly the requested native page through the provider adapter and accepts only one non-truncated agent message from the handoff's completed reviewer turn. Finding bodies are limited to 8 KiB UTF-8, never silently shortened. To select a later page, use the same `limit`, turn filter and preceding cursor as the native items read. A different message or changed body cannot overwrite collected findings. Full histories remain with the native CLIs. Persisted reviewer references retain the exact native session, turn and item identities.
248
252
 
249
- Feedback resolves installed `jg-feedback` in the author thread and carries the selected findings with exact source, reviewer, item, revision and commit references. Source contract or selected Git HEAD changes invalidate an unsent preview. The action's optional `expectedPlan` guards the author's captured revision under the existing plan lock; explicit `null` requires an unbound thread. This guard applies only to `message` and `queue`, grants no execution scope, and cannot answer tool permissions. Known native identities cannot be reassigned; existing control admission preserves the exact author for queued feedback. Existing revision gates pause stale queued feedback. Neither handoff delivery nor a completed assessment turn means recommendations were accepted or implementation was authorized.
253
+ Feedback resolves installed `jg-feedback` in the author thread and carries the selected findings with exact source, reviewer, item, revision and commit references. Source contract or selected Git HEAD changes invalidate an unsent preview. The action's optional `expectedPlan` guards the author's captured revision under the existing plan lock; explicit `null` requires an unbound thread. This guard applies only to `message` and `queue`, grants no execution scope, and cannot answer tool permissions. An explicitly selected native `jg-feedback` skill with a matching `expectedPlan` uses a neutral revision guard, so assessment remains available after execution is revoked or completed. It preserves the thread's existing implementation binding; ordinary input still requires valid execution authority. Queued assessment keeps that same revision guard at automatic or explicit dispatch. Known native identities cannot be reassigned; existing control admission preserves the exact author for queued feedback. Existing revision gates pause stale queued feedback. Neither handoff delivery nor a completed assessment turn means recommendations were accepted or implementation was authorized.
250
254
 
251
255
  Delivery is `prepared`, `queued`, `submitted`, `delivered`, `uncertain`, `failed` or `canceled`; native `completion` is separately null, `completed`, `failed` or `interrupted`. Acknowledgement alone becomes `delivered`. Busy destinations use native next-turn queues, including while waiting for permissions; feedback is never written as a permission answer. Pending native permissions remain explicit actions on their own channels. No automatic feedback, response loop, approval or publication follows collection.
252
256
 
253
257
  Lists use indexed `createdAt DESC, id DESC` seek ordering, default 32/max 128, and cursors bound to the workspace, source filter and page size. Concurrent newer inserts do not reappear on continuation; deleted unseen records disappear. Summaries do not transfer retained finding bodies through the worker. SQLite owns the small private `handoff` and `handoff_dispatch` records alongside existing actions. No database migration or transcript mirror is required. Reads are fresh and bounded; no separate cache is used.
258
+
259
+ ## Browser host integration
260
+
261
+ `jg-agent-hq/contracts` exports closed `BrowserActionSchema`, `BrowserCommandSchema` and `BrowserReceiptSchema` contracts, plus browser session, repository and bounded plan read models. These schemas contain generic identities, not a deployment registry or private handler implementation. They do not expose native RPC, executables or provider configuration. Standalone IPC continues to support only its existing owned commands.
262
+
263
+ A trusted Node host calls `runtime.performBrowserAction(request, operatorId, execute)` after validating its signed operator identity. The host supplies one dispatcher function in process. It must validate every target against its registry, check operation-specific revisions/turns, and normalize approval provenance to that authenticated operator. Never accept a callback, operator identity or handler name from HTTP input. The request has a stable UUID, current runtime epoch and one closed command: native, plan, handoff, priority, error-reservation or publication-retry. The complete serialized request is limited to 2 MiB; nested native commands retain their tighter limits.
264
+
265
+ SQLite records an HMAC digest and receipt before invoking the dispatcher. The same UUID and identical normalized request returns the stored receipt without invoking it again. Changed payloads or operators conflict. New requests require the current epoch. Replaying a saved request after restart reconciles it without granting execution authority. Submitted receipts from another runtime become uncertain. Dispatcher failure also leaves the receipt uncertain because its effects may already have committed. This state never automatically resends an effect.
266
+
267
+ The dispatcher receives a server-owned `nativeActionId`; use this ID for a native child action, preserving the validated runtime and command. `readBrowserAction(id, operatorId)` returns only that operator's receipt and joins its exact native receipt. Host acknowledgement means the handler completed, not that a native turn completed. An unknown or differently owned ID returns null. Receipts retain metadata rather than message bodies or arbitrary handler output. Subsequent domain reads provide the result. At most 32 host jobs are admitted concurrently. Runtime close rejects new host work and waits for admitted work before closing its worker.
268
+
269
+ Live reads accept `afterRevision`. A matching revision returns `unchanged: true` with an empty data array; retain the displayed window. A changed revision returns the complete bounded window. Revisions belong to one runtime. After reconnect to a new runtime, discard the revision and refresh native history. Token bodies stay in the existing bounded memory window and are never copied into lifecycle or browser-action tables.
270
+
271
+ `thread-plan` accepts a managed `threadId` and returns its nullable `ActionPlanContextSchema`. The host verifies the thread's registered checkout before exposing it. It is read-only and grants no execution authority.
@@ -3,6 +3,7 @@ import type { IHistoryItem } from '../../contracts/index.js';
3
3
  export declare class LiveItems {
4
4
  private readonly __threads;
5
5
  private __bytes;
6
+ private __revision;
6
7
  /** Starts a known presentation window without discarding retained awaiting-flush items. */
7
8
  open(nativeId: string): void;
8
9
  /** Replaces one native item without persisting token or tool-output bodies. */
@@ -14,7 +15,9 @@ export declare class LiveItems {
14
15
  /** Appends bounded text; a truncated prefix is never presented as the complete item. */
15
16
  append(nativeId: string, turnId: string, itemId: string, delta: string): void;
16
17
  /** Returns a copy of the bounded window and explicit incompleteness evidence. */
17
- read(nativeId: string): {
18
+ read(nativeId: string, afterRevision?: number): {
19
+ revision: number;
20
+ unchanged: boolean;
18
21
  data: (IHistoryItem & {
19
22
  state: 'active' | 'awaiting-flush';
20
23
  })[];
@@ -4,13 +4,14 @@ const MAX_LIVE_BYTES = 4 * 1024 * 1024;
4
4
  export class LiveItems {
5
5
  __threads = new Map();
6
6
  __bytes = 0;
7
+ __revision = 0;
7
8
  /** Starts a known presentation window without discarding retained awaiting-flush items. */
8
9
  open(nativeId) {
9
10
  if (this.__threads.has(nativeId))
10
11
  return;
11
12
  if (this.__threads.size >= 32)
12
13
  this.__remove(this.__threads.keys().next().value);
13
- this.__threads.set(nativeId, { items: new Map(), hasGap: false });
14
+ this.__threads.set(nativeId, { items: new Map(), hasGap: false, revision: ++this.__revision });
14
15
  }
15
16
  /** Replaces one native item without persisting token or tool-output bodies. */
16
17
  put(nativeId, item, isComplete) {
@@ -18,9 +19,10 @@ export class LiveItems {
18
19
  if (!thread) {
19
20
  if (this.__threads.size >= 32)
20
21
  this.__remove(this.__threads.keys().next().value);
21
- thread = { items: new Map(), hasGap: true };
22
+ thread = { items: new Map(), hasGap: true, revision: ++this.__revision };
22
23
  this.__threads.set(nativeId, thread);
23
24
  }
25
+ thread.revision = ++this.__revision;
24
26
  const key = JSON.stringify([item.turnId, item.nativeId]);
25
27
  const previous = thread.items.get(key);
26
28
  if (previous)
@@ -40,8 +42,10 @@ export class LiveItems {
40
42
  /** Marks missing native stream evidence without discarding the surviving presentation. */
41
43
  markGap(nativeId) {
42
44
  const thread = this.__threads.get(nativeId);
43
- if (thread)
45
+ if (thread) {
44
46
  thread.hasGap = true;
47
+ thread.revision = ++this.__revision;
48
+ }
45
49
  }
46
50
  /** Replaces an ephemeral stream identity when the native persisted identity arrives. */
47
51
  replace(nativeId, turnId, itemId) {
@@ -52,14 +56,17 @@ export class LiveItems {
52
56
  return;
53
57
  this.__bytes -= entry.bytes;
54
58
  thread.items.delete(key);
59
+ thread.revision = ++this.__revision;
55
60
  }
56
61
  /** Appends bounded text; a truncated prefix is never presented as the complete item. */
57
62
  append(nativeId, turnId, itemId, delta) {
58
63
  const thread = this.__threads.get(nativeId);
59
64
  const entry = thread?.items.get(JSON.stringify([turnId, itemId]));
60
65
  if (!entry || entry.isComplete) {
61
- if (thread)
66
+ if (thread) {
62
67
  thread.hasGap = true;
68
+ thread.revision = ++this.__revision;
69
+ }
63
70
  return;
64
71
  }
65
72
  if (entry.item.isTruncated)
@@ -75,10 +82,13 @@ export class LiveItems {
75
82
  }, false);
76
83
  }
77
84
  /** Returns a copy of the bounded window and explicit incompleteness evidence. */
78
- read(nativeId) {
85
+ read(nativeId, afterRevision) {
79
86
  const thread = this.__threads.get(nativeId);
87
+ const revision = thread?.revision ?? this.__revision;
80
88
  return {
81
- data: thread
89
+ revision,
90
+ unchanged: afterRevision === revision,
91
+ data: thread && afterRevision !== revision
82
92
  ? [...thread.items.values()].map((entry) => ({
83
93
  ...entry.item,
84
94
  state: entry.isComplete ? 'awaiting-flush' : 'active',
@@ -98,12 +108,15 @@ export class LiveItems {
98
108
  if (entry?.isComplete && JSON.stringify(entry.item) === JSON.stringify(item)) {
99
109
  this.__bytes -= entry.bytes;
100
110
  thread.items.delete(key);
111
+ thread.revision = ++this.__revision;
101
112
  }
102
113
  }
103
114
  }
104
115
  /** Keeps interrupted partial items available while their final native history is pending. */
105
116
  completeTurn(nativeId, turnId) {
106
117
  const thread = this.__threads.get(nativeId);
118
+ if (thread)
119
+ thread.revision = ++this.__revision;
107
120
  if (thread)
108
121
  for (const entry of thread.items.values())
109
122
  if (entry.item.turnId === turnId)
@@ -116,10 +129,12 @@ export class LiveItems {
116
129
  for (const entry of thread.items.values())
117
130
  this.__bytes -= entry.bytes;
118
131
  this.__threads.delete(nativeId);
132
+ this.__revision += 1;
119
133
  }
120
134
  /** Releases every retained body when the owning runtime has stopped. */
121
135
  clear() {
122
136
  this.__threads.clear();
137
+ this.__revision += 1;
123
138
  this.__bytes = 0;
124
139
  }
125
140
  __drop(thread, key) {
@@ -128,6 +143,7 @@ export class LiveItems {
128
143
  return;
129
144
  this.__bytes -= entry.bytes;
130
145
  thread.items.delete(key);
146
+ thread.revision = ++this.__revision;
131
147
  thread.hasGap = true;
132
148
  }
133
149
  }
@@ -2,7 +2,7 @@ import type { IHqRequest, IHqResponse } from '../contracts/index.js';
2
2
  import type { IStore } from '../store/index.js';
3
3
  import type { createActions } from './actions.js';
4
4
  type IControlRead = Extract<IHqRequest, {
5
- kind: 'receipt' | 'managed-threads' | 'managed-thread' | 'managed-thread-name' | 'actions' | 'events' | 'approvals' | 'live-items';
5
+ kind: 'thread-plan' | 'receipt' | 'managed-threads' | 'managed-thread' | 'managed-thread-name' | 'actions' | 'events' | 'approvals' | 'live-items';
6
6
  }>;
7
7
  /**
8
8
  * Authorizes bounded control reads and binds seek positions to their exact query.
@@ -75,6 +75,8 @@ export const readControl = async (command, store, actions, roots) => {
75
75
  }
76
76
  const thread = await store.getThread(command.threadId);
77
77
  await authorize(thread);
78
+ if (command.kind === 'thread-plan')
79
+ return { kind: 'thread-plan', result: thread ? await store.getThreadPlan(thread.id) : null };
78
80
  if (command.kind === 'managed-thread')
79
81
  return { kind: command.kind, result: thread };
80
82
  if (!thread)
@@ -95,7 +97,7 @@ export const readControl = async (command, store, actions, roots) => {
95
97
  return {
96
98
  kind: 'live-items',
97
99
  result: thread.nativeId
98
- ? actions.live.read(JSON.stringify([thread.provider, thread.nativeId]))
99
- : { data: [], hasGap: false },
100
+ ? actions.live.read(JSON.stringify([thread.provider, thread.nativeId]), command.afterRevision)
101
+ : { data: [], hasGap: false, revision: 0, unchanged: command.afterRevision === 0 },
100
102
  };
101
103
  };
@@ -112,7 +112,7 @@ export type IActionCommand = z.infer<typeof ActionCommandSchema>;
112
112
  export declare const PlanActionScopeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
113
113
  kind: z.ZodLiteral<"execution">;
114
114
  authorizationId: z.ZodUUID;
115
- milestoneId: z.ZodUUID;
115
+ milestoneId: z.ZodNullable<z.ZodUUID>;
116
116
  mode: z.ZodEnum<{
117
117
  supervised: "supervised";
118
118
  auto: "auto";
@@ -132,7 +132,7 @@ export declare const ActionPlanContextSchema: z.ZodObject<{
132
132
  scope: z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
133
133
  kind: z.ZodLiteral<"execution">;
134
134
  authorizationId: z.ZodUUID;
135
- milestoneId: z.ZodUUID;
135
+ milestoneId: z.ZodNullable<z.ZodUUID>;
136
136
  mode: z.ZodEnum<{
137
137
  supervised: "supervised";
138
138
  auto: "auto";
@@ -267,7 +267,7 @@ export declare const ActionEnvelopeSchema: z.ZodObject<{
267
267
  plan: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
268
268
  kind: z.ZodLiteral<"execution">;
269
269
  authorizationId: z.ZodUUID;
270
- milestoneId: z.ZodUUID;
270
+ milestoneId: z.ZodNullable<z.ZodUUID>;
271
271
  mode: z.ZodEnum<{
272
272
  supervised: "supervised";
273
273
  auto: "auto";
@@ -286,7 +286,7 @@ export declare const ActionEnvelopeSchema: z.ZodObject<{
286
286
  scope: z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
287
287
  kind: z.ZodLiteral<"execution">;
288
288
  authorizationId: z.ZodUUID;
289
- milestoneId: z.ZodUUID;
289
+ milestoneId: z.ZodNullable<z.ZodUUID>;
290
290
  mode: z.ZodEnum<{
291
291
  supervised: "supervised";
292
292
  auto: "auto";
@@ -354,7 +354,7 @@ export declare const ActionReceiptSchema: z.ZodObject<{
354
354
  scope: z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
355
355
  kind: z.ZodLiteral<"execution">;
356
356
  authorizationId: z.ZodUUID;
357
- milestoneId: z.ZodUUID;
357
+ milestoneId: z.ZodNullable<z.ZodUUID>;
358
358
  mode: z.ZodEnum<{
359
359
  supervised: "supervised";
360
360
  auto: "auto";
@@ -430,7 +430,7 @@ export declare const ActionSummarySchema: z.ZodObject<{
430
430
  scope: z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
431
431
  kind: z.ZodLiteral<"execution">;
432
432
  authorizationId: z.ZodUUID;
433
- milestoneId: z.ZodUUID;
433
+ milestoneId: z.ZodNullable<z.ZodUUID>;
434
434
  mode: z.ZodEnum<{
435
435
  supervised: "supervised";
436
436
  auto: "auto";
@@ -78,7 +78,7 @@ export const PlanActionScopeSchema = z.discriminatedUnion('kind', [
78
78
  z.strictObject({
79
79
  kind: z.literal('execution'),
80
80
  authorizationId: z.uuid(),
81
- milestoneId: z.uuid(),
81
+ milestoneId: z.uuid().nullable(),
82
82
  mode: z.enum(['supervised', 'auto']),
83
83
  revision: PlanRevisionSchema,
84
84
  }),
@@ -0,0 +1,4 @@
1
+ export { BrowserCommandSchema, BrowserActionSchema, BrowserReceiptSchema } from './types.js';
2
+ export type { IBrowserAction, IBrowserReceipt } from './types.js';
3
+ export { BrowserRepositorySchema, BrowserPlanSummarySchema, BrowserPlanPageSchema, BrowserPlanDetailSchema, BrowserSessionSchema, } from './types.js';
4
+ export { BrowserRepositoryPageSchema, BrowserSystemSchema, BrowserThreadDetailSchema, } from './types.js';
@@ -0,0 +1,6 @@
1
+ // browser requests and durable reconciliation
2
+ export { BrowserCommandSchema, BrowserActionSchema, BrowserReceiptSchema } from './types.js';
3
+ // host read models
4
+ export { BrowserRepositorySchema, BrowserPlanSummarySchema, BrowserPlanPageSchema, BrowserPlanDetailSchema, BrowserSessionSchema, } from './types.js';
5
+ // bounded host views
6
+ export { BrowserRepositoryPageSchema, BrowserSystemSchema, BrowserThreadDetailSchema, } from './types.js';