@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.
Files changed (120) hide show
  1. package/CHANGELOG.md +47 -2
  2. package/dist/types/cli/notify-cli.d.ts +42 -1
  3. package/dist/types/cli/update-cli.d.ts +9 -3
  4. package/dist/types/commands/notify.d.ts +6 -0
  5. package/dist/types/config/file-lock.d.ts +3 -0
  6. package/dist/types/coordinator/contract.d.ts +1 -1
  7. package/dist/types/coordinator-mcp/question-state.d.ts +3 -1
  8. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  9. package/dist/types/extensibility/extensions/ouroboros-ooo-bridge.d.ts +13 -0
  10. package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -1
  11. package/dist/types/extensibility/extensions/types.d.ts +2 -0
  12. package/dist/types/main.d.ts +6 -2
  13. package/dist/types/modes/components/session-selector.d.ts +1 -1
  14. package/dist/types/modes/utils/host-status.d.ts +23 -0
  15. package/dist/types/runtime-mcp/types.d.ts +2 -0
  16. package/dist/types/sdk/broker/lifecycle.d.ts +12 -0
  17. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -0
  18. package/dist/types/sdk/bus/chat-daemon-command-channel.d.ts +244 -0
  19. package/dist/types/sdk/bus/chat-daemon-control.d.ts +13 -1
  20. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +7 -0
  21. package/dist/types/sdk/bus/control-plane-frames.d.ts +43 -0
  22. package/dist/types/sdk/bus/conversation-store.d.ts +18 -0
  23. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +71 -0
  24. package/dist/types/sdk/bus/notification-service.d.ts +10 -0
  25. package/dist/types/sdk/bus/slack-daemon.d.ts +27 -0
  26. package/dist/types/sdk/bus/slack-live-provider.d.ts +11 -0
  27. package/dist/types/sdk/bus/slack-provider.d.ts +14 -0
  28. package/dist/types/sdk/bus/slack-thread-binding.d.ts +140 -0
  29. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +2 -0
  30. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +25 -7
  31. package/dist/types/sdk/bus/telegram-daemon.d.ts +104 -16
  32. package/dist/types/sdk/bus/telegram-reference.d.ts +10 -1
  33. package/dist/types/sdk/bus/topic-registry.d.ts +130 -85
  34. package/dist/types/sdk/host/host.d.ts +52 -0
  35. package/dist/types/sdk/lifecycle-session.d.ts +6 -0
  36. package/dist/types/sdk/session-activation.d.ts +41 -0
  37. package/dist/types/sdk/session.d.ts +18 -0
  38. package/dist/types/sdk/startup-capability.d.ts +8 -1
  39. package/dist/types/session/agent-session.d.ts +7 -0
  40. package/dist/types/session/artifacts.d.ts +2 -2
  41. package/dist/types/session/internal/managed-session-storage.d.ts +12 -2
  42. package/dist/types/session/session-manager.d.ts +40 -5
  43. package/dist/types/session/session-storage.d.ts +1 -0
  44. package/dist/types/tools/computer.d.ts +35 -2
  45. package/dist/types/tools/index.d.ts +8 -0
  46. package/dist/types/tools/todo-write.d.ts +2 -0
  47. package/dist/types/utils/npm-registry.d.ts +71 -0
  48. package/examples/extensions/README.md +38 -4
  49. package/examples/extensions/ooo-bridge.ts +15 -0
  50. package/package.json +8 -8
  51. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  52. package/src/async/job-manager.ts +9 -2
  53. package/src/cli/notify-cli.ts +226 -2
  54. package/src/cli/update-cli.ts +90 -23
  55. package/src/commands/notify.ts +27 -3
  56. package/src/commands/sdk.ts +1 -0
  57. package/src/config/file-lock-gc.ts +18 -0
  58. package/src/config/file-lock.ts +61 -16
  59. package/src/config/model-profiles.ts +31 -15
  60. package/src/coordinator/contract.ts +1 -0
  61. package/src/coordinator-mcp/question-state.ts +2 -0
  62. package/src/coordinator-mcp/server.ts +358 -7
  63. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
  64. package/src/extensibility/extensions/index.ts +1 -0
  65. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +235 -0
  66. package/src/extensibility/extensions/prefix-command-bridge.ts +4 -9
  67. package/src/extensibility/extensions/runner.ts +15 -2
  68. package/src/extensibility/extensions/types.ts +2 -0
  69. package/src/gjc-runtime/launch-tmux.ts +53 -1
  70. package/src/internal-urls/docs-index.generated.ts +13 -12
  71. package/src/main.ts +203 -67
  72. package/src/modes/DESIGN.md +8 -5
  73. package/src/modes/components/footer.ts +10 -18
  74. package/src/modes/components/session-selector.ts +26 -3
  75. package/src/modes/components/status-line/gh.ts +23 -2
  76. package/src/modes/controllers/event-controller.ts +3 -0
  77. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  78. package/src/modes/controllers/input-controller.ts +11 -0
  79. package/src/modes/controllers/selector-controller.ts +8 -2
  80. package/src/modes/interactive-mode.ts +7 -1
  81. package/src/modes/utils/host-status.ts +38 -0
  82. package/src/prompts/tools/computer.md +6 -1
  83. package/src/runtime-mcp/types.ts +2 -0
  84. package/src/sdk/broker/lifecycle.ts +60 -3
  85. package/src/sdk/bus/chat-daemon-cli.ts +42 -0
  86. package/src/sdk/bus/chat-daemon-command-channel.ts +921 -0
  87. package/src/sdk/bus/chat-daemon-control.ts +18 -3
  88. package/src/sdk/bus/chat-daemon-runtime.ts +257 -21
  89. package/src/sdk/bus/control-plane-frames.ts +51 -0
  90. package/src/sdk/bus/conversation-store.ts +29 -1
  91. package/src/sdk/bus/existing-thread-readiness.ts +155 -0
  92. package/src/sdk/bus/index.ts +70 -0
  93. package/src/sdk/bus/notification-service.ts +28 -1
  94. package/src/sdk/bus/rich-render.ts +3 -2
  95. package/src/sdk/bus/slack-daemon.ts +99 -4
  96. package/src/sdk/bus/slack-live-provider.ts +32 -2
  97. package/src/sdk/bus/slack-provider.ts +11 -0
  98. package/src/sdk/bus/slack-thread-binding.ts +641 -0
  99. package/src/sdk/bus/telegram-daemon-cli.ts +11 -0
  100. package/src/sdk/bus/telegram-daemon-contract.ts +25 -7
  101. package/src/sdk/bus/telegram-daemon.ts +1961 -497
  102. package/src/sdk/bus/telegram-reference.ts +42 -6
  103. package/src/sdk/bus/topic-registry.ts +674 -181
  104. package/src/sdk/host/host.ts +180 -1
  105. package/src/sdk/lifecycle-session.ts +8 -2
  106. package/src/sdk/protocol/operation-inventory.generated.json +22 -0
  107. package/src/sdk/session-activation.ts +127 -0
  108. package/src/sdk/session.ts +139 -28
  109. package/src/sdk/startup-capability.ts +10 -1
  110. package/src/session/agent-session.ts +331 -30
  111. package/src/session/artifacts.ts +56 -13
  112. package/src/session/internal/managed-session-storage.ts +440 -243
  113. package/src/session/session-manager.ts +288 -44
  114. package/src/session/session-storage.ts +3 -0
  115. package/src/setup/provider-presets.json +1 -1
  116. package/src/task/index.ts +92 -44
  117. package/src/tools/computer.ts +322 -45
  118. package/src/tools/index.ts +8 -0
  119. package/src/tools/todo-write.ts +29 -11
  120. 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`: the host needs screen-recording or accessibility permission. Ask the user to grant it.
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.
@@ -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 === "session_ready" &&
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 { id: randomUUID(), cwd, root: resolvedRoot, modelPreset, mcpServers, worktree, worktreePlan };
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(broker, launch.id, launch.root, readinessDeadline, spawnedAuthority, timing);
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 {