@gencode/uni 0.4.1 → 0.4.2

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 (60) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +9 -0
  3. package/dist/shared/src/agent-progress.d.ts +2 -0
  4. package/dist/shared/src/agent-progress.d.ts.map +1 -1
  5. package/dist/shared/src/agent-progress.js.map +1 -1
  6. package/dist/shared/src/artifacts.d.ts +46 -0
  7. package/dist/shared/src/artifacts.d.ts.map +1 -0
  8. package/dist/shared/src/artifacts.js +68 -0
  9. package/dist/shared/src/artifacts.js.map +1 -0
  10. package/dist/shared/src/index.d.ts +5 -1
  11. package/dist/shared/src/index.d.ts.map +1 -1
  12. package/dist/shared/src/index.js +2 -0
  13. package/dist/shared/src/index.js.map +1 -1
  14. package/dist/shared/src/json-value.d.ts +11 -0
  15. package/dist/shared/src/json-value.d.ts.map +1 -0
  16. package/dist/shared/src/json-value.js +67 -0
  17. package/dist/shared/src/json-value.js.map +1 -0
  18. package/dist/shared/src/run-events.d.ts +0 -14
  19. package/dist/shared/src/run-events.d.ts.map +1 -1
  20. package/dist/uni/src/mcp/artifact-server.d.ts +10 -0
  21. package/dist/uni/src/mcp/artifact-server.d.ts.map +1 -0
  22. package/dist/uni/src/mcp/artifact-server.js +186 -0
  23. package/dist/uni/src/mcp/artifact-server.js.map +1 -0
  24. package/dist/uni/src/mcp/pi-artifact-extension.d.ts +4 -0
  25. package/dist/uni/src/mcp/pi-artifact-extension.d.ts.map +1 -0
  26. package/dist/uni/src/mcp/pi-artifact-extension.js +52 -0
  27. package/dist/uni/src/mcp/pi-artifact-extension.js.map +1 -0
  28. package/dist/uni/src/providers/claude-code.d.ts.map +1 -1
  29. package/dist/uni/src/providers/claude-code.js +23 -2
  30. package/dist/uni/src/providers/claude-code.js.map +1 -1
  31. package/dist/uni/src/providers/codex.d.ts.map +1 -1
  32. package/dist/uni/src/providers/codex.js +4 -0
  33. package/dist/uni/src/providers/codex.js.map +1 -1
  34. package/dist/uni/src/providers/opencode.d.ts.map +1 -1
  35. package/dist/uni/src/providers/opencode.js +36 -0
  36. package/dist/uni/src/providers/opencode.js.map +1 -1
  37. package/dist/uni/src/providers/pi.d.ts.map +1 -1
  38. package/dist/uni/src/providers/pi.js +7 -0
  39. package/dist/uni/src/providers/pi.js.map +1 -1
  40. package/dist/uni/src/run.d.ts.map +1 -1
  41. package/dist/uni/src/run.js +87 -228
  42. package/dist/uni/src/run.js.map +1 -1
  43. package/dist/uni/src/session/artifact-store.d.ts +15 -0
  44. package/dist/uni/src/session/artifact-store.d.ts.map +1 -0
  45. package/dist/uni/src/session/artifact-store.js +177 -0
  46. package/dist/uni/src/session/artifact-store.js.map +1 -0
  47. package/dist/uni/src/session/change-operation-store.d.ts +66 -0
  48. package/dist/uni/src/session/change-operation-store.d.ts.map +1 -0
  49. package/dist/uni/src/session/change-operation-store.js +370 -0
  50. package/dist/uni/src/session/change-operation-store.js.map +1 -0
  51. package/dist/uni/src/session/session-store.d.ts +1 -2
  52. package/dist/uni/src/session/session-store.d.ts.map +1 -1
  53. package/dist/uni/src/session/session-store.js.map +1 -1
  54. package/dist/uni/src/transcript/turn-accumulator.d.ts +0 -2
  55. package/dist/uni/src/transcript/turn-accumulator.d.ts.map +1 -1
  56. package/dist/uni/src/transcript/turn-accumulator.js +0 -1
  57. package/dist/uni/src/transcript/turn-accumulator.js.map +1 -1
  58. package/dist/uni/src/types.d.ts +5 -1
  59. package/dist/uni/src/types.d.ts.map +1 -1
  60. package/package.json +2 -2
