@gajae-code/coding-agent 0.11.4 → 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 (146) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/types/cli/notify-cli.d.ts +5 -1
  3. package/dist/types/config/settings-schema.d.ts +20 -0
  4. package/dist/types/export/html/template.generated.d.ts +1 -1
  5. package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
  6. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
  7. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
  8. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
  9. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
  10. package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  12. package/dist/types/harness-control-plane/owner.d.ts +3 -1
  13. package/dist/types/internal-urls/local-protocol.d.ts +18 -0
  14. package/dist/types/main.d.ts +2 -2
  15. package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
  16. package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
  17. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
  18. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  19. package/dist/types/modes/components/tool-transcript-format.d.ts +58 -0
  20. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
  21. package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
  22. package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
  23. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
  24. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
  25. package/dist/types/plan-mode/approved-plan.d.ts +1 -0
  26. package/dist/types/runtime-mcp/manager.d.ts +2 -0
  27. package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
  28. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
  29. package/dist/types/sdk/bus/chat-daemon-control.d.ts +39 -4
  30. package/dist/types/sdk/bus/config-commands.d.ts +8 -0
  31. package/dist/types/sdk/bus/config.d.ts +22 -0
  32. package/dist/types/sdk/bus/index.d.ts +162 -2
  33. package/dist/types/sdk/bus/notification-service.d.ts +71 -8
  34. package/dist/types/sdk/bus/session-control.d.ts +11 -8
  35. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
  36. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -3
  37. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
  38. package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
  39. package/dist/types/sdk/session.d.ts +16 -1
  40. package/dist/types/secrets/index.d.ts +4 -2
  41. package/dist/types/secrets/obfuscator.d.ts +1 -1
  42. package/dist/types/session/agent-session.d.ts +31 -2
  43. package/dist/types/session/artifacts.d.ts +30 -8
  44. package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
  45. package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
  46. package/dist/types/session/session-manager.d.ts +88 -18
  47. package/dist/types/session/session-storage.d.ts +27 -0
  48. package/dist/types/session/streaming-output.d.ts +4 -0
  49. package/dist/types/task/executor.d.ts +9 -0
  50. package/dist/types/tool-discovery/tool-index.d.ts +2 -0
  51. package/dist/types/tools/ask.d.ts +48 -0
  52. package/dist/types/tools/bash-skill-urls.d.ts +2 -2
  53. package/dist/types/tools/index.d.ts +4 -0
  54. package/package.json +9 -9
  55. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  56. package/src/cli/notify-cli.ts +40 -18
  57. package/src/commands/sdk.ts +7 -2
  58. package/src/config/settings-schema.ts +17 -0
  59. package/src/daemon/runtime.ts +2 -2
  60. package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
  61. package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
  62. package/src/defaults/gjc/skills/team/SKILL.md +7 -9
  63. package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
  64. package/src/eval/py/index.ts +2 -2
  65. package/src/export/html/template.generated.ts +1 -1
  66. package/src/extensibility/plugins/marketplace/manager.ts +94 -10
  67. package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
  68. package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
  69. package/src/gjc-runtime/deep-interview-state.ts +127 -3
  70. package/src/gjc-runtime/launch-tmux.ts +61 -6
  71. package/src/gjc-runtime/managed-owner-admission.ts +352 -0
  72. package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
  73. package/src/gjc-runtime/state-runtime.ts +38 -12
  74. package/src/gjc-runtime/team-runtime.ts +1 -1
  75. package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
  76. package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
  77. package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
  78. package/src/harness-control-plane/owner.ts +59 -3
  79. package/src/hindsight/client.ts +65 -2
  80. package/src/internal-urls/agent-protocol.ts +76 -5
  81. package/src/internal-urls/docs-index.generated.ts +7 -6
  82. package/src/internal-urls/local-protocol.ts +416 -18
  83. package/src/lsp/index.ts +4 -88
  84. package/src/main.ts +37 -13
  85. package/src/modes/acp/acp-agent.ts +8 -0
  86. package/src/modes/components/ansi-display-validator.ts +54 -0
  87. package/src/modes/components/notifications-settings-editor.ts +26 -2
  88. package/src/modes/components/session-observer-overlay.ts +40 -24
  89. package/src/modes/components/settings-selector.ts +3 -1
  90. package/src/modes/components/tool-transcript-format.ts +321 -0
  91. package/src/modes/components/transcript-viewer-overlay.ts +39 -17
  92. package/src/modes/controllers/goal-mode-controller.ts +20 -1
  93. package/src/modes/controllers/plan-mode-controller.ts +2 -0
  94. package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
  95. package/src/modes/controllers/selector-controller.ts +70 -8
  96. package/src/modes/interactive-mode.ts +1 -0
  97. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
  98. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
  99. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
  100. package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
  101. package/src/plan-mode/approved-plan.ts +3 -1
  102. package/src/prompts/agents/architect.md +1 -0
  103. package/src/prompts/agents/critic.md +1 -0
  104. package/src/prompts/system/custom-system-prompt.md +1 -1
  105. package/src/prompts/system/system-prompt.md +2 -1
  106. package/src/prompts/tools/lsp.md +3 -3
  107. package/src/runtime-mcp/manager.ts +16 -1
  108. package/src/runtime-mcp/oauth-discovery.ts +116 -26
  109. package/src/sdk/broker/session-index.ts +122 -64
  110. package/src/sdk/bus/chat-daemon-cli.ts +72 -10
  111. package/src/sdk/bus/chat-daemon-control.ts +526 -130
  112. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  113. package/src/sdk/bus/config-commands.ts +22 -0
  114. package/src/sdk/bus/config.ts +37 -0
  115. package/src/sdk/bus/index.ts +384 -92
  116. package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
  117. package/src/sdk/bus/notification-service.ts +284 -62
  118. package/src/sdk/bus/recent-activity.ts +14 -5
  119. package/src/sdk/bus/session-control.ts +150 -44
  120. package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
  121. package/src/sdk/bus/telegram-daemon-contract.ts +6 -3
  122. package/src/sdk/bus/telegram-daemon-control.ts +294 -76
  123. package/src/sdk/bus/telegram-daemon.ts +1788 -267
  124. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  125. package/src/sdk/session.ts +155 -27
  126. package/src/secrets/index.ts +53 -10
  127. package/src/secrets/obfuscator.ts +21 -18
  128. package/src/secrets/regex.ts +2 -0
  129. package/src/session/agent-session.ts +495 -253
  130. package/src/session/artifacts.ts +188 -23
  131. package/src/session/context-estimation.ts +7 -2
  132. package/src/session/internal/managed-session-scope.ts +200 -38
  133. package/src/session/internal/managed-session-storage.ts +1152 -46
  134. package/src/session/session-manager.ts +1055 -148
  135. package/src/session/session-storage.ts +109 -11
  136. package/src/session/streaming-output.ts +6 -0
  137. package/src/task/executor.ts +71 -26
  138. package/src/task/index.ts +63 -20
  139. package/src/tool-discovery/tool-index.ts +18 -0
  140. package/src/tools/ask.ts +129 -32
  141. package/src/tools/bash-skill-urls.ts +14 -3
  142. package/src/tools/bash.ts +2 -2
  143. package/src/tools/index.ts +4 -0
  144. package/src/tools/plan-mode-guard.ts +1 -0
  145. package/src/tools/sqlite-reader.ts +3 -2
  146. package/src/tools/subagent.ts +2 -0
