@gajae-code/coding-agent 0.12.21 → 0.13.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 (222) hide show
  1. package/CHANGELOG.md +67 -2
  2. package/dist/types/cli/args.d.ts +5 -2
  3. package/dist/types/cli/root-flags.d.ts +246 -0
  4. package/dist/types/cli.d.ts +11 -19
  5. package/dist/types/commands/daemon.d.ts +0 -30
  6. package/dist/types/commands/launch.d.ts +11 -19
  7. package/dist/types/commands/quick-lane.d.ts +32 -0
  8. package/dist/types/commands/sdk.d.ts +21 -15
  9. package/dist/types/config/file-lock-gc.d.ts +2 -0
  10. package/dist/types/config/model-equivalence.d.ts +20 -0
  11. package/dist/types/config/model-profile-activation.d.ts +62 -17
  12. package/dist/types/config/model-profiles.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +68 -1
  14. package/dist/types/config/model-resolver.d.ts +15 -4
  15. package/dist/types/config/models-config-schema.d.ts +7 -2
  16. package/dist/types/config/provider-selection-policy.d.ts +62 -0
  17. package/dist/types/config/settings-schema.d.ts +36 -78
  18. package/dist/types/config/settings.d.ts +2 -0
  19. package/dist/types/edit/renderer.d.ts +5 -2
  20. package/dist/types/edit/streaming.d.ts +13 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
  22. package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
  23. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  24. package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
  25. package/dist/types/internal-urls/local-protocol.d.ts +1 -0
  26. package/dist/types/modes/components/model-selector.d.ts +0 -2
  27. package/dist/types/modes/components/provider-order-context.d.ts +45 -0
  28. package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
  29. package/dist/types/modes/components/settings-defs.d.ts +5 -1
  30. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  31. package/dist/types/quick-lane/classify.d.ts +40 -0
  32. package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
  33. package/dist/types/sdk/broker/broker.d.ts +56 -2
  34. package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
  35. package/dist/types/sdk/broker/session-index.d.ts +83 -2
  36. package/dist/types/sdk/broker/state-version.d.ts +1 -1
  37. package/dist/types/sdk/bus/notification-service.d.ts +6 -0
  38. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
  39. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
  40. package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
  41. package/dist/types/sdk/cli/rows.d.ts +98 -0
  42. package/dist/types/sdk/cli/session-cli.d.ts +35 -3
  43. package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
  44. package/dist/types/sdk/elevation/audit.d.ts +93 -0
  45. package/dist/types/sdk/elevation/capability.d.ts +3 -0
  46. package/dist/types/sdk/elevation/digest.d.ts +72 -0
  47. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
  48. package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
  49. package/dist/types/sdk/elevation/owner.d.ts +48 -0
  50. package/dist/types/sdk/guides/cache.d.ts +98 -0
  51. package/dist/types/sdk/guides/catalog.d.ts +156 -0
  52. package/dist/types/sdk/guides/cli.d.ts +39 -0
  53. package/dist/types/sdk/guides/manifest.d.ts +66 -0
  54. package/dist/types/sdk/guides/verify.d.ts +66 -0
  55. package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
  56. package/dist/types/sdk/host/control/index.d.ts +1 -1
  57. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  58. package/dist/types/sdk/host/query/cursor.d.ts +10 -0
  59. package/dist/types/sdk/host/query/handlers.d.ts +21 -0
  60. package/dist/types/sdk/host/session-runtime.d.ts +9 -0
  61. package/dist/types/sdk/mcp/server.d.ts +2 -2
  62. package/dist/types/sdk/prompt-status.d.ts +11 -0
  63. package/dist/types/sdk/session.d.ts +4 -0
  64. package/dist/types/sdk/transport/index.d.ts +1 -0
  65. package/dist/types/sdk/transport/relay.d.ts +1 -0
  66. package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
  67. package/dist/types/session/agent-session.d.ts +32 -18
  68. package/dist/types/session/blob-store.d.ts +13 -7
  69. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  70. package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
  71. package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
  72. package/dist/types/session/session-manager.d.ts +139 -10
  73. package/dist/types/session/session-storage.d.ts +48 -0
  74. package/dist/types/setup/provider-onboarding.d.ts +7 -1
  75. package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
  76. package/dist/types/stt/setup.d.ts +2 -0
  77. package/dist/types/task/executor.d.ts +10 -2
  78. package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
  79. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  80. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  81. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  82. package/dist/types/tools/browser.d.ts +2 -0
  83. package/dist/types/tools/image-gen.d.ts +14 -18
  84. package/dist/types/tools/index.d.ts +2 -0
  85. package/dist/types/tools/resource-gc.d.ts +2 -0
  86. package/dist/types/utils/pasted-image-path.d.ts +6 -0
  87. package/package.json +9 -9
  88. package/scripts/generate-sdk-operation-inventory.ts +8 -0
  89. package/scripts/run-test-manifest.ts +61 -21
  90. package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
  91. package/src/cli/args.ts +62 -9
  92. package/src/cli/completion-cli.ts +1 -0
  93. package/src/cli/fast-help.ts +1 -0
  94. package/src/cli/root-flags.ts +100 -0
  95. package/src/cli/setup-cli.ts +6 -2
  96. package/src/cli.ts +42 -65
  97. package/src/commands/acp.ts +1 -1
  98. package/src/commands/daemon.ts +2 -31
  99. package/src/commands/gc.ts +1 -1
  100. package/src/commands/launch.ts +8 -129
  101. package/src/commands/quick-lane.ts +51 -0
  102. package/src/commands/sdk.ts +200 -26
  103. package/src/commands/setup.ts +4 -2
  104. package/src/config/file-lock-gc.ts +4 -1
  105. package/src/config/model-equivalence.ts +76 -7
  106. package/src/config/model-profile-activation.ts +1115 -265
  107. package/src/config/model-profiles.ts +134 -2
  108. package/src/config/model-registry.ts +352 -50
  109. package/src/config/model-resolver.ts +289 -38
  110. package/src/config/models-config-schema.ts +11 -4
  111. package/src/config/provider-selection-policy.ts +146 -0
  112. package/src/config/settings-schema.ts +50 -77
  113. package/src/config/settings.ts +40 -0
  114. package/src/coordinator-mcp/server.ts +32 -2
  115. package/src/edit/renderer.ts +175 -223
  116. package/src/edit/streaming.ts +108 -1
  117. package/src/export/html/index.ts +57 -7
  118. package/src/extensibility/custom-tools/types.ts +2 -0
  119. package/src/extensibility/extensions/compact-handler.ts +3 -2
  120. package/src/extensibility/extensions/runner.ts +6 -0
  121. package/src/extensibility/extensions/types.ts +3 -0
  122. package/src/gjc-runtime/gc-runtime.ts +379 -20
  123. package/src/gjc-runtime/launch-worktree.ts +5 -1
  124. package/src/gjc-runtime/team-runtime.ts +59 -2
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
  126. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
  127. package/src/internal-urls/docs-index.generated.ts +17 -13
  128. package/src/internal-urls/local-protocol.ts +1 -0
  129. package/src/main.ts +40 -3
  130. package/src/memories/index.ts +4 -2
  131. package/src/modes/acp/acp-agent.ts +235 -91
  132. package/src/modes/components/model-selector.ts +206 -42
  133. package/src/modes/components/provider-order-context.ts +197 -0
  134. package/src/modes/components/provider-order-editor.ts +441 -0
  135. package/src/modes/components/settings-defs.ts +21 -1
  136. package/src/modes/components/settings-selector.ts +45 -2
  137. package/src/modes/components/tool-execution.ts +47 -15
  138. package/src/modes/controllers/command-controller.ts +6 -3
  139. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  140. package/src/modes/controllers/input-controller.ts +73 -2
  141. package/src/modes/controllers/selector-controller.ts +260 -153
  142. package/src/modes/prompt-suggestion-controller.ts +1 -1
  143. package/src/modes/runtime-init.ts +1 -0
  144. package/src/modes/utils/ui-helpers.ts +18 -0
  145. package/src/prompts/tools/browser.md +1 -0
  146. package/src/quick-lane/classify.ts +257 -0
  147. package/src/rlm/index.ts +1 -1
  148. package/src/runtime/stt-settings-setup.ts +57 -0
  149. package/src/sdk/acp/adapter.ts +2 -1
  150. package/src/sdk/broker/broker.ts +780 -12
  151. package/src/sdk/broker/lifecycle.ts +70 -29
  152. package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
  153. package/src/sdk/broker/session-index.ts +463 -48
  154. package/src/sdk/broker/state-version.ts +13 -9
  155. package/src/sdk/broker/transport.ts +14 -1
  156. package/src/sdk/bus/index.ts +34 -13
  157. package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
  158. package/src/sdk/bus/notification-service.ts +47 -1
  159. package/src/sdk/bus/prompt-reconciliation.ts +22 -3
  160. package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
  161. package/src/sdk/bus/telegram-daemon.ts +54 -3
  162. package/src/sdk/cli/rows.ts +247 -0
  163. package/src/sdk/cli/session-cli.ts +930 -79
  164. package/src/sdk/elevation/allowlist.ts +54 -0
  165. package/src/sdk/elevation/audit.ts +208 -0
  166. package/src/sdk/elevation/capability.ts +41 -0
  167. package/src/sdk/elevation/digest.ts +129 -0
  168. package/src/sdk/elevation/dispatch-receipt.ts +184 -0
  169. package/src/sdk/elevation/grant-ledger.ts +1142 -0
  170. package/src/sdk/elevation/owner.ts +109 -0
  171. package/src/sdk/guides/cache.ts +363 -0
  172. package/src/sdk/guides/catalog.ts +554 -0
  173. package/src/sdk/guides/cli.ts +235 -0
  174. package/src/sdk/guides/manifest.ts +152 -0
  175. package/src/sdk/guides/verify.ts +178 -0
  176. package/src/sdk/host/control/dispatch.ts +49 -1
  177. package/src/sdk/host/control/index.ts +1 -0
  178. package/src/sdk/host/control/operations.ts +8 -1
  179. package/src/sdk/host/query/cursor.ts +47 -3
  180. package/src/sdk/host/query/handlers.ts +155 -2
  181. package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
  182. package/src/sdk/host/session-runtime.test.ts +391 -1
  183. package/src/sdk/host/session-runtime.ts +308 -73
  184. package/src/sdk/host/surface-policy.ts +3 -0
  185. package/src/sdk/host/websocket-transport.ts +7 -1
  186. package/src/sdk/mcp/server.ts +88 -15
  187. package/src/sdk/prompt-status.ts +8 -0
  188. package/src/sdk/protocol/operation-inventory.generated.json +62 -0
  189. package/src/sdk/protocol/operation-registry.ts +3 -2
  190. package/src/sdk/session.ts +87 -39
  191. package/src/sdk/transport/index.ts +1 -0
  192. package/src/sdk/transport/relay.ts +3 -0
  193. package/src/sdk/transport/serve-cli.ts +111 -24
  194. package/src/secrets/obfuscator.ts +92 -12
  195. package/src/session/agent-session.ts +322 -93
  196. package/src/session/blob-store.ts +60 -8
  197. package/src/session/fallback-chain-controller.ts +35 -0
  198. package/src/session/internal/managed-session-scope.ts +25 -3
  199. package/src/session/internal/managed-session-storage.ts +574 -73
  200. package/src/session/internal/session-memory-sidecar.ts +19 -29
  201. package/src/session/session-manager.ts +2166 -743
  202. package/src/session/session-storage.ts +715 -48
  203. package/src/setup/provider-onboarding.ts +19 -3
  204. package/src/setup/provider-presets.json +3 -1
  205. package/src/slash-commands/builtin-registry.ts +85 -3
  206. package/src/stt/downloader.ts +2 -3
  207. package/src/stt/setup.ts +29 -4
  208. package/src/task/executor.ts +22 -18
  209. package/src/task/index.ts +24 -1
  210. package/src/tools/archive-reader.ts +11 -2
  211. package/src/tools/browser/runtime-diagnostics.ts +249 -0
  212. package/src/tools/browser/tab-protocol.ts +4 -0
  213. package/src/tools/browser/tab-supervisor.ts +5 -0
  214. package/src/tools/browser/tab-worker.ts +36 -1
  215. package/src/tools/browser.ts +7 -0
  216. package/src/tools/image-gen.ts +110 -166
  217. package/src/tools/index.ts +2 -0
  218. package/src/tools/read.ts +1 -1
  219. package/src/tools/resource-gc.ts +6 -1
  220. package/src/tools/tool-catalog.generated.ts +5 -1
  221. package/src/utils/pasted-image-path.ts +93 -0
  222. package/src/web/search/index.ts +3 -3
