@gajae-code/coding-agent 0.12.4 → 0.12.6

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 (139) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +4 -1
  3. package/dist/types/async/job-manager.d.ts +9 -1
  4. package/dist/types/eval/backend.d.ts +2 -2
  5. package/dist/types/exec/bash-executor.d.ts +10 -8
  6. package/dist/types/extensibility/extensions/runner.d.ts +9 -7
  7. package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
  8. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  9. package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
  10. package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
  11. package/dist/types/gjc-runtime/team-store.d.ts +0 -4
  12. package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
  13. package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
  14. package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
  15. package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
  16. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
  17. package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +83 -0
  18. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +57 -4
  19. package/dist/types/internal-urls/registry-helpers.d.ts +18 -0
  20. package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
  21. package/dist/types/modes/components/plugin-settings.d.ts +2 -0
  22. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  23. package/dist/types/sdk/broker/broker.d.ts +18 -0
  24. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
  25. package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
  26. package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -2
  27. package/dist/types/sdk/bus/index.d.ts +4 -0
  28. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -3
  29. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
  30. package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
  31. package/dist/types/sdk/host/host.d.ts +23 -2
  32. package/dist/types/sdk/host/reverse-leases.d.ts +7 -4
  33. package/dist/types/session/agent-session.d.ts +5 -4
  34. package/dist/types/session/artifacts.d.ts +8 -5
  35. package/dist/types/session/attempt-record-store.d.ts +19 -0
  36. package/dist/types/session/client-bridge.d.ts +14 -0
  37. package/dist/types/session/internal/managed-session-storage.d.ts +7 -23
  38. package/dist/types/session/messages.d.ts +4 -1
  39. package/dist/types/session/session-manager.d.ts +7 -5
  40. package/dist/types/session/session-storage.d.ts +8 -0
  41. package/dist/types/session/streaming-output.d.ts +32 -0
  42. package/dist/types/task/executor.d.ts +1 -1
  43. package/dist/types/task/index.d.ts +2 -1
  44. package/dist/types/task/receipt.d.ts +3 -0
  45. package/dist/types/task/types.d.ts +194 -0
  46. package/dist/types/task/worktree.d.ts +13 -6
  47. package/dist/types/tools/bash.d.ts +1 -1
  48. package/dist/types/tools/fetch.d.ts +3 -0
  49. package/dist/types/tools/output-meta.d.ts +37 -5
  50. package/dist/types/tools/subagent.d.ts +2 -0
  51. package/dist/types/tools/tool-result.d.ts +1 -0
  52. package/dist/types/utils/artifact-id.d.ts +2 -0
  53. package/dist/types/utils/git.d.ts +1 -0
  54. package/package.json +8 -8
  55. package/scripts/capture-sticky-viewport-showcase.ts +131 -5
  56. package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
  57. package/scripts/verify-sticky-viewport-showcase.ts +620 -93
  58. package/src/async/job-manager.ts +13 -5
  59. package/src/config/model-profiles.ts +8 -0
  60. package/src/config/settings-schema.ts +17 -3
  61. package/src/cursor.ts +20 -13
  62. package/src/defaults/gjc/skills/ultragoal/SKILL.md +5 -2
  63. package/src/eval/backend.ts +2 -2
  64. package/src/exec/bash-executor.ts +425 -151
  65. package/src/extensibility/extensions/runner.ts +80 -6
  66. package/src/extensibility/extensions/wrapper.ts +22 -15
  67. package/src/extensibility/runtime-skill-discovery.ts +4 -6
  68. package/src/gjc-runtime/launch-tmux.ts +309 -293
  69. package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
  70. package/src/gjc-runtime/psmux-detect.ts +52 -6
  71. package/src/gjc-runtime/team-launch.ts +50 -2
  72. package/src/gjc-runtime/team-runtime.ts +716 -251
  73. package/src/gjc-runtime/team-store.ts +25 -7
  74. package/src/gjc-runtime/team-workers.ts +161 -1
  75. package/src/gjc-runtime/tmux-common.ts +16 -4
  76. package/src/gjc-runtime/tmux-gc.ts +37 -5
  77. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
  78. package/src/gjc-runtime/tmux-provider-context.ts +451 -0
  79. package/src/gjc-runtime/tmux-sessions.ts +409 -156
  80. package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
  81. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
  82. package/src/gjc-runtime/ultragoal-review-source.ts +378 -0
  83. package/src/gjc-runtime/ultragoal-runtime.ts +640 -159
  84. package/src/gjc-runtime/workflow-manifest.generated.json +40 -1
  85. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  86. package/src/internal-urls/agent-protocol.ts +98 -38
  87. package/src/internal-urls/artifact-protocol.ts +28 -17
  88. package/src/internal-urls/docs-index.generated.ts +6 -6
  89. package/src/internal-urls/local-protocol.ts +39 -18
  90. package/src/internal-urls/registry-helpers.ts +62 -0
  91. package/src/modes/acp/acp-agent.ts +51 -13
  92. package/src/modes/acp/acp-client-bridge.ts +154 -11
  93. package/src/modes/components/gjc-bundle-settings.ts +2 -0
  94. package/src/modes/components/plugin-settings.ts +5 -0
  95. package/src/modes/components/settings-selector.ts +4 -0
  96. package/src/modes/controllers/selector-controller.ts +1 -0
  97. package/src/prompts/tools/bash.md +2 -2
  98. package/src/prompts/tools/monitor.md +4 -4
  99. package/src/sdk/acp/adapter.ts +200 -9
  100. package/src/sdk/broker/broker.ts +93 -10
  101. package/src/sdk/broker/lifecycle-ledger.ts +227 -11
  102. package/src/sdk/broker/lifecycle.ts +692 -98
  103. package/src/sdk/bus/chat-daemon-control.ts +33 -5
  104. package/src/sdk/bus/index.ts +347 -14
  105. package/src/sdk/bus/telegram-daemon-contract.ts +7 -3
  106. package/src/sdk/bus/telegram-daemon.ts +283 -44
  107. package/src/sdk/bus/topic-registry.ts +133 -10
  108. package/src/sdk/host/host.ts +187 -9
  109. package/src/sdk/host/reverse-leases.ts +249 -42
  110. package/src/sdk/session.ts +8 -6
  111. package/src/session/agent-session.ts +930 -354
  112. package/src/session/artifacts.ts +211 -74
  113. package/src/session/attempt-record-store.ts +106 -0
  114. package/src/session/client-bridge.ts +15 -0
  115. package/src/session/internal/managed-session-scope.ts +332 -198
  116. package/src/session/internal/managed-session-storage.ts +162 -450
  117. package/src/session/messages.ts +32 -7
  118. package/src/session/session-manager.ts +31 -28
  119. package/src/session/session-storage.ts +190 -53
  120. package/src/session/streaming-output.ts +198 -40
  121. package/src/task/executor.ts +5 -2
  122. package/src/task/index.ts +277 -64
  123. package/src/task/provider-retry-status.ts +4 -1
  124. package/src/task/receipt.ts +26 -2
  125. package/src/task/render.ts +42 -1
  126. package/src/task/types.ts +52 -0
  127. package/src/task/worktree.ts +122 -43
  128. package/src/tools/bash.ts +818 -239
  129. package/src/tools/eval.ts +99 -4
  130. package/src/tools/fetch.ts +77 -15
  131. package/src/tools/find.ts +7 -3
  132. package/src/tools/monitor.ts +7 -1
  133. package/src/tools/output-meta.ts +706 -116
  134. package/src/tools/search.ts +9 -4
  135. package/src/tools/subagent-render.ts +5 -2
  136. package/src/tools/subagent.ts +8 -0
  137. package/src/tools/tool-result.ts +1 -0
  138. package/src/utils/artifact-id.ts +6 -0
  139. package/src/utils/git.ts +2 -0
