@gajae-code/coding-agent 0.11.3 → 0.11.5

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 (213) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/types/cli/notify-cli.d.ts +5 -1
  3. package/dist/types/cli/worktree-cli.d.ts +8 -25
  4. package/dist/types/commands/gc.d.ts +4 -0
  5. package/dist/types/commands/worktree.d.ts +7 -32
  6. package/dist/types/config/settings-schema.d.ts +20 -0
  7. package/dist/types/export/html/template.generated.d.ts +1 -1
  8. package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
  9. package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
  10. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
  11. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
  12. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
  13. package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
  14. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
  15. package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
  16. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  17. package/dist/types/harness-control-plane/owner.d.ts +3 -1
  18. package/dist/types/internal-urls/local-protocol.d.ts +18 -0
  19. package/dist/types/main.d.ts +2 -2
  20. package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
  21. package/dist/types/modes/components/custom-editor.d.ts +8 -3
  22. package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
  23. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
  24. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  25. package/dist/types/modes/components/tool-transcript-format.d.ts +77 -0
  26. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
  27. package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
  28. package/dist/types/modes/controllers/input-controller.d.ts +8 -2
  29. package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
  30. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
  31. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
  32. package/dist/types/plan-mode/approved-plan.d.ts +1 -0
  33. package/dist/types/runtime-mcp/manager.d.ts +2 -0
  34. package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
  35. package/dist/types/sdk/broker/broker.d.ts +1 -0
  36. package/dist/types/sdk/broker/discovery.d.ts +10 -0
  37. package/dist/types/sdk/broker/ensure.d.ts +24 -1
  38. package/dist/types/sdk/broker/internal.d.ts +3 -0
  39. package/dist/types/sdk/broker/session-index.d.ts +12 -0
  40. package/dist/types/sdk/broker/state-version.d.ts +2 -1
  41. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
  42. package/dist/types/sdk/bus/chat-daemon-control.d.ts +39 -4
  43. package/dist/types/sdk/bus/config-commands.d.ts +8 -0
  44. package/dist/types/sdk/bus/config.d.ts +22 -0
  45. package/dist/types/sdk/bus/index.d.ts +162 -2
  46. package/dist/types/sdk/bus/notification-service.d.ts +71 -8
  47. package/dist/types/sdk/bus/session-control.d.ts +11 -8
  48. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
  49. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -3
  50. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
  51. package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
  52. package/dist/types/sdk/session.d.ts +16 -1
  53. package/dist/types/secrets/index.d.ts +4 -2
  54. package/dist/types/secrets/obfuscator.d.ts +1 -1
  55. package/dist/types/session/agent-session.d.ts +31 -2
  56. package/dist/types/session/artifacts.d.ts +30 -8
  57. package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
  58. package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
  59. package/dist/types/session/session-manager.d.ts +88 -18
  60. package/dist/types/session/session-storage.d.ts +27 -0
  61. package/dist/types/session/streaming-output.d.ts +4 -0
  62. package/dist/types/task/executor.d.ts +9 -0
  63. package/dist/types/tool-discovery/tool-index.d.ts +2 -0
  64. package/dist/types/tools/ask.d.ts +87 -12
  65. package/dist/types/tools/bash-skill-urls.d.ts +2 -2
  66. package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
  67. package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
  68. package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
  69. package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
  70. package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
  71. package/dist/types/tools/index.d.ts +4 -0
  72. package/dist/types/tools/path-utils.d.ts +9 -14
  73. package/dist/types/tools/render-utils.d.ts +1 -8
  74. package/dist/types/tools/resource-gc.d.ts +14 -0
  75. package/dist/types/utils/image-loading.d.ts +23 -0
  76. package/dist/types/utils/image-resize.d.ts +2 -0
  77. package/dist/types/utils/pasted-image-loading.d.ts +39 -0
  78. package/dist/types/utils/pasted-image-path.d.ts +21 -19
  79. package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
  80. package/dist/types/web/scrapers/types.d.ts +1 -5
  81. package/package.json +25 -9
  82. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  83. package/scripts/run-threat-model-check.ts +60 -0
  84. package/src/cli/notify-cli.ts +40 -18
  85. package/src/cli/worktree-cli.ts +9 -287
  86. package/src/cli.ts +10 -9
  87. package/src/commands/gc.ts +11 -1
  88. package/src/commands/sdk.ts +10 -4
  89. package/src/commands/worktree.ts +10 -53
  90. package/src/config/model-profiles.ts +15 -15
  91. package/src/config/settings-schema.ts +17 -0
  92. package/src/daemon/runtime.ts +2 -2
  93. package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
  94. package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
  95. package/src/defaults/gjc/skills/team/SKILL.md +7 -9
  96. package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
  97. package/src/eval/js/shared/runtime.ts +1 -1
  98. package/src/eval/py/index.ts +2 -2
  99. package/src/export/html/template.generated.ts +1 -1
  100. package/src/extensibility/plugins/marketplace/manager.ts +94 -10
  101. package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
  102. package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
  103. package/src/gjc-runtime/deep-interview-state.ts +127 -3
  104. package/src/gjc-runtime/gc-render.ts +19 -1
  105. package/src/gjc-runtime/gc-runtime.ts +68 -3
  106. package/src/gjc-runtime/launch-tmux.ts +61 -6
  107. package/src/gjc-runtime/managed-owner-admission.ts +352 -0
  108. package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
  109. package/src/gjc-runtime/state-argv.ts +292 -0
  110. package/src/gjc-runtime/state-runtime.ts +80 -222
  111. package/src/gjc-runtime/team-runtime.ts +1 -1
  112. package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
  113. package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
  114. package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
  115. package/src/harness-control-plane/owner.ts +59 -3
  116. package/src/hindsight/client.ts +65 -2
  117. package/src/internal-urls/agent-protocol.ts +76 -5
  118. package/src/internal-urls/docs-index.generated.ts +7 -6
  119. package/src/internal-urls/local-protocol.ts +416 -18
  120. package/src/lsp/index.ts +4 -88
  121. package/src/main.ts +37 -13
  122. package/src/modes/acp/acp-agent.ts +8 -0
  123. package/src/modes/components/ansi-display-validator.ts +54 -0
  124. package/src/modes/components/custom-editor.ts +103 -32
  125. package/src/modes/components/custom-provider-wizard.ts +26 -1
  126. package/src/modes/components/notifications-settings-editor.ts +26 -2
  127. package/src/modes/components/session-observer-overlay.ts +40 -37
  128. package/src/modes/components/settings-selector.ts +3 -1
  129. package/src/modes/components/tool-transcript-format.ts +393 -0
  130. package/src/modes/components/transcript-viewer-overlay.ts +63 -1
  131. package/src/modes/controllers/goal-mode-controller.ts +20 -1
  132. package/src/modes/controllers/input-controller.ts +98 -37
  133. package/src/modes/controllers/plan-mode-controller.ts +2 -0
  134. package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
  135. package/src/modes/controllers/selector-controller.ts +71 -9
  136. package/src/modes/interactive-mode.ts +14 -2
  137. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
  138. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
  139. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
  140. package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
  141. package/src/plan-mode/approved-plan.ts +3 -1
  142. package/src/prompts/agents/architect.md +1 -0
  143. package/src/prompts/agents/critic.md +1 -0
  144. package/src/prompts/system/custom-system-prompt.md +1 -1
  145. package/src/prompts/system/system-prompt.md +2 -1
  146. package/src/prompts/tools/lsp.md +3 -3
  147. package/src/runtime-mcp/manager.ts +16 -1
  148. package/src/runtime-mcp/oauth-discovery.ts +116 -26
  149. package/src/sdk/broker/broker.ts +139 -32
  150. package/src/sdk/broker/discovery.ts +125 -0
  151. package/src/sdk/broker/ensure.ts +103 -19
  152. package/src/sdk/broker/internal.ts +10 -0
  153. package/src/sdk/broker/session-index.ts +309 -38
  154. package/src/sdk/broker/state-version.ts +5 -2
  155. package/src/sdk/bus/chat-daemon-cli.ts +72 -10
  156. package/src/sdk/bus/chat-daemon-control.ts +526 -130
  157. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  158. package/src/sdk/bus/config-commands.ts +22 -0
  159. package/src/sdk/bus/config.ts +37 -0
  160. package/src/sdk/bus/index.ts +384 -92
  161. package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
  162. package/src/sdk/bus/notification-service.ts +284 -62
  163. package/src/sdk/bus/recent-activity.ts +14 -5
  164. package/src/sdk/bus/session-control.ts +150 -44
  165. package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
  166. package/src/sdk/bus/telegram-daemon-contract.ts +6 -3
  167. package/src/sdk/bus/telegram-daemon-control.ts +294 -76
  168. package/src/sdk/bus/telegram-daemon.ts +1842 -281
  169. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  170. package/src/sdk/session.ts +155 -27
  171. package/src/secrets/index.ts +53 -10
  172. package/src/secrets/obfuscator.ts +21 -18
  173. package/src/secrets/regex.ts +2 -0
  174. package/src/session/agent-session.ts +495 -253
  175. package/src/session/artifacts.ts +188 -23
  176. package/src/session/context-estimation.ts +7 -2
  177. package/src/session/internal/managed-session-scope.ts +215 -39
  178. package/src/session/internal/managed-session-storage.ts +1152 -46
  179. package/src/session/session-manager.ts +1055 -148
  180. package/src/session/session-storage.ts +109 -11
  181. package/src/session/streaming-output.ts +6 -0
  182. package/src/task/executor.ts +71 -26
  183. package/src/task/index.ts +63 -20
  184. package/src/tool-discovery/tool-index.ts +18 -0
  185. package/src/tools/ask.ts +329 -57
  186. package/src/tools/bash-allowed-prefixes.ts +79 -29
  187. package/src/tools/bash-skill-urls.ts +14 -3
  188. package/src/tools/bash.ts +2 -2
  189. package/src/tools/browser/launch.ts +3 -1
  190. package/src/tools/browser/readable.ts +1 -1
  191. package/src/tools/browser/screenshot-format.ts +34 -0
  192. package/src/tools/browser/tab-protocol.ts +3 -0
  193. package/src/tools/browser/tab-supervisor.ts +125 -122
  194. package/src/tools/browser/tab-worker-entry.ts +33 -14
  195. package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
  196. package/src/tools/browser/tab-worker-smoke.ts +87 -0
  197. package/src/tools/browser/tab-worker.ts +7 -5
  198. package/src/tools/index.ts +4 -0
  199. package/src/tools/path-utils.ts +42 -35
  200. package/src/tools/plan-mode-guard.ts +1 -0
  201. package/src/tools/read.ts +7 -1
  202. package/src/tools/render-utils.ts +1 -27
  203. package/src/tools/resource-gc.ts +123 -34
  204. package/src/tools/sqlite-reader.ts +3 -2
  205. package/src/tools/subagent.ts +2 -0
  206. package/src/utils/image-loading.ts +85 -34
  207. package/src/utils/image-resize.ts +16 -9
  208. package/src/utils/pasted-image-loading.ts +514 -0
  209. package/src/utils/pasted-image-path.ts +223 -98
  210. package/src/web/scrapers/html-to-markdown.ts +68 -0
  211. package/src/web/scrapers/readthedocs.ts +1 -1
  212. package/src/web/scrapers/types.ts +1 -68
  213. package/src/web/search/providers/openai-compatible.ts +76 -23
