@gajae-code/coding-agent 0.16.0 → 0.16.1

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 (163) hide show
  1. package/CHANGELOG.md +41 -1
  2. package/dist/types/async/job-manager.d.ts +32 -1
  3. package/dist/types/browser-backend.d.ts +12 -0
  4. package/dist/types/cli/update-cli.d.ts +1 -0
  5. package/dist/types/commands/sdk.d.ts +2 -0
  6. package/dist/types/config/billing-path.d.ts +25 -0
  7. package/dist/types/config/config-schema-version.d.ts +13 -1
  8. package/dist/types/config/model-registry.d.ts +16 -0
  9. package/dist/types/config/settings-schema.d.ts +17 -7
  10. package/dist/types/extensibility/extensions/types.d.ts +8 -3
  11. package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
  12. package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
  13. package/dist/types/main.d.ts +1 -0
  14. package/dist/types/modes/acp/acp-agent.d.ts +21 -21
  15. package/dist/types/modes/components/model-selector.d.ts +4 -0
  16. package/dist/types/modes/components/session-selector.d.ts +1 -1
  17. package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
  18. package/dist/types/modes/interactive-mode.d.ts +16 -2
  19. package/dist/types/modes/theme/theme.d.ts +2 -1
  20. package/dist/types/modes/types.d.ts +2 -0
  21. package/dist/types/sdk/broker/broker.d.ts +2 -1
  22. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  23. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
  24. package/dist/types/sdk/broker/lifecycle.d.ts +14 -0
  25. package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
  26. package/dist/types/sdk/broker/state-version.d.ts +17 -0
  27. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
  28. package/dist/types/sdk/bus/index.d.ts +3 -1
  29. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
  30. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
  31. package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
  32. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  33. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  34. package/dist/types/sdk/cli/rows.d.ts +8 -2
  35. package/dist/types/sdk/cli/session-cli.d.ts +2 -1
  36. package/dist/types/sdk/host/events.d.ts +1 -0
  37. package/dist/types/sdk/host/query/cursor.d.ts +4 -2
  38. package/dist/types/sdk/host/query/handlers.d.ts +1 -0
  39. package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
  40. package/dist/types/sdk/host/session-runtime.d.ts +18 -17
  41. package/dist/types/sdk/lifecycle-session.d.ts +1 -1
  42. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
  43. package/dist/types/sdk/receipt-state.d.ts +2 -0
  44. package/dist/types/sdk/router/session-router.d.ts +4 -0
  45. package/dist/types/sdk/turn-result.d.ts +6 -0
  46. package/dist/types/session/agent-session.d.ts +47 -7
  47. package/dist/types/session/fold-coordinator.d.ts +29 -2
  48. package/dist/types/session/session-manager.d.ts +14 -2
  49. package/dist/types/tools/ask.d.ts +1 -0
  50. package/dist/types/tools/bash.d.ts +12 -0
  51. package/dist/types/tools/browser/launch.d.ts +10 -0
  52. package/dist/types/tools/browser/registry.d.ts +8 -0
  53. package/dist/types/tools/index.d.ts +6 -10
  54. package/dist/types/tools/job.d.ts +3 -1
  55. package/dist/types/utils/external-editor.d.ts +7 -0
  56. package/examples/extensions/README.md +1 -1
  57. package/examples/extensions/pirate.ts +7 -4
  58. package/package.json +7 -7
  59. package/scripts/generate-sdk-operation-inventory.ts +7 -1
  60. package/src/async/job-manager.ts +107 -9
  61. package/src/browser-backend.ts +29 -0
  62. package/src/cli/plugin-cli.ts +29 -0
  63. package/src/cli/update-cli.ts +63 -12
  64. package/src/commands/sdk.ts +22 -7
  65. package/src/config/billing-path.ts +42 -0
  66. package/src/config/config-schema-version.ts +18 -1
  67. package/src/config/model-preset-registry.ts +177 -18
  68. package/src/config/model-registry.ts +268 -42
  69. package/src/config/settings-schema.ts +19 -7
  70. package/src/config/settings.ts +46 -15
  71. package/src/coordinator-mcp/projection-scan.ts +6 -6
  72. package/src/coordinator-mcp/server.ts +21 -1
  73. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  74. package/src/extensibility/extensions/runner.ts +27 -0
  75. package/src/extensibility/extensions/types.ts +8 -3
  76. package/src/extensibility/gjc-plugins/installer.ts +51 -11
  77. package/src/extensibility/runtime-skill-discovery.ts +110 -3
  78. package/src/extensibility/skills.ts +22 -12
  79. package/src/gjc-runtime/launch-worktree.ts +285 -4
  80. package/src/gjc-runtime/session-state-lock.ts +154 -28
  81. package/src/gjc-runtime/session-state-sidecar.ts +10 -17
  82. package/src/gjc-runtime/tmux-owner-isolation.ts +16 -1
  83. package/src/internal-urls/docs-index.generated.ts +10 -10
  84. package/src/main.ts +4 -1
  85. package/src/modes/acp/acp-agent.ts +5 -5
  86. package/src/modes/components/model-selector.ts +76 -8
  87. package/src/modes/components/session-selector.ts +23 -15
  88. package/src/modes/components/sessions-dashboard.ts +8 -5
  89. package/src/modes/controllers/command-controller.ts +1 -1
  90. package/src/modes/controllers/event-controller.ts +30 -4
  91. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  92. package/src/modes/controllers/input-controller.ts +2 -2
  93. package/src/modes/controllers/selector-controller.ts +10 -3
  94. package/src/modes/interactive-mode.ts +36 -12
  95. package/src/modes/prompt-action-autocomplete.ts +30 -4
  96. package/src/modes/runtime-init.ts +12 -9
  97. package/src/modes/theme/theme.ts +5 -0
  98. package/src/modes/types.ts +2 -0
  99. package/src/modes/utils/ui-helpers.ts +22 -3
  100. package/src/prompts/agent-fragments/browser-aside.md +22 -0
  101. package/src/prompts/tools/ask.md +1 -1
  102. package/src/prompts/tools/bash.md +1 -0
  103. package/src/prompts/tools/job.md +1 -1
  104. package/src/sdk/acp/adapter.ts +5 -0
  105. package/src/sdk/broker/broker.ts +10 -1
  106. package/src/sdk/broker/ensure.ts +76 -3
  107. package/src/sdk/broker/lifecycle-ledger.ts +6 -0
  108. package/src/sdk/broker/lifecycle.ts +254 -34
  109. package/src/sdk/broker/spawn-substrate.ts +45 -2
  110. package/src/sdk/broker/startup-budget.ts +144 -2
  111. package/src/sdk/broker/state-version.ts +17 -0
  112. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  113. package/src/sdk/bus/chat-daemon-runtime.ts +111 -24
  114. package/src/sdk/bus/discord-daemon.ts +50 -10
  115. package/src/sdk/bus/index.ts +33 -3
  116. package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
  117. package/src/sdk/bus/lifecycle-commands.ts +4 -0
  118. package/src/sdk/bus/notification-service.ts +22 -5
  119. package/src/sdk/bus/prompt-reconciliation.ts +7 -2
  120. package/src/sdk/bus/reconciliation-store.ts +55 -31
  121. package/src/sdk/bus/slack-conversation.ts +1 -1
  122. package/src/sdk/bus/slack-daemon.ts +72 -38
  123. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  124. package/src/sdk/cli/rows.ts +30 -7
  125. package/src/sdk/cli/session-cli.ts +33 -21
  126. package/src/sdk/host/control/dispatch.ts +2 -2
  127. package/src/sdk/host/events.ts +1 -0
  128. package/src/sdk/host/host.ts +2 -0
  129. package/src/sdk/host/query/cursor.ts +52 -7
  130. package/src/sdk/host/query/handlers.ts +33 -12
  131. package/src/sdk/host/query/revision-store.ts +12 -0
  132. package/src/sdk/host/session-runtime.test.ts +463 -8
  133. package/src/sdk/host/session-runtime.ts +383 -54
  134. package/src/sdk/host/surface-policy.ts +3 -3
  135. package/src/sdk/lifecycle-session.ts +7 -2
  136. package/src/sdk/prompt-deadline-manager.ts +2 -1
  137. package/src/sdk/protocol/operation-inventory.generated.json +54 -3
  138. package/src/sdk/protocol/operation-registry.ts +1 -1
  139. package/src/sdk/receipt-state.ts +10 -0
  140. package/src/sdk/router/session-router.ts +152 -59
  141. package/src/sdk/session.ts +60 -41
  142. package/src/sdk/turn-result.ts +22 -0
  143. package/src/session/agent-session.ts +1033 -260
  144. package/src/session/fold-coordinator.ts +40 -3
  145. package/src/session/session-manager.ts +517 -77
  146. package/src/slash-commands/builtin-registry.ts +41 -0
  147. package/src/task/executor.ts +9 -4
  148. package/src/tools/ask-contract.test.ts +64 -0
  149. package/src/tools/ask-contract.ts +36 -1
  150. package/src/tools/ask.ts +10 -8
  151. package/src/tools/bash-interactive.ts +11 -2
  152. package/src/tools/bash.ts +156 -33
  153. package/src/tools/browser/launch.ts +37 -0
  154. package/src/tools/browser/registry.ts +99 -9
  155. package/src/tools/cron.ts +6 -4
  156. package/src/tools/descriptors.test.ts +15 -0
  157. package/src/tools/descriptors.ts +5 -1
  158. package/src/tools/index.ts +6 -6
  159. package/src/tools/job.ts +4 -1
  160. package/src/tools/monitor.ts +12 -9
  161. package/src/tools/resolve.ts +30 -10
  162. package/src/tools/tool-catalog.generated.ts +2 -2
  163. package/src/utils/external-editor.ts +72 -1
