jg-agent-hq 2.0.0 → 3.0.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 (75) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +4 -4
  3. package/api/README.md +51 -8
  4. package/dist/cli/cli.js +35 -3
  5. package/dist/cli/utilities.d.ts +3 -0
  6. package/dist/cli/utilities.js +30 -0
  7. package/dist/contracts/index.d.ts +2 -2
  8. package/dist/contracts/index.js +1 -1
  9. package/dist/contracts/types.d.ts +102 -0
  10. package/dist/contracts/types.js +24 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +2 -0
  13. package/dist/plans/authority.d.ts +2 -0
  14. package/dist/plans/authority.js +8 -0
  15. package/dist/plans/documents.d.ts +17 -0
  16. package/dist/plans/documents.js +24 -2
  17. package/dist/plans/files.js +3 -1
  18. package/dist/plans/plans.js +3 -1
  19. package/dist/plans/types.d.ts +5 -0
  20. package/dist/providers/claude/claude.js +2 -0
  21. package/dist/providers/claude/history/types.d.ts +23 -0
  22. package/dist/providers/claude/transformers.js +14 -1
  23. package/dist/providers/codex/codex.js +4 -0
  24. package/dist/providers/codex/transformers.js +30 -3
  25. package/dist/providers/codex/types.d.ts +34 -1
  26. package/dist/providers/codex/types.js +11 -2
  27. package/dist/runtime/runtime.d.ts +2 -0
  28. package/dist/runtime/runtime.js +2 -0
  29. package/dist/runtime/state/index.d.ts +1 -1
  30. package/dist/runtime/state/index.js +1 -1
  31. package/dist/runtime/state/state.d.ts +16 -1
  32. package/dist/runtime/state/state.js +94 -3
  33. package/dist/snapshots/capture.d.ts +14 -0
  34. package/dist/snapshots/capture.js +142 -0
  35. package/dist/snapshots/database.d.ts +2 -0
  36. package/dist/snapshots/database.js +15 -51
  37. package/dist/snapshots/fences.d.ts +15 -0
  38. package/dist/snapshots/fences.js +64 -0
  39. package/dist/snapshots/index.d.ts +2 -0
  40. package/dist/snapshots/index.js +3 -0
  41. package/dist/snapshots/snapshots.d.ts +4 -1
  42. package/dist/snapshots/snapshots.js +27 -113
  43. package/dist/snapshots/snapshots.worker.js +5 -1
  44. package/dist/snapshots/types.d.ts +11 -0
  45. package/dist/snapshots/types.js +5 -1
  46. package/dist/storage/index.d.ts +2 -1
  47. package/dist/storage/index.js +2 -1
  48. package/dist/storage/schema/constants.d.ts +2 -0
  49. package/dist/storage/schema/constants.js +3 -0
  50. package/dist/storage/schema/index.d.ts +4 -0
  51. package/dist/storage/schema/index.js +5 -0
  52. package/dist/storage/schema/schema.d.ts +16 -0
  53. package/dist/storage/schema/schema.js +73 -0
  54. package/dist/storage/schema/types.d.ts +17 -0
  55. package/dist/storage/schema/types.js +8 -0
  56. package/dist/storage/schema/v1-to-v2.sql +1 -0
  57. package/dist/storage/schema/v1.sql +538 -0
  58. package/dist/storage/types.d.ts +0 -5
  59. package/dist/storage/types.js +0 -6
  60. package/dist/storage/upgrade/index.d.ts +2 -0
  61. package/dist/storage/upgrade/index.js +2 -0
  62. package/dist/storage/upgrade/types.d.ts +61 -0
  63. package/dist/storage/upgrade/types.js +42 -0
  64. package/dist/storage/upgrade/upgrade.d.ts +33 -0
  65. package/dist/storage/upgrade/upgrade.js +196 -0
  66. package/dist/storage/upgrade/upgrade.worker.d.ts +1 -0
  67. package/dist/storage/upgrade/upgrade.worker.js +220 -0
  68. package/dist/store/schema.d.ts +2 -0
  69. package/dist/store/schema.js +16 -25
  70. package/dist/store/schema.sql +2 -1
  71. package/dist/store/store.d.ts +2 -0
  72. package/dist/store/store.js +2 -0
  73. package/docs/recovery.md +15 -1
  74. package/errors/README.md +34 -28
  75. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.0
4
+
5
+ - Check protocol 0.2 plans with jg-plans 0.3.0 at each stage that grants authority: plan approval requires plan.md to conform, and finalization, sequence approval, authorization and execution require plan.md and milestones.md to conform together. Reading, status and revision workflows are unaffected, and plans without the 0.2 marker keep their current behavior.
6
+
7
+ - Version HQ storage independently of host user_version. Add explicit offline Node/CLI upgrades with a verified pre-upgrade backup, transactional rollback and uncertainty handling, retaining published 2.0.0 snapshots and version-specific restore fences.
8
+
9
+ - Expose native Codex credit balances and available earned resets without consuming them. Preserve valid single-bucket quota responses when the multi-bucket view is absent.
10
+ - Identify Claude five-hour and seven-day usage meters with explicit model/surface scopes, preserving independent windows, reset times and account-bound freshness. Keep older provider observations compatible.
11
+
3
12
  ## 2.0.0
4
13
 
5
14
  - Add provider-neutral session capabilities, native lifecycle/configuration, file inputs, structured results, permissions, account observations and extension controls. Detect usable native contracts instead of restricting CLI version labels; isolate unavailable providers.
package/README.md CHANGED
@@ -41,13 +41,13 @@ Install the providers you want and sign in through their native CLIs, then start
41
41
 
42
42
  The Node API exposes `createAgentHq({ stateRoot, allowedRoots })` and `requestAgentHq(stateRoot, command)`. Configuration comes from these trusted operator inputs. Coding-instruction files are not runtime configuration.
43
43
 
44
- The controller owns its SQLite worker and native connections independently of client connections. Its provider registry starts each native integration only when selected. Storage and managed-session reads work without an installed coding CLI; an unavailable provider does not disable another provider. Private state contains the lifetime owner, Unix socket and SQLite discovery metadata. Conversation histories remain in native storage; HQ retains complete bounded final results on their action receipts. Browsing never resumes an external session or starts inference. Quota windows come from native account observations, with explicit stale and unavailable states.
44
+ The controller owns its SQLite worker and native connections independently of client connections. Its provider registry starts each native integration only when selected. Storage and managed-session reads work without an installed coding CLI; an unavailable provider does not disable another provider. Private state contains the lifetime owner, Unix socket and SQLite discovery metadata. Conversation histories remain in native storage; HQ retains complete bounded final results on their action receipts. Browsing never resumes an external session or starts inference. Quota windows come from native account observations, with explicit stale and unavailable states. Usage reads include Codex credit balances and available earned-reset counts when reported, plus Claude five-hour and seven-day meters with their model/surface scopes and reset times.
45
45
 