@@ -1,3 +1,4 @@
1
+ import type { WriteFileOptions } from "node:fs";
1
2
  import type { Settings } from "../../config/settings";
2
3
  /**
3
4
  * Strip secrets from a human-facing diagnostic string. Redacts the configured
@@ -21,7 +22,18 @@ export interface NotificationExactUnlinkResult {
21
22
  ok: boolean;
22
23
  code?: string;
23
24
  detachedPath?: string;
24
- }
25
+ /** A live publisher successor retained after an exact-unlink race. */
26
+ retainedSuccessorPath?: string;
27
+ /** An internal exchange placeholder whose verified cleanup failed. */
28
+ retainedPlaceholderPath?: string;
29
+ /** A cleanup entry whose identity could not be verified after a race. */
30
+ retainedUnknownPath?: string;
31
+ }
32
+ /** Read one regular file together with the identity required for exact removal. */
33
+ export declare function readNotificationEndpointFile(file: string): Promise<NotificationEndpointFile>;
34
+ /** Bump when the native exact-deletion identity contract changes. */
35
+ export declare const NATIVE_PATH_IDENTITY_CONTRACT_VERSION = 1;
36
+ export declare function exactUnlinkNotificationFile(file: string, identity: NotificationEndpointFileIdentity, quarantineName: string): NotificationExactUnlinkResult;
25
37
  /** Minimal filesystem surface the service needs; injectable for tests. */
