@gajae-code/coding-agent 0.12.18 → 0.12.20

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 (100) hide show
  1. package/CHANGELOG.md +26 -9
  2. package/dist/types/commands/gc.d.ts +4 -0
  3. package/dist/types/commands/sdk.d.ts +66 -0
  4. package/dist/types/config/settings-schema.d.ts +34 -0
  5. package/dist/types/gjc-runtime/gc-runtime.d.ts +97 -1
  6. package/dist/types/modes/acp/acp-agent.d.ts +33 -1
  7. package/dist/types/modes/components/bash-execution.d.ts +7 -0
  8. package/dist/types/modes/components/eval-execution.d.ts +7 -0
  9. package/dist/types/modes/utils/ui-helpers.d.ts +10 -1
  10. package/dist/types/sdk/acp/adapter.d.ts +9 -17
  11. package/dist/types/sdk/acp/index.d.ts +1 -1
  12. package/dist/types/sdk/broker/broker.d.ts +93 -1
  13. package/dist/types/sdk/broker/ensure.d.ts +17 -0
  14. package/dist/types/sdk/broker/index.d.ts +1 -0
  15. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +10 -1
  16. package/dist/types/sdk/broker/lifecycle.d.ts +81 -2
  17. package/dist/types/sdk/broker/startup-budget.d.ts +44 -0
  18. package/dist/types/sdk/bus/chat-daemon-control.d.ts +7 -1
  19. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +11 -1
  20. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -0
  21. package/dist/types/sdk/bus/index.d.ts +20 -9
  22. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -6
  23. package/dist/types/sdk/bus/slack-daemon.d.ts +2 -2
  24. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -2
  25. package/dist/types/sdk/host/query/revision-store.d.ts +5 -0
  26. package/dist/types/sdk/host/session-runtime.d.ts +4 -0
  27. package/dist/types/sdk/lifecycle-session.d.ts +13 -2
  28. package/dist/types/sdk/prompt-failure.d.ts +13 -0
  29. package/dist/types/sdk/prompt-watchdog.d.ts +82 -0
  30. package/dist/types/sdk/session-reconnect.d.ts +20 -0
  31. package/dist/types/sdk/startup-capability.d.ts +1 -1
  32. package/dist/types/session/agent-session.d.ts +14 -4
  33. package/dist/types/session/blob-store.d.ts +32 -0
  34. package/dist/types/session/internal/managed-session-storage.d.ts +40 -2
  35. package/dist/types/session/internal/session-memory-sidecar.d.ts +881 -0
  36. package/dist/types/session/session-manager.d.ts +301 -27
  37. package/dist/types/session/session-storage.d.ts +170 -0
  38. package/dist/types/task/executor.d.ts +2 -1
  39. package/dist/types/tools/bash.d.ts +1 -0
  40. package/dist/types/tui/output-block.d.ts +6 -0
  41. package/package.json +8 -8
  42. package/src/cli/args.ts +75 -34
  43. package/src/commands/acp.ts +7 -1
  44. package/src/commands/gc.ts +31 -2
  45. package/src/commands/sdk.ts +141 -7
  46. package/src/config/settings-schema.ts +43 -0
  47. package/src/coordinator-mcp/server.ts +6 -1
  48. package/src/export/html/index.ts +83 -39
  49. package/src/gjc-runtime/gc-runtime.ts +1283 -4
  50. package/src/internal-urls/docs-index.generated.ts +1 -1
  51. package/src/internal-urls/local-protocol.ts +14 -0
  52. package/src/main.ts +40 -9
  53. package/src/modes/acp/acp-agent.ts +564 -37
  54. package/src/modes/acp/terminal-auth.ts +2 -2
  55. package/src/modes/components/bash-execution.ts +14 -0
  56. package/src/modes/components/eval-execution.ts +14 -0
  57. package/src/modes/controllers/command-controller.ts +63 -8
  58. package/src/modes/utils/ui-helpers.ts +81 -11
  59. package/src/sdk/acp/adapter.ts +46 -45
  60. package/src/sdk/acp/index.ts +1 -1
  61. package/src/sdk/broker/broker.ts +392 -11
  62. package/src/sdk/broker/ensure.ts +131 -61
  63. package/src/sdk/broker/index.ts +1 -0
  64. package/src/sdk/broker/lifecycle-ledger.ts +59 -5
  65. package/src/sdk/broker/lifecycle.ts +278 -63
  66. package/src/sdk/broker/startup-budget.ts +103 -0
  67. package/src/sdk/bus/chat-daemon-control.ts +9 -3
  68. package/src/sdk/bus/chat-daemon-runtime.ts +565 -29
  69. package/src/sdk/bus/conversation-store.ts +1 -1
  70. package/src/sdk/bus/discord-daemon.ts +2 -1
  71. package/src/sdk/bus/discord-live-provider.ts +34 -6
  72. package/src/sdk/bus/index.ts +118 -20
  73. package/src/sdk/bus/kind-aware-reconciliation.ts +2 -1
  74. package/src/sdk/bus/prompt-reconciliation.ts +4 -9
  75. package/src/sdk/bus/slack-daemon.ts +91 -18
  76. package/src/sdk/bus/telegram-daemon-contract.ts +4 -2
  77. package/src/sdk/bus/topic-registry.ts +7 -1
  78. package/src/sdk/cli/session-cli.ts +8 -1
  79. package/src/sdk/host/query/handlers.ts +1 -1
  80. package/src/sdk/host/query/revision-store.ts +48 -12
  81. package/src/sdk/host/session-runtime.test.ts +50 -0
  82. package/src/sdk/host/session-runtime.ts +12 -2
  83. package/src/sdk/lifecycle-session.ts +26 -3
  84. package/src/sdk/mcp/server.ts +6 -1
  85. package/src/sdk/prompt-failure.ts +42 -0
  86. package/src/sdk/prompt-watchdog.ts +162 -0
  87. package/src/sdk/session-reconnect.ts +43 -0
  88. package/src/sdk/startup-capability.ts +13 -4
  89. package/src/session/agent-session.ts +525 -222
  90. package/src/session/blob-store.ts +89 -0
  91. package/src/session/internal/managed-session-scope.ts +36 -26
  92. package/src/session/internal/managed-session-storage.ts +303 -32
  93. package/src/session/internal/session-memory-sidecar.ts +2160 -0
  94. package/src/session/session-manager.ts +7027 -394
  95. package/src/session/session-storage.ts +1134 -11
  96. package/src/task/executor.ts +17 -4
  97. package/src/tools/bash.ts +109 -7
  98. package/src/tools/find.ts +55 -26
  99. package/src/tools/todo-contract.ts +40 -8
  100. package/src/tui/output-block.ts +11 -1