@@ -1,5 +1,25 @@
1
1
  import { SDK_STATE_VERSION } from "./state-version";
2
- export type SessionIndexEventType = "host_registered" | "host_heartbeat" | "host_unregistered" | "lifecycle_started" | "lifecycle_terminal" | "session_closed" | "record_reconciled";
2
+ export type SessionIndexEventType = "host_registered" | "host_heartbeat" | "host_unregistered" | "lifecycle_started" | "lifecycle_terminal" | "session_closed" | "session_deleted" | "record_reconciled";
3
+ export type SessionActivityState = "active" | "idle";
4
+ /** Coalesced broker-owned heartbeat checkpoint (C2): state plus the observation time. */
5
+ export interface SessionActivity {
6
+ state: SessionActivityState;
7
+ at: number;
8
+ }
9
+ /** Events persisted without an OS process incarnation (v1/v2 era) are legacy provenance. */
10
+ export type SessionIdentityProvenance = "composite" | "legacy";
11
+ export type SessionTombstoneRule = "retain" | "expire";
12
+ /**
13
+ * Injected retention policy (C3). The broker schedules compaction independently of
14
+ * rotation; settings apply at the next scheduled compaction. `clock` drives both
15
+ * retention expiry and heartbeat-freshness liveness reads.
16
+ */
17
+ export interface RetentionPolicy {
18
+ clock?: () => number;
19
+ maxAgeMs?: number;
20
+ maxRows?: number;
21
+ tombstoneRule?: SessionTombstoneRule;
22
+ }
3
23
  export interface SessionIndexEvent {
4
24
  version: typeof SDK_STATE_VERSION;
5
25
  indexSeq: number;
@@ -11,9 +31,20 @@ export interface SessionIndexEvent {
11
31
  };
12
32
  endpointGeneration: number;
13
33
  pid: number;
34
+ /**
35
+ * OS start incarnation of `pid`, published by the host that owns that pid. A
36
+ * pid is reusable, so teardown needs this binding to prove the pid is still
37
+ * the same process; keeping it here, in broker-owned storage, is what lets
38
+ * that proof outlive the session's own workspace.
39
+ */
40
+ processIncarnation?: string;
14
41
  endpointMtimeMs?: number;
15
42
  lifecycleRequestId?: string;
16
43
  terminalUncertain?: boolean;
44
+ /** OS process incarnation (C1); absent on legacy v1/v2 events. */
45
+ hostIncarnation?: string;
46
+ /** Present on host_heartbeat checkpoints (C2). */
47
+ activity?: SessionActivity;
17
48
  ts: number;
18
49
  checksum: string;
19
50
  }
