@the-open-engine/zeroshot 6.39.1 → 6.40.0

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 (121) hide show
  1. package/README.md +45 -1
  2. package/cli/agent-provider-boundary.js +19 -0
  3. package/cli/agent-provider-boundary.ts +79 -0
  4. package/cli/export-stream.js +94 -0
  5. package/cli/export-stream.ts +118 -0
  6. package/cli/index.js +132 -61
  7. package/cli/json-export.js +2 -38
  8. package/cli/json-export.ts +2 -56
  9. package/cli/semantic-canonical.js +60 -0
  10. package/cli/semantic-canonical.ts +59 -0
  11. package/cli/semantic-contract.js +97 -0
  12. package/cli/semantic-contract.ts +153 -0
  13. package/cli/semantic-events.js +53 -0
  14. package/cli/semantic-events.ts +65 -0
  15. package/cli/semantic-evidence.js +63 -0
  16. package/cli/semantic-evidence.ts +65 -0
  17. package/cli/semantic-export.js +186 -0
  18. package/cli/semantic-export.ts +245 -0
  19. package/cli/semantic-json.js +69 -0
  20. package/cli/semantic-json.ts +69 -0
  21. package/cli/semantic-line-scanner.js +56 -0
  22. package/cli/semantic-line-scanner.ts +54 -0
  23. package/cli/semantic-parser.js +78 -0
  24. package/cli/semantic-parser.ts +95 -0
  25. package/cli/semantic-provider-line.js +91 -0
  26. package/cli/semantic-provider-line.ts +106 -0
  27. package/cli/trace-evidence.js +86 -0
  28. package/cli/trace-evidence.ts +115 -0
  29. package/cli/trace-export.js +120 -0
  30. package/cli/trace-export.ts +164 -0
  31. package/cli/trace-output-record.js +15 -0
  32. package/cli/trace-output-record.ts +18 -0
  33. package/cli/trace-output.js +98 -0
  34. package/cli/trace-output.ts +119 -0
  35. package/cluster-templates/conductor-bootstrap.json +1 -1
  36. package/lib/agent-cli-provider/adapters/claude.js +1 -1
  37. package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
  38. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  39. package/lib/agent-cli-provider/adapters/codex.js +26 -1
  40. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  41. package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -1
  42. package/lib/agent-cli-provider/adapters/omp.js +1 -1
  43. package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
  44. package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -1
  45. package/lib/agent-cli-provider/log-prefix.js +8 -6
  46. package/lib/agent-cli-provider/log-prefix.js.map +1 -1
  47. package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts +4 -0
  48. package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts.map +1 -0
  49. package/lib/agent-cli-provider/omp/sdk-environment-policy.js +37 -0
  50. package/lib/agent-cli-provider/omp/sdk-environment-policy.js.map +1 -0
  51. package/lib/agent-cli-provider/omp/sdk-process-private-runtime.d.ts.map +1 -1
  52. package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js +4 -15
  53. package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js.map +1 -1
  54. package/lib/agent-cli-provider/omp/sdk-process-result.d.ts +2 -0
  55. package/lib/agent-cli-provider/omp/sdk-process-result.d.ts.map +1 -1
  56. package/lib/agent-cli-provider/omp/sdk-process-result.js +2 -2
  57. package/lib/agent-cli-provider/omp/sdk-process-result.js.map +1 -1
  58. package/lib/agent-cli-provider/omp/sdk-process-runner.d.ts.map +1 -1
  59. package/lib/agent-cli-provider/omp/sdk-process-runner.js +6 -1
  60. package/lib/agent-cli-provider/omp/sdk-process-runner.js.map +1 -1
  61. package/lib/agent-cli-provider/provider-registry.d.ts +3 -1
  62. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  63. package/lib/agent-cli-provider/provider-registry.js +9 -2
  64. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  65. package/lib/agent-cli-provider/single-agent-runtime.d.ts +4 -1
  66. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  67. package/lib/agent-cli-provider/single-agent-runtime.js +18 -15
  68. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  69. package/lib/agent-cli-provider/types.d.ts +3 -0
  70. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  71. package/lib/agent-cli-provider/types.js.map +1 -1
  72. package/lib/cluster-worker/engine-adapter-common.js +18 -0
  73. package/lib/cluster-worker/engine-adapter.js +2 -1
  74. package/lib/git-remote-utils.js +22 -5
  75. package/lib/stream-json-parser.js +6 -5
  76. package/npm-shrinkwrap.json +2 -2
  77. package/package.json +7 -4
  78. package/scripts/omp/runtime-identities.js +31 -18
  79. package/scripts/omp/runtime.js +2 -0
  80. package/src/agent/agent-lifecycle.js +17 -1
  81. package/src/agent/agent-task-executor.js +36 -40
  82. package/src/agent/output-extraction-failures.js +73 -0
  83. package/src/agent/output-extraction-failures.ts +100 -0
  84. package/src/agent/output-extraction-json.js +5 -6
  85. package/src/agent/output-extraction-json.ts +4 -7
  86. package/src/agent/output-extraction-types.ts +5 -0
  87. package/src/agent/provider-terminal-failure.js +2 -2
  88. package/src/agent/provider-terminal-failure.ts +4 -2
  89. package/src/agent-cli-provider/adapters/claude.ts +1 -1
  90. package/src/agent-cli-provider/adapters/codex.ts +36 -1
  91. package/src/agent-cli-provider/adapters/omp.ts +2 -1
  92. package/src/agent-cli-provider/log-prefix.ts +12 -7
  93. package/src/agent-cli-provider/omp/sdk-environment-policy.ts +38 -0
  94. package/src/agent-cli-provider/omp/sdk-process-private-runtime.ts +4 -16
  95. package/src/agent-cli-provider/omp/sdk-process-result.ts +4 -2
  96. package/src/agent-cli-provider/omp/sdk-process-runner.ts +7 -1
  97. package/src/agent-cli-provider/provider-registry.ts +10 -2
  98. package/src/agent-cli-provider/single-agent-runtime.ts +24 -15
  99. package/src/agent-cli-provider/types.ts +3 -0
  100. package/src/agents/git-pusher-template.js +61 -71
  101. package/src/attach/socket-paths.js +1 -1
  102. package/src/attach/socket-paths.ts +1 -1
  103. package/src/claude-task-runner.js +9 -9
  104. package/src/foreground-benchmark-run.js +24 -12
  105. package/src/legacy-lib/git-remote-utils.ts +23 -5
  106. package/src/legacy-lib/stream-json-parser.ts +10 -5
  107. package/src/omp-session-verifier.js +8 -5
  108. package/src/orchestrator.js +42 -0
  109. package/src/preflight.js +31 -6
  110. package/src/providers/index.js +4 -22
  111. package/src/task-execution-context.js +22 -0
  112. package/src/task-log-line.d.ts +20 -0
  113. package/src/task-log-line.js +80 -0
  114. package/src/watcher-prompt-channel.js +1 -1
  115. package/src/worktree-claude-config.js +25 -0
  116. package/src/worktree-claude-config.ts +28 -0
  117. package/task-lib/commands/logs.js +5 -8
  118. package/task-lib/runner.js +57 -22
  119. package/task-lib/sdk-watcher-output.js +31 -0
  120. package/task-lib/sdk-watcher.js +4 -11
  121. package/task-lib/watcher-output-runtime.js +30 -16