@@ -1,16 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { isAgentDiagnosticEvent } from "@gencode/shared";
4
- import { ArtifactAccumulator } from "./artifacts/accumulator.js";
5
- import { ARTIFACTS_START_TAG, parseAndStripArtifacts, } from "./artifacts/parser.js";
6
- import { ArtifactStreamFilter } from "./artifacts/stream-filter.js";
7
- import { inferArtifactsFromToolExecution } from "./artifacts/tool-inference.js";
8
4
  import { generateApiKey } from "./auth/api-key.js";
9
5
  import { normalizeProviderEvent } from "./events/normalizer.js";
10
6
  import { UniHookDispatcher } from "./hooks.js";
11
7
  import { providerStateDir, providerStateRoot } from "./providers/provider-state.js";
12
8
  import { createBuiltInProviderRegistry } from "./providers/registry.js";
13
- import { createArtifactOperationRecorder } from "./session/artifact-operation-store.js";
9
+ import { createChangeOperationRecorder } from "./session/change-operation-store.js";
10
+ import { clearArtifactPersistenceFailure, loadArtifactPersistenceFailure, } from "./mcp/artifact-server.js";
14
11
  import { createUniContextStore, } from "./session/context-store.js";
15
12
  import { resolveRunInput } from "./session/input.js";
16
13
  import { initializeUniLogger, uniLogger } from "./session/logger.js";
@@ -29,18 +26,39 @@ const ALIGNED_RUNTIME_AGENTS = new Set([
29
26
  "opencode",
30
27
  "pi",
31
28
  ]);
