@gajae-code/coding-agent 0.12.10 → 0.12.12
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.
- package/CHANGELOG.md +47 -2
- package/dist/types/cli/notify-cli.d.ts +42 -1
- package/dist/types/cli/update-cli.d.ts +9 -3
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +3 -1
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/ouroboros-ooo-bridge.d.ts +13 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -1
- package/dist/types/extensibility/extensions/types.d.ts +2 -0
- package/dist/types/main.d.ts +6 -2
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +23 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +12 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -0
- package/dist/types/sdk/bus/chat-daemon-command-channel.d.ts +244 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +13 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +7 -0
- package/dist/types/sdk/bus/control-plane-frames.d.ts +43 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +18 -0
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +71 -0
- package/dist/types/sdk/bus/notification-service.d.ts +10 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +27 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +11 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +14 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +140 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +25 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +104 -16
- package/dist/types/sdk/bus/telegram-reference.d.ts +10 -1
- package/dist/types/sdk/bus/topic-registry.d.ts +130 -85
- package/dist/types/sdk/host/host.d.ts +52 -0
- package/dist/types/sdk/lifecycle-session.d.ts +6 -0
- package/dist/types/sdk/session-activation.d.ts +41 -0
- package/dist/types/sdk/session.d.ts +18 -0
- package/dist/types/sdk/startup-capability.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +7 -0
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +12 -2
- package/dist/types/session/session-manager.d.ts +40 -5
- package/dist/types/session/session-storage.d.ts +1 -0
- package/dist/types/tools/computer.d.ts +35 -2
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/todo-write.d.ts +2 -0
- package/dist/types/utils/npm-registry.d.ts +71 -0
- package/examples/extensions/README.md +38 -4
- package/examples/extensions/ooo-bridge.ts +15 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/src/async/job-manager.ts +9 -2
- package/src/cli/notify-cli.ts +226 -2
- package/src/cli/update-cli.ts +90 -23
- package/src/commands/notify.ts +27 -3
- package/src/commands/sdk.ts +1 -0
- package/src/config/file-lock-gc.ts +18 -0
- package/src/config/file-lock.ts +61 -16
- package/src/config/model-profiles.ts +31 -15
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +2 -0
- package/src/coordinator-mcp/server.ts +358 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +235 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +4 -9
- package/src/extensibility/extensions/runner.ts +15 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/gjc-runtime/launch-tmux.ts +53 -1
- package/src/internal-urls/docs-index.generated.ts +13 -12
- package/src/main.ts +203 -67
- package/src/modes/DESIGN.md +8 -5
- package/src/modes/components/footer.ts +10 -18
- package/src/modes/components/session-selector.ts +26 -3
- package/src/modes/components/status-line/gh.ts +23 -2
- package/src/modes/controllers/event-controller.ts +3 -0
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +11 -0
- package/src/modes/controllers/selector-controller.ts +8 -2
- package/src/modes/interactive-mode.ts +7 -1
- package/src/modes/utils/host-status.ts +38 -0
- package/src/prompts/tools/computer.md +6 -1
- package/src/runtime-mcp/types.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +60 -3
- package/src/sdk/bus/chat-daemon-cli.ts +42 -0
- package/src/sdk/bus/chat-daemon-command-channel.ts +921 -0
- package/src/sdk/bus/chat-daemon-control.ts +18 -3
- package/src/sdk/bus/chat-daemon-runtime.ts +257 -21
- package/src/sdk/bus/control-plane-frames.ts +51 -0
- package/src/sdk/bus/conversation-store.ts +29 -1
- package/src/sdk/bus/existing-thread-readiness.ts +155 -0
- package/src/sdk/bus/index.ts +70 -0
- package/src/sdk/bus/notification-service.ts +28 -1
- package/src/sdk/bus/rich-render.ts +3 -2
- package/src/sdk/bus/slack-daemon.ts +99 -4
- package/src/sdk/bus/slack-live-provider.ts +32 -2
- package/src/sdk/bus/slack-provider.ts +11 -0
- package/src/sdk/bus/slack-thread-binding.ts +641 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +11 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +25 -7
- package/src/sdk/bus/telegram-daemon.ts +1961 -497
- package/src/sdk/bus/telegram-reference.ts +42 -6
- package/src/sdk/bus/topic-registry.ts +674 -181
- package/src/sdk/host/host.ts +180 -1
- package/src/sdk/lifecycle-session.ts +8 -2
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session-activation.ts +127 -0
- package/src/sdk/session.ts +139 -28
- package/src/sdk/startup-capability.ts +10 -1
- package/src/session/agent-session.ts +331 -30
- package/src/session/artifacts.ts +56 -13
- package/src/session/internal/managed-session-storage.ts +440 -243
- package/src/session/session-manager.ts +288 -44
- package/src/session/session-storage.ts +3 -0
- package/src/setup/provider-presets.json +1 -1
- package/src/task/index.ts +92 -44
- package/src/tools/computer.ts +322 -45
- package/src/tools/index.ts +8 -0
- package/src/tools/todo-write.ts +29 -11
- package/src/utils/npm-registry.ts +660 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-terminal agent status markers.
|
|
3
|
+
*
|
|
4
|
+
* Interactive turns announce their state as an OSC 777 sequence:
|
|
5
|
+
*
|
|
6
|
+
* ESC ] 777 ; notify;Terax;gjc;<event> BEL
|
|
7
|
+
*
|
|
8
|
+
* A terminal that understands the sequence (Terax reads it to drive its per-tab
|
|
9
|
+
* agent status and header bell) reacts; every other terminal discards the OSC
|
|
10
|
+
* as unknown, exactly like the OSC 133 prompt marks and OSC 7 cwd reports that
|
|
11
|
+
* shells emit unconditionally. So there is no host sniffing and no env gate:
|
|
12
|
+
* any host that wants agent status can parse the stream without gjc knowing it
|
|
13
|
+
* exists.
|
|
14
|
+
*
|
|
15
|
+
* `notify;Terax;` is the wire identifier of the only protocol that currently
|
|
16
|
+
* carries per-turn agent state, not a host check - the payload names gjc as the
|
|
17
|
+
* reporting agent.
|
|
18
|
+
*
|
|
19
|
+
* Interactive TUI only. Print and RPC mode stdout stays byte-for-byte parseable
|
|
20
|
+
* because they never reach these controllers.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export type HostStatusEvent = "working" | "attention" | "finished";
|
|
24
|
+
|
|
25
|
+
function marker(event: HostStatusEvent): string {
|
|
26
|
+
return `\x1b]777;notify;Terax;gjc;${event}\x07`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function emitHostStatus(
|
|
30
|
+
event: HostStatusEvent,
|
|
31
|
+
output: Pick<NodeJS.WriteStream, "write"> = process.stdout,
|
|
32
|
+
): void {
|
|
33
|
+
try {
|
|
34
|
+
output.write(marker(event));
|
|
35
|
+
} catch {
|
|
36
|
+
// Best-effort host integration only.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- Native execution remains supervisor-gated. If the stop/suspend supervisor is unavailable, stale, suspended, permissioned off, display-stale, or cancelled, the action fails closed with a `COMPUTER_*` code. Coordinate actions carry the latest known screenshot display epoch when one is available so display-topology changes fail with `COMPUTER_DISPLAY_STALE`.
|
|
10
10
|
- Respect the user's stop/suspend request immediately. Do not loop desktop actions after a stop/suspend/error.
|
|
11
11
|
- The user can stop or suspend the session at any time with the configured kill-switch hotkey (default `Control+Option+Command+Escape`). If you see `COMPUTER_CANCELLED` or `COMPUTER_SUPERVISOR_NOT_LIVE`, stop and wait for the user.
|
|
12
|
+
- Native side-effecting actions restore the global cursor after releasing held input. An input batch owns one serialized native capture-to-restore transaction across its ordered steps. This does not restore application focus or isolate input to a PID/window, and concurrent manual cursor movement can be overwritten.
|
|
12
13
|
|
|
13
14
|
## Coordinate contract
|
|
14
15
|
|
|
@@ -68,7 +69,11 @@ Run a focused sequence in one batch — screenshot first, then act, so coordinat
|
|
|
68
69
|
- `COMPUTER_COORD_INVALID`: the coordinate was outside the latest screenshot bounds. Capture a fresh screenshot and re-derive coordinates.
|
|
69
70
|
- `COMPUTER_DISPLAY_STALE`: the display changed since the screenshot. Capture a fresh screenshot before acting.
|
|
70
71
|
- `COMPUTER_SUPERVISOR_NOT_LIVE` / `COMPUTER_SUSPENDED` / `COMPUTER_CANCELLED`: stop acting and wait for the user.
|
|
71
|
-
- `COMPUTER_PERMISSION_REQUIRED`:
|
|
72
|
+
- `COMPUTER_PERMISSION_REQUIRED`: Accessibility permission is required for input. Ask the user to grant it.
|
|
73
|
+
- `COMPUTER_SCREENSHOT_FAILED`: screen capture failed, commonly because Screen Recording permission is missing. Ask the user to grant it before retrying.
|
|
72
74
|
- `COMPUTER_DISABLED`: the tool is disabled or the host is unsupported. Do not retry.
|
|
75
|
+
- `COMPUTER_CURSOR_CAPTURE_FAILED`: no input was sent because the original cursor position could not be captured.
|
|
76
|
+
- `COMPUTER_CURSOR_RESTORE_FAILED`: input may have completed, but cursor restoration failed. Stop and ask the user to inspect the desktop before retrying; a retained primary error describes any action failure.
|
|
77
|
+
- `COMPUTER_TRANSACTION_FAILED`: native input cleanup could not be trusted. Stop and ask the user to inspect the desktop before retrying.
|
|
73
78
|
|
|
74
79
|
After any error, resume with a fresh screenshot rather than guessing.
|
package/src/runtime-mcp/types.ts
CHANGED
|
@@ -217,6 +217,8 @@ export type MCPContent = MCPTextContent | MCPImageContent | MCPResourceContent;
|
|
|
217
217
|
export interface MCPToolCallResult {
|
|
218
218
|
content: MCPContent[];
|
|
219
219
|
isError?: boolean;
|
|
220
|
+
/** MCP result metadata, encoded as `_meta` on the wire. */
|
|
221
|
+
_meta?: Record<string, unknown>;
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
// =============================================================================
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../../session/session-storage";
|
|
32
32
|
import type { SessionLifecycleMcpServer } from "../acp/mcp";
|
|
33
33
|
import { SdkClient, SdkClientError } from "../client/client";
|
|
34
|
+
import { SESSION_PREPARED_EVENT } from "../host/host";
|
|
34
35
|
import {
|
|
35
36
|
type LogicalSessionCandidate,
|
|
36
37
|
listManagedSessionCandidates,
|
|
@@ -202,6 +203,16 @@ export interface SessionLifecycleTranscriptIdentity {
|
|
|
202
203
|
sha256: string;
|
|
203
204
|
}
|
|
204
205
|
|
|
206
|
+
/**
|
|
207
|
+
* When a lifecycle-managed session publishes its replayable readiness signal.
|
|
208
|
+
*
|
|
209
|
+
* `immediate` is the stock contract. `deferred` prepares the session instead:
|
|
210
|
+
* the child publishes a distinct prepared signal, keeps `session_ready`
|
|
211
|
+
* withheld, and stays unusable for input until it is explicitly activated. It
|
|
212
|
+
* is broker-issued and session-scoped precisely so a prepared session can never
|
|
213
|
+
* be produced by an inherited process-global flag.
|
|
214
|
+
*/
|
|
215
|
+
export type SessionLifecycleReadiness = "immediate" | "deferred";
|
|
205
216
|
export interface SessionLifecycleLaunchRequest {
|
|
206
217
|
operation: "session.create" | "session.fork" | "session.resume";
|
|
207
218
|
sessionId: string;
|
|
@@ -218,6 +229,8 @@ export interface SessionLifecycleLaunchRequest {
|
|
|
218
229
|
modelPreset?: string;
|
|
219
230
|
mcpServers?: SessionLifecycleMcpServer[];
|
|
220
231
|
worktree?: SessionLifecycleWorktreeTarget;
|
|
232
|
+
/** Absent means the stock immediate contract; `deferred` prepares the session. */
|
|
233
|
+
readiness?: SessionLifecycleReadiness;
|
|
221
234
|
receivedAt: number;
|
|
222
235
|
requestedReadinessTimeoutMs: number;
|
|
223
236
|
semanticReadyDeadlineAt: number;
|
|
@@ -363,6 +376,8 @@ export function readSessionLifecycleLaunchRequest(
|
|
|
363
376
|
now,
|
|
364
377
|
) ||
|
|
365
378
|
(request.worktree !== undefined && !isLifecycleWorktreeTarget(request.worktree)) ||
|
|
379
|
+
(request.readiness !== undefined && request.readiness !== "immediate" && request.readiness !== "deferred") ||
|
|
380
|
+
(request.readiness === "deferred" && request.operation !== "session.create") ||
|
|
366
381
|
(request.operation === "session.resume" &&
|
|
367
382
|
!hasValidTranscriptAuthority(request.sessionPath, request.sessionIdentity)) ||
|
|
368
383
|
(request.operation === "session.fork" &&
|
|
@@ -386,6 +401,7 @@ type SessionLaunch = {
|
|
|
386
401
|
modelPreset?: string;
|
|
387
402
|
mcpServers?: SessionLifecycleMcpServer[];
|
|
388
403
|
worktree?: SessionLifecycleWorktreeTarget;
|
|
404
|
+
readiness?: SessionLifecycleReadiness;
|
|
389
405
|
worktreePlan?: GjcLaunchWorktreePlan;
|
|
390
406
|
};
|
|
391
407
|
|
|
@@ -2294,6 +2310,16 @@ function sameReadyAuthority(left: ReadyAuthority, right: ReadyAuthority): boolea
|
|
|
2294
2310
|
);
|
|
2295
2311
|
}
|
|
2296
2312
|
|
|
2313
|
+
/**
|
|
2314
|
+
* Wait for the child's semantic completion signal at exactly this endpoint.
|
|
2315
|
+
*
|
|
2316
|
+
* `session_ready` is the stock signal. A deferred launch waits on
|
|
2317
|
+
* `session_prepared` instead: it is the same authenticated, replayable proof
|
|
2318
|
+
* that the child finished initializing and owns its endpoint, minus the
|
|
2319
|
+
* readiness no consumer may act on yet. Both are additionally bound to the
|
|
2320
|
+
* owner-proved lifecycle receipt through `currentReadyAuthority`, so an
|
|
2321
|
+
* endpoint file appearing on its own never satisfies either wait.
|
|
2322
|
+
*/
|
|
2297
2323
|
async function waitForReady(
|
|
2298
2324
|
broker: Broker,
|
|
2299
2325
|
id: string,
|
|
@@ -2301,6 +2327,7 @@ async function waitForReady(
|
|
|
2301
2327
|
deadline: number,
|
|
2302
2328
|
expected: EffectMarker,
|
|
2303
2329
|
timing: LifecycleTiming,
|
|
2330
|
+
signal: "session_ready" | typeof SESSION_PREPARED_EVENT = "session_ready",
|
|
2304
2331
|
): Promise<ReadinessResult> {
|
|
2305
2332
|
while (timing.now() < deadline) {
|
|
2306
2333
|
const startupFailure = await readSessionLifecycleFailure(root, id, expected);
|
|
@@ -2349,7 +2376,7 @@ async function waitForReady(
|
|
|
2349
2376
|
const frame = event as Record<string, unknown>;
|
|
2350
2377
|
return (
|
|
2351
2378
|
frame.type === "event" &&
|
|
2352
|
-
frame.name ===
|
|
2379
|
+
frame.name === signal &&
|
|
2353
2380
|
frame.sessionId === id &&
|
|
2354
2381
|
frame.generation === authority.endpointGeneration
|
|
2355
2382
|
);
|
|
@@ -2446,8 +2473,28 @@ async function launchInput(
|
|
|
2446
2473
|
return fail("invalid_input", "mcpServers must contain unique valid stdio, HTTP, or SSE server definitions.");
|
|
2447
2474
|
const mcpServers = input.mcpServers as SessionLifecycleMcpServer[] | undefined;
|
|
2448
2475
|
|
|
2476
|
+
/**
|
|
2477
|
+
* Prepared readiness is an explicit creation-only intent. Only the two exact
|
|
2478
|
+
* enum values are admissible, and a foreign value is refused rather than
|
|
2479
|
+
* collapsed into the stock immediate contract.
|
|
2480
|
+
*/
|
|
2481
|
+
if (input.readiness !== undefined && input.readiness !== "immediate" && input.readiness !== "deferred")
|
|
2482
|
+
return fail("invalid_input", "readiness must be either immediate or deferred.");
|
|
2483
|
+
const readiness = input.readiness as SessionLifecycleReadiness | undefined;
|
|
2484
|
+
if (readiness === "deferred" && operation !== "session.create")
|
|
2485
|
+
return fail("invalid_input", "readiness deferred is only supported for session.create.");
|
|
2486
|
+
|
|
2449
2487
|
if (operation === "session.create")
|
|
2450
|
-
return {
|
|
2488
|
+
return {
|
|
2489
|
+
id: randomUUID(),
|
|
2490
|
+
cwd,
|
|
2491
|
+
root: resolvedRoot,
|
|
2492
|
+
modelPreset,
|
|
2493
|
+
mcpServers,
|
|
2494
|
+
worktree,
|
|
2495
|
+
worktreePlan,
|
|
2496
|
+
...(readiness ? { readiness } : {}),
|
|
2497
|
+
};
|
|
2451
2498
|
if (operation === "session.resume") {
|
|
2452
2499
|
if (!requested) return fail("invalid_input", "sessionId is required to resume a saved session.");
|
|
2453
2500
|
const savedPath = text(input.sessionPath);
|
|
@@ -3107,6 +3154,7 @@ async function executeLifecycleResponse(
|
|
|
3107
3154
|
...(launch.modelPreset ? { modelPreset: launch.modelPreset } : {}),
|
|
3108
3155
|
...(launch.mcpServers ? { mcpServers: launch.mcpServers } : {}),
|
|
3109
3156
|
...(launch.worktree ? { worktree: launch.worktree } : {}),
|
|
3157
|
+
...(launch.readiness ? { readiness: launch.readiness } : {}),
|
|
3110
3158
|
};
|
|
3111
3159
|
let child: ChildProcess | undefined;
|
|
3112
3160
|
let spawnedAuthority: EffectMarker | undefined;
|
|
@@ -3161,7 +3209,15 @@ async function executeLifecycleResponse(
|
|
|
3161
3209
|
if (!child || !spawnedAuthority)
|
|
3162
3210
|
return fail("spawn_failed", "Unable to retain the spawned session process identity.");
|
|
3163
3211
|
await broker.ledger.transition(identity, "awaiting_ready", { intendedSessionId: launch.id, effectMarker });
|
|
3164
|
-
const readiness = await waitForReady(
|
|
3212
|
+
const readiness = await waitForReady(
|
|
3213
|
+
broker,
|
|
3214
|
+
launch.id,
|
|
3215
|
+
launch.root,
|
|
3216
|
+
readinessDeadline,
|
|
3217
|
+
spawnedAuthority,
|
|
3218
|
+
timing,
|
|
3219
|
+
launch.readiness === "deferred" ? SESSION_PREPARED_EVENT : "session_ready",
|
|
3220
|
+
);
|
|
3165
3221
|
|
|
3166
3222
|
if (readiness.kind !== "ready") {
|
|
3167
3223
|
const terminated = await terminateSpawnedChild(
|
|
@@ -3220,6 +3276,7 @@ async function executeLifecycleResponse(
|
|
|
3220
3276
|
sessionId: launch.id,
|
|
3221
3277
|
cwd: launch.cwd,
|
|
3222
3278
|
endpoint: verified.endpoint,
|
|
3279
|
+
...(launch.readiness === "deferred" ? { readiness: "prepared" as const } : {}),
|
|
3223
3280
|
...(worktreeReceipt ? { worktree: worktreeReceipt } : {}),
|
|
3224
3281
|
},
|
|
3225
3282
|
};
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
type ChatDaemonCommandBindInput,
|
|
5
|
+
type ChatDaemonCommandOutcome,
|
|
6
|
+
serveChatDaemonCommandsOnce,
|
|
7
|
+
} from "./chat-daemon-command-channel";
|
|
3
8
|
import {
|
|
4
9
|
acquireChatDaemonOwnership,
|
|
5
10
|
type ChatDaemonKind,
|
|
11
|
+
chatDaemonGeneration,
|
|
6
12
|
clearChatDaemonControlRequest,
|
|
13
|
+
hasSafeChatDaemonStateShape,
|
|
7
14
|
readChatDaemonControlRequest,
|
|
8
15
|
readChatDaemonState,
|
|
9
16
|
releaseChatDaemonOwnership,
|
|
@@ -22,6 +29,8 @@ export interface ChatDaemonRuntimeHandle {
|
|
|
22
29
|
start(): Promise<void>;
|
|
23
30
|
stop(): Promise<void>;
|
|
24
31
|
transportHealthy?(): boolean;
|
|
32
|
+
/** Executes operator commands that must run inside the owning daemon. */
|
|
33
|
+
bindExistingRoot?(request: ChatDaemonCommandBindInput): Promise<ChatDaemonCommandOutcome>;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
export interface RunChatDaemonInternalDeps {
|
|
@@ -212,12 +221,45 @@ export async function runChatDaemonInternal(
|
|
|
212
221
|
}
|
|
213
222
|
})();
|
|
214
223
|
}, 5_000);
|
|
224
|
+
const ownedIncarnation = incarnation;
|
|
225
|
+
const stillOwner = async (): Promise<boolean> => {
|
|
226
|
+
const current = await readChatDaemonState(agentDir, kind);
|
|
227
|
+
return (
|
|
228
|
+
hasSafeChatDaemonStateShape(current) &&
|
|
229
|
+
current.kind === kind &&
|
|
230
|
+
current.ownerId === ownerId &&
|
|
231
|
+
current.pid === daemonPid &&
|
|
232
|
+
current.incarnation === ownedIncarnation &&
|
|
233
|
+
current.generation === chatDaemonGeneration(kind) &&
|
|
234
|
+
current.stoppedAt === undefined
|
|
235
|
+
);
|
|
236
|
+
};
|
|
237
|
+
const bindExistingRoot = activeRuntime.bindExistingRoot?.bind(activeRuntime);
|
|
215
238
|
while (!stopping) {
|
|
216
239
|
const request = await readChatDaemonControlRequest(agentDir, kind);
|
|
217
240
|
if (request?.ownerId === ownerId && request.incarnation === incarnation) {
|
|
218
241
|
await clearChatDaemonControlRequest(agentDir, kind, request.requestId);
|
|
219
242
|
break;
|
|
220
243
|
}
|
|
244
|
+
// Operator commands are served in place: unlike a lifecycle request they
|
|
245
|
+
// must never end this loop. A serving failure degrades to the caller's
|
|
246
|
+
// timeout rather than terminating a healthy transport.
|
|
247
|
+
if (bindExistingRoot) {
|
|
248
|
+
try {
|
|
249
|
+
await serveChatDaemonCommandsOnce({
|
|
250
|
+
agentDir,
|
|
251
|
+
kind,
|
|
252
|
+
ownerId,
|
|
253
|
+
pid: daemonPid,
|
|
254
|
+
incarnation: ownedIncarnation,
|
|
255
|
+
generation: chatDaemonGeneration(kind),
|
|
256
|
+
handler: { bindExistingRoot },
|
|
257
|
+
verifyOwnership: stillOwner,
|
|
258
|
+
});
|
|
259
|
+
} catch {
|
|
260
|
+
// Retried on the next poll; the submitter observes a timeout.
|
|
261
|
+
}
|
|
262
|
+
}
|
|
221
263
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
222
264
|
}
|
|
223
265
|
} finally {
|