@@ -7,6 +7,7 @@ import { createRequire } from 'module';
7
7
  // Import cluster's stream parser (shared between task and cluster)
8
8
  const require = createRequire(import.meta.url);
9
9
  const { parseChunk } = require('../../lib/stream-json-parser');
10
+ const { decodeTaskLogLine } = require('../../src/task-log-line');
10
11
 
11
12
  // Tool icons for different tool types
12
13
  const TOOL_ICONS = {
@@ -271,16 +272,12 @@ function handleMulti(event) {
271
272
  }
272
273
 
273
274
  // Parse a raw log line (may have timestamp prefix)
274
- function parseLogLine(line) {
275
- let trimmed = line.trim();
275
+ export function parseLogLine(line) {
276
+ const decoded = decodeTaskLogLine(line);
277
+ if (!decoded.providerOutput) return [];
278
+ const trimmed = decoded.content.trim();
276
279
  if (!trimmed) return [];
277
280
 
278
- // Strip timestamp prefix if present: [1234567890]{...} -> {...}
279
- const timestampMatch = trimmed.match(/^\[\d+\](.*)$/);
280
- if (timestampMatch) {
281
- trimmed = timestampMatch[1];
282
- }
283
-
284
281
  // Non-JSON lines output as-is
285
282
  if (!trimmed.startsWith('{')) {
286
283
  return [{ type: 'text', text: trimmed + '\n' }];
@@ -1,5 +1,5 @@
1
- import { fork } from 'child_process';
2
- import { join, dirname, resolve as resolvePath } from 'path';
1
+ import { execFileSync, spawn } from 'child_process';
2
+ import { isAbsolute, join, dirname, relative, resolve as resolvePath } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { mkdirSync } from 'fs';
5
5
  import { LOGS_DIR } from './config.js';
@@ -17,6 +17,8 @@ import {
17
17
  import { createRequire } from 'module';
18
18
 
19
19
  const require = createRequire(import.meta.url);
20
+ const { resolveTaskExecutionContext } = require('../src/task-execution-context.js');
21
+ export { resolveTaskExecutionContext };
20
22
  const {
21
23
  buildOmpPrompt,
22
24
  getProviderRegistryEntry,
@@ -43,8 +45,6 @@ const {
43
45
  partitionPathFor,
44
46
  createOmpSessionPartitionDirectory,
45
47
  } = require('../src/omp-session-partition');
46
- const TASK_EXECUTION_CONTEXT_ENV = 'ZEROSHOT_TASK_EXECUTION_CONTEXT';
47
- const TASK_EXECUTION_CONTEXTS = new Set(['host', 'detached', 'docker', 'benchmark']);
48
48
  export {
49
49
  isOwnedProcessTreeRunning,
50
50
  isProcessRunning,
@@ -401,8 +401,22 @@ function resolveOutputFormat(options) {
401
401
  }
402
402
 
403
403
  function resolveJsonSchema(options, outputFormat) {
404
- let jsonSchema = options.jsonSchema || null;
405
- if (jsonSchema && outputFormat !== 'json') {
404
+ let jsonSchema = options.jsonSchema ?? null;
405
+ if (typeof jsonSchema === 'string') {
406
+ try {
407
+ jsonSchema = JSON.parse(jsonSchema);
408
+ } catch (error) {
409
+ throw new Error(`--json-schema must be valid JSON: ${error.message}`);
410
+ }
411
+ }
412
+ if (
413
+ jsonSchema !== null &&
414
+ typeof jsonSchema !== 'boolean' &&
415
+ (typeof jsonSchema !== 'object' || Array.isArray(jsonSchema))
416
+ ) {
417
+ throw new Error('--json-schema must be a boolean or JSON Schema object.');
418
+ }
419
+ if (jsonSchema !== null && outputFormat !== 'json') {
406
420
  console.warn('Warning: --json-schema requires --output-format json, ignoring schema');
407
421
  jsonSchema = null;
408
422
  }
@@ -411,11 +425,13 @@ function resolveJsonSchema(options, outputFormat) {
411
425
 
412
426
  function buildProviderOptions(options, runtime, modelSelection) {
413
427
  const structuredOutputRecovery = options.structuredOutputRecovery === true;
428
+ const executionContext = resolveTaskExecutionContext();
414
429
  return {
415
430
  outputFormat: runtime.outputFormat,
416
431
  jsonSchema: runtime.jsonSchema,
417
432
  cwd: runtime.cwd,
418
- executionContext: resolveTaskExecutionContext(),
433
+ ...codexGitMetadataOption(options, runtime.cwd, executionContext),
434
+ executionContext,
419
435
  autoApprove: !structuredOutputRecovery,
420
436
  ...(modelSelection === undefined ? {} : { modelSpec: modelSelection.modelSpec }),
421
437
  ...(structuredOutputRecovery ? {} : mcpConfigOption(options)),
@@ -430,15 +446,35 @@ function buildProviderOptions(options, runtime, modelSelection) {
430
446
  };
431
447
  }
432
448
 
433
- export function resolveTaskExecutionContext(environment = process.env) {
434
- const context = environment[TASK_EXECUTION_CONTEXT_ENV];
435
- if (context === undefined) return 'detached';
436
- if (!TASK_EXECUTION_CONTEXTS.has(context)) {
437
- throw new Error(
438
- `${TASK_EXECUTION_CONTEXT_ENV} must be one of: ${[...TASK_EXECUTION_CONTEXTS].join(', ')}.`
439
- );
449
+ function codexGitMetadataOption(options, cwd, executionContext) {
450
+ const settings = loadSettings();
451
+ const providerName = normalizeProviderName(
452
+ options.provider || settings.defaultProvider || getDefaultProviderId()
453
+ );
454
+ if (providerName !== 'codex') return {};
455
+ if (executionContext === 'docker' || executionContext === 'benchmark') return {};
456
+ const directories = resolveCodexGitMetadataDirectories(cwd);
457
+ return directories.length === 0 ? {} : { additionalWritableDirectories: directories };
458
+ }
459
+
460
+ export function resolveCodexGitMetadataDirectories(cwd, runGit = execFileSync) {
461
+ try {
462
+ const raw = runGit('git', ['rev-parse', '--git-common-dir'], {
463
+ cwd,
464
+ encoding: 'utf8',
465
+ stdio: ['ignore', 'pipe', 'ignore'],
466
+ });
467
+ const commonDirectory = resolvePath(cwd, raw.trim());
468
+ const fromWorkspace = relative(resolvePath(cwd), commonDirectory);
469
+ const outsideWorkspace =
470
+ isAbsolute(fromWorkspace) ||
471
+ fromWorkspace === '..' ||
472
+ fromWorkspace.startsWith('../') ||
473
+ fromWorkspace.startsWith('..\\');
474
+ return outsideWorkspace ? [commonDirectory] : [];
475
+ } catch {
476
+ return [];
440
477
  }
441
- return context;
442
478
  }
443
479
 
444
480
  function claudeSettingsFileOption() {
@@ -655,14 +691,14 @@ function resolveWatcherScript(options, providerName, invoke) {
655
691
  function spawnWatcher({ watcherScript, id, cwd, logFile, finalArgs, watcherConfig, rpcPrompt }) {
656
692
  const sendsPrompt = typeof rpcPrompt === 'string';
657
693
  const watcherEnv = buildWatcherEnv();
658
- const watcher = fork(
659
- watcherScript,
660
- [id, cwd, logFile, JSON.stringify(finalArgs), JSON.stringify(watcherConfig)],
694
+ const watcher = spawn(
695
+ process.execPath,
696
+ [watcherScript, id, cwd, logFile, JSON.stringify(finalArgs), JSON.stringify(watcherConfig)],
661
697
  {
662
698
  detached: true,
663
- // A prompt-carrying lane needs a real pipe on fd 0 to receive it; every other lane keeps
664
- // stdio fully ignored. fork() requires an explicit 'ipc' slot once stdio is an array.
665
- stdio: sendsPrompt ? ['pipe', 'ignore', 'ignore', 'ipc'] : 'ignore',
699
+ // A prompt-carrying lane needs only a private stdin pipe; detached watchers have no parent
700
+ // messaging contract, so an IPC channel would add a second, racy wrapper-lifetime owner.
701
+ stdio: sendsPrompt ? ['pipe', 'ignore', 'ignore'] : 'ignore',
666
702
  windowsHide: true,
667
703
  env: watcherEnv,
668
704
  }
@@ -673,7 +709,6 @@ function spawnWatcher({ watcherScript, id, cwd, logFile, finalArgs, watcherConfi
673
709
  if (sendsPrompt) sendWatcherPrompt(watcher.stdin, rpcPrompt);
674
710
 
675
711
  watcher.unref();
676
- watcher.disconnect(); // Close IPC channel so parent can exit
677
712
  }
678
713
 
679
714
  export function buildWatcherEnv(sourceEnv = process.env) {
@@ -0,0 +1,31 @@
1
+ import { createRequire } from 'module';
2
+
3
+ const require = createRequire(import.meta.url);
4
+ const {
5
+ formatTaskLogMarker,
6
+ formatTaskLogStderr,
7
+ formatTaskLogStdout,
8
+ } = require('../src/task-log-line.js');
9
+
10
+ export function markSdkTaskLog(log, timestamp = Date.now()) {
11
+ log(formatTaskLogMarker(timestamp));
12
+ }
13
+
14
+ function normalizedTerminalEvent(terminal) {
15
+ if (terminal.type === 'result') return terminal.event;
16
+ return {
17
+ type: 'result',
18
+ success: false,
19
+ error: { ...terminal.frame.error },
20
+ };
21
+ }
22
+
23
+ export function logSdkTerminal(log, result, now = Date.now) {
24
+ for (const frame of result.progress) {
25
+ log(formatTaskLogStdout(now(), JSON.stringify(frame)));
26
+ }
27
+ log(formatTaskLogStdout(now(), JSON.stringify(normalizedTerminalEvent(result.terminal))));
28
+ for (const line of (result.diagnosticStderr || '').replace(/\r/g, '').split('\n')) {
29
+ if (line) log(formatTaskLogStderr(now(), line));
30
+ }
31
+ }
@@ -7,6 +7,7 @@
7
7
  import { appendFileSync } from 'fs';
8
8
  import { getTask, updateTask } from './store.js';
9
9
  import { createCommandSpecCleanup } from './command-spec-cleanup.js';
10
+ import { logSdkTerminal, markSdkTaskLog } from './sdk-watcher-output.js';
10
11
  import {
11
12
  completePendingWatcherCancellation,
12
13
  completeWatcherFailure,
@@ -72,16 +73,6 @@ function completionFor(result) {
72
73
  };
73
74
  }
74
75
 
75
- function logTerminal(result) {
76
- for (const frame of result.progress) log(`[${Date.now()}]${JSON.stringify(frame)}\n`);
77
- const terminal =
78
- result.terminal.type === 'result' ? result.terminal.event : result.terminal.frame;
79
- log(`[${Date.now()}]${JSON.stringify(terminal)}\n`);
80
- if (result.diagnosticStderr) {
81
- log(`[${Date.now()}][SDK-DIAGNOSTIC] ${result.diagnosticStderr}\n`);
82
- }
83
- }
84
-
85
76
  async function terminateOwnedProviderBoundary() {
86
77
  if (terminalResult) return terminalResult.cleanupAttestation.clean === true;
87
78
  if (!running) return true;
@@ -116,6 +107,8 @@ process.on('unhandledRejection', (reason) => {
116
107
  void crashWithError(reason, 'unhandledRejection');
117
108
  });
118
109
 
110
+ markSdkTaskLog(log);
111
+
119
112
  if (
120
113
  await completePendingWatcherCancellation({
121
114
  taskId,
@@ -139,7 +132,7 @@ try {
139
132
  if (getTask(taskId)?.cancelRequested) running.cancel();
140
133
 
141
134
  terminalResult = await running.result;
142
- logTerminal(terminalResult);
135
+ logSdkTerminal(log, terminalResult);
143
136
  await completeWatcherTask({
144
137
  taskId,
145
138
  completion: completionFor(terminalResult),
@@ -8,6 +8,14 @@ import {
8
8
  supportsProviderStructuredOutputRecovery,
9
9
  } from './provider-helper-runtime.js';
10
10
  import { terminateProcess } from './process-termination.js';
11
+ import { createRequire } from 'module';
12
+
13
+ const require = createRequire(import.meta.url);
14
+ const {
15
+ TASK_LOG_STDOUT_PREFIX,
16
+ formatTaskLogMarker,
17
+ formatTaskLogStdout,
18
+ } = require('../src/task-log-line.js');
11
19
 
12
20
  export const COMMAND_CLEANUP_UNINITIALIZED = Symbol('command-cleanup-uninitialized');
13
21
 
@@ -69,7 +77,7 @@ function createCodexOutputPassthrough({ log, captureProviderSession }) {
69
77
  let offset = 0;
70
78
  while (offset < text.length) {
71
79
  if (atLineStart) {
72
- logged.push(`[${timestamp}]`);
80
+ logged.push(`[${timestamp}]${TASK_LOG_STDOUT_PREFIX}`);
73
81
  atLineStart = false;
74
82
  }
75
83
  const newline = text.indexOf('\n', offset);
@@ -128,7 +136,7 @@ function createBoundedLinePassthrough({
128
136
  }
129
137
  if (byteLength > MAX_WATCHER_CONTROL_RECORD_BYTES) {
130
138
  if (deferRawUntilOverflow && log) {
131
- log(`[${lineTimestamp}]${inspectionParts.join('')}${part}`);
139
+ log(`[${lineTimestamp}]${linePrefix}${inspectionParts.join('')}${part}`);
132
140
  rawOverflowStreaming = true;
133
141
  }
134
142
  inspectable = false;
@@ -310,8 +318,10 @@ export function completeWatcherFailure({ error, source, ...completionOptions })
310
318
  * frames, prompt text, or control payloads.
311
319
  */
312
320
  export function createRpcWatcherOutputRuntime({ log }) {
321
+ log(formatTaskLogMarker(Date.now()));
322
+
313
323
  function logEvent(event) {
314
- log(`[${Date.now()}]${JSON.stringify(event)}\n`);
324
+ log(formatTaskLogStdout(Date.now(), JSON.stringify(event)));
315
325
  }
316
326
 
317
327
  function complete(result) {
@@ -350,6 +360,7 @@ export function createWatcherOutputRuntime({
350
360
  let finalResultJson = null;
351
361
  let streamingModeError = null;
352
362
  let fatalError = null;
363
+ log(formatTaskLogMarker(Date.now()));
353
364
  const captureProviderSession = providerSessionCapture?.captureLine || (() => {});
354
365
  const codexOutputPassthrough =
355
366
  providerName === 'codex' ? createCodexOutputPassthrough({ log, captureProviderSession }) : null;
@@ -358,6 +369,7 @@ export function createWatcherOutputRuntime({
358
369
  : createBoundedLinePassthrough({
359
370
  log,
360
371
  deferRawUntilOverflow: silentJsonMode,
372
+ linePrefix: TASK_LOG_STDOUT_PREFIX,
361
373
  handleLine: (line, timestamp, { oversized }) =>
362
374
  handleOutputLine(line, timestamp, {
363
375
  alreadyLogged: !silentJsonMode,
@@ -366,19 +378,20 @@ export function createWatcherOutputRuntime({
366
378
  });
367
379
  const stderrPassthrough = createBoundedLinePassthrough({
368
380
  log,
369
- // Pi reserves stdout for its JSON protocol and deliberately routes ordinary writes to
370
- // stderr. Keep those diagnostics in the task log with explicit provenance so followers can
371
- // exclude them from strict JSON validation.
372
- linePrefix: providerName === 'pi' ? '[ZEROSHOT][PROVIDER_STDERR] ' : '',
373
- handleLine: (line, timestamp) => maybeHandleFatalError(line, timestamp),
381
+ // Stderr must never be mistaken for provider protocol output. Fatal detection still receives
382
+ // the raw line through handleLine; only the persisted task-log representation is tagged.
383
+ linePrefix: '[ZEROSHOT][PROVIDER_STDERR] ',
384
+ handleLine: (line, timestamp) => maybeHandleFatalError(line, timestamp, true),
374
385
  });
375
386
 
376
- function maybeHandleFatalError(line, timestamp) {
387
+ function maybeHandleFatalError(line, timestamp, rawPersisted = false) {
377
388
  if (fatalError) return false;
378
389
  const detected = detectProviderFatalError(providerName, line);
379
390
  if (!detected) return false;
380
391
  fatalError = detected;
381
- if (silentJsonMode) log(`[${timestamp}]${line}\n`);
392
+ if (silentJsonMode && !rawPersisted) {
393
+ log(formatTaskLogStdout(timestamp, line));
394
+ }
382
395
  log(`[${timestamp}][ZEROSHOT][FATAL] ${detected}\n`);
383
396
  stopProvider(timestamp);
384
397
  return true;
@@ -413,12 +426,12 @@ export function createWatcherOutputRuntime({
413
426
  if (silentJsonMode && !line.trim()) return;
414
427
  // Pi reserves stdout for JSON lifecycle events. Error text inside an assistant message may be
415
428
  // followed by an automatic retry, so only Pi stderr can prove a pre-agent startup failure.
416
- if (providerName !== 'pi') maybeHandleFatalError(line, timestamp);
429
+ if (providerName !== 'pi') maybeHandleFatalError(line, timestamp, alreadyLogged);
417
430
  if (captureStreamingError(line, timestamp)) return;
418
431
  if (silentJsonMode) {
419
432
  maybeCaptureStructuredOutput(line);
420
433
  } else if (!alreadyLogged) {
421
- log(`[${timestamp}]${line}\n`);
434
+ log(formatTaskLogStdout(timestamp, line));
422
435
  }
423
436
  }
424
437
 
@@ -444,11 +457,10 @@ export function createWatcherOutputRuntime({
444
457
  if (silentJsonMode) {
445
458
  finalResultJson = recoveredLine;
446
459
  } else {
447
- log(`[${timestamp}]${recoveredLine}\n`);
460
+ log(formatTaskLogStdout(timestamp, recoveredLine));
448
461
  }
449
462
  } else if (streamingModeError.line) {
450
- const prefix = silentJsonMode ? '' : `[${streamingModeError.timestamp}]`;
451
- log(`${prefix}${streamingModeError.line}\n`);
463
+ log(formatTaskLogStdout(streamingModeError.timestamp, streamingModeError.line));
452
464
  }
453
465
  return recovered;
454
466
  }
@@ -463,7 +475,9 @@ export function createWatcherOutputRuntime({
463
475
  if (stderrBuffer !== null) stderrPassthrough.flush();
464
476
  const recovered = attemptRecovery(code, timestamp);
465
477
  const sessionIdentityError = providerSessionCapture?.getCompletionError() || null;
466
- if (silentJsonMode && finalResultJson) log(`${finalResultJson}\n`);
478
+ if (silentJsonMode && finalResultJson) {
479
+ log(formatTaskLogStdout(timestamp, finalResultJson));
480
+ }
467
481
  if (config.outputFormat !== 'json') {
468
482
  log(`\n${'='.repeat(50)}\n`);
469
483
  log(`Finished: ${new Date().toISOString()}\n`);