@gajae-code/coding-agent 0.11.0 → 0.11.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 (36) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/types/async/job-manager.d.ts +46 -7
  3. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +11 -0
  4. package/dist/types/modes/controllers/command-controller.d.ts +2 -2
  5. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -0
  6. package/dist/types/modes/interactive-mode.d.ts +2 -2
  7. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +1 -0
  8. package/dist/types/modes/types.d.ts +2 -2
  9. package/dist/types/sdk/broker/ensure.d.ts +9 -0
  10. package/dist/types/sdk/bus/discord-daemon.d.ts +5 -0
  11. package/dist/types/sdk/bus/telegram-daemon.d.ts +23 -0
  12. package/dist/types/tools/subagent-render.d.ts +1 -0
  13. package/dist/types/tools/subagent.d.ts +6 -1
  14. package/package.json +8 -8
  15. package/scripts/build-sdk-package-smoke.ts +5 -0
  16. package/src/async/job-manager.ts +406 -63
  17. package/src/gjc-runtime/ultragoal-guard.ts +55 -8
  18. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +102 -1
  19. package/src/gjc-runtime/ultragoal-runtime.ts +72 -8
  20. package/src/internal-urls/docs-index.generated.ts +2 -2
  21. package/src/modes/controllers/command-controller.ts +9 -15
  22. package/src/modes/controllers/extension-ui-controller.ts +48 -24
  23. package/src/modes/interactive-mode.ts +28 -16
  24. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +7 -3
  25. package/src/modes/types.ts +2 -2
  26. package/src/sdk/broker/discovery.ts +20 -1
  27. package/src/sdk/broker/ensure.ts +82 -24
  28. package/src/sdk/bus/chat-effect-journal.ts +6 -4
  29. package/src/sdk/bus/discord-daemon.ts +25 -8
  30. package/src/sdk/bus/index.ts +57 -21
  31. package/src/sdk/bus/native-runtime-compatibility.ts +1 -1
  32. package/src/sdk/bus/telegram-daemon.ts +102 -10
  33. package/src/session/agent-session.ts +176 -29
  34. package/src/task/index.ts +26 -16
  35. package/src/tools/subagent-render.ts +19 -8
  36. package/src/tools/subagent.ts +84 -32
@@ -23,6 +23,8 @@ import {
23
23
  } from "./render-utils";
24
24
  import { type SubagentSnapshot, type SubagentToolDetails, subagentAwaitRenderedStateSignature } from "./subagent";
25
25
 
26
+ export { subagentAwaitRenderedStateSignature } from "./subagent";
27
+
26
28
  const PREVIEW_LINES_COLLAPSED = 1;
27
29
  const PREVIEW_LINES_EXPANDED = 4;
28
30
  const PREVIEW_LINE_WIDTH = 80;
@@ -128,7 +130,7 @@ function renderSubagentStatusLine(snapshot: SubagentSnapshot, theme: Theme, spin
128
130
  theme,
129
131
  snapshot.status === "running" ? spinnerFrame : undefined,
130
132
  );
131
- const id = theme.fg("muted", snapshot.id);
133
+ const id = theme.fg("muted", truncateToWidth(replaceTabs(snapshot.id), PREVIEW_LINE_WIDTH, Ellipsis.Unicode));
132
134
  const status = theme.fg("dim", snapshot.status);
133
135
  const duration = theme.fg("dim", formatDuration(snapshot.durationMs));
134
136
  return `${icon} ${id} ${status} ${duration}`;
@@ -195,7 +197,12 @@ function renderSubagentSnapshotBody(snapshot: SubagentSnapshot, expanded: boolea
195
197
  }
196
198
  }
197
199
 
198
- if (snapshot.guidance) lines.push(` ${theme.fg("dim", snapshot.guidance)}`);
200
+ if (snapshot.guidance) {
201
+ for (const line of getPreviewLines(snapshot.guidance, 1, PREVIEW_LINE_WIDTH, Ellipsis.Unicode)) {
202
+ lines.push(` ${theme.fg("dim", replaceTabs(line))}`);
203
+ }
204
+ }
205
+
199
206
  return lines;
200
207
  }
201
208
 
@@ -218,6 +225,7 @@ export const subagentToolRenderer = {
218
225
  }
219
226
 
220
227
  const runningCount = subagents.filter(s => s.status === "running").length;
228
+ const interrupted = result.details?.interrupted === true;
221
229
 
222
230
  // Each snapshot's rendered-state signature is constant for this component
223
231
  // instance, so compute them at most once; the heavy per-subagent bodies are