@@ -25,11 +56,22 @@ export interface IndexedSession {
25
56
  };
26
57
  endpointGeneration: number;
27
58
  pid: number;
59
+ /** OS start incarnation of `pid` as published by its own host at registration. */
60
+ processIncarnation?: string;
28
61
  endpointMtimeMs?: number;
29
62
  live: boolean;
30
63
  indexSeq: number;
31
64
  lifecycleRequestId?: string;
32
65
  terminalUncertain?: boolean;
66
+ hostIncarnation?: string;
67
+ identityProvenance: SessionIdentityProvenance;
68
+ activity?: SessionActivity;
69
+ /** Wall-clock timestamp of the latest admitted heartbeat, when one exists. */
70
+ lastHeartbeatAt?: number;
71
+ /** True when the same sessionId maps to more than one stateRoot (cross-repo duplicate). */
72
+ ambiguous: boolean;
73
+ /** True when the identity's latest event is host_unregistered or session_closed (DR-1: stopped rows are retained). */
74
+ terminal: boolean;
33
75
  }
34
76
  export interface SessionList {
35
77
  indexSeq: number;
@@ -46,10 +88,30 @@ export interface SessionIndexRepairResult extends SessionIndexDiagnosis {
46
88
  repaired: boolean;
47
89
  quarantinePath?: string;
48
90
  }
91
+ /** Admission-fence rejection codes recorded in the durable index audit (C5/C4). */
92
+ export type SessionIndexAuditCode = "rejected_superseded_incarnation" | "rejected_after_tombstone";
93
+ export interface SessionIndexAuditRecord {
94
+ version: typeof SDK_STATE_VERSION;
95
+ code: SessionIndexAuditCode;
96
+ /** indexSeq of the rejected event (unique per record; used for idempotent dedupe). */
97
+ indexSeq: number;
98
+ sessionId: string;
99
+ endpointGeneration: number;
100
+ stateRoot: string;
101
+ hostIncarnation?: string;
102
+ supersededByIncarnation?: string;
103
+ /** indexSeq of the superseding registration, or of the tombstone for post-delete rejections. */
104
+ supersededByIndexSeq: number;
105
+ ts: number;
106
+ }
49
107
  export declare const sessionIndexChecksum: (event: Omit<SessionIndexEvent, "checksum">) => string;
108
+ /** Coalesced heartbeat checkpoint rate cap (C2): at most one per session per minute. */
109
+ export declare const SESSION_HEARTBEAT_INTERVAL_MS = 60000;
110
+ export declare const DEFAULT_SESSION_RETENTION_MAX_AGE_MS: number;
111
+ export declare const DEFAULT_SESSION_RETENTION_MAX_ROWS = 25000;
50
112
  export declare class SessionIndex {
51
113
  #private;
52
- constructor(agentDir: string);
114
+ constructor(agentDir: string, policy?: RetentionPolicy);
53
115
  open(): Promise<this>;
54
116
  replay(): Promise<void>;
55
117
  diagnose(): Promise<SessionIndexDiagnosis>;
@@ -57,8 +119,27 @@ export declare class SessionIndex {
57
119
  refresh(): Promise<void>;
58
120
  get indexSeq(): number;
59
121
  append(input: Omit<SessionIndexEvent, "version" | "indexSeq" | "checksum" | "ts"> & Partial<Pick<SessionIndexEvent, "ts">>): Promise<SessionIndexEvent>;
122
+ /** Hold the canonical index lock across an authority-sensitive operation. */
123
+ withLocked<T>(callback: () => Promise<T>): Promise<T>;
60
124
  snapshot(): Promise<void>;
125
+ /**
126
+ * Broker-scheduled compaction (C3), independent of rotation size: applies the
127
+ * injected retention policy to a fresh snapshot and truncates the log.
128
+ */
129
+ compact(): Promise<void>;
61
130
  listSessions(): SessionList;
131
+ /**
132
+ * Production coalesced heartbeat checkpoint pass (C2): appends one
133
+ * `host_heartbeat` per session at most once per {@link SESSION_HEARTBEAT_INTERVAL_MS}.
134
+ * The pass observes liveness the same way the projection does — the host process
135
+ * must be alive and, for composite identities, still carry the recorded OS process
136
+ * incarnation (a reused PID is never checkpointed). Stopped/terminal rows and rows
137
+ * whose heartbeat is still fresh are skipped. After a broker restart, sessions whose
138
+ * host survived are re-observed as live on the first pass; sessions whose host died
139
+ * while the broker was down keep their stale or missing heartbeat and read as
140
+ * unknown/not-live (never fresh forever). Returns the number of checkpoints written.
141
+ */
142
+ checkpointLiveHeartbeats(now?: number): Promise<number>;
62
143
  hostUnregisteredAfter(registration: Pick<IndexedSession, "sessionId" | "endpointGeneration" | "pid" | "indexSeq" | "lifecycleRequestId">): {
63
144
  indexSeq: number;
64
145
  lifecycleRequestId?: string;
@@ -1,5 +1,5 @@
1
1
  export declare const SDK_STATE_VERSION = 1;
2
- export declare const SESSION_INDEX_SNAPSHOT_VERSION = 2;
2
+ export declare const SESSION_INDEX_SNAPSHOT_VERSION = 3;
3
3
  export declare class UnsupportedStateVersionError extends Error {
4
4
  readonly file: string;
5
5
  readonly version: number;
@@ -170,6 +170,12 @@ export interface DaemonHealth {
170
170
  stopped: boolean;
171
171
  heartbeatAt: number | undefined;
172
172
  heartbeatAgeMs: number | undefined;
173
+ /**
174
+ * Session endpoints the live owner reported an OPEN socket to in its latest
175
+ * matching heartbeat sidecar. `undefined` means the owner never published the
176
+ * field (older daemon, no stable owner tag, no matching sidecar): unknown, not zero.
177
+ */
178
+ attachedEndpoints: number | undefined;
173
179
  generation: number | undefined;
174
180
  currentGeneration: number;
175
181
  generationRelation: DaemonGenerationRelation;
@@ -16,7 +16,9 @@
16
16
  * Session-scoped durable retention (kind-aware store) is provided by reconciliation-store.ts (#3032).
17
17
  * - The clientRef index is session-runtime scoped; a ref conflicts only while
18
18
  * retained and must never be reused as a retry mechanism.
19
- * - Terminal transitions settle once: the first terminal outcome wins.
19
+ * - Terminal transitions settle once: first terminal outcome wins. A late
20
+ * `agent_failed` may still attach its sanitized reason to an already-terminal
21
+ * record that has none, but never changes status, terminalAt, or retention.
20
22
  */
21
23
  import { PROMPT_FAILURE_CODE_MAX, sanitizePromptFailure } from "../prompt-failure";
22
24
  export { PROMPT_FAILURE_CODE_MAX, sanitizePromptFailure };
@@ -60,6 +62,11 @@ export type TurnPromptReconciliation = {
60
62
  acceptedAt: number;
61
63
  startedAt?: number;
62
64
  terminalAt: number;
65
+ /** Present when a late `agent_failed` supplied the only failure reason. */
66
+ error?: {
67
+ code: string;
68
+ message: string;
69
+ };
63
70
  } | {
64
71
  status: "failed";
65
72
  commandId: string;
@@ -91,8 +91,11 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
91
91
  * later session from creating a topic or replaying notifications. Generation
92
92
  * 58 preserves that invariant during durable-fence load promotion and restores
93
93
  * the exact grace deadline when a failed archive publication rolls back.
94
+ * Generation 59 publishes the owner's attached OPEN-socket count in the
95
+ * heartbeat sidecar so `gjc notify health` can report a live daemon that is
96
+ * attached to no registered endpoint (#4128).
94
97
  */
95
- export declare const DAEMON_GENERATION = 58;
98
+ export declare const DAEMON_GENERATION = 59;
96
99
  /**
97
100
  * Serving-compatibility boundary for daemon lifecycle requests. Epoch 5
98
101
  * requires the complete generation-36 topic authority contract, so older
@@ -273,11 +273,23 @@ type OwnerHeartbeatSidecar = {
273
273
  ownerId: string;
274
274
  acquisitionId: string;
275
275
  heartbeatAt: number;
276
+ /**
277
+ * Session endpoints this owner had an OPEN WebSocket to when the heartbeat was
278
+ * published. Optional: a sidecar written by an older daemon omits it and must
279
+ * read back as unknown, never as zero.
280
+ */
281
+ attachedEndpoints?: number;
276
282
  };
277
283
  export interface OwnerFreshnessSnapshot {
278
284
  ownerTag: Pick<OwnerHeartbeatSidecar, "pid" | "incarnation" | "ownerId" | "acquisitionId"> | null;
279
285
  effectiveHeartbeatAt: number | undefined;
280
286
  legacyEmbedded: boolean;
287
+ /**
288
+ * Session endpoints the current owner reported as attached in its latest matching
289
+ * heartbeat sidecar. `undefined` means the owner never published the field (older
290
+ * daemon, no stable owner tag, or no matching sidecar) — that is unknown, not zero.
291
+ */
292
+ attachedEndpoints: number | undefined;
281
293
  state: DaemonState | undefined;
282
294
  }
283
295
  export declare function readOwnerFreshnessSnapshot(input: {
@@ -293,6 +305,12 @@ export declare function renewOwnerHeartbeatSidecar(input: {
293
305
  now?: () => number;
294
306
  pid?: number;
295
307
  pidIncarnation?: (pid: number) => string | undefined;
308
+ /**
309
+ * Session endpoints the caller currently holds an OPEN WebSocket to. Omitted by
310
+ * non-daemon callers (bootstrap/proof paths) so their sidecar reports unknown
311
+ * rather than falsely claiming zero attachments.
312
+ */
313
+ attachedEndpoints?: number;
296
314
  }): Promise<boolean>;
297
315
  export interface NotificationRootRegistration {
298
316
  root?: string;
@@ -1252,6 +1270,14 @@ export declare class TelegramNotificationDaemon {
1252
1270
  private notifyThreadedFallback;
1253
1271
  private startFlushTimer;
1254
1272
  private stopFlushTimer;
1273
+ /**
1274
+ * Sessions this owner can actually deliver to right now. Only `WebSocket.OPEN`
1275
+ * counts: a CONNECTING socket is constructed but cannot carry a frame, and
1276
+ * CLOSING/CLOSED ones are gone. Every delivery path in this daemon refuses to
1277
+ * send unless `readyState === WebSocket.OPEN`, so OPEN is exactly the state
1278
+ * that means a notification can leave this process for that session.
1279
+ */
1280
+ private attachedEndpointCount;
1255
1281
  private renewOwnershipHeartbeat;
1256
1282
  /**
1257
1283
  * Ownership must be renewed independently of Telegram's 25-second long poll:
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Versioned DTO rows for `gjc sdk session` semantic verbs (row DTO v1, DR-12).
3
+ *
4
+ * Every semantic verb renders its output through these rows so machine
5
+ * consumers get one deterministic, versioned envelope per verb. Credentials
6
+ * are never part of the row schema: broker `session.list` rows carry no
7
+ * credentials, offline inspect rows are projected from endpoint discovery
8
+ * records with secret-shaped fields stripped before they can reach a row, and
9
+ * pass-through host payloads are redacted recursively before rendering.
10
+ */
11
+ export declare const SESSION_ROWS_VERSION = 1;
12
+ export type SdkSessionActivityState = "active" | "idle";
13
+ export interface SdkSessionActivityV1 {
14
+ state: SdkSessionActivityState;
15
+ at: number;
16
+ }
17
+ export interface SdkSessionRowV1 {
18
+ sessionId: string;
19
+ locator: {
20
+ repo: string;
21
+ stateRoot: string;
22
+ };
23
+ endpointGeneration: number;
24
+ /** Process-bound incarnation of the hosting process (C1); absent for legacy-provenance rows. */
25
+ hostIncarnation?: string;
26
+ pid: number;
27
+ live: boolean;
28
+ /** Tombstone flag (C4): deleted sessions are excluded from list/endpoint/resumable surfaces. */
29
+ deleted: boolean;
30
+ indexSeq: number;
31
+ terminalUncertain?: boolean;
32
+ lifecycleRequestId?: string;
33
+ endpointMtimeMs?: number;
34
+ /** Coalesced broker-owned heartbeat checkpoint (C2). */
35
+ activity?: SdkSessionActivityV1;
36
+ /** Wall-clock timestamp of the latest admitted heartbeat, when one exists. */
37
+ lastHeartbeatAt?: number;
38
+ /** "legacy" for v1/v2-era rows that predate process-incarnation identity. */
39
+ identityProvenance?: "composite" | "legacy";
40
+ /** True when the same sessionId maps to more than one stateRoot (cross-repo duplicate). */
41
+ ambiguous?: boolean;
42
+ }
43
+ export interface SdkCheckpointRecordV1 {
44
+ revision: number;
45
+ generation: number;
46
+ seq: number;
47
+ }
48
+ export interface SdkRetentionGapV1 {
49
+ code: "retention_gap";
50
+ missing?: {
51
+ from: number;
52
+ to: number;
53
+ };
54
+ resync?: SdkCheckpointRecordV1;
55
+ }
56
+ /** One tail item: a retained transcript entry or a live event-ring event. */
57
+ export interface SdkTailItemV1 {
58
+ /** "transcript" for retained transcript entries, otherwise the event-ring kind. */
59
+ kind: string;
60
+ id?: string;
61
+ generation?: number;
62
+ seq?: number;
63
+ payload: unknown;
64
+ }
65
+ export interface SdkTailEnvelopeV1 {
66
+ version: typeof SESSION_ROWS_VERSION;
67
+ source: "session" | "offline";
68
+ session: SdkSessionRowV1;
69
+ checkpoint?: SdkCheckpointRecordV1;
70
+ gap?: SdkRetentionGapV1;
71
+ items: SdkTailItemV1[];
72
+ }
73
+ /** Recursively removes secret-shaped fields (defense in depth for DTO output). */
74
+ export declare function stripSecretFields(value: unknown): unknown;
75
+ /**
76
+ * Projects one broker `session.list` row into the credential-free v1 row DTO.
77
+ * The row is a superset-tolerant projection: identity fields introduced by
78
+ * later index versions (hostIncarnation, activity, deleted) pass through when
79
+ * present and are omitted otherwise, so the same mapper serves both v2 and v3
80
+ * index shapes.
81
+ */
82
+ export declare function toSessionRowV1(value: unknown): SdkSessionRowV1;
83
+ /**
84
+ * Offline inspect row projected from a local SDK endpoint discovery record.
85
+ * The discovery record carries the endpoint credential (`url`/`token`); neither
86
+ * is ever projected into the row. `path` is the endpoint file
87
+ * `<stateRoot>/sdk/<sessionId>.json`, so the state root is recovered from the
88
+ * path when the layout matches.
89
+ */
90
+ export declare function toOfflineSessionRowV1(value: unknown): SdkSessionRowV1;
91
+ export declare function toCheckpointRecordV1(value: unknown): SdkCheckpointRecordV1 | undefined;
92
+ export declare function toRetentionGapV1(value: unknown): SdkRetentionGapV1 | undefined;
93
+ /** Projects a transcript entry or ring event into a tail item with dedupe keys. */
94
+ export declare function toTailItemV1(value: unknown, fallback: {
95
+ kind: string;
96
+ generation?: number;
97
+ seq?: number;
98
+ }): SdkTailItemV1;
@@ -1,19 +1,51 @@
1
- export type SdkSessionCliAction = "list" | "control" | "query" | "global";
1
+ import { type OperationKind } from "../protocol/operation-registry";
2
+ export type SdkSessionCliAction = "list" | "inspect" | "send" | "status" | "tail" | "elevate" | "raw";
3
+ export type SdkSessionCliRawKind = "control" | "query" | "global";
2
4
  export interface SdkSessionCliArgs {
3
5
  action?: string;
6
+ /** Raw dispatch kind when action is "raw" (control | query | global). */
7
+ rawAction?: string;
4
8
  sessionId?: string;
9
+ /** Caller-chosen operation reference (ULID) for `send`; required target for `status`. */
10
+ opRef?: string;
5
11
  operation?: string;
6
12
  query?: string;
13
+ /** Convenience `send` body: forwarded as the `text` field of turn.prompt. */
14
+ text?: string;
7
15
  jsonInput?: string;
8
16
  jsonInputFile?: string;
9
- idempotencyKey?: string;
10
17
  jsonInputStdin?: boolean;
18
+ idempotencyKey?: string;
19
+ elevationRequestId?: string;
20
+ operationKind?: OperationKind;
11
21
  confirm?: boolean;
22
+ /** Raw query continuation cursor, or a saved checkpoint token to resume `tail` from (re-minted per connection). */
12
23
  cursor?: string;
13
24
  showEndpointCredential?: boolean;
14
25
  yes?: boolean;
26
+ /** `send --wait`: poll turn.prompt_status until terminal or the wait window elapses. */
27
+ wait?: boolean;
28
+ /** Bounded wait for `send --wait`, `status`, and live `tail` follow. */
29
+ timeoutMs?: number;
30
+ /** `tail --strict`: fail closed on retention gaps (exit 1 retention_gap). */
31
+ strict?: boolean;
32
+ /** `tail --until-idle`: exit once the observed event stream reaches a terminal turn state. */
33
+ untilIdle?: boolean;
34
+ /** `tail --all-events`: include every event-ring kind instead of the tail subset. */
35
+ allEvents?: boolean;
15
36
  repo?: string;
16
37
  agentDir?: string;
17
38
  }
18
- /** Runs the pure-SDK `gjc daemon session` command family. */
39
+ /**
40
+ * Time-ordered operation reference (ULID): 48-bit millisecond timestamp plus
41
+ * 80 bits of randomness, Crockford base32, lowercase. The ref is used as the
42
+ * `turn.prompt` clientRef so `send`/`status` correlate without a broker round
43
+ * trip, and matches the plan's durable op-ref ledger shape (C7).
44
+ */
45
+ export declare function createOperationRef(now?: number): string;
46
+ export declare function runTail(repo: string, agentDir: string, sessionId: string, args: SdkSessionCliArgs): Promise<unknown>;
47
+ /**
48
+ * Runs the `gjc sdk session` command family: semantic verbs (list, inspect,
49
+ * send, status, tail) plus the explicit raw control/query/global hatch.
50
+ */
19
51
  export declare function runSdkSessionCli(args: SdkSessionCliArgs, writeOutput?: (value: unknown) => void, setExitCode?: (exitCode: 1 | 2) => void): Promise<void>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Exact elevation-gated allowlist, fully qualified and enumerative.
3
+ *
4
+ * The elevation allowlist is the exact set of registry operations below (the
5
+ * raw destructive lifecycle and workflow-approval operations). It is declared
6
+ * ONCE here as a `Set<string>` of `"kind:sdkId"` keys and mechanically pinned
7
+ * against `operation-registry.ts` by the allowlist unit test: every key must
8
+ * resolve via `findOperation(kind, sdkId)` and no elevatable operation may be
9
+ * silently missing. Any operation NOT in this set is rejected at issue with
10
+ * `elevation_not_allowlisted` before any ledger reservation. There is no
11
+ * generalized policy engine: this is the deterministic predicate.
12
+ */
13
+ export declare const ELEVATION_ALLOWLIST_KEYS: readonly ["global:session.close", "global:session.delete", "control:session.delete", "control:session.close", "control:context.clear", "control:session.cwd.move", "control:compaction.run", "control:bash.execute", "control:bash.background", "control:queue.message.remove", "control:queue.message.update", "control:retry.abort", "control:retry.now", "control:bash.abort", "control:retry.auto.set", "control:compaction.auto.set", "control:extension.set_enabled", "control:queue.interrupt_mode.set", "control:queue.follow_up_mode.set", "control:queue.steering_mode.set", "control:tools.active.set", "control:permission_mode.set", "control:service_tier.set", "control:workflow.gate_answer", "control:workflow.plan_approve"];
14
+ /**
15
+ * Whether `(kind, sdkId)` is the fully qualified identity of an allowlisted
16
+ * elevation-gated operation. The pair is validated against the operation
17
+ * registry before any elevation is admitted, so a key can never name an
18
+ * operation that does not exist.
19
+ */
20
+ export declare function isElevationAllowlisted(kind: unknown, sdkId: unknown): boolean;
@@ -0,0 +1,93 @@
1
+ import { SDK_STATE_VERSION } from "../broker/state-version";
2
+ import { classifyProcessIncarnationLiveness } from "./owner";
3
+ /**
4
+ * Durable elevation audit trail.
5
+ *
6
+ * Append-only JSONL under the same elevation-local lock as the grant ledger,
7
+ * mode 0600. Every state transition of a grant — issue, operator answer,
8
+ * expiry, misuse, duplicate answer, claim, and dispatch outcome — is
9
+ * recorded with the exact identity evidence the transition consumed, so an
10
+ * auditor can reproduce why a grant reached its terminal state without
11
+ * trusting in-memory state.
12
+ *
13
+ * The audit stream is an evidence trail, never an authority: audit rows are
14
+ * append-only, are never re-read to drive decisions, and a corrupted row is
15
+ * quarantined (like ledger rows) instead of failing the ledger.
16
+ */
17
+ export type ElevationAuditEvent = {
18
+ kind: "issued";
19
+ } | {
20
+ kind: "answered";
21
+ outcome: "granted" | "denied" | "expired" | "misused" | "target_unavailable" | "duplicate_answer";
22
+ } | {
23
+ kind: "expired";
24
+ } | {
25
+ kind: "misused";
26
+ } | {
27
+ kind: "duplicate_answer";
28
+ } | {
29
+ kind: "claimed";
30
+ claim: {
31
+ ownerId: string;
32
+ epoch: number;
33
+ pid: number;
34
+ incarnation: string;
35
+ };
36
+ } | {
37
+ kind: "dispatched";
38
+ dispatch: {
39
+ ownerId: string;
40
+ epoch: number;
41
+ dispatchedAt: number;
42
+ };
43
+ outcome: {
44
+ status: "ok" | "failed";
45
+ code?: string;
46
+ message?: string;
47
+ dispatchedAt: number;
48
+ };
49
+ } | {
50
+ kind: "consumed";
51
+ message: string;
52
+ } | {
53
+ kind: "unavailable";
54
+ reason: string;
55
+ };
56
+ export interface ElevationAuditEntry {
57
+ version: typeof SDK_STATE_VERSION;
58
+ ts: number;
59
+ elevationRequestId: string;
60
+ issueIndex: number;
61
+ event: ElevationAuditEvent;
62
+ }
63
+ export type ElevationAuditEventName = ElevationAuditEvent["kind"];
64
+ export declare function isElevationAuditEntry(value: unknown): value is ElevationAuditEntry;
65
+ export interface ElevationAuditOptions {
66
+ /** Injectable clock. */
67
+ now?: () => number;
68
+ /**
69
+ * Injectable tri-state process-incarnation liveness classifier, defaulting
70
+ * to `classifyProcessIncarnationLiveness`. The audit trail records the
71
+ * liveness verdict behind `consumed`/`unavailable` settlements so the
72
+ * crash-truth settlement is reproducible; the verdict never drives the
73
+ * settlement itself (that lives in the ledger).
74
+ */
75
+ classifyLiveness?: typeof classifyProcessIncarnationLiveness;
76
+ }
77
+ export declare class ElevationAudit {
78
+ #private;
79
+ constructor(agentDir: string, options?: ElevationAuditOptions);
80
+ get warnings(): readonly string[];
81
+ /**
82
+ * Appends one audit row for an elevation grant transition. Callers MUST
83
+ * hold the same elevation-local lock that guards the grant ledger; the
84
+ * audit row and the grant row therefore commit together.
85
+ */
86
+ append(entry: Omit<ElevationAuditEntry, "version">): Promise<void>;
87
+ /**
88
+ * Opens the audit stream: creates the 0600 directory/file if absent and
89
+ * quarantines any corrupt tail so subsequent appends are always readable.
90
+ * Called under the same lock as `ElevationLedger.open()`.
91
+ */
92
+ open(): Promise<this>;
93
+ }
@@ -0,0 +1,3 @@
1
+ export declare function signElevationCapability(authorityToken: string, requestId: string, sdkId: string, input: Record<string, unknown>): string;
2
+ export declare function verifyElevationCapability(authorityToken: string, capability: string, sdkId: string, input: Record<string, unknown>): boolean;
3
+ export declare function elevationAuthorityPath(stateRoot: string, sessionId: string): string;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Fully qualified elevation request digest.
3
+ *
4
+ * An elevation grant binds the exact operation the operator approved. The
5
+ * digest covers the complete `{kind, sdkId, input}` triple so substituting a
6
+ * different operation or different input bytes changes the digest and the
7
+ * gate presentation fails closed (`misused`). The top-level
8
+ * `elevationRequestId` correlation field is deliberately excluded: it is the
9
+ * durable correlation ID assigned to a request, not part of the approved
10
+ * content, and the same content re-issued under a new ID must produce the
11
+ * same digest.
12
+ *
13
+ * Only allowlisted operations can be elevated; anything else is refused so a
14
+ * caller cannot mint a grant for an operation outside the elevation policy.
15
+ */
16
+ export declare const ELEVATION_OPERATION_KINDS: readonly ["control", "query", "global"];
17
+ export type ElevationOperationKind = (typeof ELEVATION_OPERATION_KINDS)[number];
18
+ export interface ElevationOperation {
19
+ kind: ElevationOperationKind;
20
+ sdkId: string;
21
+ }
22
+ /**
23
+ * Operations that require an elevation grant. Default SDK scope
24
+ * (list/query/send/tail) stays grant-free; destructive lifecycle operations
25
+ * and workflow approval operations are elevatable and therefore gated.
26
+ */
27
+ export declare const ELEVATION_ALLOWLIST: readonly {
28
+ kind: ElevationOperationKind;
29
+ sdkId: string;
30
+ }[];
31
+ export declare function isElevatableOperation(kind: unknown, sdkId: unknown): boolean;
32
+ /**
33
+ * Whether the `(kind, sdkId)` pair names a registry operation that the
34
+ * elevation allowlist recognizes. Used by the ledger row validator so a
35
+ * persisted row can never bind an operation outside the allowlist.
36
+ */
37
+ export declare function isKnownElevationOperation(kind: unknown, sdkId: unknown): boolean;
38
+ /**
39
+ * Canonical JSON encoding for elevation digest leaves.
40
+ *
41
+ * Intentionally byte-compatible with the broker's identity canonicalization
42
+ * (`canonicalJson` in `sdk/broker/broker.ts`): object keys sorted, compact
43
+ * separators, `undefined` fields preserved verbatim, arrays joined without
44
+ * separators. The broker computes the request digest at issue time and the
45
+ * ledger recomputes/compares the same encoding, so digest verification is
46
+ * stable across issue, gate presentation, and answer.
47
+ */
48
+ export declare function canonicalElevationJson(value: unknown): string;
49
+ export type ElevationDigestResult = {
50
+ ok: true;
51
+ digest: string;
52
+ } | {
53
+ ok: false;
54
+ error: {
55
+ code: string;
56
+ message: string;
57
+ };
58
+ };
59
+ /**
60
+ * Computes the fully qualified digest for an elevation request.
61
+ *
62
+ * `input` is digested exactly as supplied except that a top-level
63
+ * `elevationRequestId` field is removed first (it is the correlation ID, not
64
+ * approved content). Operations outside `ELEVATION_ALLOWLIST` are refused
65
+ * with `elevation_not_required` so the broker never mints a grant for an
66
+ * operation that does not need one.
67
+ */
68
+ export declare function elevationRequestDigest(request: {
69
+ kind: unknown;
70
+ sdkId: unknown;
71
+ input: unknown;
72
+ }): ElevationDigestResult;
@@ -0,0 +1,85 @@
1
+ export declare const ELEVATION_RECEIPT_VERSION = 1;
2
+ /**
3
+ * Identity of the broker tenure that claims a grant and dispatches it.
4
+ *
5
+ * `ownerId` is the broker instance owner id and `epoch` increments per broker
6
+ * start, so a restart (even of the same object) yields a new tenure. `pid` and
7
+ * `incarnation` are retained for truthful tri-state liveness replay.
8
+ */
9
+ export interface ElevationClaimIdentity {
10
+ ownerId: string;
11
+ epoch: number;
12
+ pid: number;
13
+ incarnation: string;
14
+ }
15
+ export type ElevationReceiptState = "claimed" | "dispatched" | "uncertain";
16
+ /**
17
+ * Durable dispatch outcome. `unknown` is the crash truth: the grant was
18
+ * consumed by a claim but the dispatch outcome is not knowable, so retry
19
+ * requires a new grant.
20
+ */
21
+ export type ElevationDispatchOutcome = {
22
+ status: "ok";
23
+ dispatchedAt: number;
24
+ } | {
25
+ status: "failed";
26
+ code: string;
27
+ message: string;
28
+ dispatchedAt: number;
29
+ } | {
30
+ status: "unknown";
31
+ message: string;
32
+ dispatchedAt: number;
33
+ };
34
+ /**
35
+ * Owner/epoch-bound dispatch receipt.
36
+ *
37
+ * State machine: `claimed` (grant spent, dispatch in flight, bound to the
38
+ * claiming broker tenure) -> `dispatched` (terminal, outcome recorded) or
39
+ * `uncertain` (terminal truth: the claim's outcome is unknowable after its
40
+ * broker tenure died). Replay never fabricates an outcome: a receipt left in
41
+ * `claimed` by a dead tenure is truthfully terminalized to `uncertain`.
42
+ */
43
+ export interface ElevationDispatchReceipt {
44
+ version: typeof ELEVATION_RECEIPT_VERSION;
45
+ elevationRequestId: string;
46
+ issueIndex: number;
47
+ state: ElevationReceiptState;
48
+ claim: ElevationClaimIdentity & {
49
+ claimedAt: number;
50
+ };
51
+ dispatch?: {
52
+ ownerId: string;
53
+ epoch: number;
54
+ dispatchedAt: number;
55
+ };
56
+ outcome?: ElevationDispatchOutcome;
57
+ ts: number;
58
+ }
59
+ export declare function isElevationRequestId(value: unknown): value is string;
60
+ export declare function isElevationClaimIdentity(value: unknown): value is ElevationClaimIdentity;
61
+ export declare function isElevationDispatchOutcome(value: unknown): value is ElevationDispatchOutcome;
62
+ export declare function isElevationDispatchReceipt(value: unknown): value is ElevationDispatchReceipt;
63
+ export declare function claimReceipt(requestId: string, issueIndex: number, claim: ElevationClaimIdentity, claimedAt: number): ElevationDispatchReceipt;
64
+ export declare function dispatchReceipt(receipt: ElevationDispatchReceipt, dispatch: {
65
+ ownerId: string;
66
+ epoch: number;
67
+ dispatchedAt: number;
68
+ }, outcome: ElevationDispatchOutcome): ElevationDispatchReceipt;
69
+ export declare function uncertainReceipt(receipt: ElevationDispatchReceipt, at: number): ElevationDispatchReceipt;
70
+ export declare function receiptClaimMatches(receipt: ElevationDispatchReceipt, identity: ElevationClaimIdentity): boolean;
71
+ /**
72
+ * Who may answer an elevation gate.
73
+ *
74
+ * Internal operator-only: the answer boundary is the broker's internal
75
+ * operator attestation (`local_operator`), reached only through broker-owned
76
+ * surfaces. There is no public `elevation.answer` operation and no
77
+ * requester-correlation check: the answerer is structurally an operator
78
+ * attestation, so requester self-approval is impossible by construction.
79
+ */
80
+ export type ElevationAnswerAuthority = {
81
+ source: "local_operator";
82
+ attestedBy: string;
83
+ };
84
+ /** Structural guard for the internal operator attestation. */
85
+ export declare function isElevationAnswerAuthority(value: unknown): value is ElevationAnswerAuthority;