@@ -2,6 +2,13 @@ import type { Settings } from "../../config/settings";
2
2
  import type { BuiltInDaemonController, DaemonOperationOptions, DaemonOperationResult, DaemonRuntimeInfo, DaemonStatus } from "../../daemon/control-types";
3
3
  export type ChatDaemonKind = "discord" | "slack";
4
4
  export type ChatDaemonAction = "stop" | "reload";
5
+ /**
6
+ * Operational generations of the Discord/Slack daemon lifecycle contracts.
7
+ * These are intentionally separate from per-session endpoint generations.
8
+ * Generation 6 carries the retained managed filesystem authority boundary.
9
+ */
10
+ export declare const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>>;
11
+ export declare function chatDaemonGeneration(kind: ChatDaemonKind): number;
5
12
  export interface ChatDaemonState {
6
13
  version: 1;
7
14
  kind: ChatDaemonKind;
@@ -12,8 +19,16 @@ export interface ChatDaemonState {
12
19
  startedAt: number;
13
20
  heartbeatAt: number;
14
21
  transportHealthy: boolean;
22
+ generation: number;
15
23
  stoppedAt?: number;
16
24
  }
25
+ /**
26
+ * State files are untrusted persisted input. A record must be completely valid
27
+ * before its PID can be treated as an owner, stopped, or safe to replace.
28
+ */
29
+ /** A legacy owner is recognized only when the sole missing field is generation. */
30
+ export declare function isRecognizedLegacyGeneration(value: unknown): value is undefined;
31
+ export declare function hasSafeChatDaemonStateShape(value: unknown): value is ChatDaemonState;
17
32
  export interface ChatDaemonControlRequest {
18
33
  version: 1;
19
34
  requestId: string;
@@ -23,9 +38,13 @@ export interface ChatDaemonControlRequest {
23
38
  createdAt: number;
24
39
  incarnation: string;
25
40
  }
41
+ export interface ChatDaemonProcessReference {
42
+ incarnation: string;
43
+ signalRoot(signal: NodeJS.Signals): void;
44
+ }
26
45
  export interface ChatDaemonControlDeps {
27
46
  pidAlive?: (pid: number) => boolean;
28
- sendSignal?: (pid: number, signal: NodeJS.Signals) => void;
47
+ processReference?: (pid: number) => ChatDaemonProcessReference | undefined;
29
48
  spawn?: (command: string, args: string[], opts: {
30
49
  detached: boolean;
31
50
  stdio: "ignore";
@@ -36,7 +55,10 @@ export interface ChatDaemonControlDeps {
36
55
  ownerPid?: number;
37
56
  randomId?: () => string;
38
57
  pidIncarnation?: (pid: number) => string | undefined;
58
+ /** Test seam for platform-specific default stable-process authority. */
59
+ platform?: NodeJS.Platform;
39
60
  sleep?: (ms: number) => Promise<void>;
61
+ spawnReadyTimeoutMs?: number;
40
62
  }
41
63
  export declare function chatDaemonPaths(agentDir: string, kind: ChatDaemonKind): {
42
64
  dir: string;
@@ -71,7 +93,15 @@ export declare class ChatDaemonController implements BuiltInDaemonController {
71
93
  ensure(): Promise<EnsureChatDaemonResult>;
72
94
  private operate;
73
95
  private incarnation;
74
- private isLiveState;
96
+ private processReference;
97
+ private isDefinitelyStoppedState;
98
+ private stateHealth;
99
+ private isSignalableMatchingOwner;
100
+ /** A live PID with an invalid ownership record is never safe to overwrite. */
101
+ private isAmbiguouslyLiveState;
102
+ private isHealthyFreshState;
103
+ private classify;
104
+ private isCurrentCompatibleState;
75
105
  private stopForReplacement;
76
106
  private ownsCapturedState;
77
107
  private signalIfOwner;
@@ -80,6 +110,7 @@ export declare class ChatDaemonController implements BuiltInDaemonController {
80
110
  private waitForDeath;
81
111
  private sleep;
82
112
  private spawn;
113
+ private waitForOwnership;
83
114
  }
84
115
  export type EnsureChatDaemonResult = "disabled" | "owner_spawned" | "attached";
85
116
  export declare function ensureDiscordDaemon(settings: Settings, deps?: ChatDaemonControlDeps): Promise<EnsureChatDaemonResult>;
@@ -101,11 +132,15 @@ export declare function renewChatDaemonHeartbeat(input: {
101
132
  pid?: number;
102
133
  incarnation?: string;
103
134
  transportHealthy: boolean;
135
+ pidAlive?: (pid: number) => boolean;
136
+ pidIncarnation?: (pid: number) => string | undefined;
104
137
  }): Promise<boolean>;
105
138
  export declare function releaseChatDaemonOwnership(input: {
106
139
  agentDir: string;
107
140
  kind: ChatDaemonKind;
108
141
  ownerId: string;
109
- pid?: number;
110
- incarnation?: string;
142
+ pid: number;
143
+ incarnation: string;
144
+ pidAlive?: (pid: number) => boolean;
145
+ pidIncarnation?: (pid: number) => string | undefined;
111
146
  }): Promise<void>;
@@ -67,3 +67,11 @@ export declare function parseInThreadConfigCommand(text: string): ConfigCommandC
67
67
  * daemon-locally, so it never becomes a `config_command` frame or a user turn.
68
68
  */
69
69
  export declare function parseRichToggleCommand(text: string): boolean | undefined;
70
+ /**
71
+ * Parse a `/toolactivity on|off` toggle for Telegram tool-activity delivery.
72
+ *
73
+ * This is daemon-local delivery policy rather than a host-session config
74
+ * command, so it works without a connected session and persists globally.
75
+ * Addressed commands are accepted only for the exact current bot username.
76
+ */
77
+ export declare function parseToolActivityToggleCommand(text: string, botUsername?: string): boolean | undefined;
@@ -37,6 +37,12 @@ export interface NotificationSettingsSnapshot {
37
37
  richDraft: {
38
38
  enabled: boolean;
39
39
  };
40
+ toolActivity: {
41
+ enabled: boolean;
42
+ };
43
+ streaming: {
44
+ enabled: boolean;
45
+ };
40
46
  topics: {
41
47
  nameTemplate?: string;
42
48
  };
@@ -109,6 +115,12 @@ export interface NotificationConfig {
109
115
  richDraft: {
110
116
  enabled: boolean;
111
117
  };
118
+ toolActivity: {
119
+ enabled: boolean;
120
+ };
121
+ streaming: {
122
+ enabled: boolean;
123
+ };
112
124
  topics: {
113
125
  /**
114
126
  * Optional Telegram forum-topic name template with `{repo}`, `{branch}`,
@@ -149,6 +161,16 @@ type NotificationConfigFile = z.infer<typeof notificationConfigSchema>;
149
161
  export declare function loadNotificationConfigFile(agentDir: string): LoadResult<NotificationConfigFile>;
150
162
  export declare function notificationConfigFromFile(value: NotificationConfigFile): Pick<NotificationConfig, "enabled" | "discord" | "slack" | "redact" | "verbosity">;
151
163
  export declare function hasNonBlankValue(value: string | undefined): boolean;
164
+ /**
165
+ * Resolve live assistant streaming independently of generic notification
166
+ * lifecycle enablement. Explicit environment values override the durable
167
+ * Telegram preference; otherwise streaming is available only to an active
168
+ * configured Telegram identity.
169
+ */
170
+ export declare function isNotificationStreamingEnabled(input: {
171
+ cfg: NotificationConfig;
172
+ env: NodeJS.ProcessEnv;
173
+ }): boolean;
152
174
  /** Is Telegram configured with usable non-blank boundary credentials? */
153
175
  export declare function isTelegramConfigured(cfg: NotificationConfig): cfg is NotificationConfig & {
154
176
  botToken: string;
@@ -21,8 +21,13 @@ import type { Tool } from "@gajae-code/ai";
21
21
  import { NotificationServer } from "@gajae-code/natives";
22
22
  import { Settings } from "../../config/settings";
23
23
  import type { ExtensionAPI, ExtensionContext } from "../../extensibility/extensions";
24
- import type { WorkflowGateTerminalProof } from "../../modes/shared/agent-wire/workflow-gate-broker";
25
- import type { AskRemoteControl } from "../../tools";
24
+ import { type WorkflowGateEmitter, type WorkflowGateTerminalProof } from "../../modes/shared/agent-wire/workflow-gate-broker";
25
+ import type { AgentSessionEvent } from "../../session/agent-session";
26
+ import type { AskAnswerSourceResult, AskRemoteControl } from "../../tools";
27
+ import { SessionSdkHost } from "../host";
28
+ import { CursorRegistry, RevisionStore } from "../host/query";
29
+ import { type SdkStartupFailure } from "../startup-capability";
30
+ import { type NotificationConfig } from "./config";
26
31
  import { NotificationSessionController } from "./session-control";
27
32
  import { type EnsureDaemonResult } from "./telegram-daemon";
28
33
  /** Where a `session_create` should run. Discriminated by `kind`. */
@@ -182,6 +187,17 @@ export interface SessionReadyFrame {
182
187
  * instead of the worktree directory (e.g. `feat-foo-01047f11`).
183
188
  */
184
189
  export declare function readGitRepoName(cwd: string): string | undefined;
190
+ interface PendingInteractiveAsk {
191
+ resolve: (result: AskAnswerSourceResult) => void;
192
+ options: string[];
193
+ controls: readonly AskRemoteControl[];
194
+ actionId?: string;
195
+ retireForDirectControl: () => RetireStatus;
196
+ reissue: () => boolean;
197
+ complete: (actionId: string) => void;
198
+ completeDirect: () => void;
199
+ fail: (actionId: string) => void;
200
+ }
185
201
  interface UnattendedGatePresentation {
186
202
  gateId: string;
187
203
  sessionId: string;
@@ -260,9 +276,139 @@ export declare class PresentationArbiter {
260
276
  cancel(gateId: string, reason: string): void;
261
277
  dispose(): void;
262
278
  }
279
+ interface SessionRuntime {
280
+ server: NotificationServer;
281
+ host: SessionSdkHost;
282
+ /** Owns stateRoot-backed revisions and removes their spills on terminal shutdown. */
283
+ revisions: RevisionStore;
284
+ /** Releases all snapshot pins before the revision store is closed. */
285
+ cursors: CursorRegistry;
286
+ /** Current endpoint session identity; never re-key an existing host across a switch. */
287
+ id: string;
288
+ idleSeq: number;
289
+ /** Interactive asks awaiting a remote answer, by action id. */
290
+ pendingInteractive: Map<string, PendingInteractiveAsk>;
291
+ /** Deregisters this session's ask answer source. */
292
+ disposeAnswerSource: () => void;
293
+ /** Deregisters this session's Telegram file sink. */
294
+ disposeFileSink: () => void;
295
+ /** Deregisters this session's workflow-gate listener. */
296
+ disposeGateListener: () => void;
297
+ /** Whether notification-only delivery and answer resources are active. */
298
+ notificationsActive: boolean;
299
+ /** Set as soon as terminal teardown is requested, before startup settles. */
300
+ stopping: boolean;
301
+ /** Recreates notification-only resources after `/notify on`. */
302
+ enableNotifications: () => void;
303
+ /** Deregisters canonical workflow-gate terminal cleanup. */
304
+ disposeGateTerminalController: () => void;
305
+ disposeAckRecoveryParticipant: () => void;
306
+ disposeGateEmitterListener: () => void;
307
+ /** Aborts and fences side turns while notification delivery is disabled. */
308
+ disableEphemeralTurns: () => void;
309
+ waitForGateResolutionQuiescence: () => Promise<void>;
310
+ trackGateResolution: <T>(resolution: Promise<T>) => Promise<T>;
311
+ workflowGate?: WorkflowGateEmitter;
312
+ gatePresentations?: PresentationArbiter;
313
+ redact: boolean;
314
+ /** Last stable policy's redaction state, retained while provisional policy is held. */
315
+ committedRedact: boolean;
316
+ /** Provisional policy suppresses delivery without changing committed-side effects. */
317
+ policySuspended: boolean;
318
+ /** Monotonic policy epoch fences asynchronous notification delivery. */
319
+ policyGeneration: number;
320
+ /** True only after the exact host generation was registered with the broker index. */
321
+ brokerRegistrationActive: boolean;
322
+ /** Terminal cleanup proof retained across retries; each owner is released at most once after proof. */
323
+ hostStopped: boolean;
324
+ serverStopped: boolean;
325
+ brokerRegistrationReleased: boolean;
326
+ verbosity: "lean" | "verbose";
327
+ sessionTag: string;
328
+ /** Whether the agent loop is currently running (drives the typing indicator). */
329
+ busy: boolean;
330
+ /** Prompt command/turn identities awaiting their corresponding agent_start. */
331
+ pendingPromptCorrelations: Array<{
332
+ commandId: string;
333
+ turnId: string;
334
+ }>;
335
+ /** Identity bound to the agent lifecycle currently in flight. */
336
+ activePromptCorrelation?: {
337
+ commandId: string;
338
+ turnId: string;
339
+ };
340
+ /** Records a correlated prompt terminal boundary after agent unwind. */
341
+ /** Atomically claims a correlated prompt terminal boundary after agent unwind. */
342
+ recordPromptTerminal: (correlation: {
343
+ commandId: string;
344
+ turnId: string;
345
+ } | undefined) => boolean;
346
+ /** Records correlated lifecycle frames for replay and delivers them only to the accepted requester after acknowledgement. */
347
+ emitPromptLifecycle: (correlation: {
348
+ commandId: string;
349
+ turnId: string;
350
+ } | undefined, frame: {
351
+ type: "agent_start" | "agent_end";
352
+ sessionId: string;
353
+ commandId?: string;
354
+ turnId?: string;
355
+ } | {
356
+ type: "agent_failed";
357
+ sessionId: string;
358
+ commandId: string;
359
+ turnId: string;
360
+ error: {
361
+ code: string;
362
+ message: string;
363
+ };
364
+ }) => void;
365
+ /** Publishes one canonical agent-wire event to the client that owns the active prompt. */
366
+ emitPromptEvent: (event: AgentSessionEvent) => void;
367
+ /** Inbound Telegram update ids injected but not yet consumed by a turn. */
368
+ pendingInbound: Set<number>;
369
+ /** Latest assistant text of the in-flight turn (from message_update). */
370
+ currentTurnText?: string;
371
+ /** Assistant text already flushed before an ask this turn (turn-scoped dedupe
372
+ * so turn_end does not re-emit the pre-ask lead-in). Reset each turn. */
373
+ preAskFlushedText?: string;
374
+ /** Live streaming: opt-in flag, monotonic per-turn ref, and emit throttle state. */
375
+ stream: boolean;
376
+ turnSeq?: number;
377
+ liveRef?: string;
378
+ lastLiveAt?: number;
379
+ lastLiveText?: string;
380
+ /** True between turn_end and the next turn_start: drops late async message_update
381
+ * frames so a stale live edit can never be emitted after the finalized turn. */
382
+ turnClosed?: boolean;
383
+ /** Finalized while provisional policy was held; flush exactly once on stable activation. */
384
+ pendingFinal?: {
385
+ text?: string;
386
+ messageRef?: string;
387
+ };
388
+ /** Durable gates emitted while ownership is provisional; presented only after stable activation. */
389
+ deferredGatePresentations: Array<() => void>;
390
+ /** SDK control frames received during provisional ownership; replayed only after stable activation. */
391
+ deferredInboundControls: Array<() => void>;
392
+ /** Started tool calls awaiting a terminal activity frame, keyed by tool call id. */
393
+ inFlightTools: Map<string, {
394
+ toolName: string;
395
+ args: unknown;
396
+ }>;
397
+ /** Cancels the postmortem cleanup that emits `session_closed` on process teardown. */
398
+ cancelPostmortemCleanup: () => void;
399
+ /** Disposes side-turn resources when their owning logical session becomes unavailable. */
400
+ abortEphemeralTurns: () => void;
401
+ }
263
402
  /** Stable projection of the tool-owned safe-display seam (never the full Tool surface). */
264
403
  type SafeSummaryTool = Pick<Tool, "safeSummary" | "safeSummaryFields">;
265
404
  export declare function projectToolSummary(tool: SafeSummaryTool | undefined, kind: "args" | "result", value: unknown): string | undefined;
405
+ type SessionStartStatus = "started" | "already" | "disabled" | "failed";
406
+ type SessionStartResult = {
407
+ status: SessionStartStatus;
408
+ runtime?: SessionRuntime;
409
+ failure?: SdkStartupFailure;
410
+ suppressExtensionError?: boolean;
411
+ };
266
412
  export declare function notificationsEnabled(): boolean;
267
413
  /** Workflow-gate answer shape. */
268
414
  interface GateAnswer {
@@ -328,6 +474,12 @@ export declare class EphemeralTurnHost {
328
474
  /** Testable event-ring eviction boundary; tombstones remain idempotency authority. */
329
475
  evictTerminalEvents(): void;
330
476
  }
477
+ /**
478
+ * Ensures every configured chat-provider daemon is ready before the SDK
479
+ * publishes session identity. A rejected ensure is startup-fatal: presenting
480
+ * an identity for a transport that never became available is false success.
481
+ */
482
+ export declare function ensureConfiguredProviderDaemons(settings: Settings, cfg: NotificationConfig, ensureProviderDaemon?: (provider: "discord" | "slack", settings: Settings) => Promise<unknown>): Promise<void>;
331
483
  export declare function createNotificationsExtension(api: ExtensionAPI, options?: {
332
484
  settings?: Settings;
333
485
  ensureTelegramDaemon?: (input: {
@@ -335,6 +487,7 @@ export declare function createNotificationsExtension(api: ExtensionAPI, options?
335
487
  cwd: string;
336
488
  sessionId: string;
337
489
  }) => Promise<EnsureDaemonResult>;
490
+ ensureProviderDaemon?: (provider: "discord" | "slack", settings: Settings) => Promise<unknown>;
338
491
  /** Suppress auto-delivery for a GJC-spawned child under `sessionScope=primary`. */
339
492
  spawnedByGjc?: boolean;
340
493
  controller?: NotificationSessionController;
@@ -344,5 +497,12 @@ export declare function createNotificationsExtension(api: ExtensionAPI, options?
344
497
  runEphemeralTurn?: (promptText: string, signal: AbortSignal) => Promise<{
345
498
  replyText: string;
346
499
  }>;
500
+ /** Observes settlement of optional session-branch startup after reconciliation completes. */
501
+ onBranchStartupSettled?: (receipt: {
502
+ sessionId: string;
503
+ status: SessionStartResult["status"];
504
+ }) => void;
505
+ readNotificationFile?: (path: string) => Promise<Buffer>;
506
+ readNotificationDiffStat?: (cwd: string) => Promise<string | undefined>;
347
507
  }): void;
348
508
  export {};
@@ -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
  }