@@ -232,11 +240,12 @@ export const subagentToolRenderer = {
232
240
  // it is never gated by the heavy body cache.
233
241
  const header = renderStatusLine(
234
242
  {
235
- icon: runningCount > 0 ? "info" : "success",
236
- spinnerFrame: runningCount > 0 ? options.spinnerFrame : undefined,
237
- title: "Subagent",
238
- description:
239
- runningCount > 0
243
+ icon: interrupted ? "warning" : runningCount > 0 ? "info" : "success",
244
+ spinnerFrame: !interrupted && runningCount > 0 ? options.spinnerFrame : undefined,
245
+ title: interrupted ? "Subagent await interrupted" : "Subagent",
246
+ description: interrupted
247
+ ? "child subagents continue"
248
+ : runningCount > 0
240
249
  ? `awaiting ${runningCount} of ${subagents.length}`
241
250
  : `${subagents.length} ${subagents.length === 1 ? "subagent" : "subagents"}`,
242
251
  },
@@ -249,7 +258,9 @@ export const subagentToolRenderer = {
249
258
  out.push(truncateToWidth(` ${theme.fg("dim", "(ctrl+s to observe sessions)")}`, width, Ellipsis.Omit));
250
259
  }
251
260
 
252
- snapshotSignatures ??= subagents.map(snapshot => subagentAwaitRenderedStateSignature([snapshot]));
261
+ snapshotSignatures ??= subagents.map(snapshot =>
262
+ subagentAwaitRenderedStateSignature([snapshot], result.details),
263
+ );
253
264
  subagents.forEach((snapshot, index) => {
254
265
  // Fresh per-subagent status line (cheap), then the cached heavy body.
255
266
  out.push(
@@ -80,8 +80,17 @@ export interface SubagentSnapshot {
80
80
  modelFellBack?: boolean;
81
81
  }
82
82
 
83
+ export type SubagentAwaitOutcome = "completed" | "timed_out" | "interrupted";
84
+
85
+ const AWAIT_INTERRUPTED_GUIDANCE =
86
+ "Await interrupted; this subagent continues. Inspect its current state or cancel it only when necessary.";
87
+
83
88
  export interface SubagentToolDetails {
84
89
  subagents: SubagentSnapshot[];
90
+ /** Await outcome for a live await receipt; omitted when no wait was started. */
91
+ awaitOutcome?: SubagentAwaitOutcome;
92
+ /** True only when the parent await was interrupted; the child was not cancelled. */
93
+ interrupted?: true;
85
94
  }
86
95
 
87
96
  export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentToolDetails> {
@@ -370,35 +379,50 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
370
379
  // the viewport (the source of the await-panel repaint storms).
371
380
  emitIfChanged(true);
372
381
 
373
- let timedOut = false;
382
+ let awaitOutcome: SubagentAwaitOutcome;
383
+ const { promise: timeoutPromise, resolve: resolveTimeout } = Promise.withResolvers<SubagentAwaitOutcome>();
384
+ const timeoutTimer = setTimeout(() => resolveTimeout("timed_out"), timeoutMs);
385
+ const completionPromise = Promise.all(runningJobs.map(job => job.promise)).then(() => "completed" as const);
386
+ let onAbort: (() => void) | undefined;
374
387
  try {
375
- const completionPromise = Promise.all(runningJobs.map(job => job.promise));
376
- const timeoutPromise = Bun.sleep(timeoutMs).then(() => {
377
- timedOut = true;
378
- });
379
388
  if (signal) {
380
- const { promise: abortPromise, resolve: abortResolve } = Promise.withResolvers<void>();
381
- const onAbort = () => abortResolve();
382
- signal.addEventListener("abort", onAbort, { once: true });
383
- try {
384
- await Promise.race([completionPromise, timeoutPromise, abortPromise]);
385
- } finally {
386
- signal.removeEventListener("abort", onAbort);
387
- }
389
+ const { promise: abortPromise, resolve: resolveAbort } = Promise.withResolvers<SubagentAwaitOutcome>();
390
+ onAbort = () => resolveAbort("interrupted");
391
+ if (signal.aborted) onAbort();
392
+ else signal.addEventListener("abort", onAbort, { once: true });
393
+ awaitOutcome = await Promise.race([completionPromise, timeoutPromise, abortPromise]);
388
394
  } else {
389
- await Promise.race([completionPromise, timeoutPromise]);
395
+ awaitOutcome = await Promise.race([completionPromise, timeoutPromise]);
390
396
  }
391
397
  } finally {
392
- manager.unwatchJobs(watchedJobIds);
398
+ clearTimeout(timeoutTimer);
399
+ if (signal && onAbort) signal.removeEventListener("abort", onAbort);
393
400
  if (progressTimer) clearInterval(progressTimer);
394
401
  }
402
+ if (awaitOutcome !== "interrupted") {
403
+ const terminalWatchedJobIds = watchedJobIds.filter(jobId => {
404
+ const status = manager.getJob(jobId)?.status;
405
+ return status === "completed" || status === "failed" || status === "cancelled";
406
+ });
407
+ manager.acknowledgeDeliveries(terminalWatchedJobIds);
408
+ }
409
+ manager.unwatchJobs(watchedJobIds);
395
410
 
396
- return await this.#buildRecordResult(manager, records, {
397
- title: "Subagent await",
398
- notFoundIds,
399
- timedOut,
411
+ const finalRecords = this.#visibleRecordsByIds(
412
+ manager,
413
+ records.map(record => record.subagentId),
414
+ ownerFilter,
415
+ );
416
+ const finalNotFoundIds = [...new Set([...notFoundIds, ...records.map(record => record.subagentId)])].filter(
417
+ id => !finalRecords.some(record => record.subagentId === id),
418
+ );
419
+ return await this.#buildRecordResult(manager, finalRecords, {
420
+ title: awaitOutcome === "interrupted" ? "Subagent await interrupted" : "Subagent await",
421
+ notFoundIds: finalNotFoundIds,
422
+ timedOut: awaitOutcome === "timed_out",
400
423
  verbosity: params.verbosity ?? "receipt",
401
424
  attachLiveProgress: true,
425
+ awaitOutcome,
402
426
  });
403
427
  }
404
428
 
@@ -516,6 +540,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
516
540
  timedOut?: boolean;
517
541
  verbosity?: SubagentParams["verbosity"];
518
542
  attachLiveProgress?: boolean;
543
+ awaitOutcome?: SubagentAwaitOutcome;
519
544
  },
520
545
  ): Promise<AgentToolResult<SubagentToolDetails>> {
521
546
  const verifiedOutputIds = await this.#verifiedOutputIds(records);
@@ -530,18 +555,34 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
530
555
  for (const id of options.notFoundIds ?? []) {
531
556
  snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
532
557
  }
533
- manager.acknowledgeDeliveries(
534
- snapshots
535
- .filter(
536
- s =>
537
- s.status !== "running" && s.status !== "paused" && s.status !== "queued" && s.status !== "not_found",
538
- )
539
- .map(s => s.jobId),
540
- );
541
- return this.#buildSnapshotResult(snapshots, options.title);
558
+ if (options.awaitOutcome !== "interrupted") {
559
+ manager.acknowledgeDeliveries(
560
+ snapshots
561
+ .filter(
562
+ s =>
563
+ s.status !== "running" &&
564
+ s.status !== "paused" &&
565
+ s.status !== "queued" &&
566
+ s.status !== "not_found",
567
+ )
568
+ .map(s => s.jobId),
569
+ );
570
+ }
571
+ if (options.awaitOutcome === "interrupted") {
572
+ for (const snapshot of snapshots) {
573
+ if (snapshot.status === "running" || snapshot.status === "paused" || snapshot.status === "queued") {
574
+ snapshot.guidance = AWAIT_INTERRUPTED_GUIDANCE;
575
+ }
576
+ }
577
+ }
578
+ return this.#buildSnapshotResult(snapshots, options.title, options.awaitOutcome);
542
579
  }