46
46
  Use `sessions` for discovery and `session-evidence` for newest-first historical evidence. These reads fit within 65,536 serialized bytes, retain exact native identities and content hashes, and continue large bodies in UTF-8 chunks. Select an item, turn or item kinds with filters. Matching names remain separate sessions. Provenance distinguishes native evidence from exact host submissions; historical text and quoted feedback never grant approval. See [bounded session evidence](api/README.md#bounded-session-evidence).
47
47
 
48
- Trusted Node hosts can open the database described by `hq.storage` and keep application tables under their own prefix. HQ reserves `hq_`, validates its current schema stamp on startup, and leaves application schema ownership and `user_version` with the host. Use `hq.withStorageMaintenance(callback)` for host schema changes; it drains admitted work and rejects active native execution before the callback. Start with a fresh development state root when the HQ schema changes. See the [shared storage contract](api/README.md#shared-storage).
48
+ Trusted Node hosts can open the database described by `hq.storage` and keep application tables under their own prefix. HQ reserves `hq_`, validates its current schema stamp on startup, and leaves application schema ownership and `user_version` with the host. Use `hq.withStorageMaintenance(callback)` for host schema changes; it drains admitted work and rejects active native execution before the callback. Fresh state starts at HQ storage version 2. Published 2.0.0 state requires an explicit offline upgrade; startup never migrates. Stop HQ and independent database/plan writers, then run `jg-agent-hq upgrade-storage --state-root /path/to/state --backup-directory /private/backups/before-upgrade --workspace-root /path/to/workspace`. The backup destination must be absent under an existing private canonical parent. Keep the verified backup. Trusted Node hosts use `upgradeHqStorage(options)` for the same operation. See the [shared storage contract](api/README.md#shared-storage).
49
49
 
50
- `createHqSnapshot(hq, destination)` captures quiescent local state through SQLite's backup API, including host tables, stored reports, action.key and registered plan companions. Busy checkpoints return a conflict without interrupting work. `inspectHqSnapshot(directory)` verifies the complete snapshot; `restoreHqSnapshot({ snapshotDirectory, destination })` validates and stages it into a new private recovery workspace. Restored automation is paused, skill grants are revoked, native ownership needs reconciliation and allowed roots must be selected explicitly. Hosts own remote backups; a failed disk can lose changes since the last successful off-machine backup. See [snapshot and restore](api/README.md#snapshot-and-restore) and [recovery](docs/recovery.md).
50
+ `createHqSnapshot(hq, destination)` captures quiescent local state through SQLite's backup API, including host tables, stored reports, action.key and registered plan companions. Busy checkpoints return a conflict without interrupting work. `inspectHqSnapshot(directory)` verifies the complete snapshot; `restoreHqSnapshot({ snapshotDirectory, destination })` validates and stages it into a new private recovery workspace. Version 1 and version 2 snapshots remain supported. Restored version 1 storage requires the explicit offline upgrade before startup. Restored automation is paused, skill grants are revoked, native ownership needs reconciliation and allowed roots must be selected explicitly. Hosts own remote backups; a failed disk can lose changes since the last successful off-machine backup. See [snapshot and restore](api/README.md#snapshot-and-restore) and [recovery](docs/recovery.md).
51
51
 
52
52
  Provider IDs are bounded lowercase identifiers. Codex and Claude are built in; trusted Node hosts can register additional `IAgentProviderFactory` implementations through `providerFactories`. Capabilities report current operation support, provider/session scope and unavailable reasons. Native version labels are diagnostic metadata and never determine admission. Unknown essential control data fails explicitly. `jg-agent-hq/contracts` contains browser-safe schemas and types; importing it does not import the host runtime.
53
53
 
@@ -127,7 +127,7 @@ The [qualification matrix](api/README.md#qualification) records native journeys,
127
127
  - `src/collaboration/identity/` and `src/agent-tools/`: native-bound identity, private bridge credentials, shared agent tools and the stdio MCP entry.
128
128
  - `src/actions/`: durable action dispatch, native input routing and bounded live presentation.
129
129
  - `src/store/`: SQLite discovery metadata, collaboration streams and the action/control ledger in one worker.
130
- - `src/storage/`: trusted host connection metadata and exclusive maintenance admission.
130
+ - `src/storage/`: trusted host metadata, exclusive maintenance, schema lineage and offline upgrades.
131
131
  - `src/snapshots/`: quiescent capture, streamed inventory validation and offline recovery with execution paused.
132
132
  - `src/contracts/`: browser-safe command and response contracts.
133
133
  - `scripts/package/`: build-output cleanup and packed-consumer checks.
package/api/README.md CHANGED
@@ -22,12 +22,49 @@ All operations may expose the relevant intentional contracts in [errors](../erro
22
22
 
23
23
  `hq.storage` returns an independent `IStorageDescriptor` containing the canonical `databasePath`, `schemaHash`, `reservedPrefix` (`hq_`), and required `connectionSettings`: `foreignKeys` true, `busyTimeoutMs` 5000, `journalMode` wal, and `synchronous` full. Exported Node types are `IStorageDescriptor` and `IStorageMaintenanceOperation<T>`. No database handle, storage callback, raw SQL command or storage path is exposed by browser contracts or IPC.
24
24
 
25
- For node:sqlite, open the host connection with `new DatabaseSync(storage.databasePath, { timeout: storage.connectionSettings.busyTimeoutMs, enableForeignKeyConstraints: storage.connectionSettings.foreignKeys })`. These options enable foreign keys and a 5,000 ms busy timeout; the descriptor itself is not a DatabaseSync options object. See the [Node 24 SQLite API](https://nodejs.org/download/release/v24.15.0/docs/api/sqlite.html#new-databasesyncpath-options). Keep WAL mode and FULL synchronization; do not change database-wide settings while HQ is running. Hosts own their table prefix, definitions, connection lifetime and user_version. HQ-owned tables and indexes are reserved. A nonempty database requires the matching singleton hq_schema stamp; otherwise startup throws JG_AGENT_HQ_STORAGE_INVALID without changing the database. The stamp also binds the SHA-256 fingerprint of action.key; a different valid-length key fails with JG_AGENT_HQ_ACTION_KEY_UNAVAILABLE before a writer opens. Fresh initialization is transactional.
25
+ For node:sqlite, open the host connection with `new DatabaseSync(storage.databasePath, { timeout: storage.connectionSettings.busyTimeoutMs, enableForeignKeyConstraints: storage.connectionSettings.foreignKeys })`. These options enable foreign keys and a 5,000 ms busy timeout; the descriptor itself is not a DatabaseSync options object. See the [Node 24 SQLite API](https://nodejs.org/download/release/v24.15.0/docs/api/sqlite.html#new-databasesyncpath-options). Keep WAL mode and FULL synchronization; do not change database-wide settings while HQ is running. Hosts own their table prefix, definitions, connection lifetime and user_version. HQ-owned tables and indexes are reserved. A nonempty database requires a recognized singleton hq_schema stamp. Fresh state uses version 2. Exact published 2.0.0 state is version 1 and startup throws JG_AGENT_HQ_STORAGE_UPGRADE_REQUIRED before opening a writer. Unknown well-formed versions/hashes use JG_AGENT_HQ_STORAGE_VERSION_UNSUPPORTED; malformed stamps and conflicting known version/hash pairs use JG_AGENT_HQ_STORAGE_INVALID. Rejected storage remains unchanged. The stamp also binds the SHA-256 fingerprint of action.key; a different valid-length key fails with JG_AGENT_HQ_ACTION_KEY_UNAVAILABLE before a writer opens. Fresh initialization is transactional.
26
26
 
27
27
  `await hq.withStorageMaintenance(operation)` passes the storage descriptor to the async operation and returns its result. It closes admission before draining accepted HQ operations, allows their awaited nested calls and required native evidence to finish, and checks for active native execution or prepared plan writes before invoking the callback. Concurrent maintenance, new HQ requests, browser host effects and shutdown conflict while maintenance is held. During the callback, native event persistence waits in its existing bounded lane, and an already accepted scheduled stop waits for release. The callback must not call HQ, start native work or leave transactions open. Its errors propagate unchanged; it owns rollback and reconciliation for its SQL effects. After success or failure, HQ admission resumes. Calls after shutdown use JG_AGENT_HQ_RUNTIME_NOT_RUNNING.
28
28
 
29
29
  The host must coordinate every application connection that can write during maintenance. A shared file gives SQLite transaction isolation, not an atomic transaction spanning host SQL and HQ API calls. HQ does not expose its worker connection. Snapshot scheduling and remote backup operations belong to the host application.
30
30
 
31
+ ## Offline storage upgrade
32
+
33
+ Import `upgradeHqStorage`, `IHqStorageUpgradeOptions` and `IHqStorageUpgradeResult` from the Node root. This operation is absent from browser contracts, IPC and agent tools.
34
+
35
+ ```ts
36
+ interface IHqStorageUpgradeOptions {
37
+ stateRoot: string;
38
+ backupDirectory: string;
39
+ allowedRoots: string[];
40
+ coordinationRoot?: string;
41
+ }
42
+ interface IHqStorageUpgradeResult {
43
+ stateRoot: string;
44
+ state: 'upgraded' | 'current';
45
+ fromVersion: number;
46
+ toVersion: number;
47
+ fromSchemaHash: string;
48
+ toSchemaHash: string;
49
+ backupDirectory: string | null;
50
+ }
51
+ // upgradeHqStorage(options): Promise<IHqStorageUpgradeResult>
52
+ ```
53
+
54
+ Stop HQ and its native children, close independent application database writers and pause independent plan writers first. Supply existing canonical absolute state/workspace paths and an absent backup directory beneath an existing private canonical parent. The backup must not overlap the source state or any registered plan source. coordinationRoot defaults to stateRoot. A separate coordinationRoot may be a normal project directory; its .runtime lock directory remains private. Inputs are trusted operator selections; stored roots never grant access. Existing live, dead, incomplete or unverifiable lifetime owners block the operation and are never automatically reclaimed.
55
+
56
+ The installed CLI invokes the same API directly without a controller, listening socket or provider:
57
+
58
+ ```sh
59
+ jg-agent-hq upgrade-storage --state-root /path/to/state --backup-directory /private/backups/before-upgrade --workspace-root /path/to/workspace --workspace-root /path/to/other-workspace --coordination-root /path/to/coordination
60
+ ```
61
+
62
+ At least one --workspace-root is required; --coordination-root is optional. Other command flags are rejected, and --backup-directory is rejected on other commands. Success writes one stdout JSON line `{ schemaVersion: 1, kind: 'upgrade-storage', result }`. Failure retains exit 1 and stderr `{ schemaVersion: 1, success: false, error: { kind, message } }`. Only the four canonical storage upgrade/version messages bypass generic CLI classifications; private causes, paths and diagnostic suffixes are removed. `serve` presents the same explicit-upgrade-required message on legacy state. Help documents this operator path.
63
+
64
+ Read-only classification rejects unsupported/corrupt state before a writer or backup reservation. A current version returns current with backupDirectory null, without DDL, backup reservation or startup reconciliation. For version 1, lifetime ownership and the metadata lock protect a BEGIN IMMEDIATE checkpoint. A separate read-only connection captures the same complete format-1 snapshot as createHqSnapshot, including host tables, the private key, exact reports and registered companions. Full verification precedes DDL. Migration updates version/hash in one transaction, verifies integrity/foreign keys and preserves application objects, user_version, keys, receipts, queues, grants and collaboration state. Migration grants no authority and starts no native work.
65
+
66
+ The dedicated worker has no whole-operation size deadline. The caller waits for actual exit before lifetime ownership is released. A pre-commit failure rolls back; once a backup verifies, it remains the operator's recovery deliverable even if migration fails. Post-commit, acknowledgement or cleanup uncertainty uses JG_AGENT_HQ_STORAGE_UPGRADE_UNCERTAIN with trusted Node phase/backup diagnostics. A lost worker result may identify only the requested backup path; inspect its completion evidence before using it. Never automatically reverse or replay it. An explicit retry reads the durable stamp and returns current if committed. Process death can leave ownership requiring deliberate inspection. Keep the verified backup and follow the [recovery procedure](../docs/recovery.md); no down-migration is supplied.
67
+
31
68
  ## Snapshot and restore
32
69
 
33
70
  Import createHqSnapshot, inspectHqSnapshot and restoreHqSnapshot from jg-agent-hq in a trusted Node host. They are absent from browser contracts, IPC and agent tools. The exported types are IHqSnapshotInspection, IHqRestoreOptions and IHqRestoreResult.
@@ -44,15 +81,15 @@ SQLite's worker-owned backup API includes committed WAL data and every table in
44
81
 
45
82
  The one inventory format is manifest.jsonl: header, database, action-key and ordered plan records. Each plan's generated directory is `plans/<SHA-256 of source directory>`. Records retain exact source directory, observed plan ID/revision, companion byte counts/hashes and bounded diagnostics. Missing source plans, parser observations and corrupt coordination remain visible evidence; capture does not repair them or require checker conformance. Registered plans outside the runtime's current allowed roots retain an unavailable record without reading their files. A final complete.json binds the complete manifest hash and record count. Directories are private (0700), files are private (0600), and destinations are created exclusively. Runtime sockets, owner records, process IDs, bridge credentials and provider authentication are excluded.
46
83
 
47
- Inspection streams the inventory, verifies each listed regular file and every expected/absent companion, rejects links and unsafe paths, checks the current HQ schema stamp and database/key pairing, SQLite integrity/foreign keys, exact report hashes and complete registered-plan coverage. It rechecks captured plan identities and diagnostics. Full database verification runs in a dedicated worker. Inspection returns bounded directory/header/hash/count metadata; it does not load a growing inventory into memory. Hashes detect corruption and bind content; they do not authenticate a snapshot supplied by an untrusted party.
84
+ Inspection streams the inventory, verifies each listed regular file and every expected/absent companion, rejects links and unsafe paths, checks an exact supported HQ schema stamp (version 1 or 2) and database/key pairing, SQLite integrity/foreign keys, exact report hashes and complete registered-plan coverage. It rechecks captured plan identities and diagnostics. Full database verification runs in a dedicated worker. Inspection returns bounded directory/header/hash/count metadata; it does not load a growing inventory into memory. Hashes detect corruption and bind content; they do not authenticate a snapshot supplied by an untrusted party.
48
85
 
49
86
  Restore leaves every existing destination untouched, including an empty directory, repository or live state root. Use a new directory. It validates the source before reserving that directory and validates the staged copy independently against the initially inspected manifest hash before changing recovery authority. On success, stateRoot points to destination/state, recoveryRoot to destination/plans, recovery.jsonl retains the source inventory and recovered.json records completion. Caught failures remove only the operation's newly created output. A crash can leave incomplete output; do not open it as a recovered runtime.
50
87
 
51
88
  Restored task and agent lifecycles are paused unless already stopped, automatic dispatch is disabled, all skill grants are revoked, live approvals expire and queued input loses readiness. Submitted actions retain uncertainty. Managed native identities become uncertain and require explicit ownership reconciliation. IDs, stored reports, waiting child sets/continuation reservations, delivery links, historical receipts and exact prepared document intents remain intact. No native operation runs during restore.
52
89
 
53
- Call createAgentHq with the returned stateRoot and an explicitly selected allowedRoots array. Runtime construction never imports source root permissions from the snapshot and always assigns a new epoch; stored execution and revision leases cannot authorize work in it. Native source workspace paths and plan bindings remain evidence and are not rewritten. Recover repository/native files separately. Inspect recovery.jsonl and copy selected companions into explicitly chosen plan locations only after comparing current files; restore never overwrites a source repository. Rebind task plan references and obtain fresh plan/execution authority where needed, reconcile pending document writes using their exact IDs/baselines, and make explicit current-revision lifecycle/native ownership decisions before enabling dispatch. Source paths outside the newly selected roots remain unavailable.
90
+ Restore never upgrades storage. Both supported versions explicitly select restore fence version 1; a registered version without a compatible fence is refused before output is reserved. A version 1 restore remains version 1 and requires upgradeHqStorage before startup. Call createAgentHq with the returned stateRoot and an explicitly selected allowedRoots array. Runtime construction never imports source root permissions from the snapshot and always assigns a new epoch; stored execution and revision leases cannot authorize work in it. Native source workspace paths and plan bindings remain evidence and are not rewritten. Recover repository/native files separately. Inspect recovery.jsonl and copy selected companions into explicitly chosen plan locations only after comparing current files; restore never overwrites a source repository. Rebind task plan references and obtain fresh plan/execution authority where needed, reconcile pending document writes using their exact IDs/baselines, and make explicit current-revision lifecycle/native ownership decisions before enabling dispatch. Source paths outside the newly selected roots remain unavailable.
54
91
 
55
- These exports use SNAPSHOT_INVALID for incomplete, corrupt, unsafe or occupied snapshot/recovery selections and SNAPSHOT_FAILED for operational copy, backup, lock-release, worker or cleanup failures. Capture additionally exposes ACTION_CONFLICT for a busy checkpoint. Operational messages retain private diagnostic causes; no snapshot exception is exposed through public RPC.
92
+ These exports use SNAPSHOT_INVALID for incomplete, corrupt, unsafe or occupied snapshot/recovery selections and SNAPSHOT_FAILED for operational copy, backup, lock-release, worker or cleanup failures. Capture additionally exposes ACTION_CONFLICT for a busy checkpoint. Inspection/restore expose STORAGE_VERSION_UNSUPPORTED for an unsupported lineage or missing compatible restore fence. Operational messages retain private diagnostic causes; no snapshot exception is exposed through public RPC.
56
93
 
57
94
  Snapshots are plaintext local state. The host owns encryption/key recovery, scheduling, uploads, retention and last-success status. A disk failure can lose every change after the last successful off-machine backup. A local snapshot alone provides no remote durability. See the [recovery workflow](../docs/recovery.md).
58
95
 
@@ -317,11 +354,17 @@ Items expose only normalized presentation fields. User/agent text, commands and
317
354
 
318
355
  ## Subscription observations
319
356
 
357
+ The additive usage metadata is optional for compatibility with older providers and clients. Built-in adapters populate unknown metadata with null. Windows expose period (five-hour, seven-day or other), nativeKind, group and scope. Scope contains a nullable model with id/displayName and a nullable surface. Codex derives period from the actual duration; Claude normalizes recognized session and weekly groups to 300 and 10080 minutes. Primary/secondary remains the native position, not the period's identity. Existing bucket and window IDs remain stable.
358
+
359
+ credits contains independent bucket observations with hasCredits, isUnlimited and an exact nullable balance string. Its null value means unknown, never zero; do not add balances across buckets or assume a currency. resetCredits contains a nonnegative safe availableCount, independent of the provider's potentially capped reset detail list. Native account IDs and reset detail rows are not exposed. Credit/reset metadata can be available even when quota windows are unavailable; it grants no permission and provides no reset-redemption or purchase operation.
360
+
361
+ Codex prefers a valid nonempty rateLimitsByLimitId map and falls back to rateLimits when the map is absent, null or empty, without merging both views. A legacy bucket without a native limitId uses legacy as its bucket ID. Missing or malformed optional credit/reset metadata becomes unknown while valid windows remain visible. Invalid quota-map content does not bypass boundary validation through the fallback.
362
+
320
363
  Usage retains actual bucket/window identities, optional native labels, reported used percentage, remaining percentage, window duration and reset time. Percentages are never estimated from token counts or summed across sessions. Reported usage above 100 is preserved; remaining percentage floors at zero. `ordinaryUsageAllowed` is nullable when the native response does not report permission.
321
364
 
322
- `available` means a usable observation exists. `exhausted` requires Codex's native permission/reached signal or a fully used Claude meter that the native response marks active. Neither label authorizes a model operation. `unavailable` covers missing windows or an observation that could not be obtained. Failed refreshes preserve a measured snapshot as `stale`, with its original `observedAt`. Observations older than 60 seconds or past a reported reset are stale. Codex account/rate-limit notifications trigger a coalesced refresh. Claude auth notifications discard account measurements and invalidate responses started for that account; rate-limit events mark the observation stale until the current or next structured refresh succeeds.
365
+ `available` means a usable observation exists. `exhausted` requires Codex's native permission/reached signal or a fully used Claude meter that the native response marks active. Neither label authorizes a model operation. `unavailable` covers missing windows or an observation that could not be obtained. Failed refreshes preserve a measured snapshot as `stale`, with its original `observedAt`. The complete prior observation, including credit/reset metadata and scopes, shares this freshness; an account change clears every field and invalidates responses started for the prior account. Observations older than 60 seconds or past a reported reset are stale. Codex account/rate-limit notifications trigger a coalesced refresh. Claude auth notifications discard account measurements and invalidate responses started for that account; rate-limit events mark the observation stale until the current or next structured refresh succeeds.
323
366
 
324
- Claude uses the experimental `get_usage` control with `skip_behaviors: true`, and validates the native `rate_limits.limits` rows rather than inferring quotas from session costs or reading credentials. The [published provider control types](https://app.unpkg.com/@anthropic-ai/claude-agent-sdk@0.3.301/files/sdk.d.ts) describe this protocol; HQ invokes the installed CLI directly. Independent model/surface windows stay separate. Null percentages are omitted; zero is retained when reported. Unsupported shapes are unavailable. Claude does not report a duration or ordinary-usage permission in this contract, so those fields remain null. No private OAuth endpoint, credential scrape or status-line execution is used.
367
+ Claude uses the experimental `get_usage` control with `skip_behaviors: true`, and validates the native `rate_limits.limits` rows rather than inferring quotas from session costs or reading credentials. The [published provider control types](https://app.unpkg.com/@anthropic-ai/claude-agent-sdk@0.3.301/files/sdk.d.ts) describe this protocol; HQ invokes the installed CLI directly. Independent model/surface windows stay separate. Null percentages are omitted; zero is retained when reported. Unsupported shapes are unavailable. Claude does not report ordinary-usage permission, so it remains null. Recognized session and weekly groups have normalized five-hour and seven-day periods; unknown groups retain their native identity with period other and a null duration. No private OAuth endpoint, credential scrape or status-line execution is used.
325
368
 
326
369
  Every usage read requests current native observations; the last successful snapshot is retained only to explain freshness after a failure. There is no TTL cache that suppresses a native refresh. Multiple simultaneous reads share one pending refresh.
327
370
 
@@ -337,7 +380,7 @@ Output is one JSON line with `schemaVersion: 1`. Read output includes the comman
337
380
 
338
381
  `computePlanRevision({ body, metadata, milestones })` is the pure Node API for execution identity. `metadata` contains `scope`, `repositories`, `errorReservations` and JSON-safe `extensions`. Reservation entries contain `repository`, `namespace`, inclusive `start`/`end`, `registryPath` and `purpose`; this API hashes them and does not allocate codes. Repository and reservation order is canonicalized; object keys use strict deterministic JSON. The exact parsed body, including its line endings, is contractual.
339
382
 
340
- Send `{ kind: 'plan', command }` through the existing local request API. Every command selects `target: { directory }`, an absolute directory inside an allowed workspace. The default profile uses jg-plans observations: absent HQ fields default to local scope and empty repository/reservation lists; unknown fields, including plan `id`, remain in `extensions`. Only `created_at`, `updated_at`, `revision` and `status` are administrative. Malformed, non-mapping or non-JSON metadata fails closed. No preferred-document conformance gate is imposed.
383
+ Send `{ kind: 'plan', command }` through the existing local request API. Every command selects `target: { directory }`, an absolute directory inside an allowed workspace. The default profile uses jg-plans observations: absent HQ fields default to local scope and empty repository/reservation lists; unknown fields, including plan `id`, remain in `extensions`. Only `created_at`, `updated_at`, `revision` and `status` are administrative. Malformed, non-mapping or non-JSON metadata fails closed. Plans without `protocol: "0.2"` get no preferred-document conformance gate. A plan that declares protocol 0.2 must pass the jg-plans check at each stage that grants authority: plan approval needs plan.md to conform on its own, while finalization, sequence approval, authorization and execution checks need plan.md and milestones.md to conform together. A nonconforming plan stays readable, so `status`, inspection and revision workflows continue. `approve`, `finalize` and `authorize` reject it with `JG_AGENT_HQ_PLAN_INVALID`, and `check-execution` and admission stay closed.
341
384
 
342
385
  A trusted private host may supply `parsePlanContract({ directory, content })`. It must return exactly the body observed by jg-plans and valid HQ metadata. That host owns its alternative metadata profile and must put its contractual extension fields in `extensions`. It cannot exclude body prose. The private server resolves operator identity itself and never forwards browser-supplied roots, contract exclusions or metadata.
343
386
 
@@ -363,7 +406,7 @@ Milestones are optional. Finalizing a plan with no milestone file returns `conte
363
406
 
364
407
  Skills bindings are optional for generic HQ plans. When present, their syntax must be valid and their plan ID, revision and structural fingerprint must match before finalization or execution. A valid but stale binding remains visible in status: plan approval and explicit revision invocations remain available so jg-plan can retain the old sequence until jg-breakdown refreshes its coverage. Malformed bindings fail as invalid artifacts. A stale binding never supplies sequence approval or completion, even when semantic execution digests are unchanged.
365
408
 
366
- 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 preserves existing Overview text 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.
409
+ 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. In a protocol 0.2 sequence, a milestone's `**Acceptance criteria:**` coverage line follows its ID line and is milestone contract prose. The initial finalizer rejects duplicate, malformed or misplaced IDs and inconsistent framing. It preserves existing Overview text 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.
367
410
 
368
411
  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 semantic execution revisions. Administrative frontmatter excluded by the trusted contract parser, session labels, status advancement and coordination receipts do not change execution digests.
369
412
 
package/dist/cli/cli.js CHANGED
@@ -4,12 +4,13 @@ import { parseArgs } from 'node:util';
4
4
  import { setTimeout as wait } from 'node:timers/promises';
5
5
  import { Exception, hasErrorCode, isException } from 'jg-exception';
6
6
  import { HqRequestSchema } from '../contracts/index.js';
7
- import { toPublicError } from '../errors/index.js';
7
+ import { upgradeHqStorage } from '../storage/upgrade/index.js';
8
8
  import { requestAgentHq } from '../ipc/index.js';
9
9
  import { createAgentHq } from '../runtime/index.js';
10
10
  import { readRuntimeOwner } from '../runtime/state/index.js';
11
11
  import { serveAgentBridge } from '../agent-tools/mcp/index.js';
12
12
  import { AGENT_BRIDGE_FILE_ENV } from '../agent-tools/index.js';
13
+ import { toCliError } from './utilities.js';
13
14
  /** Runs the installed CLI without creating provider work merely to read history. */
14
15
  const main = async () => {
15
16
  if (process.argv[2] === 'agent-bridge') {
@@ -26,6 +27,7 @@ const main = async () => {
26
27
  allowPositionals: true,
27
28
  options: {
28
29
  'state-root': { type: 'string' },
30
+ 'backup-directory': { type: 'string' },
29
31
  'coordination-root': { type: 'string' },
30
32
  directory: { type: 'string' },
31
33
  'workspace-root': { type: 'string', multiple: true },
@@ -50,7 +52,7 @@ const main = async () => {
50
52
  });
51
53
  const [kind] = positionals;
52
54
  if (values.help || !kind || kind === 'help') {
53
- process.stdout.write('jg-agent-hq serve|status|stop|capabilities|threads|thread|turns|items|sessions|session-evidence|usage|account|models|configuration|mcp-servers|mcp-tools|mcp-resources|managed-thread-name|suggest-thread-name|skills|skill-preview|plan-status|plan-milestones|request --state-root <path> [--provider <provider-id>] [--workspace-root <path>] [--native-id <id>] [--limit <1..128>] [--cursor <cursor>] [--name <name>] [--topic <topic>] [--thread-id <id>] [--server-name <name>] [--skill-id <id>] [--arguments <text>] [--directory <path>]\nserve accepts repeated --codex-extra-skill-root and --claude-additional-directory.\nsession-evidence accepts --native-item-id and repeated --evidence-kind.\nrequest reads one validated HQ command as JSON from stdin.\n');
55
+ process.stdout.write('jg-agent-hq serve|upgrade-storage|status|stop|capabilities|threads|thread|turns|items|sessions|session-evidence|usage|account|models|configuration|mcp-servers|mcp-tools|mcp-resources|managed-thread-name|suggest-thread-name|skills|skill-preview|plan-status|plan-milestones|request --state-root <path> [--provider <provider-id>] [--workspace-root <path>] [--native-id <id>] [--limit <1..128>] [--cursor <cursor>] [--name <name>] [--topic <topic>] [--thread-id <id>] [--server-name <name>] [--skill-id <id>] [--arguments <text>] [--directory <path>]\nserve accepts repeated --codex-extra-skill-root and --claude-additional-directory.\nsession-evidence accepts --native-item-id and repeated --evidence-kind.\nrequest reads one validated HQ command as JSON from stdin.\nupgrade-storage --state-root <existing-path> --backup-directory <absent-path> --workspace-root <path> [--workspace-root <path> ...] [--coordination-root <path>]\nStop HQ/native children and independent database/plan writers first. Existing owners are never reclaimed.\nLegacy serve errors require upgrade-storage; startup never migrates. Keep the verified backup. Uncertain outcomes require explicit reconciliation; retry inspects the durable stamp.\n');
54
56
  return;
55
57
  }
56
58
  if (positionals.length !== 1 || !values['state-root'])
@@ -60,6 +62,36 @@ const main = async () => {
60
62
  });
61
63
  const stateRoot = resolve(values['state-root']);
62
64
  const roots = (values['workspace-root'] ?? []).map((root) => resolve(root));
65
+ if (kind === 'upgrade-storage') {
66
+ const allowed = new Set([
67
+ 'state-root',
68
+ 'backup-directory',
69
+ 'workspace-root',
70
+ 'coordination-root',
71
+ ]);
72
+ if (!values['backup-directory'] ||
73
+ roots.length === 0 ||
74
+ Object.keys(values).some((key) => !allowed.has(key)))
75
+ throw new Exception('The HQ request is invalid', {
76
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
77
+ data: { kind: 'invalid-request' },
78
+ });
79
+ const result = await upgradeHqStorage({
80
+ stateRoot,
81
+ backupDirectory: resolve(values['backup-directory']),
82
+ allowedRoots: roots,
83
+ coordinationRoot: values['coordination-root']
84
+ ? resolve(values['coordination-root'])
85
+ : undefined,
86
+ });
87
+ process.stdout.write(`${JSON.stringify({ schemaVersion: 1, kind: 'upgrade-storage', result })}\n`);
88
+ return;
89
+ }
90
+ if (values['backup-directory'] !== undefined)
91
+ throw new Exception('The HQ request is invalid', {
92
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
93
+ data: { kind: 'invalid-request' },
94
+ });
63
95
  if (kind === 'request') {
64
96
  if (Object.keys(values).some((key) => key !== 'state-root'))
65
97
  throw new Exception('The HQ request is invalid', {
@@ -281,6 +313,6 @@ try {
281
313
  await main();
282
314
  }
283
315
  catch (error) {
284
- process.stderr.write(`${JSON.stringify({ schemaVersion: 1, success: false, error: toPublicError(error) })}\n`);
316
+ process.stderr.write(`${JSON.stringify({ schemaVersion: 1, success: false, error: toCliError(error) })}\n`);
285
317
  process.exitCode = 1;
286
318
  }
@@ -0,0 +1,3 @@
1
+ import { toPublicError } from '../errors/index.js';
2
+ /** Presents static storage instructions without exposing diagnostic paths, suffixes or causes. */
3
+ export declare const toCliError: (error: unknown) => ReturnType<typeof toPublicError>;
@@ -0,0 +1,30 @@
1
+ import { isException } from 'jg-exception';
2
+ import { toPublicError } from '../errors/index.js';
3
+ // only these operator-facing storage diagnoses bypass the generic IPC presentation
4
+ const STORAGE_MESSAGES = {
5
+ JG_AGENT_HQ_STORAGE_UPGRADE_REQUIRED: {
6
+ kind: 'state-conflict',
7
+ message: 'The HQ storage requires an explicit upgrade',
8
+ },
9
+ JG_AGENT_HQ_STORAGE_VERSION_UNSUPPORTED: {
10
+ kind: 'state-conflict',
11
+ message: 'The HQ storage version is unsupported',
12
+ },
13
+ JG_AGENT_HQ_STORAGE_UPGRADE_FAILED: {
14
+ kind: 'operation-failed',
15
+ message: 'The HQ storage upgrade failed',
16
+ },
17
+ JG_AGENT_HQ_STORAGE_UPGRADE_UNCERTAIN: {
18
+ kind: 'operation-failed',
19
+ message: 'The HQ storage upgrade requires reconciliation',
20
+ },
21
+ };
22
+ /** Presents static storage instructions without exposing diagnostic paths, suffixes or causes. */
23
+ export const toCliError = (error) => {
24
+ if (isException(error)) {
25
+ for (const [code, presentation] of Object.entries(STORAGE_MESSAGES))
26
+ if (error.code === code)
27
+ return presentation;
28
+ }
29
+ return toPublicError(error);
30
+ };
@@ -3,10 +3,10 @@ export type { IAccountControlCommand, ILoginObservation, INativeControlResult, }
3
3
  export { ExtensionControlCommandSchema } from './extensions/index.js';
4
4
  export type { IExtensionControlCommand } from './extensions/index.js';
5
5
  export { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, PUBLIC_ERROR_CODES, PUBLIC_ERROR_MESSAGES, } from './constants.js';
6
- export { HqRequestSchema, ThreadSummarySchema, TurnSummarySchema, HistoryItemSchema, LiveItemsSchema, UsageWindowSchema, UsageSnapshotSchema, RuntimeStatusSchema, HqResponseSchema, ErrorKindSchema, IpcRequestSchema, IpcResponseSchema, } from './types.js';
6
+ export { HqRequestSchema, ThreadSummarySchema, TurnSummarySchema, HistoryItemSchema, LiveItemsSchema, UsageWindowSchema, UsageSnapshotSchema, UsageCreditsSchema, UsageResetCreditsSchema, UsageWindowScopeSchema, RuntimeStatusSchema, HqResponseSchema, ErrorKindSchema, IpcRequestSchema, IpcResponseSchema, } from './types.js';
7
7
  export { ProviderIdSchema, ProviderCapabilitySchema, ProviderCapabilitySetSchema, ProviderCapabilitiesSchema, ProviderStatusSchema, MAX_PROVIDERS, } from './providers/index.js';
8
8
  export type { IProviderId, IProviderCapability, IProviderCapabilitySet, IProviderCapabilities, IProviderStatus, } from './providers/index.js';
9
- export type { IHqRequest, IHqRequestInput, IThreadSummary, ITurnSummary, IHistoryItem, IUsageSnapshot, IRuntimeStatus, IHqResponse, IErrorKind, IIpcResponse, } from './types.js';
9
+ export type { IHqRequest, IHqRequestInput, IThreadSummary, ITurnSummary, IHistoryItem, IUsageSnapshot, IUsageCredits, IUsageResetCredits, IUsageWindowScope, IRuntimeStatus, IHqResponse, IErrorKind, IIpcResponse, } from './types.js';
10
10
  export { NativeLineageSchema } from './identities/index.js';
11
11
  export type { INativeLineage } from './identities/index.js';
12
12
  export { ProviderModelSchema, ProviderModelsRequestSchema, ProviderModelPageSchema, } from './models/index.js';
@@ -4,7 +4,7 @@ export { ExtensionControlCommandSchema } from './extensions/index.js';
4
4
  // constants
5
5
  export { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, PUBLIC_ERROR_CODES, PUBLIC_ERROR_MESSAGES, } from './constants.js';
6
6
  // wire contracts
7
- export { HqRequestSchema, ThreadSummarySchema, TurnSummarySchema, HistoryItemSchema, LiveItemsSchema, UsageWindowSchema, UsageSnapshotSchema, RuntimeStatusSchema, HqResponseSchema, ErrorKindSchema, IpcRequestSchema, IpcResponseSchema, } from './types.js';
7
+ export { HqRequestSchema, ThreadSummarySchema, TurnSummarySchema, HistoryItemSchema, LiveItemsSchema, UsageWindowSchema, UsageSnapshotSchema, UsageCreditsSchema, UsageResetCreditsSchema, UsageWindowScopeSchema, RuntimeStatusSchema, HqResponseSchema, ErrorKindSchema, IpcRequestSchema, IpcResponseSchema, } from './types.js';
8
8
  // provider identity and capability discovery
9
9
  export { ProviderIdSchema, ProviderCapabilitySchema, ProviderCapabilitySetSchema, ProviderCapabilitiesSchema, ProviderStatusSchema, MAX_PROVIDERS, } from './providers/index.js';
10
10
  // native identities
@@ -1684,6 +1684,25 @@ export declare const LiveItemsSchema: z.ZodObject<{
1684
1684
  }, z.core.$strip>>;
1685
1685
  hasGap: z.ZodBoolean;
1686
1686
  }, z.core.$strip>;
1687
+ export declare const UsageCreditsSchema: z.ZodObject<{
1688
+ bucket: z.ZodString;
1689
+ hasCredits: z.ZodBoolean;
1690
+ isUnlimited: z.ZodBoolean;
1691
+ balance: z.ZodNullable<z.ZodString>;
1692
+ }, z.core.$strip>;
1693
+ export type IUsageCredits = z.infer<typeof UsageCreditsSchema>;
1694
+ export declare const UsageResetCreditsSchema: z.ZodObject<{
1695
+ availableCount: z.ZodNumber;
1696
+ }, z.core.$strip>;
1697
+ export type IUsageResetCredits = z.infer<typeof UsageResetCreditsSchema>;
1698
+ export declare const UsageWindowScopeSchema: z.ZodObject<{
1699
+ model: z.ZodNullable<z.ZodObject<{
1700
+ id: z.ZodNullable<z.ZodString>;
1701
+ displayName: z.ZodString;
1702
+ }, z.core.$strip>>;
1703
+ surface: z.ZodNullable<z.ZodString>;
1704
+ }, z.core.$strip>;
1705
+ export type IUsageWindowScope = z.infer<typeof UsageWindowScopeSchema>;
1687
1706
  export declare const UsageWindowSchema: z.ZodObject<{
1688
1707
  id: z.ZodString;
1689
1708
  bucket: z.ZodString;
@@ -1696,6 +1715,20 @@ export declare const UsageWindowSchema: z.ZodObject<{
1696
1715
  remainingPercent: z.ZodNumber;
1697
1716
  durationMinutes: z.ZodNullable<z.ZodNumber>;
1698
1717
  resetsAt: z.ZodNullable<z.ZodNumber>;
1718
+ period: z.ZodOptional<z.ZodEnum<{
1719
+ "five-hour": "five-hour";
1720
+ "seven-day": "seven-day";
1721
+ other: "other";
1722
+ }>>;
1723
+ nativeKind: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1724
+ group: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1725
+ scope: z.ZodOptional<z.ZodNullable<z.ZodObject<{
1726
+ model: z.ZodNullable<z.ZodObject<{
1727
+ id: z.ZodNullable<z.ZodString>;
1728
+ displayName: z.ZodString;
1729
+ }, z.core.$strip>>;
1730
+ surface: z.ZodNullable<z.ZodString>;
1731
+ }, z.core.$strip>>>;
1699
1732
  }, z.core.$strip>;
1700
1733
  export declare const UsageSnapshotSchema: z.ZodObject<{
1701
1734
  provider: z.ZodString;
@@ -1720,7 +1753,30 @@ export declare const UsageSnapshotSchema: z.ZodObject<{
1720
1753
  remainingPercent: z.ZodNumber;
1721
1754
  durationMinutes: z.ZodNullable<z.ZodNumber>;
1722
1755
  resetsAt: z.ZodNullable<z.ZodNumber>;
1756
+ period: z.ZodOptional<z.ZodEnum<{
1757
+ "five-hour": "five-hour";
1758
+ "seven-day": "seven-day";
1759
+ other: "other";
1760
+ }>>;
1761
+ nativeKind: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1762
+ group: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1763
+ scope: z.ZodOptional<z.ZodNullable<z.ZodObject<{
1764
+ model: z.ZodNullable<z.ZodObject<{
1765
+ id: z.ZodNullable<z.ZodString>;
1766
+ displayName: z.ZodString;
1767
+ }, z.core.$strip>>;
1768
+ surface: z.ZodNullable<z.ZodString>;
1769
+ }, z.core.$strip>>>;
1723
1770
  }, z.core.$strip>>;
1771
+ credits: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
1772
+ bucket: z.ZodString;
1773
+ hasCredits: z.ZodBoolean;
1774
+ isUnlimited: z.ZodBoolean;
1775
+ balance: z.ZodNullable<z.ZodString>;
1776
+ }, z.core.$strip>>>>;
1777
+ resetCredits: z.ZodOptional<z.ZodNullable<z.ZodObject<{
1778
+ availableCount: z.ZodNumber;
1779
+ }, z.core.$strip>>>;
1724
1780
  }, z.core.$strip>;
1725
1781
  export type IUsageSnapshot = z.infer<typeof UsageSnapshotSchema>;
1726
1782
  export declare const RuntimeStatusSchema: z.ZodObject<{
@@ -6028,7 +6084,30 @@ export declare const HqResponseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
6028
6084
  remainingPercent: z.ZodNumber;
6029
6085
  durationMinutes: z.ZodNullable<z.ZodNumber>;
6030
6086
  resetsAt: z.ZodNullable<z.ZodNumber>;
6087
+ period: z.ZodOptional<z.ZodEnum<{
6088
+ "five-hour": "five-hour";
6089
+ "seven-day": "seven-day";
6090
+ other: "other";
6091
+ }>>;
6092
+ nativeKind: z.ZodOptional<z.ZodNullable<z.ZodString>>;
6093
+ group: z.ZodOptional<z.ZodNullable<z.ZodString>>;
6094
+ scope: z.ZodOptional<z.ZodNullable<z.ZodObject<{
6095
+ model: z.ZodNullable<z.ZodObject<{
6096
+ id: z.ZodNullable<z.ZodString>;
6097
+ displayName: z.ZodString;
6098
+ }, z.core.$strip>>;
6099
+ surface: z.ZodNullable<z.ZodString>;
6100
+ }, z.core.$strip>>>;
6031
6101
  }, z.core.$strip>>;
6102
+ credits: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
6103
+ bucket: z.ZodString;
6104
+ hasCredits: z.ZodBoolean;
6105
+ isUnlimited: z.ZodBoolean;
6106
+ balance: z.ZodNullable<z.ZodString>;
6107
+ }, z.core.$strip>>>>;
6108
+ resetCredits: z.ZodOptional<z.ZodNullable<z.ZodObject<{
6109
+ availableCount: z.ZodNumber;
6110
+ }, z.core.$strip>>>;
6032
6111
  }, z.core.$strip>;
6033
6112
  }, z.core.$strip>, z.ZodObject<{
6034
6113
  kind: z.ZodLiteral<"skills">;
@@ -12603,7 +12682,30 @@ export declare const IpcResponseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
12603
12682
  remainingPercent: z.ZodNumber;
12604
12683
  durationMinutes: z.ZodNullable<z.ZodNumber>;
12605
12684
  resetsAt: z.ZodNullable<z.ZodNumber>;
12685
+ period: z.ZodOptional<z.ZodEnum<{
12686
+ "five-hour": "five-hour";
12687
+ "seven-day": "seven-day";
12688
+ other: "other";
12689
+ }>>;
12690
+ nativeKind: z.ZodOptional<z.ZodNullable<z.ZodString>>;
12691
+ group: z.ZodOptional<z.ZodNullable<z.ZodString>>;
12692
+ scope: z.ZodOptional<z.ZodNullable<z.ZodObject<{
12693
+ model: z.ZodNullable<z.ZodObject<{
12694
+ id: z.ZodNullable<z.ZodString>;
12695
+ displayName: z.ZodString;
12696
+ }, z.core.$strip>>;
12697
+ surface: z.ZodNullable<z.ZodString>;
12698
+ }, z.core.$strip>>>;
12606
12699
  }, z.core.$strip>>;
12700
+ credits: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
12701
+ bucket: z.ZodString;
12702
+ hasCredits: z.ZodBoolean;
12703
+ isUnlimited: z.ZodBoolean;
12704
+ balance: z.ZodNullable<z.ZodString>;
12705
+ }, z.core.$strip>>>>;
12706
+ resetCredits: z.ZodOptional<z.ZodNullable<z.ZodObject<{
12707
+ availableCount: z.ZodNumber;
12708
+ }, z.core.$strip>>>;
12607
12709
  }, z.core.$strip>;
12608
12710
  }, z.core.$strip>, z.ZodObject<{
12609
12711
  kind: z.ZodLiteral<"skills">;
@@ -143,6 +143,24 @@ export const LiveItemsSchema = z.object({
143
143
  data: z.array(HistoryItemSchema.extend({ state: z.enum(['active', 'awaiting-flush']) })).max(128),
144
144
  hasGap: z.boolean(),
145
145
  });
146
+ // exact native credit observations; balances have no assumed currency or arithmetic semantics
147
+ export const UsageCreditsSchema = z.object({
148
+ bucket: z.string().max(256),
149
+ hasCredits: z.boolean(),
150
+ isUnlimited: z.boolean(),
151
+ balance: z.string().max(256).nullable(),
152
+ });
153
+ // availability is independent of the provider's potentially capped reset-credit detail list
154
+ export const UsageResetCreditsSchema = z.object({
155
+ availableCount: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
156
+ });
157
+ // native model/surface scope identifies independent account meters
158
+ export const UsageWindowScopeSchema = z.object({
159
+ model: z
160
+ .object({ id: z.string().max(256).nullable(), displayName: z.string().max(256) })
161
+ .nullable(),
162
+ surface: z.string().max(256).nullable(),
163
+ });
146
164
  export const UsageWindowSchema = z.object({
147
165
  id: z.string(),
148
166
  bucket: z.string(),
@@ -152,6 +170,10 @@ export const UsageWindowSchema = z.object({
152
170
  remainingPercent: z.number().min(0).max(100),
153
171
  durationMinutes: z.number().int().positive().nullable(),
154
172
  resetsAt: z.number().int().nullable(),
173
+ period: z.enum(['five-hour', 'seven-day', 'other']).optional(),
174
+ nativeKind: z.string().max(256).nullable().optional(),
175
+ group: z.string().max(256).nullable().optional(),
176
+ scope: UsageWindowScopeSchema.nullable().optional(),
155
177
  });
156
178
  export const UsageSnapshotSchema = z.object({
157
179
  provider: ProviderIdSchema,
@@ -160,6 +182,8 @@ export const UsageSnapshotSchema = z.object({
160
182
  observedAt: z.number().int().nullable(),
161
183
  ordinaryUsageAllowed: z.boolean().nullable(),
162
184
  windows: z.array(UsageWindowSchema).max(MAX_PAGE_SIZE),
185
+ credits: z.array(UsageCreditsSchema).max(MAX_PAGE_SIZE).nullable().optional(),
186
+ resetCredits: UsageResetCreditsSchema.nullable().optional(),
163
187
  });
164
188
  export const RuntimeStatusSchema = z.object({
165
189
  runtimeEpoch: z.uuid(),
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ export type { IProviderId, IProviderCapability, IProviderCapabilitySet, IProvide
8
8
  export type { IProviderModel, IProviderModelPage, IProviderModelsRequest, } from './contracts/models/index.js';
9
9
  export type { ITurnSettings, ISessionSettings, IConfigurationRequest, IConfigurationChoice, ISessionConfiguration, } from './contracts/configuration/index.js';
10
10
  export type { INativeLineage } from './contracts/identities/index.js';
11
+ export { upgradeHqStorage } from './storage/upgrade/index.js';
12
+ export type { IHqStorageUpgradeOptions, IHqStorageUpgradeResult } from './storage/upgrade/index.js';
11
13
  export type { IStorageDescriptor, IStorageMaintenanceOperation } from './storage/index.js';
12
14
  export { createHqSnapshot, inspectHqSnapshot, restoreHqSnapshot } from './snapshots/index.js';
13
15
  export type { IHqSnapshotInspection, IHqRestoreOptions, IHqRestoreResult, } from './snapshots/index.js';
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@ export { RUNTIME_DIRECTORY, HQ_LOCK_FILE, getOwnerLiveness, acquireHqLock, withH
3
3
  // native runtime and local client
4
4
  export { createAgentHq } from './runtime/index.js';
5
5
  export { requestAgentHq } from './ipc/index.js';
6
+ // trusted offline storage upgrades
7
+ export { upgradeHqStorage } from './storage/upgrade/index.js';
6
8
  // trusted host snapshot and offline recovery
7
9
  export { createHqSnapshot, inspectHqSnapshot, restoreHqSnapshot } from './snapshots/index.js';
8
10
  // pure execution revision contracts
@@ -10,9 +10,11 @@ export declare const describePlan: (artifacts: IPlanArtifacts, pendingActionId?:
10
10
  export declare const canExecutePlanUnit: (artifacts: IPlanArtifacts, milestoneId: string | null) => boolean;
11
11
  /**
12
12
  * Applies one explicit operator decision without native dispatch or filesystem mutation.
13
+ * A protocol 0.2 plan must pass the jg-plans check for the decision's stage.
13
14
  * @returns Updated current authority records.
14
15
  * @throws
15
16
  * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
17
+ * - JG_AGENT_HQ_PLAN_INVALID: The HQ plan is invalid
16
18
  */
17
19
  export declare const updatePlanAuthority: (artifacts: IPlanArtifacts, command: Extract<IPlanCommand, {
18
20
  kind: "approve" | "revoke" | "authorize";
@@ -1,6 +1,7 @@
1
1
  import { Exception } from 'jg-exception';
2
2
  import { isSamePlanRevision } from './utilities.js';
3
3
  import { createCompletionLookup } from './reconciliation.js';
4
+ import { invalid } from './documents.js';
4
5
  const conflict = () => {
5
6
  throw new Exception('The HQ action conflicts with its recorded state', {
6
7
  code: 'JG_AGENT_HQ_ACTION_CONFLICT',
@@ -110,6 +111,7 @@ export const canExecutePlanUnit = (artifacts, milestoneId) => {
110
111
  const status = describePlan(artifacts);
111
112
  const execution = artifacts.coordination.execution;
112
113
  if (!execution ||
114
+ !artifacts.conformance.sequence ||
113
115
  !status.isPlanApproved ||
114
116
  !status.isFinalized ||
115
117
  ['blocked', 'completed', 'revising'].includes(status.state))
@@ -128,13 +130,17 @@ const revokeExecution = (coordination, action) => {
128
130
  };
129
131
  /**
130
132
  * Applies one explicit operator decision without native dispatch or filesystem mutation.
133
+ * A protocol 0.2 plan must pass the jg-plans check for the decision's stage.
131
134
  * @returns Updated current authority records.
132
135
  * @throws
133
136
  * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
137
+ * - JG_AGENT_HQ_PLAN_INVALID: The HQ plan is invalid
134
138
  */
135
139
  export const updatePlanAuthority = (artifacts, command, runtimeEpoch) => {
136
140
  const coordination = structuredClone(artifacts.coordination);
137
141
  if (command.kind === 'approve') {
142
+ if (!artifacts.conformance[command.scope])
143
+ invalid();
138
144
  const status = describePlan(artifacts);
139
145
  if (command.scope === 'sequence' &&
140
146
  (artifacts.milestones === null || !status.isFinalized || !status.isPlanApproved))
@@ -166,6 +172,8 @@ export const updatePlanAuthority = (artifacts, command, runtimeEpoch) => {
166
172
  revokeExecution(coordination, command.action);
167
173
  }
168
174
  else {
175
+ if (!artifacts.conformance.sequence)
176
+ invalid();
169
177
  const status = describePlan(artifacts);
170
178
  if (coordination.revision &&
171
179
  coordination.revision.phase !== 'awaiting-approval' &&
@@ -1,5 +1,22 @@
1
1
  import { z } from 'zod';
2
2
  import { type IPlanMetadata, type IPlanStatus } from '../contracts/index.js';
3
+ import type { IPlanConformance } from './types.js';
4
+ /**
5
+ * Rejects a plan HQ cannot read or act on.
6
+ * @param cause The validation failure, when one exists.
7
+ * @throws
8
+ * - JG_AGENT_HQ_PLAN_INVALID: The HQ plan is invalid
9
+ */
10
+ export declare const invalid: (cause?: unknown) => never;
11
+ /**
12
+ * Checks a protocol 0.2 plan with jg-plans for the stages HQ gates. Plan approval needs plan.md
13
+ * to conform alone; finalization, sequence approval, authorization, and execution need it to
14
+ * conform with its milestones. Plans without the 0.2 marker always conform, as before.
15
+ * @param plan Exact plan.md source.
16
+ * @param milestones Exact milestones.md source, or null when absent.
17
+ * @returns The conformance of each stage.
18
+ */
19
+ export declare const observePlanConformance: (plan: string, milestones: string | null) => IPlanConformance;
3
20
  /**
4
21
  * Reads the persisted identity independently of an optional consumer metadata projection.
5
22
  * @throws