32
- const UNI_ARTIFACT_DECLARATION_PROMPT = [
33
- "## Artifact Declaration",
34
- "Before your final answer, audit the current turn for user-relevant persistent files and user-openable URLs.",
35
- "If this turn produced, modified, exported, downloaded, uploaded, shared, or published any such artifact, append exactly one JSON array block in this form:",
36
- "<aimax_artifacts>",
37
- '[{"kind":"file","file":"/absolute/or/workspace-relative/path","label":"short description"},{"kind":"url","url":"https://example.test/result","label":"short description"}]',
38
- "</aimax_artifacts>",
39
- "Audit successful write/edit/apply_patch operations, command outputs and redirections, plugin/skill results, online resources, and explicit child-agent artifacts.",
40
- "Do not declare deleted, failed, read-only, temporary/cache, or internal AIMax session files.",
41
- "If there are no artifacts, omit the block. Do not duplicate paths or URLs.",
42
- "The block is for the runtime parser. It must be the last content in the answer, with no text after </aimax_artifacts>.",
43
- ].join("\n");
29
+ function artifactToolPrompt(runtimeAgent) {
30
+ const toolName = runtimeAgent === "pi"
31
+ ? "record_artifacts"
32
+ : runtimeAgent === "opencode"
33
+ ? "aimax_record_artifacts"
34
+ : "mcp__aimax__record_artifacts";
35
+ return [
36
+ "## Mandatory Current-Run Finalization Checkpoint",
37
+ `The final-artifact tool exposed by this provider is \`${toolName}\`.`,
38
+ "This instruction applies to the CURRENT user request, including every follow-up, resume, retry, or new turn in an existing Session.",
39
+ "For THIS run, call the final-artifact tool exactly once after all generation, modification, export, publication, download, upload, and verification work is complete.",
40
+ "A final-artifact tool call visible in earlier conversation history is already consumed by that earlier run and never satisfies THIS run.",
41
+ "Before calling it, audit the entire run and submit every final user-deliverable file and URL in one call.",
42
+ "The artifact tool must be the final tool call. After it succeeds, immediately provide the final answer and call no other tool.",
43
+ "If this run produced no final artifacts, still call it with `{ \"artifacts\": [] }`.",
44
+ "Do not provide the final answer until THIS run's fresh final-artifact tool call has completed.",
45
+ "A final artifact must have been created, generated, exported, downloaded, published, or materially modified in this run specifically for the user. It must be part of the requested result, remain valid when the task ends, be something the user will directly open/use, and not be an intermediate output.",
46
+ "Record by default:",
47
+ "- Final PDF, DOCX, XLSX, PPTX, images, audio, video, reports, data exports, archives, installers, and release packages.",
48
+ "- Standalone scripts, examples, or projects explicitly requested as deliverables.",
49
+ "- Files downloaded into the workspace for delivery to the user.",
50
+ "- User-openable online documents, Wiki pages, published pages, share links, and download links.",
51
+ "- Modified source files only when the user explicitly requested those files themselves as deliverables.",
52
+ "- Child-agent deliverables only when the root run adopts them as part of the final result.",
53
+ "Do not record by default:",
54
+ "- Ordinary repository code changes, unit-test changes, configuration files, lockfiles, changesets, or routine engineering-documentation edits.",
55
+ "- Temporary files, caches, logs, test stdout/stderr, build intermediates, node_modules, or dependency caches.",
56
+ "- Files that were only read, or outputs that were deleted, failed, rolled back, replaced, or are no longer valid.",
57
+ "- AIMax internal state, session files, memory files, context snapshots, or tool-result spill files.",
58
+ "Decision test: without looking at the git diff, is this file or URL itself something the user asked to take away, open, or use? If not, do not record it.",
59
+ "Final reminder: an earlier call never counts. THIS run must make its own fresh call, including when resuming the same Session.",
60
+ ].join("\n");
61
+ }
44
62
  export const UNSUPPORTED_UNI_RUN_OPTION_MESSAGES = {
45
63
  agent: "--agent is not supported by @gencode/uni yet",
46
64
  skillsLoadPaths: "--skillsLoadPaths is not supported by @gencode/uni yet",
@@ -274,21 +292,22 @@ function promptText(input) {
274
292
  return input.message;
275
293
  return input.singleUserText ?? JSON.stringify(input.messages);
276
294
  }
277
- function providerInput(input, aligned) {
295
+ function providerInput(input, aligned, runtimeAgent) {
278
296
  if (!aligned)
279
297
  return input;
298
+ const prompt = artifactToolPrompt(runtimeAgent);
280
299
  if (input.kind === "text") {
281
- return { kind: "text", message: `${input.message}\n\n${UNI_ARTIFACT_DECLARATION_PROMPT}` };
300
+ return { kind: "text", message: `${input.message}\n\n${prompt}` };
282
301
  }
283
302
  const messages = [
284
303
  ...input.messages,
285
- { role: "user", content: UNI_ARTIFACT_DECLARATION_PROMPT },
304
+ { role: "user", content: prompt },
286
305
  ];
287
306
  return {
288
307
  kind: "messages",
289
308
  messages,
290
309
  ...(input.singleUserText
291
- ? { singleUserText: `${input.singleUserText}\n\n${UNI_ARTIFACT_DECLARATION_PROMPT}` }
310
+ ? { singleUserText: `${input.singleUserText}\n\n${prompt}` }
292
311
  : {}),
293
312
  };
294
313
  }
@@ -444,46 +463,6 @@ function firstString(record, keys) {
444
463
  }
445
464
  return undefined;
446
465
  }
447
- function patchSummary(input) {
448
- const added = [];
449
- const modified = [];
450
- const deleted = [];
451
- const changes = input.changes;
452
- if (Array.isArray(changes)) {
453
- for (const raw of changes) {
454
- if (!raw || typeof raw !== "object")
455
- continue;
456
- const change = raw;
457
- const file = firstString(change, ["path", "file", "file_path"]);
458
- if (!file)
459
- continue;
460
- const kind = firstString(change, ["kind", "type", "operation"])?.toLowerCase();
461
- if (kind === "add" || kind === "create")
462
- added.push(file);
463
- else if (kind === "delete" || kind === "remove")
464
- deleted.push(file);
465
- else
466
- modified.push(file);
467
- }
468
- }
469
- const patch = firstString(input, ["patch", "input"]);
470
- if (patch) {
471
- for (const line of patch.split(/\r?\n/)) {
472
- const match = line.match(/^\*\*\* (Add|Update|Delete) File:\s*(.+)$/);
473
- if (!match?.[2])
474
- continue;
475
- if (match[1] === "Add")
476
- added.push(match[2].trim());
477
- else if (match[1] === "Delete")
478
- deleted.push(match[2].trim());
479
- else
480
- modified.push(match[2].trim());
481
- }
482
- }
483
- if (added.length + modified.length + deleted.length === 0)
484
- return undefined;
485
- return { added, modified, deleted };
486
- }
487
466
  function structuredFileChangesPatch(input) {
488
467
  const changes = toRecord(input).changes;
489
468
  if (!Array.isArray(changes))
@@ -512,19 +491,19 @@ function structuredFileChangesPatch(input) {
512
491
  markers.push("*** End Patch");
513
492
  return markers.length > 2 ? markers.join("\n") : undefined;
514
493
  }
515
- function artifactAuditStartEvent(event) {
494
+ function changeAuditStartEvent(event) {
516
495
  if (canonicalToolName(event.name) !== "apply_patch")
517
496
  return event;
518
497
  const patch = structuredFileChangesPatch(event.input);
519
498
  return patch ? { ...event, name: "apply_patch", input: { patch } } : event;
520
499
  }
521
- function artifactAuditEndEvent(event) {
500
+ function changeAuditEndEvent(event) {
522
501
  if (canonicalToolName(event.name) !== "apply_patch")
523
502
  return event;
524
503
  const patch = structuredFileChangesPatch(event.input);
525
504
  return patch ? { ...event, name: "apply_patch", input: { patch } } : event;
526
505
  }
527
- function artifactAuditFileOperation(event) {
506
+ function changeAuditFileOperation(event) {
528
507
  const name = canonicalToolName(event.name);
529
508
  if (name !== "apply_patch")
530
509
  return { ...event, name };
@@ -535,94 +514,6 @@ function artifactAuditFileOperation(event) {
535
514
  ...(patch ? { input: { patch } } : {}),
536
515
  };
537
516
  }
538
- function inferenceResultForTool(params) {
539
- const input = toRecord(params.input);
540
- const name = canonicalToolName(params.name);
541
- const content = [{ type: "text", text: params.output }];
542
- if (name === "write_file") {
543
- return { content, details: { path: firstString(input, ["path", "file_path", "filePath"]) } };
544
- }
545
- if (name === "edit_file") {
546
- return {
547
- content,
548
- details: {
549
- path: firstString(input, ["path", "file_path", "filePath"]),
550
- occurrences: params.isError ? 0 : 1,
551
- },
552
- };
553
- }
554
- if (name === "apply_patch")
555
- return { content, details: { summary: patchSummary(input) } };
556
- if (name === "exec" || name === "bash" || name === "process") {
557
- return {
558
- content,
559
- details: {
560
- status: params.isError ? "failed" : "completed",
561
- cwd: firstString(input, ["cwd"]) ?? undefined,
562
- },
563
- };
564
- }
565
- return params.output;
566
- }
567
- function inferenceResult(event) {
568
- return inferenceResultForTool(event);
569
- }
570
- function isInternalArtifact(artifact, dataDir) {
571
- if (artifact.kind !== "file")
572
- return false;
573
- const internalRoot = path.resolve(dataDir, ".aimax");
574
- const candidate = path.resolve(artifact.file);
575
- return candidate === internalRoot || candidate.startsWith(`${internalRoot}${path.sep}`);
576
- }
577
- function parseArtifactArray(value, workspaceDir) {
578
- if (!Array.isArray(value))
579
- return [];
580
- const now = new Date().toISOString();
581
- return value.flatMap((raw) => {
582
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
583
- return [];
584
- const record = raw;
585
- const label = typeof record.label === "string" && record.label.trim()
586
- ? record.label.trim()
587
- : undefined;
588
- if (record.kind === "file" && typeof record.file === "string" && record.file.trim() && label) {
589
- const file = path.isAbsolute(record.file) ? path.normalize(record.file) : path.resolve(workspaceDir, record.file);
590
- return [{ kind: "file", file, label, timestamp: now }];
591
- }
592
- if (record.kind === "url" && typeof record.url === "string" && record.url.trim() && label) {
593
- try {
594
- const url = new URL(record.url).toString();
595
- return [{ kind: "url", url, label, timestamp: now }];
596
- }
597
- catch {
598
- return [];
599
- }
600
- }
601
- return [];
602
- });
603
- }
604
- function extractChildArtifacts(toolName, output, workspaceDir) {
605
- const normalized = toolName.trim().toLowerCase().replaceAll("-", "_");
606
- if (!normalized.includes("subagent") && normalized !== "task" && normalized !== "batch")
607
- return [];
608
- let parsed;
609
- try {
610
- parsed = JSON.parse(output);
611
- }
612
- catch {
613
- return [];
614
- }
615
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
616
- return [];
617
- const record = parsed;
618
- const direct = parseArtifactArray(record.artifacts, workspaceDir);
619
- if (direct.length > 0)
620
- return direct;
621
- const result = record.result;
622
- return result && typeof result === "object" && !Array.isArray(result)
623
- ? parseArtifactArray(result.artifacts, workspaceDir)
624
- : [];
625
- }
626
517
  export async function runUni(options) {
627
518
  const config = await resolveRunConfig(options);
628
519
  initializeUniLogger(config.dataDir);
@@ -691,13 +582,28 @@ export async function runUni(options) {
691
582
  runtimeAgent: config.runtimeAgent,
692
583
  messageId: config.messageId,
693
584
  });
694
- const streamFilter = aligned ? new ArtifactStreamFilter() : undefined;
695
- const artifactAccumulator = aligned
696
- ? new ArtifactAccumulator({ urlWhitelist: config.artifactsUrlWhitelist })
585
+ const workspaceDir = config.projectDir ?? path.join(config.dataDir, "workspace");
586
+ const artifactStoreOptions = aligned
587
+ ? {
588
+ dataDir: config.dataDir,
589
+ logicalSessionId: config.sessionId,
590
+ storeName: config.sessionStore,
591
+ workspaceDir,
592
+ projectDir: config.projectDir,
593
+ urlWhitelist: config.artifactsUrlWhitelist,
594
+ }
595
+ : undefined;
596
+ if (artifactStoreOptions) {
597
+ await clearArtifactPersistenceFailure(artifactStoreOptions);
598
+ }
599
+ const artifactTool = artifactStoreOptions
600
+ ? {
601
+ configBase64: Buffer.from(JSON.stringify(artifactStoreOptions), "utf8")
602
+ .toString("base64url"),
603
+ }
697
604
  : undefined;
698
- const workspaceDir = config.projectDir ?? config.dataDir;
699
605
  const operationRecorder = aligned && config.channel !== "CRON"
700
- ? createArtifactOperationRecorder({
606
+ ? createChangeOperationRecorder({
701
607
  dataDir: config.dataDir,
702
608
  logicalSessionId: config.sessionId,
703
609
  storeName: config.sessionStore,
@@ -713,6 +619,7 @@ export async function runUni(options) {
713
619
  let observedUsage;
714
620
  let observedModel;
715
621
  let observedError;
622
+ let artifactPersistenceFailure;
716
623
  let observedTitle;
717
624
  const emitProgress = async (event) => {
718
625
  progressEvents.push(event);
@@ -741,9 +648,7 @@ export async function runUni(options) {
741
648
  });
742
649
  const emitVisibleText = async (type, text) => {
743
650
  rawProviderText += text;
744
- const visible = streamFilter ? streamFilter.push(text) : text;
745
- if (!visible)
746
- return;
651
+ const visible = text;
747
652
  await flushCompletedTools();
748
653
  turn.addText(visible);
749
654
  const normalized = normalizeProviderEvent(config.sessionId, config.messageId, { type, text: visible });
@@ -803,8 +708,9 @@ export async function runUni(options) {
803
708
  flashModel: config.flashModel,
804
709
  timeoutMs: config.timeoutMs,
805
710
  runtimeConfig: config.runtimeConfig,
806
- input: providerInput(config.input, aligned),
711
+ input: providerInput(config.input, aligned, config.runtimeAgent),
807
712
  signal: abortController.signal,
713
+ artifactTool,
808
714
  }, async (providerEvent) => {
809
715
  let eventForProgress = providerEvent;
810
716
  if (providerEvent.type === "session_started") {
@@ -838,18 +744,9 @@ export async function runUni(options) {
838
744
  return;
839
745
  }
840
746
  if (providerEvent.type === "file_operation") {
841
- const auditEvent = artifactAuditFileOperation(providerEvent);
747
+ const auditEvent = changeAuditFileOperation(providerEvent);
842
748
  await Promise.resolve(operationRecorder?.recordObservedFileOperation(auditEvent))
843
749
  .catch(() => undefined);
844
- if (!providerEvent.isError && artifactAccumulator) {
845
- artifactAccumulator.addInferred(inferArtifactsFromToolExecution({
846
- toolName: canonicalToolName(providerEvent.name),
847
- input: toRecord(providerEvent.input),
848
- result: inferenceResultForTool(providerEvent),
849
- isError: false,
850
- workspaceDir,
851
- }).filter((artifact) => !isInternalArtifact(artifact, config.dataDir)));
852
- }
853
750
  return;
854
751
  }
855
752
  if (providerEvent.type === "tool_start") {
@@ -861,7 +758,7 @@ export async function runUni(options) {
861
758
  startedAt: Date.now(),
862
759
  });
863
760
  turn.startTool(providerEvent);
864
- await Promise.resolve(operationRecorder?.onToolStart(artifactAuditStartEvent(providerEvent)))
761
+ await Promise.resolve(operationRecorder?.onToolStart(changeAuditStartEvent(providerEvent)))
865
762
  .catch(() => undefined);
866
763
  await hookDispatcher.dispatchBeforeToolCall({
867
764
  toolCallId: providerEvent.toolCallId,
@@ -879,9 +776,14 @@ export async function runUni(options) {
879
776
  input: active.params,
880
777
  }
881
778
  : providerEvent;
779
+ const normalizedToolName = toolEndEvent.name.toLowerCase();
780
+ if (toolEndEvent.isError
781
+ && normalizedToolName.includes("record_artifacts")) {
782
+ artifactPersistenceFailure = toolEndEvent.output || "record_artifacts failed";
783
+ }
882
784
  eventForProgress = toolEndEvent;
883
- const paired = turn.endTool(toolEndEvent);
884
- await Promise.resolve(operationRecorder?.onToolEnd(artifactAuditEndEvent(toolEndEvent)))
785
+ turn.endTool(toolEndEvent);
786
+ await Promise.resolve(operationRecorder?.onToolEnd(changeAuditEndEvent(toolEndEvent)))
885
787
  .catch(() => undefined);
886
788
  if (config.runtimeAgent === "opencode"
887
789
  && !observedToolStartIds.has(toolEndEvent.toolCallId)
@@ -903,17 +805,6 @@ export async function runUni(options) {
903
805
  }).catch((error) => {
904
806
  uniLogger.warn("failed to persist observable tool result", { error: String(error) });
905
807
  });
906
- if (aligned && paired && !providerEvent.isError && artifactAccumulator) {
907
- artifactAccumulator.addChild(extractChildArtifacts(toolEndEvent.name, toolEndEvent.output, workspaceDir)
908
- .filter((artifact) => !isInternalArtifact(artifact, config.dataDir)));
909
- artifactAccumulator.addInferred(inferArtifactsFromToolExecution({
910
- toolName: canonicalToolName(toolEndEvent.name),
911
- input: params,
912
- result: inferenceResult(toolEndEvent),
913
- isError: toolEndEvent.isError,
914
- workspaceDir,
915
- }).filter((artifact) => !isInternalArtifact(artifact, config.dataDir)));
916
- }
917
808
  await hookDispatcher.dispatchAfterToolCall({
918
809
  toolCallId: providerEvent.toolCallId,
919
810
  toolName: toolEndEvent.name,
@@ -957,47 +848,12 @@ export async function runUni(options) {
957
848
  await bindProviderSession(providerResult.sessionId);
958
849
  observedProviderSessionIds.add(providerResult.sessionId);
959
850
  }
960
- const filterTail = streamFilter?.finish() ?? "";
961
- if (filterTail) {
962
- await flushCompletedTools();
963
- turn.addText(filterTail);
964
- for (const event of normalizeProviderEvent(config.sessionId, config.messageId, {
965
- type: "text_delta",
966
- text: filterTail,
967
- }))
968
- await emitProgress(event);
969
- }
970
851
  const hasExplicitTerminalText = providerResult.text !== undefined || observedDoneText !== undefined;
971
852
  const rawProviderFinalText = providerResult.text ?? observedDoneText ?? rawProviderText;
972
853
  const providerFinalText = aligned
973
854
  ? redactKnownProviderIds(rawProviderFinalText, observedProviderSessionIds)
974
855
  : rawProviderFinalText;
975
- const parsedArtifacts = aligned
976
- ? parseAndStripArtifacts(providerFinalText, { workspaceDir })
977
- : { cleanedText: providerFinalText, artifacts: [], warnings: [] };
978
- const streamedArtifactParse = aligned
979
- && rawProviderText !== providerFinalText
980
- && rawProviderText.includes(ARTIFACTS_START_TAG)
981
- ? parseAndStripArtifacts(rawProviderText, { workspaceDir })
982
- : undefined;
983
- artifactAccumulator?.addOwn(parsedArtifacts.artifacts.filter((artifact) => !isInternalArtifact(artifact, config.dataDir)));
984
- artifactAccumulator?.addOwn((streamedArtifactParse?.artifacts ?? [])
985
- .filter((artifact) => !isInternalArtifact(artifact, config.dataDir)));
986
- for (const warning of new Set([
987
- ...parsedArtifacts.warnings,
988
- ...(streamedArtifactParse?.warnings ?? []),
989
- ])) {
990
- await emitProgress({
991
- type: "diagnostic",
992
- sessionId: config.sessionId,
993
- messageId: config.messageId,
994
- level: "warn",
995
- scope: "runner",
996
- phase: "artifact_declaration",
997
- message: warning,
998
- });
999
- }
1000
- if (rawProviderText && state.finalText !== parsedArtifacts.cleanedText) {
856
+ if (rawProviderText && state.finalText !== providerFinalText) {
1001
857
  await emitProgress({
1002
858
  type: "diagnostic",
1003
859
  sessionId: config.sessionId,
@@ -1010,12 +866,17 @@ export async function runUni(options) {
1010
866
  }
1011
867
  const usage = providerResult.usage ?? observedUsage;
1012
868
  const rawError = providerResult.error ?? observedError;
1013
- const error = rawError
869
+ const providerError = rawError
1014
870
  ? redactKnownProviderIds(rawError, observedProviderSessionIds)
1015
871
  : undefined;
872
+ artifactPersistenceFailure ??= artifactStoreOptions
873
+ ? await loadArtifactPersistenceFailure(artifactStoreOptions)
874
+ : undefined;
875
+ const error = artifactPersistenceFailure
876
+ ? `Artifact persistence failed: ${artifactPersistenceFailure}`
877
+ : providerError;
1016
878
  const durationMs = Date.now() - startedAt;
1017
- const artifacts = artifactAccumulator?.dump() ?? [];
1018
- state.finalText = parsedArtifacts.cleanedText;
879
+ state.finalText = providerFinalText;
1019
880
  if (!usage && aligned) {
1020
881
  await emitProgress({
1021
882
  type: "diagnostic",
@@ -1028,12 +889,11 @@ export async function runUni(options) {
1028
889
  });
1029
890
  }
1030
891
  const finalEntries = turn.finalize({
1031
- fallbackContent: parsedArtifacts.cleanedText,
892
+ fallbackContent: providerFinalText,
1032
893
  forceFallbackContent: hasExplicitTerminalText,
1033
894
  usage,
1034
895
  durationMs,
1035
896
  error,
1036
- artifacts: artifacts.length > 0 ? artifacts : undefined,
1037
897
  });
1038
898
  await appendTurnEntries(finalEntries);
1039
899
  if (usage && contextStore) {
@@ -1067,11 +927,10 @@ export async function runUni(options) {
1067
927
  });
1068
928
  }
1069
929
  const resultPayload = {
1070
- text: parsedArtifacts.cleanedText,
930
+ text: providerFinalText,
1071
931
  usage: usage ?? { ...ZERO_USAGE },
1072
932
  durationMs,
1073
933
  ...(error ? { error } : {}),
1074
- ...(aligned && artifacts.length > 0 ? { artifacts } : {}),
1075
934
  };
1076
935
  if (error) {
1077
936
  uniLogger.error("uni run failed", {