@@ -92,18 +92,27 @@ export interface LifecycleLedgerLimits {
92
92
  maxBytes?: number;
93
93
  maxLineBytes?: number;
94
94
  maxRows?: number;
95
+ /**
96
+ * Cap for the `.corrupt` quarantine sidecar. Reaching it rotates one
97
+ * generation aside instead of appending forever.
98
+ */
99
+ maxCorruptBytes?: number;
95
100
  }
96
101
  export declare class LifecycleLedger {
97
102
  #private;
98
103
  constructor(agentDir: string, limits?: LifecycleLedgerLimits);
99
104
  open(): Promise<this>;
100
105
  /**
101
- * Reads terminal proof for one request without recovering or changing the ledger.
106
+ * Reads the durable terminal record for one request without recovering or changing the ledger.
102
107
  *
103
108
  * This intentionally accepts an unrelated unterminated final write: concurrent
104
109
  * appenders may have started a different row after this request's synced terminal
105
110
  * row. Complete rows are still decoded and validated strictly, and any incomplete
106
111
  * tail that identifies this request withholds proof.
112
+ *
113
+ * `terminal_uncertain` is a durable terminal record too: an operation that concluded
114
+ * uncertainly persists that conclusion, and refusing to read it back would report a
115
+ * synced row as unpersisted and replace its true reason with a generic one.
107
116
  */
108
117
  readTerminal(identity: string, requestHash: string): Promise<LifecycleLedgerEntry | undefined>;
109
118
  get warnings(): readonly string[];
@@ -1,5 +1,5 @@
1
1
  import type { SessionLifecycleMcpServer } from "../acp/mcp";
2
- import type { SdkStartupFailure, SdkStartupRollbackResult } from "../startup-capability";
2
+ import { type SdkStartupFailure, type SdkStartupRollbackResult } from "../startup-capability";
3
3
  import type { Broker, BrokerCleanupEvidence, BrokerResponse } from "./broker";
4
4
  import type { LifecycleDurableEffectsReceipt, LifecycleStartupFailureReceipt } from "./lifecycle-ledger";
5
5
  import { type ProcessIncarnationCommandRunner, type ProcessIncarnationOptions, parseDarwinProcessIncarnation, processIncarnation } from "./process-incarnation";
@@ -15,7 +15,7 @@ export interface LifecycleDeadlines {
15
15
  export declare function deriveLifecycleDeadlines(receivedAt: number, requestedReadinessTimeoutMs: number): LifecycleDeadlines;
16
16
  export interface LifecycleTiming {
17
17
  now(): number;
18
- sleep(ms: number): Promise<void>;
18
+ sleep(ms: number, signal?: AbortSignal): Promise<void>;
19
19
  }
20
20
  type LifecycleCommand = SdkInternalSpawnCommand | {
21
21
  file: string;
@@ -117,3 +117,82 @@ export interface LifecycleExecutionOutcome {
117
117
  }
118
118
  /** Returns the response together with every durable lifecycle fact needed for truthful replay. */
119
119
  export declare function executeLifecycle(broker: Broker, operation: string, input: Input, identity: string, cleanup?: CleanupEvidence): Promise<LifecycleExecutionOutcome>;
120
+ /**
121
+ * The live client/socket subscription count of the SDK session endpoint this
122
+ * process serves, or `undefined` while this process serves no endpoint.
123
+ *
124
+ * Only the SDK session runtime owns the real socket table, so it publishes a
125
+ * reader here instead of every consumer re-deriving attachment from the OS.
126
+ * Consumers must treat `undefined` as "no evidence" and never as "detached".
127
+ */
128
+ export type SessionHostAttachmentReader = () => number;
129
+ /** What one serving runtime reports about itself while its transport is up. */
130
+ export interface SessionHostRuntimeEvidence {
131
+ /** This runtime's own live SDK client/socket subscription count. */
132
+ attachedClients: SessionHostAttachmentReader;
133
+ /** Whether this runtime currently has agent work in flight. */
134
+ workInFlight: () => boolean;
135
+ }
136
+ /** One runtime's live publication, retractable only by its owner. */
137
+ export interface SessionHostRuntimePublication {
138
+ /**
139
+ * Withdraws the evidence this handle published. Idempotent, and never able
140
+ * to touch a sibling runtime's evidence.
141
+ */
142
+ retract(): void;
143
+ }
144
+ /**
145
+ * Publishes this runtime's own liveness evidence for the lifetime of its
146
+ * transport. Retraction goes through the returned handle, so a runtime can only
147
+ * ever withdraw evidence it owns.
148
+ */
149
+ export declare function publishSessionHostRuntimeEvidence(evidence: SessionHostRuntimeEvidence): SessionHostRuntimePublication;
150
+ /**
151
+ * This process's currently attached SDK client count, summed across every
152
+ * serving runtime, or `undefined` when no runtime publishes a readable count
153
+ * (before startup, after teardown, or when every reader itself fails). Never
154
+ * guesses: absence of a reader is absence of evidence.
155
+ */
156
+ export declare function sessionHostAttachedClients(): number | undefined;
157
+ /**
158
+ * Whether any runtime in this process has agent work in flight right now.
159
+ *
160
+ * Positive evidence only: a runtime that publishes nothing, or whose reader
161
+ * fails, reports no work, so this can never keep an abandoned host alive.
162
+ */
163
+ export declare function sessionHostWorkInFlight(): boolean;
164
+ /**
165
+ * How often a live broker re-checks its own session registrations against OS
166
+ * process liveness.
167
+ *
168
+ * This sweep can never disturb healthy work: a registration is only dropped
169
+ * when `process.kill(pid, 0)` proves the exact published pid is gone, and a
170
+ * working host answers that probe for its entire life no matter how long a turn
171
+ * runs. One minute keeps `gjc_sessions`/`session.get_endpoint` from advertising
172
+ * a corpse for longer than a single poll while costing one index refresh per
173
+ * minute on an otherwise idle broker.
174
+ */
175
+ export declare const BROKER_DEAD_REGISTRATION_SWEEP_MS = 60000;
176
+ /** One reaped registration, as recorded by {@link reapDeadSessionRegistrations}. */
177
+ export interface ReapedSessionRegistration {
178
+ sessionId: string;
179
+ pid: number;
180
+ endpointGeneration: number;
181
+ }
182
+ /**
183
+ * Drops every indexed session registration whose host process is provably gone.
184
+ *
185
+ * Liveness is the session index's own `alive(pid)` probe (`listSessions().live`),
186
+ * which reports live for both a running pid and an EPERM pid, so an alien or
187
+ * unreadable process is never mistaken for a dead one. Registrations already
188
+ * marked `terminalUncertain` are left alone: their disposition belongs to the
189
+ * terminal-uncertainty reconciliation path, and silently dropping them would
190
+ * turn a fail-closed `session.close`/`session.delete` refusal into `not_found`.
191
+ */
192
+ export declare function reapDeadSessionRegistrations(broker: Pick<Broker, "index">): Promise<ReapedSessionRegistration[]>;
193
+ /**
194
+ * Runs {@link reapDeadSessionRegistrations} on {@link BROKER_DEAD_REGISTRATION_SWEEP_MS}.
195
+ * The timer is unref'd, so it never keeps an otherwise idle broker process alive.
196
+ * Returns a disposer.
197
+ */
198
+ export declare function startBrokerDeadRegistrationSweep(broker: Pick<Broker, "index">, intervalMs?: number): () => void;
@@ -0,0 +1,44 @@
1
+ /** Readiness budget the broker grants a lifecycle request that does not size one itself. */
2
+ export declare const DEFAULT_READINESS_TIMEOUT_MS = 10000;
3
+ export declare const MIN_READINESS_TIMEOUT_MS = 4000;
4
+ export declare const MAX_READINESS_TIMEOUT_MS = 60000;
5
+ export declare function isValidReadinessTimeoutMs(value: unknown): value is number;
6
+ export declare const READINESS_TIMEOUT_INVALID_MESSAGE = "readinessTimeoutMs must be an integer between 4000 and 60000.";
7
+ /**
8
+ * Sleep until the duration elapses or the caller cancels the wait. Queue admission
9
+ * uses this so a granted or refused waiter does not retain its cutoff timer for the
10
+ * rest of the readiness budget.
11
+ */
12
+ export declare function cancellableSleep(ms: number, signal?: AbortSignal): Promise<void>;
13
+ /**
14
+ * Whether a broker operation waits in the host-startup admission queue before it runs.
15
+ * Only these spawn a host, so only these can be parked behind a full queue; the other
16
+ * lifecycle operations start their readiness clock as soon as they are received.
17
+ */
18
+ export declare function isStartupLifecycleOperation(operation: string): boolean;
19
+ /**
20
+ * Bounded time a lifecycle startup may wait for a host-startup admission slot. It
21
+ * is the readiness budget itself: a startup still queued after that long has spent
22
+ * the whole window the request was sized for, so refusing it beats launching a host
23
+ * that is already late.
24
+ */
25
+ export declare function startupQueueWaitMs(requestedReadinessTimeoutMs: number): number;
26
+ /**
27
+ * Broker-side wall clock a lifecycle startup may consume: the admission wait plus
28
+ * the readiness budget, which is granted fresh at admission and so is never shortened
29
+ * by queueing. Callers MUST size their own request deadline against this rather than
30
+ * `readinessTimeoutMs` alone, or a request admitted late fails client-side while the
31
+ * broker keeps running it to a durably persisted terminal result.
32
+ */
33
+ export declare function lifecycleStartupBudgetMs(requestedReadinessTimeoutMs: number): number;
34
+ /**
35
+ * Request deadline a caller MUST grant a broker operation, or `undefined` when the
36
+ * operation carries no readiness budget of its own and the client default already
37
+ * covers it.
38
+ *
39
+ * A request that omits `readinessTimeoutMs` is sized against the broker's default
40
+ * rather than left unextended: the broker queues it for exactly as long as one that
41
+ * asked, so the common path would otherwise fail client-side while the broker runs
42
+ * the startup to a durably persisted terminal result.
43
+ */
44
+ export declare function lifecycleRequestTimeoutMs(operation: string, input: Record<string, unknown>): number | undefined;
@@ -38,7 +38,13 @@ export type ChatDaemonAction = "stop" | "reload";
38
38
  * them against an exact owner tuple, so an owner at an earlier generation may
39
39
  * not serve or answer a request captured against this contract. Discord
40
40
  * generation 27 / slack generation 26 move shared exact unlink and process-
41
- * incarnation authority behind lazy native bindings.
41
+ * incarnation authority behind lazy native bindings. Discord generation 28 /
42
+ * slack generation 27 dial attached-session clients on the long-lived session
43
+ * reconnect budget, so an owner at an earlier generation gives up reconnecting
44
+ * before the host heartbeat TTL expires and permanently loses its attachment. Discord
45
+ * generation 29 / slack generation 28 advance the replay cursor only after a frame is
46
+ * published, so an owner at an earlier generation acknowledges an event before delivering
47
+ * it and loses that event for good the first time a surface refuses it.
42
48
  */
43
49
  export declare const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>>;
44
50
  export declare function chatDaemonGeneration(kind: ChatDaemonKind): number;
@@ -28,7 +28,17 @@ export interface ChatDaemonRuntimeConfig {
28
28
  }
29
29
  export interface ChatDaemonSdkClient {
30
30
  onFrame(handler: (frame: Record<string, unknown>) => void): () => void;
31
- request(frame: Record<string, unknown>): Promise<Record<string, unknown>>;
31
+ /**
32
+ * Announces that a replacement socket is live. Optional because command-scoped
33
+ * clients (the broker client, one-shot doubles) never outlive one socket, while
34
+ * every long-lived attachment's client carries it.
35
+ */
36
+ onReconnect?(handler: () => void): () => void;
37
+ /** Re-dials a retired socket; a no-op on a live one. */
38
+ connect?(): Promise<void>;
39
+ request(frame: Record<string, unknown>, options?: {
40
+ timeoutMs?: number;
41
+ }): Promise<Record<string, unknown>>;
32
42
  close(): Promise<void>;
33
43
  send(frame: Record<string, unknown>): void;
34
44
  }
@@ -31,6 +31,7 @@ export interface DiscordNotificationInput {
31
31
  threadName?: string;
32
32
  actionId?: string;
33
33
  options?: string[];
34
+ publicationId?: string;
34
35
  }
35
36
  /** SDK-only Discord threaded notification daemon. It owns no AgentSession and never retains endpoint credentials. */
36
37
  export declare class DiscordNotificationDaemon {
@@ -26,6 +26,7 @@ import type { ExtensionAPI, ExtensionContext } from "../../extensibility/extensi
26
26
  import { type WorkflowGateEmitter, type WorkflowGateTerminalProof } from "../../modes/shared/agent-wire/workflow-gate-broker";
27
27
  import type { AgentSessionEvent } from "../../session/agent-session";
28
28
  import type { AskAnswerSource, AskAnswerSourceResult, AskRemoteControl } from "../../tools";
29
+ import { type SessionHostRuntimePublication } from "../broker/lifecycle";
29
30
  import { type SessionSdkHost } from "../host";
30
31
  import { CursorRegistry, RevisionStore } from "../host/query";
31
32
  import { type SdkPromptTerminalOutcome } from "../prompt-status";
@@ -35,6 +36,22 @@ import { NotificationSessionController } from "./session-control";
35
36
  import { type EnsureDaemonResult, type RegisterNotificationRootResult } from "./telegram-daemon";
36
37
  export type { IdentityControlSuccessPathInput, IdentityControlTerminalPathInput, TerminalSendOutcome, } from "./control-drain-lease";
37
38
  export { isNativeControlDrainAvailable, runIdentityControlSuccessPath, runIdentityControlTerminalPath, } from "./control-drain-lease";
39
+ type PromptTerminalDiagnostic = {
40
+ reason?: unknown;
41
+ loopStopReason?: string;
42
+ assistantStopReason?: string;
43
+ errorKind?: string;
44
+ intentionalCancellation?: boolean;
45
+ };
46
+ type PromptTerminalExtra = {
47
+ finalText?: string;
48
+ error?: {
49
+ code: string;
50
+ message: string;
51
+ };
52
+ diagnostic?: PromptTerminalDiagnostic;
53
+ diagnosticAlreadyLogged?: boolean;
54
+ };
38
55
  export declare function formatPromptSettlementDiagnostic(proof: Extract<RunSettlementProof, {
39
56
  status: "unfenced";
40
57
  }>, now?: number): string;
@@ -357,6 +374,8 @@ interface SessionRuntime {
357
374
  /** Terminal cleanup proof retained across retries; each owner is released at most once after proof. */
358
375
  hostStopped: boolean;
359
376
  serverStopped: boolean;
377
+ /** This runtime's own host-liveness publication; only its teardown may retract it. */
378
+ evidencePublication?: SessionHostRuntimePublication;
360
379
  brokerRegistrationReleased: boolean;
361
380
  /** Managed Telegram root registration released during terminal teardown. */
362
381
  notificationRootRegistration?: {
@@ -392,15 +411,7 @@ interface SessionRuntime {
392
411
  terminalizePrompt: (correlation: {
393
412
  commandId: string;
394
413
  turnId: string;
395
- }, outcome: SdkPromptTerminalOutcome, extra?: {
396
- finalText?: string;
397
- }) => Promise<void>;
398
- /** Records a correlated prompt terminal boundary after agent unwind. */
399
- /** Atomically claims a correlated prompt terminal boundary after agent unwind. */
400
- recordPromptTerminal: (correlation: {
401
- commandId: string;
402
- turnId: string;
403
- } | undefined) => boolean;
414
+ }, outcome: SdkPromptTerminalOutcome, extra?: PromptTerminalExtra) => Promise<void>;
404
415
  /** Transitions the authoritative reconciliation record at lifecycle ingress; terminal outcomes settle once. */
405
416
  notePromptReconciliation: (correlation: {
406
417
  commandId: string;
@@ -18,10 +18,11 @@
18
18
  * retained and must never be reused as a retry mechanism.
19
19
  * - Terminal transitions settle once: the first terminal outcome wins.
20
20
  */
21
+ import { PROMPT_FAILURE_CODE_MAX, sanitizePromptFailure } from "../prompt-failure";
22
+ export { PROMPT_FAILURE_CODE_MAX, sanitizePromptFailure };
21
23
  export declare const PROMPT_RECONCILIATION_ACTIVE_CAPACITY = 128;
22
24
  export declare const PROMPT_RECONCILIATION_TERMINAL_CAPACITY = 256;
23
25
  export declare const PROMPT_RECONCILIATION_TERMINAL_TTL_MS: number;
24
- export declare const PROMPT_FAILURE_CODE_MAX = 64;
25
26
  export type PromptReconciliationStatus = "accepted" | "in_flight" | "terminal_ok" | "failed";
26
27
  export interface PromptCorrelation {
27
28
  commandId: string;
@@ -96,11 +97,6 @@ export interface PromptReconciliation {
96
97
  cleanup(): void;
97
98
  activeCount(): number;
98
99
  }
99
- /** Safe-token code capped at 64; arbitrary failure text is never retained. */
100
- export declare function sanitizePromptFailure(error: unknown): {
101
- code: string;
102
- message: string;
103
- };
104
100
  export declare function createPromptReconciliation(options?: {
105
101
  now?: () => number;
106
102
  }): PromptReconciliation;
@@ -74,12 +74,12 @@ export declare class SlackNotificationDaemon {
74
74
  */
75
75
  bindExistingRoot(sessionId: string, rootTs: string, commitAuthority?: () => Promise<boolean>): Promise<SlackConversation>;
76
76
  /** Deliver a notification into the mapped root thread, creating that root once. */
77
- notify(sessionId: string, body: string, actionId?: string, endpointGeneration?: number): Promise<SlackConversation>;
77
+ notify(sessionId: string, body: string, actionId?: string, endpointGeneration?: number, publicationId?: string): Promise<SlackConversation>;
78
78
  /** Posts a safe command outcome to the active mapped root thread. */
79
79
  postCommandResult(sessionId: string, content: string): Promise<boolean>;
80
80
  resolveAction(sessionId: string, actionId: string): Promise<void>;
81
81
  close(sessionId: string, marker?: string): Promise<boolean>;
82
- resume(sessionId: string, body: string, endpointGeneration?: number): Promise<SlackConversation>;
82
+ resume(sessionId: string, body: string, endpointGeneration?: number, publicationId?: string): Promise<SlackConversation>;
83
83
  findSession(sessionId: string, includeInactive: boolean): Promise<{
84
84
  key: string;
85
85
  record: SlackConversation;
@@ -88,9 +88,11 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
88
88
  * lazy native bindings for the startup-cost cut (#3846). Generation 57 clears
89
89
  * disconnect-grace-only fields whenever a topic enters an archive state, so
90
90
  * one stale session cannot make the shared registry unreadable and block every
91
- * later session from creating a topic or replaying notifications.
91
+ * later session from creating a topic or replaying notifications. Generation
92
+ * 58 preserves that invariant during durable-fence load promotion and restores
93
+ * the exact grace deadline when a failed archive publication rolls back.
92
94
  */
93
- export declare const DAEMON_GENERATION = 57;
95
+ export declare const DAEMON_GENERATION = 58;
94
96
  /**
95
97
  * Serving-compatibility boundary for daemon lifecycle requests. Epoch 5
96
98
  * requires the complete generation-36 topic authority contract, so older
@@ -40,6 +40,11 @@ export declare class RevisionStore {
40
40
  complete: boolean;
41
41
  offset: number;
42
42
  } | undefined>;
43
+ readIndexedFieldRange(resourceKind: string, resourceId: string, id: string, itemId: string, field: string, offset: number, length: number): Promise<{
44
+ body: string;
45
+ complete: boolean;
46
+ offset: number;
47
+ } | undefined>;
43
48
  describeIndexedItem(resourceKind: string, resourceId: string, id: string, offset: number): Promise<{
44
49
  itemId?: string;
45
50
  fields: string[];
@@ -90,6 +90,10 @@ export interface InvocationReconciliation {
90
90
  }): unknown;
91
91
  hydrate(): Promise<void>;
92
92
  }
93
+ export declare function createInvocationReconciliation(options?: {
94
+ stateRoot?: string;
95
+ sessionId?: string;
96
+ }): InvocationReconciliation;
93
97
  export interface SdkSurfaceFactoryOptions {
94
98
  ctx: ExtensionContext;
95
99
  id: string;
@@ -5,13 +5,24 @@ export type CreateLifecycleAgentSessionResult = {
5
5
  session: AgentSession;
6
6
  capability: SdkStartupCapability;
7
7
  rollback: SdkStartupRollbackTracker;
8
+ /**
9
+ * Starts the memory backend that construction deliberately skipped. The
10
+ * caller MUST run it only after readiness is published.
11
+ */
12
+ startDeferredMemoryBackend: () => Promise<void>;
8
13
  } | {
9
14
  capability: SdkStartupCapability;
10
15
  rollback: SdkStartupRollbackTracker;
11
16
  failure: SdkStartupFailure;
12
17
  };
13
- /** Options accepted by lifecycle-only session construction. */
14
- export type CreateLifecycleAgentSessionOptions = CreateAgentSessionOptions & {
18
+ /**
19
+ * Options accepted by lifecycle-only session construction.
20
+ *
21
+ * `deferMemoryBackendStartup` is not accepted: lifecycle sessions are launched
22
+ * by the broker under a fixed readiness deadline, and memory startup runs
23
+ * unbounded LLM work, so deferral is an invariant rather than a caller choice.
24
+ */
25
+ export type CreateLifecycleAgentSessionOptions = Omit<CreateAgentSessionOptions, "deferMemoryBackendStartup"> & {
15
26
  /**
16
27
  * Startup budget for ACP lifecycle MCP launches, in milliseconds. Set only
17
28
  * when the lifecycle request supplies `mcpServers`; ordinary consumers keep
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Safe failure shaping shared by SDK transports and reconciliation stores.
3
+ * Provider error text is retained only in the local diagnostic log; wire and
4
+ * persisted reconciliation details expose a fixed redacted message.
5
+ */
6
+ export declare const PROMPT_FAILURE_CODE_MAX = 64;
7
+ /** Safe-token code capped at 64; arbitrary failure text is never retained. */
8
+ export declare function sanitizePromptFailure(error: unknown): {
9
+ code: string;
10
+ message: string;
11
+ };
12
+ /** Best-effort local diagnostic text that never crosses the SDK boundary. */
13
+ export declare function formatPromptFailureForLocalLog(error: unknown): string;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Inactivity bound for a prompt whose session host has no tool call in flight: after
3
+ * this much silence the host is treated as having stopped producing, so the prompt is
4
+ * settled instead of hanging the client forever.
5
+ *
6
+ * Every step of a live turn emits a frame (`agent_start`, message events, tool events,
7
+ * `agent_end`), so this bound only has to exceed the longest frame-free gap a healthy
8
+ * turn can produce *with nothing executing*: one model round trip, from the request
9
+ * leaving the host to the first streamed event coming back, possibly straddling a
10
+ * transport reconnect.
11
+ *
12
+ * `TOOL_TIMEOUTS` is this runtime's own statement of how long a single unattended step
13
+ * may block before it is declared stuck, and its slowest **default**
14
+ * ({@link DEFAULT_TOOL_RUNTIME_MS}) is the longest such wait nobody explicitly opted
15
+ * into. That is well above the agent's own model-stream watchdog (120s idle / 100s
16
+ * first event in `packages/ai/src/utils/idle-iterator.ts`), which aborts a stalled
17
+ * provider stream and republishes it as an error frame — itself a frame — long before
18
+ * this bound. {@link SDK_RECONNECT_BUDGET_MS} is added on top so a reconnect landing
19
+ * inside that gap cannot trip it either. Two providers widen their first-event
20
+ * fallback past this bound (alibaba-token-plan 600s, kimi-code 300s); a turn that
21
+ * emits nothing at all for this long on those is reported as abandoned rather than
22
+ * left `running`, and the session still accepts the next prompt.
23
+ *
24
+ * {@link MAX_TOOL_RUNTIME_MS} is deliberately *not* used here. `max` is what a caller
25
+ * may request, not what tools take, so sizing every gap for it produced a 63min bound
26
+ * that could never act as a safety net. Turns that legitimately reach that ceiling are
27
+ * covered by {@link ACP_PROMPT_TOOL_ACTIVITY_TIMEOUT_MS} instead, which is gated on
28
+ * evidence that a tool is actually executing.
29
+ */
30
+ export declare const ACP_PROMPT_INACTIVITY_TIMEOUT_MS: number;
31
+ /**
32
+ * Inactivity bound that applies only while the session host has an unfinished tool
33
+ * call — a `tool_execution_start`/`tool_execution_update` with no matching
34
+ * `tool_execution_end`.
35
+ *
36
+ * A long `bash` is protected by the evidence that it is running, not by a constant
37
+ * sized for the worst case, so this wide bound stands only for as long as that
38
+ * evidence does. It still terminates: no tool call may outlive
39
+ * {@link MAX_TOOL_RUNTIME_MS}, after which the host must publish `tool_execution_end`,
40
+ * so a host that dies mid-tool is still reported (one reconnect budget later).
41
+ */
42
+ export declare const ACP_PROMPT_TOOL_ACTIVITY_TIMEOUT_MS: number;
43
+ /**
44
+ * Inactivity bound that applies while a prompt is awaiting the model: a provider call is in
45
+ * flight and has not produced the first frame of its response yet.
46
+ *
47
+ * Tool execution had evidence and model inference did not, so a turn thinking after
48
+ * `agent_start` looked exactly like a dead producer and was killed at
49
+ * {@link ACP_PROMPT_INACTIVITY_TIMEOUT_MS}. Inference gets the same evidence-based
50
+ * tolerance: the wide bound stands only as long as a model call is observably unanswered.
51
+ *
52
+ * It still terminates. A provider stream that yields nothing within
53
+ * {@link MAX_PROVIDER_FIRST_EVENT_TIMEOUT_MS} is aborted by the agent's own stream watchdog,
54
+ * which republishes as an error/retry frame, so a host that dies mid-inference is still
55
+ * reported one reconnect budget later.
56
+ */
57
+ export declare const ACP_PROMPT_INFERENCE_TIMEOUT_MS: number;
58
+ /**
59
+ * Per-prompt view of what the session host is observably doing: which tool calls it has
60
+ * started but not finished, and whether it is waiting on a model call. Inbound frames are
61
+ * the only evidence the ACP side has, so "a tool is running" means exactly "a start was
62
+ * observed and its end was not", and "awaiting the model" means exactly "a model call was
63
+ * dispatched and nothing from its response has been observed".
64
+ */
65
+ export declare class PromptActivity {
66
+ #private;
67
+ /** Folds one inbound frame's lifecycle event into the in-flight tool and model state. */
68
+ observe(eventType: string | undefined, toolCallId: string | undefined, messageRole: string | undefined): void;
69
+ /** True while at least one started tool call has not reported an end. */
70
+ get running(): boolean;
71
+ /** True while a dispatched model call has not produced the first frame of its response. */
72
+ get awaitingModel(): boolean;
73
+ /** Bound that applies to the next frame-free gap, given what is observably executing. */
74
+ get inactivityBoundMs(): number;
75
+ }
76
+ /** Timer surface behind the prompt watchdog; tests substitute a virtual clock. */
77
+ export interface PromptWatchdogClock {
78
+ now(): number;
79
+ /** Runs `handler` after `delayMs`; the returned callback cancels the pending run. */
80
+ schedule(handler: () => void, delayMs: number): () => void;
81
+ }
82
+ export declare const systemPromptWatchdogClock: PromptWatchdogClock;
@@ -0,0 +1,20 @@
1
+ import type { SdkClientOptions } from "./client";
2
+ type SessionReconnectOptions = Required<Pick<SdkClientOptions, "reconnectAttempts" | "reconnectBackoffMs" | "reconnectMaxBackoffMs">>;
3
+ /**
4
+ * Reconnect budget for every long-lived SDK session client: ACP sessions and the
5
+ * chat daemon's attached sessions alike. Named for the ACP session that first
6
+ * needed it; it lives here because both live under the same host reaper and the
7
+ * bus layer must not import the ACP layer to reach it.
8
+ *
9
+ * Invariant: the client's total reconnect budget must outlive the host heartbeat
10
+ * TTL ({@link HEARTBEAT_TTL_MS}). The SDK host drops a session whose client has
11
+ * not ponged within that TTL, so a client that gives up reconnecting sooner turns
12
+ * every stall the host reaps into a permanently lost session ("SDK WebSocket
13
+ * reconnect attempts exhausted"). The transport defaults (3 attempts at 25ms base
14
+ * = 175ms total) are correct only for one-shot request clients.
15
+ *
16
+ * Backoff ramps 250 -> 500 -> 1000 and then holds at the 2s cap, so individual
17
+ * sleeps stay short and recovery is prompt once the host answers again.
18
+ */
19
+ export declare const ACP_SESSION_RECONNECT: SessionReconnectOptions;
20
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { type ModelProfileErrorCode, type ModelProfileErrorDetails } from "../config/model-profile-contract";
2
2
  export type SdkStartupPhase = "registration" | "startup";
3
- export type SdkStartupReason = "disabled" | "ineligible" | "factory_absent" | "runner_absent" | "pending" | "failed";
3
+ export type SdkStartupReason = "disabled" | "ineligible" | "factory_absent" | "runner_absent" | "admission_timeout" | "pending" | "failed";
4
4
  export interface SdkStartupFailure {
5
5
  phase: SdkStartupPhase;
6
6
  reason: SdkStartupReason;
@@ -110,7 +110,8 @@ import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermission
110
110
  import { type ContributionPrepOptions, type ContributionPrepResult } from "./contribution-prep";
111
111
  import type { MemoryGuardRestoreResult } from "./memory-guard-checkpoint-participant";
112
112
  import { type CustomMessage } from "./messages";
113
- import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, PreparedNewSession, RecoveryHydrationContext, RecoveryHydrationPromotionFence, SessionContext, SessionManager, SessionManagerCloseOutcome } from "./session-manager";
113
+ import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, PreparedNewSession, RecoveryHydrationContext, RecoveryHydrationPromotionFence, SessionContext, SessionManagerCloseOutcome, SessionMemoryStats } from "./session-manager";
114
+ import { SessionManager } from "./session-manager";
114
115
  import { ToolChoiceQueue } from "./tool-choice-queue";
115
116
  import { YieldQueue } from "./yield-queue";
116
117
  /** Session-specific events that extend the core AgentEvent */
@@ -385,8 +386,8 @@ export interface PromptOptions {
385
386
  */
386
387
  onPreflightAccepted?: () => void;
387
388
  /**
388
- * Awaitable durable-accept fence. Called after preflight and before queue mutation
389
- * or `#promptAgentWithIdleRetry`. SDK bus installs a closure that fsyncs acceptance.
389
+ * Awaitable durable-accept fence. Called after preflight and immediately before
390
+ * agent execution begins. SDK bus installs a closure that fsyncs acceptance.
390
391
  */
391
392
  onPreflightAcceptCommit?: () => void | Promise<void>;
392
393
  /** Skill-only: prepared metadata before the durable fence (path/lineCount/cleanedArgs). */
@@ -444,6 +445,7 @@ export interface SessionStats {
444
445
  premiumRequests: number;
445
446
  cost: number;
446
447
  costBreakdown?: Usage["cost"];
448
+ sessionMemory: SessionMemoryStats;
447
449
  }
448
450
  export type EphemeralTurnPurpose = "btw" | "background";
449
451
  interface EphemeralTurnBaseArgs {
@@ -560,7 +562,7 @@ export declare class StreamingEditFileCache {
560
562
  export declare class AgentSession {
561
563
  #private;
562
564
  readonly agent: Agent;
563
- readonly sessionManager: SessionManager;
565
+ sessionManager: SessionManager;
564
566
  readonly settings: Settings;
565
567
  readonly memoryBackend: LazyService<MemoryBackend>;
566
568
  readonly notificationSessionController: NotificationSessionController | undefined;
@@ -1363,9 +1365,12 @@ export declare class AgentSession {
1363
1365
  * @param command The bash command to execute
1364
1366
  * @param onChunk Optional streaming callback for output
1365
1367
  * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)
1368
+ * @param options.onPersisted Called once the execution's message is in session state
1369
+ * (immediately when idle, at the post-turn flush while streaming)
1366
1370
  */
1367
1371
  executeBash(command: string, onChunk?: (chunk: string) => void, options?: {
1368
1372
  excludeFromContext?: boolean;
1373
+ onPersisted?: () => void;
1369
1374
  }): Promise<BashResult>;
1370
1375
  /**
1371
1376
  * Record a bash execution result in session history.
@@ -1373,6 +1378,7 @@ export declare class AgentSession {
1373
1378
  */
1374
1379
  recordBashResult(command: string, result: BashResult, options?: {
1375
1380
  excludeFromContext?: boolean;
1381
+ onPersisted?: () => void;
1376
1382
  }): void;
1377
1383
  /**
1378
1384
  * Cancel running bash command.
@@ -1388,9 +1394,12 @@ export declare class AgentSession {
1388
1394
  * @param code The Python code to execute
1389
1395
  * @param onChunk Optional streaming callback for output
1390
1396
  * @param options.excludeFromContext If true, execution won't be sent to LLM ($$ prefix)
1397
+ * @param options.onPersisted Called once the execution's message is in session state
1398
+ * (immediately when idle, at the post-turn flush while streaming)
1391
1399
  */
1392
1400
  executePython(code: string, onChunk?: (chunk: string) => void, options?: {
1393
1401
  excludeFromContext?: boolean;
1402
+ onPersisted?: () => void;
1394
1403
  }): Promise<PythonResult>;
1395
1404
  assertEvalExecutionAllowed(): void;
1396
1405
  /**
@@ -1402,6 +1411,7 @@ export declare class AgentSession {
1402
1411
  */
1403
1412
  recordPythonResult(code: string, result: PythonResult, options?: {
1404
1413
  excludeFromContext?: boolean;
1414
+ onPersisted?: () => void;
1405
1415
  }): void;
1406
1416
  /**
1407
1417
  * Cancel running Python execution.
@@ -210,4 +210,36 @@ export declare function resolveResidentImageDataSync(blobStore: BlobStore, data:
210
210
  sessionId?: string;
211
211
  sessionFile?: string;
212
212
  }): string;
213
+ /** One canonical blob file (`<blobsDir>/<sha256-hex>`) observed on disk. */
214
+ export interface CanonicalBlobEntry {
215
+ readonly hash: string;
216
+ readonly path: string;
217
+ readonly bytes: number;
218
+ readonly mtimeMs: number;
219
+ }
220
+ /** Longest possible `blob:sha256:<hash>` reference, used to bound chunked scans. */
221
+ export declare const BLOB_REFERENCE_MAX_LENGTH: number;
222
+ /** Collect every `blob:sha256:<hash>` reference that appears in `text`. */
223
+ export declare function collectBlobReferences(text: string, into: Set<string>): void;
224
+ /**
225
+ * List canonical blob files. Anything that is not a plain, owner-visible regular
226
+ * file named after its own hash (temp files, symlinks, subdirectories) is
227
+ * skipped: a sweep must never reason about entries it cannot classify.
228
+ */
229
+ export declare function listCanonicalBlobs(dir: string): Promise<CanonicalBlobEntry[]>;
230
+ /**
231
+ * Remove one canonical blob, fail-closed on identity drift. The entry captured
232
+ * at scan time must still describe the file at unlink time (same inode, size and
233
+ * mtime, still a single-link regular file), otherwise the blob is left in place.
234
+ *
235
+ * `failed` distinguishes an actual IO failure (which a caller should surface as
236
+ * a failed reclaim) from a revalidation refusal (which is an ordinary KEEP).
237
+ */
238
+ export declare function removeCanonicalBlob(entry: CanonicalBlobEntry): Promise<{
239
+ removed: true;
240
+ } | {
241
+ removed: false;
242
+ reason: string;
243
+ failed?: true;
244
+ }>;
213
245
  export {};