@@ -291,8 +291,8 @@ function invoke(
291
291
  return surface.setQueueMode("steering", input.mode);
292
292
  case "queue.follow_up_mode.set":
293
293
  return surface.setQueueMode("follow_up", input.mode);
294
- case "queue.interrupt_mode.set":
295
- return surface.setQueueMode("interrupt", input.mode);
294
+ case "queue.tool_interrupt_policy.set":
295
+ return surface.setQueueMode("tool_interrupt", input.mode);
296
296
  case "compaction.run":
297
297
  return surface.runCompaction();
298
298
  case "compaction.auto.set":
@@ -2,6 +2,7 @@ import type { SdkFrame } from "./types";
2
2
 
3
3
  export interface EventFrame extends SdkFrame {
4
4
  type: "event";
5
+ revision?: number;
5
6
  generation: number;
6
7
  seq: number;
7
8
  }
@@ -354,6 +354,8 @@ export class SessionSdkHost {
354
354
  if (this.events.generation !== generation) return "generation_changed";
355
355
  if (this.#readyGeneration === generation) return "already";
356
356
  }
357
+ if (!this.#started || this.#stopping) return "not_prepared";
358
+ if (this.events.generation !== generation) return "generation_changed";
357
359
  this.#publishReadiness();
358
360
  return "activated";
359
361
  }
@@ -18,6 +18,7 @@ export interface CursorEnvelope {
18
18
  issuedAt?: number;
19
19
  /** TTL metadata minted by `CursorRegistry.grant`: wall-clock ms when the cursor expires. */
20
20
  expiresAt?: number;
21
+ purpose?: "checkpoint";
21
22
  position: unknown;
22
23
  direction: string;
23
24
  pageShape: unknown;
@@ -68,6 +69,7 @@ export interface CursorQueryContext {
68
69
  resourceId?: string;
69
70
  direction: string;
70
71
  pageShape: unknown;
72
+ purpose?: "checkpoint";
71
73
  }
72
74
 
73
75
  export class CursorError extends Error {
@@ -170,26 +172,68 @@ export class CursorRegistry {
170
172
  cursor: string,
171
173
  connectionId: string,
172
174
  expected: CursorQueryContext,
173
- resourceKind: string,
175
+ _resourceKind: string,
174
176
  resourceId: string,
175
177
  ): Promise<{ cursor: string; envelope: CursorEnvelope }> {
176
178
  const envelope = verifyCursor(cursor, this.sessionToken);
177
- if (!envelope) throw new CursorError("invalid_cursor", false);
178
- if (envelope.expiresAt === undefined || envelope.expiresAt <= this.now()) throw new CursorError("cursor_expired");
179
+ if (!envelope) {
180
+ this.release(cursor);
181
+ throw new CursorError("invalid_cursor", true);
182
+ }
183
+ const source = this.#active.get(cursor);
184
+ if (!source || envelope.expiresAt === undefined) {
185
+ this.release(cursor);
186
+ throw new CursorError("invalid_cursor", true);
187
+ }
188
+ const validatedAt = this.now();
189
+ if (source.expiresAt <= validatedAt || envelope.expiresAt <= validatedAt) {
190
+ this.release(cursor);
191
+ throw new CursorError("cursor_expired");
192
+ }
179
193
  if (
180
194
  envelope.sessionId !== expected.sessionId ||
181
195
  (expected.resource !== undefined && envelope.resource !== expected.resource) ||
182
196
  envelope.direction !== expected.direction ||
183
- canonicalJson(envelope.pageShape) !== canonicalJson(expected.pageShape)
197
+ canonicalJson(envelope.pageShape) !== canonicalJson(expected.pageShape) ||
198
+ envelope.purpose !== expected.purpose ||
199
+ _resourceKind !== envelope.resource ||
200
+ source.resourceId !== resourceId
184
201
  )
185
202
  throw new CursorError("invalid_input", false, "cursor does not match query");
203
+ // Preserve the already-validated source while removing expired target
204
+ // grants before capacity accounting. The shared timestamp prevents the
205
+ // source from changing classification at a TTL boundary mid-exchange.
206
+ this.sweep(cursor, validatedAt);
207
+ const targetConnectionCount = [...this.#active.values()].filter(
208
+ item => item.connectionId === connectionId,
209
+ ).length;
210
+ if (
211
+ this.#active.size > MAX_CURSORS_PER_SESSION ||
212
+ targetConnectionCount - (source.connectionId === connectionId ? 1 : 0) >= MAX_CURSORS_PER_CONNECTION
213
+ )
214
+ throw new CursorError("snapshot_capacity_exceeded", false);
186
215
  const exchanged: CursorEnvelope = {
187
216
  ...envelope,
188
217
  nonce: randomUUID(),
189
218
  issuedAt: undefined,
190
219
  expiresAt: undefined,
191
220
  };
192
- const exchangedCursor = await this.grant(connectionId, exchanged, resourceKind, resourceId);
221
+ const issuedAt = this.now();
222
+ exchanged.issuedAt = issuedAt;
223
+ exchanged.expiresAt = issuedAt + CURSOR_TTL_MS;
224
+ const exchangedCursor = signCursor(exchanged, this.sessionToken);
225
+ try {
226
+ this.revisions.transferPin(cursor, exchangedCursor);
227
+ } catch (error) {
228
+ if (error instanceof RevisionStoreError) throw new CursorError(error.code, false, error.message);
229
+ throw error;
230
+ }
231
+ this.#active.delete(cursor);
232
+ this.#active.set(exchangedCursor, {
233
+ connectionId,
234
+ resourceId,
235
+ expiresAt: exchanged.expiresAt,
236
+ });
193
237
  return { cursor: exchangedCursor, envelope: exchanged };
194
238
  }
195
239
 
@@ -227,8 +271,9 @@ export class CursorRegistry {
227
271
  close(): void {
228
272
  for (const cursor of this.#active.keys()) this.release(cursor);
229
273
  }
230
- sweep(): void {
231
- for (const [cursor, active] of this.#active) if (active.expiresAt <= this.now()) this.release(cursor);
274
+ sweep(exceptCursor?: string, at = this.now()): void {
275
+ for (const [cursor, active] of this.#active)
276
+ if (cursor !== exceptCursor && active.expiresAt <= at) this.release(cursor);
232
277
  this.revisions.sweep();
233
278
  }
234
279
  get size(): number {
@@ -108,6 +108,7 @@ export interface SdkCheckpointRecord {
108
108
  revision: number;
109
109
  generation: number;
110
110
  seq: number;
111
+ idle: boolean;
111
112
  }
112
113
 
113
114
  function isNonNegativeSafeInteger(value: unknown): value is number {
@@ -119,10 +120,11 @@ export function isCheckpointRecord(value: unknown): value is SdkCheckpointRecord
119
120
  const record = value as Record<string, unknown>;
120
121
  const keys = Object.keys(record);
121
122
  return (
122
- keys.length === 3 &&
123
+ keys.length === 4 &&
123
124
  isNonNegativeSafeInteger(record.revision) &&
124
125
  isNonNegativeSafeInteger(record.generation) &&
125
- isNonNegativeSafeInteger(record.seq)
126
+ isNonNegativeSafeInteger(record.seq) &&
127
+ typeof record.idle === "boolean"
126
128
  );
127
129
  }
128
130
  const sources: Record<string, { resource: string; method: keyof SessionSurface; mvcc: boolean }> = {
@@ -296,9 +298,10 @@ export class QueryHandlers {
296
298
  let snapshot: unknown;
297
299
  // The cursor may arrive either as the top-level continuation cursor or,
298
300
  // on Q01 only (validated in `dispatch`), as the `checkpointToken` resume
299
- // seed. Both are signed checkpoint cursors consumed through the same
300
- // CursorRegistry authority; neither mints a fresh snapshot.
301
- const rawCursorToken = request.cursor ?? (queryId === "Q01" ? request.input?.checkpointToken : undefined);
301
+ // seed. Both are consumed through the same CursorRegistry authority, but
302
+ // only the named input token is required to carry checkpoint purpose.
303
+ const checkpointToken = queryId === "Q01" ? request.input?.checkpointToken : undefined;
304
+ const rawCursorToken = request.cursor ?? checkpointToken;
302
305
  if (rawCursorToken !== undefined && typeof rawCursorToken !== "string")
303
306
  return this.#error(request, "invalid_request", false, "checkpointToken must be a non-empty string.");
304
307
  const cursorToken = rawCursorToken;
@@ -309,6 +312,8 @@ export class QueryHandlers {
309
312
  direction: "forward",
310
313
  pageShape: { targetBytes: TARGET_PAGE_BYTES },
311
314
  });
315
+ if (checkpointToken !== undefined && cursor.purpose !== "checkpoint")
316
+ throw new CursorError("invalid_input", false, "checkpointToken does not carry checkpoint purpose");
312
317
  selector = assertCursorSelector(cursorSelector(cursor.position), selector);
313
318
  resourceId = selector.resourceId ?? "default";
314
319
  revision = cursor.revision;
@@ -467,10 +472,20 @@ export class QueryHandlers {
467
472
  resource: "transcript",
468
473
  direction: "forward",
469
474
  pageShape: { targetBytes: TARGET_PAGE_BYTES },
475
+ purpose: "checkpoint",
470
476
  },
471
477
  "transcript",
472
478
  "default",
473
479
  );
480
+ if (!isCheckpointRecord(exchanged.envelope.highWatermark)) {
481
+ this.cursors.release(exchanged.cursor);
482
+ return this.#error(
483
+ request,
484
+ "invalid_input",
485
+ false,
486
+ "checkpointToken is not an authoritative checkpoint.",
487
+ );
488
+ }
474
489
  return {
475
490
  id: request.id,
476
491
  ok: true,
@@ -494,13 +509,18 @@ export class QueryHandlers {
494
509
  // Atomic synchronous capture (C9): entries and event-ring watermark come
495
510
  // from the same host-owned call, so the pinned snapshot revision and the
496
511
  // subscribe position can never straddle a concurrent append.
497
- const captured =
498
- typeof this.surface.getCheckpointSnapshot === "function" ? this.surface.getCheckpointSnapshot() : undefined;
499
- const entries = captured !== undefined ? captured.entries : await this.surface.getTranscriptEntries();
500
- const head: SdkCheckpointRecord =
501
- captured !== undefined
502
- ? captured.watermark
503
- : { revision: Array.isArray(entries) ? entries.length : 0, generation: 0, seq: 0 };
512
+ if (typeof this.surface.getCheckpointSnapshot !== "function")
513
+ return this.#error(request, "unavailable", false, "Atomic session checkpoint capture is unavailable.");
514
+ const captured = this.surface.getCheckpointSnapshot();
515
+ const entries = captured.entries;
516
+ const head = captured.watermark;
517
+ if (!isCheckpointRecord(head))
518
+ return this.#error(
519
+ request,
520
+ "unavailable",
521
+ false,
522
+ "Atomic session checkpoint capture returned an invalid watermark.",
523
+ );
504
524
  // Mint the snapshot from the exact entries captured with the watermark;
505
525
  // entries appended after this point are excluded from replay by
506
526
  // construction (append-during-checkpoint), and the returned token is the
@@ -517,6 +537,7 @@ export class QueryHandlers {
517
537
  highWatermark: head,
518
538
  nonce,
519
539
  position: { offset: 0, selector: { queryId: "Q01" } },
540
+ purpose: "checkpoint",
520
541
  direction: "forward",
521
542
  pageShape: { targetBytes: TARGET_PAGE_BYTES },
522
543
  };
@@ -524,6 +524,18 @@ export class RevisionStore {
524
524
  await this.#enforceMemory();
525
525
  }
526
526
 
527
+ transferPin(sourceCursorId: string, replacementCursorId: string): void {
528
+ if (sourceCursorId === replacementCursorId) return;
529
+ if (this.#pinIndex.has(replacementCursorId))
530
+ throw new RevisionStoreError("snapshot_capacity_exceeded", "replacement cursor is already pinned");
531
+ const revision = this.#pinIndex.get(sourceCursorId);
532
+ if (!revision) throw new RevisionStoreError("snapshot_capacity_exceeded", "snapshot pin is unavailable");
533
+ revision.pins.delete(sourceCursorId);
534
+ revision.pins.add(replacementCursorId);
535
+ this.#pinIndex.delete(sourceCursorId);
536
+ this.#pinIndex.set(replacementCursorId, revision);
537
+ }
538
+
527
539
  unpin(cursorId: string): void {
528
540
  const revision = this.#pinIndex.get(cursorId);
529
541
  if (!revision) return;