package/src/tools/eval.ts CHANGED
@@ -11,8 +11,17 @@ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
11
11
  import { truncateToVisualLines } from "../modes/components/visual-truncate";
12
12
  import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
13
13
  import evalDescription from "../prompts/tools/eval.md" with { type: "text" };
14
- import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary, TailBuffer } from "../session/streaming-output";
14
+ import {
15
+ DEFAULT_ARTIFACT_MAX_BYTES,
16
+ DEFAULT_MAX_BYTES,
17
+ OutputSink,
18
+ type OutputSummary,
19
+ TailBuffer,
20
+ type TerminalArtifactPublisher,
21
+ truncateHeadBytes,
22
+ } from "../session/streaming-output";
15
23
  import { getTreeBranch, getTreeContinuePrefix, renderCodeCell } from "../tui";
24
+ import { isValidArtifactId } from "../utils/artifact-id";
16
25
  import { resolveEvalBackends, type ToolSession } from ".";
17
26
  import {
18
27
  formatStyledTruncationWarning,
@@ -163,6 +172,55 @@ interface ResolvedEvalCell {
163
172
  resolved: ResolvedBackend;
164
173
  }
165
174
 
175
+ const EVAL_ARTIFACT_ALLOCATION_WAIT_MS = 500;
176
+ const EVAL_ARTIFACT_ALLOCATION_LIMIT = 64;
177
+ const pendingEvalArtifactAllocations = new Set<Promise<unknown>>();
178
+
179
+ async function allocateEvalArtifact(
180
+ session: ToolSession,
181
+ signal?: AbortSignal,
182
+ ): Promise<{ path?: string; id?: string; diagnostic?: string }> {
183
+ if (!session.allocateOutputArtifact) return {};
184
+ if (signal?.aborted) throw new ToolAbortError();
185
+ if (pendingEvalArtifactAllocations.size >= EVAL_ARTIFACT_ALLOCATION_LIMIT) {
186
+ return { diagnostic: "pending Eval artifact allocation limit reached" };
187
+ }
188
+ const allocation = Promise.resolve().then(() => session.allocateOutputArtifact!("eval"));
189
+ pendingEvalArtifactAllocations.add(allocation);
190
+ void allocation.then(
191
+ () => pendingEvalArtifactAllocations.delete(allocation),
192
+ () => pendingEvalArtifactAllocations.delete(allocation),
193
+ );
194
+ const aborted = Promise.withResolvers<{ status: "aborted" }>();
195
+ const onAbort = () => aborted.resolve({ status: "aborted" });
196
+ signal?.addEventListener("abort", onAbort, { once: true });
197
+ try {
198
+ const outcome = await Promise.race([
199
+ allocation.then(
200
+ value => ({ status: "ok" as const, value }),
201
+ error => ({
202
+ status: "failed" as const,
203
+ diagnostic: error instanceof Error ? error.message : String(error),
204
+ }),
205
+ ),
206
+ Bun.sleep(EVAL_ARTIFACT_ALLOCATION_WAIT_MS).then(() => ({
207
+ status: "timeout" as const,
208
+ diagnostic: `did not settle within ${EVAL_ARTIFACT_ALLOCATION_WAIT_MS}ms`,
209
+ })),
210
+ aborted.promise,
211
+ ]);
212
+ if (outcome.status === "aborted") throw new ToolAbortError();
213
+ if (outcome.status !== "ok") return { diagnostic: outcome.diagnostic.slice(0, 512) };
214
+ const { path, id } = outcome.value ?? {};
215
+ if (!path || !isValidArtifactId(id)) {
216
+ return { diagnostic: "artifact storage is unavailable or returned an invalid artifact id" };
217
+ }
218
+ return { path, id };
219
+ } finally {
220
+ signal?.removeEventListener("abort", onAbort);
221
+ }
222
+ }
223
+
166
224
  function uniqueEvalLanguages(cells: ResolvedEvalCell[]): EvalLanguage[] {
167
225
  return [...new Set(cells.map(cell => cell.resolved.backend.id))];
168
226
  }
@@ -265,10 +323,12 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
265
323
  const sessionAbortController = new AbortController();
266
324
  let outputSink: OutputSink | undefined;
267
325
  let outputSummary: OutputSummary | undefined;
326
+ let artifactAllocationDiagnostic: string | undefined;
268
327
  let outputDumped = false;
269
328
  const finalizeOutput = async (): Promise<OutputSummary | undefined> => {
270
329
  if (outputDumped || !outputSink) return outputSummary;
271
330
  outputSummary = await outputSink.dump();
331
+ if (artifactAllocationDiagnostic) outputSummary.artifactFailureDiagnostic = artifactAllocationDiagnostic;
272
332
  outputDumped = true;
273
333
  return outputSummary;
274
334
  };
@@ -334,11 +394,34 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
334
394
 
335
395
  const sessionFile = session.getSessionFile?.() ?? undefined;
336
396
  const kernelOwnerId = session.getEvalKernelOwnerId?.() ?? undefined;
337
- const { path: artifactPath, id: artifactId } = (await session.allocateOutputArtifact?.("eval")) ?? {};
397
+ const artifactManager = session.getArtifactManager?.();
398
+ const managedArtifactPublisher: TerminalArtifactPublisher | undefined = artifactManager?.getManagedStore()
399
+ ? Object.assign(
400
+ async (content: string) => {
401
+ const contentBytes = Buffer.byteLength(content, "utf8");
402
+ const artifactId = await artifactManager.save(content, "eval");
403
+ if (contentBytes <= DEFAULT_ARTIFACT_MAX_BYTES) {
404
+ return { status: "published" as const, artifactId };
405
+ }
406
+ const retainedBytes = truncateHeadBytes(content, DEFAULT_ARTIFACT_MAX_BYTES).bytes;
407
+ return {
408
+ status: "published" as const,
409
+ artifactId,
410
+ omittedBytes: contentBytes - retainedBytes,
411
+ };
412
+ },
413
+ { owner: artifactManager },
414
+ )
415
+ : undefined;
416
+ const allocation = managedArtifactPublisher ? {} : await allocateEvalArtifact(session, signal);
417
+ artifactAllocationDiagnostic = allocation.diagnostic;
418
+ const { path: artifactPath, id: artifactId } = allocation;
419
+ if (signal?.aborted) throw new ToolAbortError();
338
420
  session.assertEvalExecutionAllowed?.();
339
421
  outputSink = new OutputSink({
340
422
  artifactPath,
341
423
  artifactId,
424
+ artifactPublisher: managedArtifactPublisher,
342
425
  headBytes: resolveOutputSinkHeadBytes(session.settings),
343
426
  maxColumns: resolveOutputMaxColumns(session.settings),
344
427
  onChunk: chunk => {
@@ -376,8 +459,6 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
376
459
  session,
377
460
  deadlineMs,
378
461
  reset: cell.reset,
379
- artifactPath,
380
- artifactId,
381
462
  onChunk: chunk => {
382
463
  outputSink!.push(chunk);
383
464
  },
@@ -545,6 +626,7 @@ async function summarizeFinal(
545
626
  const outputBytes = Buffer.byteLength(combinedOutput, "utf-8");
546
627
  const missingLines = Math.max(0, rawSummary.totalLines - rawSummary.outputLines);
547
628
  const missingBytes = Math.max(0, rawSummary.totalBytes - rawSummary.outputBytes);
629
+ const artifactRepresentationIncomplete = rawSummary.output !== combinedOutput || rawSummary.artifactId !== undefined;
548
630
  return {
549
631
  output: combinedOutput,
550
632
  truncated: rawSummary.truncated,
@@ -553,6 +635,19 @@ async function summarizeFinal(
553
635
  outputLines,
554
636
  outputBytes,
555
637
  artifactId: rawSummary.artifactId,
638
+ artifactVerified: rawSummary.artifactVerified,
639
+ artifactTruncatedBytes: rawSummary.artifactTruncatedBytes,
640
+ sourceTruncatedBytes: rawSummary.sourceTruncatedBytes,
641
+ sourceCaptureIncomplete:
642
+ rawSummary.sourceCaptureIncomplete === true || artifactRepresentationIncomplete || undefined,
643
+ headRange: rawSummary.headRange,
644
+ tailRange: rawSummary.tailRange,
645
+ elidedBytes: rawSummary.elidedBytes,
646
+ elidedLines: rawSummary.elidedLines,
647
+ columnDroppedBytes: rawSummary.columnDroppedBytes,
648
+ columnTruncatedLines: rawSummary.columnTruncatedLines,
649
+ columnMax: rawSummary.columnMax,
650
+ artifactFailureDiagnostic: rawSummary.artifactFailureDiagnostic,
556
651
  };
557
652
  }
558
653
 
@@ -10,9 +10,15 @@ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
10
10
  import { type Theme, theme } from "../modes/theme/theme";
11
11
  import type { ToolSession } from "../sdk";
12
12
  import type { AgentStorage } from "../session/agent-storage";
13
- import { DEFAULT_MAX_BYTES, type TruncationDirection, truncateContent } from "../session/streaming-output";
13
+ import {
14
+ DEFAULT_ARTIFACT_MAX_BYTES,
15
+ DEFAULT_MAX_BYTES,
16
+ type TruncationDirection,
17
+ truncateContent,
18
+ } from "../session/streaming-output";
14
19
  import { renderStatusLine } from "../tui";
15
20
  import { CachedOutputBlock } from "../tui/output-block";
21
+ import { isValidArtifactId } from "../utils/artifact-id";
16
22
  import { formatDimensionNote, resizeImage } from "../utils/image-resize";
17
23
  import { parseHtmlLazy } from "../utils/linkedom";
18
24
  import { INSANE_NOTES } from "../web/insane/bridge";
@@ -22,7 +28,7 @@ import type { RenderResult } from "../web/scrapers/types";
22
28
  import { finalizeOutput, loadPage, looksLikeHtml, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
23
29
  import { convertWithMarkit, fetchBinary } from "../web/scrapers/utils";
24
30
  import { applyListLimit } from "./list-limit";
25
- import { formatStyledArtifactReference, type OutputMeta } from "./output-meta";
31
+ import { formatArtifactEvidenceNotice, type OutputMeta } from "./output-meta";
26
32
  import { formatExpandHint, getDomain, replaceTabs } from "./render-utils";
27
33
  import { ToolAbortError, ToolError } from "./tool-errors";
28
34
  import { toolResult } from "./tool-result";
@@ -1144,6 +1150,9 @@ export interface ReadUrlToolDetails {
1144
1150
 
1145
1151
  interface ReadUrlCacheEntry {
1146
1152
  artifactId?: string;
1153
+ artifactVerified?: boolean;
1154
+ artifactFailureDiagnostic?: string;
1155
+ artifactStorageIncomplete?: boolean;
1147
1156
  details: ReadUrlToolDetails;
1148
1157
  image?: FetchImagePayload;
1149
1158
  /** Complete URL output, including its preamble and body. */
@@ -1180,7 +1189,7 @@ async function materializeReadUrlCacheEntry(
1180
1189
  entry: ReadUrlCacheEntry,
1181
1190
  ): Promise<ReadUrlCacheEntry | null> {
1182
1191
  if (entry.output.length > 0) return entry;
1183
- if (entry.artifactId) {
1192
+ if (isValidArtifactId(entry.artifactId) && entry.artifactVerified === true) {
1184
1193
  const artifactOutput = await readArtifactOutput(session, entry.artifactId);
1185
1194
  if (artifactOutput !== null) {
1186
1195
  // Artifacts retain their trust-boundary wrapper. Keep the raw preamble
@@ -1188,6 +1197,7 @@ async function materializeReadUrlCacheEntry(
1188
1197
  return {
1189
1198
  ...entry,
1190
1199
  output: artifactOutput,
1200
+ artifactVerified: true,
1191
1201
  ...(entry.wrappedPreambleChars !== undefined ? { wrappedPreambleChars: entry.wrappedPreambleChars } : {}),
1192
1202
  };
1193
1203
  }
@@ -1195,17 +1205,60 @@ async function materializeReadUrlCacheEntry(
1195
1205
  return null;
1196
1206
  }
1197
1207
 
1198
- async function persistReadUrlArtifact(session: ToolSession, output: string): Promise<string | undefined> {
1199
- const { path: artifactPath, id } = (await session.allocateOutputArtifact?.("read")) ?? {};
1200
- if (!artifactPath) return undefined;
1201
- await Bun.write(artifactPath, wrapUntrustedContent(output));
1202
- return id;
1208
+ const READ_URL_ARTIFACT_WAIT_MS = 500;
1209
+ const READ_URL_ARTIFACT_PENDING_LIMIT = 64;
1210
+ const pendingReadUrlArtifactWrites = new Set<Promise<unknown>>();
1211
+
1212
+ async function persistReadUrlArtifact(
1213
+ session: ToolSession,
1214
+ output: string,
1215
+ ): Promise<{ artifactId?: string; diagnostic?: string; storageIncomplete?: boolean }> {
1216
+ if (pendingReadUrlArtifactWrites.size >= READ_URL_ARTIFACT_PENDING_LIMIT) {
1217
+ return { diagnostic: "pending URL artifact persistence limit reached" };
1218
+ }
1219
+ const promise = Promise.resolve().then(async () => {
1220
+ const wrapped = wrapUntrustedContent(output);
1221
+ const artifactManager = session.getArtifactManager?.();
1222
+ if (artifactManager?.getManagedStore()) {
1223
+ const artifactId = await artifactManager.save(wrapped, "read");
1224
+ const contentBytes = Buffer.byteLength(wrapped, "utf8");
1225
+ return {
1226
+ artifactId,
1227
+ storageIncomplete: contentBytes > DEFAULT_ARTIFACT_MAX_BYTES || undefined,
1228
+ };
1229
+ }
1230
+ const { path: artifactPath, id } = (await session.allocateOutputArtifact?.("read")) ?? {};
1231
+ if (!artifactPath || !isValidArtifactId(id)) {
1232
+ return { diagnostic: "artifact storage is unavailable or returned an invalid artifact id" };
1233
+ }
1234
+ await Bun.write(artifactPath, wrapped);
1235
+ return { artifactId: id };
1236
+ });
1237
+ pendingReadUrlArtifactWrites.add(promise);
1238
+ void promise.then(
1239
+ () => pendingReadUrlArtifactWrites.delete(promise),
1240
+ () => pendingReadUrlArtifactWrites.delete(promise),
1241
+ );
1242
+ return Promise.race([
1243
+ promise.catch(error => ({
1244
+ diagnostic: error instanceof Error ? error.message.slice(0, 512) : String(error).slice(0, 512),
1245
+ })),
1246
+ Bun.sleep(READ_URL_ARTIFACT_WAIT_MS).then(() => ({
1247
+ diagnostic: `did not settle within ${READ_URL_ARTIFACT_WAIT_MS}ms`,
1248
+ })),
1249
+ ]);
1203
1250
  }
1204
1251
 
1205
1252
  async function ensureReadUrlCacheArtifact(session: ToolSession, entry: ReadUrlCacheEntry): Promise<ReadUrlCacheEntry> {
1206
- if (entry.artifactId) return entry;
1207
- const artifactId = await persistReadUrlArtifact(session, entry.output);
1208
- return artifactId ? { ...entry, artifactId } : entry;
1253
+ if (isValidArtifactId(entry.artifactId) && entry.artifactVerified === true) return entry;
1254
+ const persisted = await persistReadUrlArtifact(session, entry.output);
1255
+ return {
1256
+ ...entry,
1257
+ artifactId: persisted.artifactId,
1258
+ artifactVerified: persisted.artifactId !== undefined,
1259
+ artifactFailureDiagnostic: persisted.diagnostic,
1260
+ artifactStorageIncomplete: persisted.storageIncomplete,
1261
+ };
1209
1262
  }
1210
1263
 
1211
1264
  function cacheReadUrlEntry(session: ToolSession, requestedUrl: string, raw: boolean, entry: ReadUrlCacheEntry): void {
@@ -1230,10 +1283,13 @@ async function buildReadUrlCacheEntry(
1230
1283
  const storage = session.settings.getStorage();
1231
1284
  const result = await renderUrl(url, effectiveTimeout, raw, session.settings, signal, storage);
1232
1285
  const built = buildUrlReadOutput(result, result.content);
1233
- const artifactId = options?.ensureArtifact ? await persistReadUrlArtifact(session, built.output) : undefined;
1286
+ const persistedArtifact = options?.ensureArtifact ? await persistReadUrlArtifact(session, built.output) : {};
1234
1287
 
1235
1288
  return {
1236
- artifactId,
1289
+ artifactId: persistedArtifact.artifactId,
1290
+ artifactVerified: persistedArtifact.artifactId !== undefined,
1291
+ artifactFailureDiagnostic: persistedArtifact.diagnostic,
1292
+ artifactStorageIncomplete: persistedArtifact.storageIncomplete,
1237
1293
  details: {
1238
1294
  kind: "url",
1239
1295
  url: result.url,
@@ -1371,7 +1427,7 @@ export async function executeReadUrl(
1371
1427
  direction: effectiveDirection,
1372
1428
  });
1373
1429
  const needsArtifact = truncation.truncated;
1374
- if (needsArtifact && !cacheEntry.artifactId) {
1430
+ if (needsArtifact && (!isValidArtifactId(cacheEntry.artifactId) || cacheEntry.artifactVerified !== true)) {
1375
1431
  cacheEntry = await ensureReadUrlCacheArtifact(session, cacheEntry);
1376
1432
  cacheReadUrlEntry(session, params.path, params.raw ?? false, cacheEntry);
1377
1433
  }
@@ -1398,6 +1454,9 @@ export async function executeReadUrl(
1398
1454
  resultBuilder.truncation(truncation, {
1399
1455
  direction: effectiveDirection === "both" ? "middle" : effectiveDirection === "last" ? "tail" : "head",
1400
1456
  artifactId: cacheEntry.artifactId,
1457
+ artifactVerified: cacheEntry.artifactVerified === true || undefined,
1458
+ artifactFailureDiagnostic: cacheEntry.artifactFailureDiagnostic,
1459
+ sourceCaptureIncomplete: cacheEntry.details.truncated || cacheEntry.artifactStorageIncomplete || undefined,
1401
1460
  ...(effectiveDirection !== "head" ? { maxBytes: DEFAULT_MAX_BYTES } : {}),
1402
1461
  });
1403
1462
  } else if (cacheEntry.details.truncated) {
@@ -1501,7 +1560,10 @@ export function renderReadUrlResult(
1501
1560
  metadataLines.push(`${uiTheme.fg("muted", "Chars:")} ${charCount}`);
1502
1561
  if (truncated) {
1503
1562
  metadataLines.push(uiTheme.fg("warning", `${uiTheme.status.warning} Output truncated`));
1504
- if (truncation?.artifactId) metadataLines.push(formatStyledArtifactReference(truncation.artifactId, uiTheme));
1563
+ if (truncation?.artifactId && truncation.artifactVerified === true) {
1564
+ const artifactNotice = formatArtifactEvidenceNotice(truncation);
1565
+ if (artifactNotice) metadataLines.push(uiTheme.fg("warning", artifactNotice));
1566
+ }
1505
1567
  }
1506
1568
  if (hasNotes) {
1507
1569
  metadataLines.push(`${uiTheme.fg("muted", "Notes:")} ${details.notes.join("; ")}`);
package/src/tools/find.ts CHANGED
@@ -14,7 +14,7 @@ import { type TruncationResult, truncateHead } from "../session/streaming-output
14
14
  import { Ellipsis, fileHyperlink, renderFileList, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
15
15
  import type { ToolSession } from ".";
16
16
  import { applyListLimit } from "./list-limit";
17
- import { formatFullOutputReference, type OutputMeta } from "./output-meta";
17
+ import { formatArtifactEvidenceNotice, type OutputMeta } from "./output-meta";
18
18
  import {
19
19
  formatPathRelativeToCwd,
20
20
  hasGlobPathChars,
@@ -510,8 +510,12 @@ export const findToolRenderer = {
510
510
  if (details?.resultLimitReached) truncationReasons.push(`limit ${details.resultLimitReached} results`);
511
511
  if (limits?.resultLimit) truncationReasons.push(`limit ${limits.resultLimit.reached} results`);
512
512
  if (truncation) truncationReasons.push(truncation.truncatedBy === "lines" ? "line limit" : "size limit");
513
- const artifactId = truncation && "artifactId" in truncation ? truncation.artifactId : undefined;
514
- if (artifactId) truncationReasons.push(formatFullOutputReference(artifactId));
513
+ const artifactEvidence = details?.meta?.truncation
514
+ ? formatArtifactEvidenceNotice(details.meta.truncation)
515
+ : truncation && "artifactId" in truncation
516
+ ? formatArtifactEvidenceNotice(truncation as NonNullable<OutputMeta["truncation"]>)
517
+ : undefined;
518
+ if (artifactEvidence) truncationReasons.push(artifactEvidence);
515
519
 
516
520
  const extraLines: string[] = [];
517
521
  if (truncationReasons.length > 0) {
@@ -51,6 +51,7 @@ const MONITOR_LABEL_MAX = 120;
51
51
  const MAX_PENDING_MONITOR_NOTIFICATIONS = 3;
52
52
  const MONITOR_NOTIFICATION_LINE_MAX_BYTES = 16 * 1024;
53
53
  const MONITOR_NOTIFICATION_LINE_MAX_LINES = 20;
54
+ const PERSISTENT_MONITOR_DEBOUNCE_MS = 250;
54
55
 
55
56
  function buildMonitorLabel(params: MonitorParams): string {
56
57
  const base = `[monitor:${params.kind}] ${params.description}`;
@@ -124,6 +125,7 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
124
125
  const controller = { closed: false };
125
126
  let currentJobId = "";
126
127
  let sequence = 0;
128
+ let flushTimer: NodeJS.Timeout | undefined;
127
129
  let latestLine: string | undefined;
128
130
  let coalescedCount = 0;
129
131
  let flushScheduled = false;
@@ -136,6 +138,10 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
136
138
  (message.details as { taskId?: string } | undefined)?.taskId === currentJobId;
137
139
  const flushLatest = () => {
138
140
  if (!persistent || latestLine === undefined) return;
141
+ if (flushTimer) {
142
+ clearTimeout(flushTimer);
143
+ flushTimer = undefined;
144
+ }
139
145
  const line = latestLine;
140
146
  const count = coalescedCount;
141
147
  latestLine = undefined;
@@ -205,7 +211,7 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
205
211
  coalescedCount += flushScheduled ? 1 : 0;
206
212
  if (flushScheduled) return;
207
213
  flushScheduled = true;
208
- queueMicrotask(flushLatest);
214
+ flushTimer = setTimeout(flushLatest, PERSISTENT_MONITOR_DEBOUNCE_MS);
209
215
  };
210
216
  const monitorJob = await bash.startMonitorJob(
211
217
  { command: params.command, timeout: params.timeout },