543
580
 
544
- #buildSnapshotResult(snapshots: SubagentSnapshot[], title: string): AgentToolResult<SubagentToolDetails> {
581
+ #buildSnapshotResult(
582
+ snapshots: SubagentSnapshot[],
583
+ title: string,
584
+ awaitOutcome?: SubagentAwaitOutcome,
585
+ ): AgentToolResult<SubagentToolDetails> {
545
586
  const lines = [`## ${title} (${snapshots.length})`, ""];
546
587
  for (const snapshot of snapshots) {
547
588
  lines.push(`### ${snapshot.id} — ${snapshot.status}`);
@@ -571,7 +612,11 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
571
612
  }
572
613
  return {
573
614
  content: [{ type: "text", text: lines.join("\n").trimEnd() }],
574
- details: { subagents: snapshots },
615
+ details: {
616
+ subagents: snapshots,
617
+ ...(awaitOutcome ? { awaitOutcome } : {}),
618
+ ...(awaitOutcome === "interrupted" ? { interrupted: true } : {}),
619
+ },
575
620
  };
576
621
  }
577
622
 
@@ -778,8 +823,15 @@ function previewJobOutput(
778
823
  * countdown ticking is sacrificed by design; every real transition still changes
779
824
  * the signature.
780
825
  */
781
- export function subagentAwaitRenderedStateSignature(subagents: readonly SubagentSnapshot[]): string {
782
- return JSON.stringify(subagents.map(canonicalizeSnapshotForSignature));
826
+ export function subagentAwaitRenderedStateSignature(
827
+ subagents: readonly SubagentSnapshot[],
828
+ receipt?: Pick<SubagentToolDetails, "awaitOutcome" | "interrupted">,
829
+ ): string {
830
+ return JSON.stringify({
831
+ awaitOutcome: receipt?.awaitOutcome ?? null,
832
+ interrupted: receipt?.interrupted === true,
833
+ subagents: subagents.map(canonicalizeSnapshotForSignature),
834
+ });
783
835
  }
784
836
 
785
837
  function canonicalizeSnapshotForSignature(snapshot: SubagentSnapshot): unknown {