26
38
  export interface NotificationServiceFs {
27
39
  readdir(dir: string): Promise<string[]>;
@@ -29,13 +41,10 @@ export interface NotificationServiceFs {
29
41
  readEndpointFile(file: string): Promise<NotificationEndpointFile>;
30
42
  exactUnlink(file: string, identity: NotificationEndpointFileIdentity): Promise<NotificationExactUnlinkResult>;
31
43
  unlink(file: string): Promise<void>;
32
- /**
33
- * Atomically create `file` with O_EXCL semantics: resolves `true` when this
34
- * call created it, `false` when it already existed. Used to hold the daemon
35
- * steal-mutex ({@link DaemonPaths.steal}) during owner-bound lock removal so
36
- * recovery and a concurrent daemon takeover are mutually exclusive.
37
- */
38
- createExclusive(file: string): Promise<boolean>;
44
+ writeFile?(file: string, data: string, opts?: WriteFileOptions): Promise<void>;
45
+ stat?(file: string): Promise<{
46
+ mtimeMs: number;
47
+ }>;
39
48
  }
40
49
  /** Injectable dependencies shared across service operations. */
41
50
  export interface NotificationServiceDeps {
@@ -143,6 +152,12 @@ export interface NotificationRecoveryReport {
143
152
  endpointsKept: number;
144
153
  endpointsUnreadable: number;
145
154
  endpointsDetached?: string[];
155
+ /** Successor paths retained after an exact-unlink race, distinct from stale quarantines. */
156
+ endpointsRetainedSuccessors?: string[];
157
+ /** Internal exchange placeholders retained after verified cleanup failure. */
158
+ endpointsRetainedPlaceholders?: string[];
159
+ /** Cleanup entries retained with unverified or mismatching identity. */
160
+ endpointsRetainedUnknown?: string[];
146
161
  daemon: {
147
162
  action: DaemonRecoveryAction;
148
163
  detail: string;
@@ -155,6 +170,53 @@ export interface RecoveryOptions {
155
170
  stateRoot?: string;
156
171
  deps?: NotificationServiceDeps;
157
172
  }
173
+ export interface DaemonTransitionLock {
174
+ pid: number;
175
+ incarnation: string;
176
+ createdAt: number;
177
+ /** Unique fencing generation for this particular transition acquisition. */
178
+ token: string;
179
+ }
180
+ type TransitionMarkerFs = {
181
+ readFile(file: string, encoding: "utf8"): Promise<string>;
182
+ writeFile?(file: string, data: string, opts?: WriteFileOptions): Promise<void>;
183
+ readEndpointFile?(file: string): Promise<NotificationEndpointFile>;
184
+ exactUnlink?(file: string, identity: NotificationEndpointFileIdentity): Promise<NotificationExactUnlinkResult>;
185
+ };
186
+ /** True only while the exact transition acquisition still occupies the marker path. */
187
+ export declare function daemonTransitionLockIsHeld(input: {
188
+ fs: Pick<TransitionMarkerFs, "readFile" | "readEndpointFile">;
189
+ path: string;
190
+ lock: DaemonTransitionLock;
191
+ }): Promise<boolean>;
192
+ /** Removes only the caller's exact marker through the identity-bound detach primitive. */
193
+ export declare function releaseDaemonTransitionLock(input: {
194
+ fs: TransitionMarkerFs;
195
+ path: string;
196
+ lock: DaemonTransitionLock;
197
+ }): Promise<boolean>;
198
+ /**
199
+ * Acquire the daemon lifecycle transition lock using durable owner metadata.
200
+ * The full marker is published in the single O_EXCL write which reserves it;
201
+ * canonical markers are detached only through their captured filesystem identity.
202
+ *
203
+ * Malformed and empty markers deliberately remain blocked regardless of age. They
204
+ * have no owner provenance, so reclaiming them could detach a generation-6 empty
205
+ * reservation while its paused legacy writer can still resume. Operators must
206
+ * manually clean up such legacy debris after confirming no legacy process remains.
207
+ */
208
+ export declare function acquireDaemonTransitionLock(input: {
209
+ fs: TransitionMarkerFs;
210
+ path: string;
211
+ pid: number;
212
+ pidAlive: (pid: number) => boolean;
213
+ pidIncarnation: (pid: number) => string | undefined;
214
+ now?: () => number;
215
+ sleep?: (ms: number) => Promise<void>;
216
+ retries?: number;
217
+ retryDelayMs?: number;
218
+ randomToken?: () => string;
219
+ }): Promise<DaemonTransitionLock | undefined>;
158
220
  /**
159
221
  * Ownership-protected cleanup. Removes only DEAD-owner artifacts:
160
222
  * per-session endpoint files with positive proof of death (a stale tombstone or
@@ -168,3 +230,4 @@ export interface RecoveryOptions {
168
230
  export declare function recoverNotifications(opts: RecoveryOptions): Promise<NotificationRecoveryReport>;
169
231
  /** Render a recovery report as human-readable lines (no secrets). */
170
232
  export declare function formatNotificationRecoveryReport(report: NotificationRecoveryReport): string;
233
+ export {};
@@ -14,7 +14,7 @@ export interface BoundNotificationSession<Context extends NotificationSessionCon
14
14
  unbind(): void;
15
15
  }
16
16
  export type NotificationEndpointStartResult = "started" | "already" | "disabled" | "failed";
17
- export type TelegramDaemonPreflightResult = "ready" | "blocked_identity";
17
+ export type TelegramDaemonPreflightResult = "ready" | "blocked_identity" | "failed";
18
18
  /**
19
19
  * The endpoint implementation is deliberately injected. The controller owns
20
20
  * policy and session-local state while the extension continues to own its
@@ -29,6 +29,10 @@ export interface NotificationSessionRuntime<Context extends NotificationSessionC
29
29
  * emit a frame. `blocked_identity` is fail-closed and starts nothing.
30
30
  */
31
31
  ensureTelegramDaemon?(binding: BoundNotificationSession<Context>): Promise<TelegramDaemonPreflightResult>;
32
+ /** Refresh mutable delivery policy from the same configuration snapshot used for reconciliation. */
33
+ refreshPolicy?(binding: BoundNotificationSession<Context>, policy: NotificationRuntimePolicy): void;
34
+ /** Enables delivery only after the controller has committed a stable policy. */
35
+ activate?(binding: BoundNotificationSession<Context>): void;
32
36
  }
33
37
  export interface NotificationSessionStatus {
34
38
  eligible: boolean;
@@ -41,6 +45,12 @@ export interface NotificationSessionReconcileResult {
41
45
  outcome: NotificationEndpointStartResult | "stopped";
42
46
  status: NotificationSessionStatus;
43
47
  }
48
+ export interface NotificationRuntimePolicy {
49
+ redact: boolean;
50
+ verbosity: NotificationConfig["verbosity"];
51
+ stream: boolean;
52
+ mode: "provisional" | "committed";
53
+ }
44
54
  export interface NotificationSessionControllerOptions {
45
55
  /** Gate A result, resolved once by the SDK from the canonical host predicate. */
46
56
  eligible: boolean;
@@ -49,13 +59,6 @@ export interface NotificationSessionControllerOptions {
49
59
  /** Kept as a reference so test and embedding hosts can supply their own environment. */
50
60
  env?: NodeJS.ProcessEnv;
51
61
  }
52
- /**
53
- * Shared owner of notification session policy.
54
- *
55
- * Gate A is captured at creation. Gate B is evaluated for each session
56
- * operation with `isSessionNotificationsEnabled`. Gate C verifies complete
57
- * Telegram ownership before a generic endpoint is allowed to start.
58
- */
59
62
  export declare class NotificationSessionController {
60
63
  #private;
61
64
  constructor(options: NotificationSessionControllerOptions);
@@ -30,6 +30,11 @@ export declare function createLightweightDaemonSettings(input: {
30
30
  }): LightweightDaemonSettings;
31
31
  export declare function loadLightweightDaemonSettings(agentDir: string): Promise<LightweightDaemonSettings>;
32
32
  export declare function ownerPidFromOwnerId(ownerId: string): number | undefined;
33
+ /** Creates owner-fenced daemon control hooks for the CLI lifecycle boundary. */
34
+ export declare function createDaemonControlHooks(settings: Settings): {
35
+ shouldStop: (owner: string) => Promise<boolean>;
36
+ clear: (owner: string) => Promise<void>;
37
+ };
33
38
  export declare function runDaemonSmoke(opts?: {
34
39
  agentDir?: string;
35
40
  }): Promise<void>;
@@ -11,7 +11,10 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
11
11
  * freshly-upgraded host must still recognize an older, still-live daemon that
12
12
  * predates capability-gated frame enforcement and trigger a reload. Bump this
13
13
  * on every daemon-behavior change independent of the wire version.
14
- * The current development baseline already includes #2299's generation 4;
15
- * this behavior change therefore advances the operational generation to 5.
14
+ * The current development baseline already includes #2299's generation 4,
15
+ * incarnation fencing in generation 5, owner-lock authority in generation 6,
16
+ * identity-atomic transition markers in generation 7, stable signaling plus
17
+ * tri-state foreign-owner provenance in generation 8, and retained managed
18
+ * filesystem authority changes in generation 9.
16
19
  */
17
- export declare const DAEMON_GENERATION = 5;
20
+ export declare const DAEMON_GENERATION = 9;
@@ -22,11 +22,18 @@ export declare function telegramControlRequestPath(agentDir: string): string;
22
22
  export declare function readTelegramControlRequest(settings: Settings, fsImpl?: TelegramDaemonFs): Promise<TelegramDaemonControlRequest | undefined>;
23
23
  export declare function writeTelegramControlRequest(settings: Settings, request: TelegramDaemonControlRequest, fsImpl?: TelegramDaemonFs): Promise<void>;
24
24
  export declare function clearTelegramControlRequest(settings: Settings, requestId?: string, fsImpl?: TelegramDaemonFs): Promise<void>;
25
+ export interface DaemonProcessReference {
26
+ incarnation: string;
27
+ signalRoot(signal: NodeJS.Signals): void;
28
+ }
25
29
  export interface TelegramDaemonControlDeps {
26
30
  fs?: TelegramDaemonFs;
27
31
  now?: () => number;
28
32
  pidAlive?: (pid: number) => boolean;
29
- sendSignal?: (pid: number, signal: NodeJS.Signals) => void;
33
+ processReference?: (pid: number) => DaemonProcessReference | undefined;
34
+ pidIncarnation?: (pid: number) => string | undefined;
35
+ /** Test seam for platform-specific default stable-process authority. */
36
+ platform?: NodeJS.Platform;
30
37
  spawn?: TelegramDaemonDeps["spawn"];
31
38
  execPath?: string;
32
39
  /**
@@ -40,20 +47,30 @@ export interface TelegramDaemonControlDeps {
40
47
  randomId?: () => string;
41
48
  sleep?: (ms: number) => Promise<void>;
42
49
  waitStepMs?: number;
50
+ /** Bounded startup-readiness timeout; injectable for deterministic controller tests. */
51
+ readinessTimeoutMs?: number;
43
52
  }
53
+ export type TelegramGenerationReloadResult = {
54
+ outcome: "ready";
55
+ operation: DaemonOperationResult;
56
+ } | {
57
+ outcome: "failed";
58
+ operation: DaemonOperationResult;
59
+ };
44
60
  export declare class TelegramDaemonController implements BuiltInDaemonController {
45
61
  private readonly settings;
46
62
  private readonly deps;
47
63
  readonly kind: "telegram";
48
64
  private readonly fsImpl;
49
- private readonly now;
50
65
  private readonly pidAlive;
51
- private readonly sendSignal;
66
+ private readonly now;
67
+ private readonly processReference;
52
68
  private readonly waitStepMs;
53
69
  constructor(settings: Settings, deps?: TelegramDaemonControlDeps);
54
70
  private runtimeInfo;
55
71
  status(): Promise<DaemonStatus>;
56
72
  private spawnDeps;
73
+ private spawnAndWait;
57
74
  private sleep;
58
75
  /**
59
76
  * Wait until the captured pid is dead. Ownership-file movement is NOT treated
@@ -61,10 +78,12 @@ export declare class TelegramDaemonController implements BuiltInDaemonController
61
78
  * which is what the no-409 invariant requires before spawning a fresh poller.
62
79
  */
63
80
  private waitForPidDeath;
81
+ private signalCapturedOwner;
64
82
  private result;
65
83
  reload(opts?: DaemonOperationOptions): Promise<DaemonOperationResult>;
84
+ reloadForGenerationUpgrade(opts?: DaemonOperationOptions): Promise<TelegramGenerationReloadResult>;
66
85
  stop(opts?: DaemonOperationOptions): Promise<DaemonOperationResult>;
67
86
  private stopOrReload;
68
- /** Clear our own control request unless a newer owner-scoped request replaced it. */
87
+ /** Clear only our exact control request; a successor request must survive. */
69
88
  private clearOwnRequest;
70
89
  }
@@ -2,6 +2,8 @@ import * as fs from "node:fs";
2
2
  import type { Settings } from "../../config/settings";
3
3
  import type { DaemonRuntimeInfo } from "../../daemon/control-types";
4
4
  import { HEARTBEAT_TTL_MS } from "./daemon-paths";
5
+ import { type DaemonTransitionLock, type NotificationEndpointFile, type NotificationEndpointFileIdentity, type NotificationExactUnlinkResult } from "./notification-service";
6
+ import { type DaemonProcessReference } from "./telegram-daemon-control";
5
7
  export { DAEMON_GENERATION, NOTIFICATION_PROTOCOL_VERSION } from "./telegram-daemon-contract";
6
8
  import { type ControlServerLike, type LifecycleControlServerFactory } from "./lifecycle-control-runtime";
7
9
  import type { OrchestratorDeps } from "./lifecycle-orchestrator";
@@ -10,9 +12,16 @@ import { type AliasTable, type CallbackRoute } from "./telegram-reference";
10
12
  export type EnsureDaemonResult = "owner_spawned" | "attached" | "disabled" | "blocked";
11
13
  /** Detailed result for orchestration that must distinguish a #2028 handoff from a fresh spawn. */
12
14
  export type EnsureTelegramDaemonDetailedResult = "spawned" | "reloaded" | "attached" | "disabled" | "blocked_identity";
15
+ export type TelegramDaemonOwnershipPhase = "provisional" | "ready" | "retired";
13
16
  export interface DaemonState {
14
17
  pid: number;
18
+ /** OS process-start provenance; mandatory for PID-authorized ownership actions. */
19
+ incarnation: string;
15
20
  ownerId: string;
21
+ /** Unique, durable identity for one ownership acquisition. */
22
+ acquisitionId?: string;
23
+ /** A provisional owner is physical-live but MUST NOT be attached as ready. */
24
+ ownershipPhase?: TelegramDaemonOwnershipPhase;
16
25
  tokenFingerprint: string;
17
26
  chatId: string;
18
27
  startedAt: number;
@@ -26,10 +35,8 @@ export interface DaemonState {
26
35
  version: 1;
27
36
  /**
28
37
  * Operational daemon generation of the process that owns the lock, distinct
29
- * from the persisted state-schema {@link DaemonState.version}. It records the
30
- * wire generation ({@link DAEMON_GENERATION}) the owning daemon speaks so a
31
- * freshly-upgraded host can detect — and reload — a still-live pre-upgrade
32
- * daemon whose schema version is unchanged. Absent on pre-generation state.
38
+ * from both the persisted state-schema {@link DaemonState.version} and the
39
+ * notification wire protocol version. Absent on pre-generation state.
33
40
  */
34
41
  generation?: number;
35
42
  stoppedAt?: number;
@@ -45,8 +52,14 @@ export interface TelegramDaemonFs {
45
52
  }>;
46
53
  readdir(path: string): Promise<string[]>;
47
54
  chmod(path: string, mode: number): Promise<void>;
55
+ stat?(path: string): Promise<{
56
+ mtimeMs: number;
57
+ }>;
58
+ readEndpointFile?(path: string): Promise<NotificationEndpointFile>;
59
+ exactUnlink?(path: string, identity: NotificationEndpointFileIdentity): Promise<NotificationExactUnlinkResult>;
48
60
  }
49
61
  export interface SpawnResult {
62
+ pid?: number;
50
63
  unref?: () => void;
51
64
  }
52
65
  export interface TelegramDaemonDeps {
@@ -54,6 +67,10 @@ export interface TelegramDaemonDeps {
54
67
  now?: () => number;
55
68
  pid?: number;
56
69
  pidAlive?: (pid: number) => boolean;
70
+ /** Opens an identity-stable process authority for destructive lifecycle operations. */
71
+ processReference?: (pid: number) => DaemonProcessReference | undefined;
72
+ /** Returns immutable process-start provenance, or undefined when unsupported. */
73
+ pidIncarnation?: (pid: number) => string | undefined;
57
74
  spawn?: (command: string, args: string[], opts: {
58
75
  detached: boolean;
59
76
  stdio: "ignore";
@@ -71,6 +88,8 @@ export interface TelegramDaemonDeps {
71
88
  sendSignal?: (pid: number, signal: NodeJS.Signals) => void;
72
89
  sleep?: (ms: number) => Promise<void>;
73
90
  waitStepMs?: number;
91
+ /** Bounded startup-readiness timeout; injectable for deterministic handoff tests. */
92
+ readinessTimeoutMs?: number;
74
93
  }
75
94
  export declare const HEARTBEAT_INTERVAL_MS = 5000;
76
95
  export { HEARTBEAT_TTL_MS };
@@ -104,6 +123,53 @@ export declare function startDaemonLifecycleControl(input: {
104
123
  auditRedactionKey: Uint8Array;
105
124
  env?: NodeJS.ProcessEnv;
106
125
  }): OrchestratorDeps;
126
+ export declare function tryCreateOwnershipLock(fsImpl: TelegramDaemonFs, file: string, initialization: OwnershipLockMetadata): Promise<boolean>;
127
+ type OwnershipLockMetadata = {
128
+ pid: number;
129
+ incarnation: string;
130
+ ownerId?: string;
131
+ acquisitionId?: string;
132
+ startedAt: number;
133
+ };
134
+ type LegacyOwnershipLockMetadata = {
135
+ pid: number;
136
+ incarnation?: string;
137
+ startedAt: number;
138
+ };
139
+ type OwnershipLockRead = {
140
+ kind: "missing";
141
+ } | {
142
+ kind: "malformed";
143
+ raw: string;
144
+ mtimeMs?: number;
145
+ } | {
146
+ kind: "legacy";
147
+ metadata: LegacyOwnershipLockMetadata;
148
+ } | {
149
+ kind: "valid";
150
+ metadata: OwnershipLockMetadata;
151
+ };
152
+ /** Read lock provenance without treating a corrupt legacy artifact as a filesystem failure. */
153
+ export declare function readOwnershipLock(fsImpl: TelegramDaemonFs, file: string): Promise<OwnershipLockRead>;
154
+ /**
155
+ * A live initializer lock only proves that a concurrent publisher is active.
156
+ * It never proves a ready daemon: legacy or unavailable provenance remains
157
+ * blocked, while a canonical mismatch proves PID reuse and can be reclaimed
158
+ * under the transition lock.
159
+ */
160
+ export declare function liveOwnershipLockDecision(input: {
161
+ lock: OwnershipLockRead;
162
+ pidAlive: (pid: number) => boolean;
163
+ pidIncarnation: (pid: number) => string | undefined;
164
+ }): {
165
+ acquired: false;
166
+ attached: false;
167
+ blocked: true;
168
+ } | {
169
+ acquired: false;
170
+ attached: false;
171
+ provisional: true;
172
+ } | undefined;
107
173
  export declare function registerNotificationRoot(input: {
108
174
  settings: Settings;
109
175
  cwd: string;
@@ -121,12 +187,39 @@ export declare function unregisterNotificationRoot(input: {
121
187
  sessionId: string;
122
188
  fs?: TelegramDaemonFs;
123
189
  }): Promise<UnregisterNotificationRootResult>;
190
+ export declare function hasSafeDaemonStateShape(state: unknown): state is DaemonState;
191
+ /** True for a physically live owner with this configuration, including legacy generations. */
192
+ export declare function isPhysicalMatchingOwner(input: {
193
+ state: DaemonState | undefined;
194
+ tokenFingerprint: string;
195
+ chatId: string;
196
+ pidAlive: (pid: number) => boolean;
197
+ pidIncarnation?: (pid: number) => string | undefined;
198
+ }): boolean;
199
+ /** True only for a signalable legacy owner or this generation's ready bound child. */
200
+ export declare function isSignalableMatchingOwner(input: {
201
+ state: DaemonState | undefined;
202
+ tokenFingerprint: string;
203
+ chatId: string;
204
+ pidAlive: (pid: number) => boolean;
205
+ pidIncarnation?: (pid: number) => string | undefined;
206
+ }): boolean;
124
207
  export declare function isFreshLiveOwner(input: {
125
208
  state: DaemonState | undefined;
126
209
  now: number;
127
210
  tokenFingerprint: string;
128
211
  chatId: string;
129
212
  pidAlive: (pid: number) => boolean;
213
+ pidIncarnation?: (pid: number) => string | undefined;
214
+ }): boolean;
215
+ /** True only when a physically live matching owner can serve this build's daemon lifecycle contract. */
216
+ export declare function isCurrentCompatibleOwner(input: {
217
+ state: DaemonState | undefined;
218
+ now: number;
219
+ tokenFingerprint: string;
220
+ chatId: string;
221
+ pidAlive: (pid: number) => boolean;
222
+ pidIncarnation?: (pid: number) => string | undefined;
130
223
  }): boolean;
131
224
  export declare function acquireDaemonOwnership(input: {
132
225
  settings: Settings;
@@ -137,6 +230,7 @@ export declare function acquireDaemonOwnership(input: {
137
230
  now?: () => number;
138
231
  pid?: number;
139
232
  pidAlive?: (pid: number) => boolean;
233
+ pidIncarnation?: (pid: number) => string | undefined;
140
234
  randomId?: () => string;
141
235
  /** Permit one Windows source launcher PID to daemon PID handoff. */
142
236
  allowPidRebind?: boolean;
@@ -145,32 +239,100 @@ export declare function acquireDaemonOwnership(input: {
145
239
  }): Promise<{
146
240
  acquired: boolean;
147
241
  ownerId?: string;
242
+ acquisitionId?: string;
148
243
  attached?: boolean;
149
244
  blocked?: boolean;
245
+ provisional?: boolean;
150
246
  reason?: "identity_mismatch";
151
247
  reloadRequired?: boolean;
152
248
  }>;
153
249
  export declare function renewDaemonHeartbeat(input: {
154
250
  settings: Settings;
155
251
  ownerId: string;
156
- /** Bind a daemon heartbeat to the Telegram identity that acquired the lock. */
252
+ acquisitionId?: string;
253
+ tokenFingerprint?: string;
254
+ chatId?: string;
255
+ fs?: TelegramDaemonFs;
256
+ now?: () => number;
257
+ pid?: number;
258
+ generation?: number;
259
+ pidIncarnation?: (pid: number) => string | undefined;
260
+ sleep?: (ms: number) => Promise<void>;
261
+ stealRetries?: number;
262
+ stealRetryDelayMs?: number;
263
+ }): Promise<boolean>;
264
+ /** Acquire the lifecycle transition lock with bounded retry for bind/retire races. */
265
+ export declare function acquireTransitionLock(input: {
266
+ fs: TelegramDaemonFs;
267
+ path: string;
268
+ pidAlive?: (pid: number) => boolean;
269
+ pidIncarnation?: (pid: number) => string | undefined;
270
+ sleep?: (ms: number) => Promise<void>;
271
+ retries?: number;
272
+ retryDelayMs?: number;
273
+ }): Promise<DaemonTransitionLock | undefined>;
274
+ /** Retire only the unchanged provisional acquisition after bounded readiness fails. */
275
+ export declare function retireProvisionalDaemonOwnership(input: {
276
+ settings: Settings;
277
+ ownerId: string;
278
+ acquisitionId?: string;
279
+ pidIncarnation?: (pid: number) => string | undefined;
280
+ pidAlive?: (pid: number) => boolean;
281
+ /** Detached child PID, when the launcher successfully reported it. */
282
+ pid: number;
283
+ /** PID which created the provisional reservation before the child was bound. */
284
+ launcherPid?: number;
285
+ fs?: TelegramDaemonFs;
286
+ now?: () => number;
287
+ sleep?: (ms: number) => Promise<void>;
288
+ stealRetries?: number;
289
+ stealRetryDelayMs?: number;
290
+ /** Only no-child confirmation may retire a ready-like launcher publication. */
291
+ allowReadyWithoutChildPid?: boolean;
292
+ }): Promise<boolean>;
293
+ /** Wait for a matching current-generation daemon to publish a ready state. */
294
+ export declare function waitForTelegramDaemonReady(input: {
295
+ settings: Settings;
296
+ ownerId?: string;
297
+ acquisitionId?: string;
298
+ pid?: number;
299
+ excludedPid?: number;
157
300
  tokenFingerprint: string;
158
301
  chatId: string;
159
- /** Require the current daemon PID after the launcher-to-daemon PID rebind. */
160
- pid: number;
161
302
  fs?: TelegramDaemonFs;
162
303
  now?: () => number;
304
+ pidAlive?: (pid: number) => boolean;
305
+ pidIncarnation?: (pid: number) => string | undefined;
306
+ sleep?: (ms: number) => Promise<void>;
307
+ waitStepMs?: number;
308
+ timeoutMs?: number;
163
309
  }): Promise<boolean>;
164
- export declare function releaseDaemonOwnership(input: {
310
+ /** Confirm the provisional owner or retire only its unchanged acquisition. */
311
+ export declare function confirmTelegramDaemonSpawn(input: {
165
312
  settings: Settings;
166
- ownerId: string;
167
- /** Bind release to the Telegram identity that acquired the lock. */
313
+ spawned: TelegramSpawnOwnerResult;
168
314
  tokenFingerprint: string;
169
315
  chatId: string;
170
- /** Require the current daemon PID after the launcher-to-daemon PID rebind. */
171
316
  pid: number;
172
317
  fs?: TelegramDaemonFs;
173
318
  now?: () => number;
319
+ pidAlive?: (pid: number) => boolean;
320
+ pidIncarnation?: (pid: number) => string | undefined;
321
+ sleep?: (ms: number) => Promise<void>;
322
+ waitStepMs?: number;
323
+ timeoutMs?: number;
324
+ }): Promise<boolean>;
325
+ export declare function releaseDaemonOwnership(input: {
326
+ settings: Settings;
327
+ ownerId: string;
328
+ acquisitionId?: string;
329
+ tokenFingerprint?: string;
330
+ chatId?: string;
331
+ pid?: number;
332
+ generation?: number;
333
+ pidIncarnation?: (pid: number) => string | undefined;
334
+ fs?: TelegramDaemonFs;
335
+ now?: () => number;
174
336
  }): Promise<void>;
175
337
  /** Read the persisted daemon ownership state (or undefined when absent). */
176
338
  export declare function readDaemonState(settings: Pick<Settings, "getAgentDir">, fs?: TelegramDaemonFs): Promise<DaemonState | undefined>;
@@ -182,18 +344,30 @@ export interface TelegramSpawnOwnerInput {
182
344
  tokenFingerprint: string;
183
345
  chatId: string;
184
346
  }
185
- export interface TelegramSpawnOwnerResult {
186
- result: EnsureDaemonResult;
187
- ownerId?: string;
347
+ export interface TelegramSpawnAcquisition {
348
+ readonly ownerId: string;
349
+ readonly acquisitionId: string;
350
+ /** PID of the launcher which reserved provisional ownership before spawn. */
351
+ readonly launcherPid?: number;
352
+ /** Actual detached child which must publish the ready owner state. */
353
+ readonly pid?: number;
354
+ }
355
+ export type TelegramSpawnOwnerResult = {
356
+ result: "owner_spawned";
357
+ acquisition: TelegramSpawnAcquisition;
358
+ runtime: DaemonRuntimeInfo;
359
+ warnings: string[];
360
+ } | {
361
+ result: "attached";
188
362
  runtime: DaemonRuntimeInfo;
189
363
  warnings: string[];
190
- /**
191
- * Set when ownership was NOT acquired because a still-live owner is running an
192
- * older daemon generation. The caller must hand off via a reload rather than
193
- * attach; see {@link ensureTelegramDaemonRunning}.
194
- */
195
364
  reloadRequired?: boolean;
196
- }
365
+ } | {
366
+ result: "blocked";
367
+ runtime: DaemonRuntimeInfo;
368
+ warnings: string[];
369
+ reloadRequired?: boolean;
370
+ };
197
371
  /**
198
372
  * Build the detached spawn command/args for the daemon-internal entrypoint.
199
373
  * Source mode prepends the entry script so the respawn loads edited source;
@@ -331,6 +505,7 @@ export interface TelegramDaemonOptions {
331
505
  pid?: number;
332
506
  /** Liveness probe for skipping dead-PID endpoint records in {@link TelegramNotificationDaemon.scanRoots}. */
333
507
  pidAlive?: (pid: number) => boolean;
508
+ pidIncarnation?: (pid: number) => string | undefined;
334
509
  botApi?: BotApi;
335
510
  control?: DaemonControlHooks;
336
511
  /**
@@ -358,6 +533,10 @@ export interface TelegramDaemonOptions {
358
533
  richDraft?: {
359
534
  enabled: boolean;
360
535
  };
536
+ /** Tool start/completion messages (enabled by default). */
537
+ toolActivity?: {
538
+ enabled: boolean;
539
+ };
361
540
  /**
362
541
  * Per-session Telegram forum-topic naming. `nameTemplate` supports the
363
542
  * `{repo}`, `{branch}`, and `{title}` placeholders; unset preserves the
@@ -403,6 +582,16 @@ export declare class TelegramNotificationDaemon {
403
582
  readonly messageRoutes: Map<string | number, CallbackRoute | Omit<CallbackRoute, "answer">>;
404
583
  /** Telegram message id backing each streamed `${sessionId}:${coalesceKey}`, for in-place edits. */
405
584
  private readonly liveMessages;
585
+ /** Endpoint-bound ownership for visible or dispatching tool bubbles. */
586
+ private readonly toolActivityOwners;
587
+ private readonly revokedToolEndpoints;
588
+ private readonly unresolvedToolTerminalizations;
589
+ private toolTerminalizationChain;
590
+ private toolActivityPolicyEpoch;
591
+ private toolActivityStopping;
592
+ private readonly replayToolActivityEpochs;
593
+ private toolShutdownBarrier;
594
+ private toolActivityAmbiguous;
406
595
  readonly sessions: Map<string, SessionSocket>;
407
596
  private readonly runtime;
408
597
  private readonly sessionRouter;
@@ -525,6 +714,9 @@ export declare class TelegramNotificationDaemon {
525
714
  * (so a delayed old close cannot delete a replacement), and best-effort closes
526
715
  * the socket. `scanRoots()` then reconnects the session.
527
716
  */
717
+ private enqueueToolTerminalization;
718
+ private scheduleVisibleToolTerminalization;
719
+ private beginToolActivityShutdown;
528
720
  private dropSession;
529
721
  private deleteMessageRoutes;
530
722
  private static readonly THREADED_FRAMES;
@@ -543,6 +735,8 @@ export declare class TelegramNotificationDaemon {
543
735
  private topicIdentityBase;
544
736
  private topicOwnerForIdentity;
545
737
  private sessionCanClaimIdentity;
738
+ private toolActivityOwner;
739
+ private toolActivityAuthorityIsCurrent;
546
740
  private submitThreadedFrame;
547
741
  private existingTopicForPrivateChat;
548
742
  private topicAuthorityLease;
@@ -1,4 +1,4 @@
1
- import { type AgentTelemetryConfig, ThinkingLevel } from "@gajae-code/agent-core";
1
+ import { type AgentMessage, type AgentTelemetryConfig, ThinkingLevel } from "@gajae-code/agent-core";
2
2
  import { type AuthCredentialSelector, type Model, type ProviderSessionState } from "@gajae-code/ai";
3
3
  import { createAppendOnlyContextManager, providerSupportsAppendOnlyAuto, resolveAppendOnlyMode } from "../append-only-mode";
4
4
  import { type Rule } from "../capability/rule";
@@ -25,6 +25,19 @@ import { type BuildSystemPromptResult } from "../system-prompt";
25
25
  import { BashTool, BUILTIN_TOOLS, createTools, EditTool, EvalTool, FindTool, HIDDEN_TOOLS, type LspStartupServerInfo, loadSshTool, ReadTool, ResolveTool, SearchTool, type Tool, type ToolSession, WebSearchTool, WriteTool } from "../tools";
26
26
  import { EventBus } from "../utils/event-bus";
27
27
  import { type WorkspaceTree } from "../workspace-tree";
28
+ /**
29
+ * Reconcile a resumed transcript that ends on an unpaired tool call.
30
+ *
31
+ * When a subagent finishes by calling `yield` (or any turn is torn down right
32
+ * after a tool executes), the terminating abort can land before the tool
33
+ * result is persisted, leaving the saved session ending on an assistant
34
+ * `toolCall` with no matching `toolResult`. Replaying that history verbatim on
35
+ * resume produces an invalid provider request (a tool_use not followed by a
36
+ * tool_result) and the resumed turn fails immediately. Synthesize a placeholder
37
+ * result for any such trailing unpaired tool call so a resumed session always
38
+ * starts from a valid, paired history. No-op for well-formed transcripts.
39
+ */
40
+ export declare function reconcileTrailingToolCalls(messages: AgentMessage[]): AgentMessage[];
28
41
  export interface CreateAgentSessionOptions {
29
42
  /** Working directory for project-local discovery. Default: getProjectDir() */
30
43
  cwd?: string;
@@ -152,6 +165,8 @@ export interface CreateAgentSessionOptions {
152
165
  notificationHostModeSupported?: boolean;
153
166
  /** Whether this host mode can own the root SDK endpoint. Default: true. */
154
167
  sdkHostModeSupported?: boolean;
168
+ /** Override configured Discord/Slack daemon readiness, primarily for embedded hosts and deterministic tests. */
169
+ ensureNotificationProviderDaemon?: (provider: "discord" | "slack", settings: Settings) => Promise<unknown>;
155
170
  /**
156
171
  * Opt-in OpenTelemetry instrumentation forwarded to the underlying Agent.
157
172
  * Passing `{}` enables the loop's GenAI-semantic-convention spans. See