@the-open-engine/zeroshot 6.36.0 → 6.38.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 (81) hide show
  1. package/cli/index.js +99 -29
  2. package/cli/lib/setup-scanner-worker.js +2 -9
  3. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -1
  4. package/lib/agent-cli-provider/adapters/claude-recovery.js +17 -3
  5. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -1
  6. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
  7. package/lib/agent-cli-provider/adapters/index.js +7 -0
  8. package/lib/agent-cli-provider/adapters/index.js.map +1 -1
  9. package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -1
  10. package/lib/agent-cli-provider/adapters/pi.js +86 -97
  11. package/lib/agent-cli-provider/adapters/pi.js.map +1 -1
  12. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -1
  13. package/lib/agent-cli-provider/contract-parse.js +5 -1
  14. package/lib/agent-cli-provider/contract-parse.js.map +1 -1
  15. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
  16. package/lib/agent-cli-provider/invoke-evidence.js +8 -1
  17. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
  18. package/lib/agent-cli-provider/pi/credentials.d.ts +2 -0
  19. package/lib/agent-cli-provider/pi/credentials.d.ts.map +1 -0
  20. package/lib/agent-cli-provider/pi/credentials.js +51 -0
  21. package/lib/agent-cli-provider/pi/credentials.js.map +1 -0
  22. package/lib/agent-cli-provider/pi/events.d.ts +5 -0
  23. package/lib/agent-cli-provider/pi/events.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/pi/events.js +124 -0
  25. package/lib/agent-cli-provider/pi/events.js.map +1 -0
  26. package/lib/agent-cli-provider/pi/protocol-state.d.ts +6 -0
  27. package/lib/agent-cli-provider/pi/protocol-state.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/pi/protocol-state.js +138 -0
  29. package/lib/agent-cli-provider/pi/protocol-state.js.map +1 -0
  30. package/lib/agent-cli-provider/pi/release.d.ts +4 -0
  31. package/lib/agent-cli-provider/pi/release.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/pi/release.js +7 -0
  33. package/lib/agent-cli-provider/pi/release.js.map +1 -0
  34. package/lib/agent-cli-provider/probe-availability.d.ts +5 -0
  35. package/lib/agent-cli-provider/probe-availability.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/probe-availability.js +11 -0
  37. package/lib/agent-cli-provider/probe-availability.js.map +1 -0
  38. package/lib/agent-cli-provider/provider-registry.d.ts +13 -9
  39. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  40. package/lib/agent-cli-provider/provider-registry.js +42 -8
  41. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  42. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  43. package/lib/agent-cli-provider/single-agent-runtime.js +15 -5
  44. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  45. package/lib/agent-cli-provider/types.d.ts +3 -0
  46. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  47. package/lib/agent-cli-provider/types.js.map +1 -1
  48. package/lib/cluster-worker/engine-adapter.js +9 -1
  49. package/lib/docker-config.js +6 -1
  50. package/lib/provider-credential-path.js +36 -0
  51. package/lib/start-cluster.js +8 -0
  52. package/npm-shrinkwrap.json +2 -2
  53. package/package.json +1 -1
  54. package/src/agent/agent-lifecycle.js +25 -76
  55. package/src/agent/agent-liveness-poll.js +93 -0
  56. package/src/agent/agent-task-executor.js +98 -23
  57. package/src/agent/output-extraction.js +115 -0
  58. package/src/agent/pi-terminal-lifecycle.js +242 -0
  59. package/src/agent/provider-control-plane.js +26 -0
  60. package/src/agent/provider-terminal-failure.js +49 -24
  61. package/src/agent-cli-provider/adapters/claude-recovery.ts +15 -2
  62. package/src/agent-cli-provider/adapters/index.ts +6 -0
  63. package/src/agent-cli-provider/adapters/pi.ts +105 -125
  64. package/src/agent-cli-provider/contract-parse.ts +8 -1
  65. package/src/agent-cli-provider/invoke-evidence.ts +11 -1
  66. package/src/agent-cli-provider/pi/credentials.ts +47 -0
  67. package/src/agent-cli-provider/pi/events.ts +148 -0
  68. package/src/agent-cli-provider/pi/protocol-state.ts +163 -0
  69. package/src/agent-cli-provider/pi/release.ts +4 -0
  70. package/src/agent-cli-provider/probe-availability.ts +19 -0
  71. package/src/agent-cli-provider/provider-registry.ts +48 -9
  72. package/src/agent-cli-provider/single-agent-runtime.ts +27 -6
  73. package/src/agent-cli-provider/types.ts +3 -0
  74. package/src/config-validator.js +1 -3
  75. package/src/foreground-benchmark-files.js +81 -0
  76. package/src/foreground-benchmark-result.js +233 -0
  77. package/src/foreground-benchmark-run.js +72 -0
  78. package/src/isolation-manager.js +43 -29
  79. package/src/orchestrator.js +33 -1
  80. package/task-lib/runner.js +7 -0
  81. package/task-lib/watcher-output-runtime.js +16 -5
@@ -0,0 +1,233 @@
1
+ const crypto = require('node:crypto');
2
+ const { VALID_PROVIDERS } = require('../lib/provider-names');
3
+
4
+ const RESULT_SCHEMA = 'zeroshot-benchmark-result/v1';
5
+ const TELEMETRY_SCHEMA = 'zeroshot-benchmark-telemetry/v1';
6
+ const EMPTY_DIAGNOSTIC = Object.freeze({
7
+ byteLength: 0,
8
+ sha256: crypto.createHash('sha256').update('').digest('hex'),
9
+ });
10
+ const TASK_FAILURE_REASONS = new Set(['max_iterations', 'structured_output_invalid']);
11
+ const PROVIDER_CODES = new Set(['crash', 'refusal']);
12
+ const PROVIDERS = new Set(VALID_PROVIDERS);
13
+ const PROVIDER_EVENTS = new Set(['terminal_error', 'turn.failed']);
14
+ const PROVIDER_CATEGORIES = new Set([
15
+ 'authentication',
16
+ 'permanent',
17
+ 'quota',
18
+ 'transient',
19
+ 'unknown',
20
+ ]);
21
+ const PROVIDER_KINDS = new Set([
22
+ 'permanent-pattern',
23
+ 'rate-limit',
24
+ 'retryable-pattern',
25
+ 'status-permanent',
26
+ 'status-retryable',
27
+ 'code-retryable',
28
+ 'unknown-retryable',
29
+ ]);
30
+ const TOKEN_FIELDS = [
31
+ 'inputTokens',
32
+ 'outputTokens',
33
+ 'cacheReadInputTokens',
34
+ 'cacheCreationInputTokens',
35
+ 'totalCostUsd',
36
+ 'count',
37
+ ];
38
+
39
+ function requireObject(value, label) {
40
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
41
+ throw new Error(`${label} must be an object`);
42
+ }
43
+ return value;
44
+ }
45
+
46
+ function requireClosedText(value, allowed, label) {
47
+ if (typeof value !== 'string' || !allowed.has(value)) {
48
+ throw new Error(`${label} is outside the closed result contract`);
49
+ }
50
+ return value;
51
+ }
52
+
53
+ function requireDiagnostic(value) {
54
+ if (
55
+ value &&
56
+ Number.isSafeInteger(value.byteLength) &&
57
+ value.byteLength >= 0 &&
58
+ typeof value.sha256 === 'string' &&
59
+ /^[a-f0-9]{64}$/.test(value.sha256)
60
+ ) {
61
+ return { byteLength: value.byteLength, sha256: value.sha256 };
62
+ }
63
+ throw new Error('provider diagnostic is outside the closed result contract');
64
+ }
65
+
66
+ function terminalData(message) {
67
+ return requireObject(requireObject(message.content, 'terminal content').data, 'terminal data');
68
+ }
69
+
70
+ function isExplicitTaskFailure(message, data) {
71
+ if (message.sender === 'orchestrator' || !TASK_FAILURE_REASONS.has(data.reason)) return false;
72
+ if (data.reason === 'max_iterations') return message.receiver === 'system';
73
+ return message.receiver === 'broadcast' && data.code === 'STRUCTURED_OUTPUT_INVALID';
74
+ }
75
+
76
+ function classifyTerminal(message) {
77
+ if (message.topic === 'CLUSTER_COMPLETE') {
78
+ return {
79
+ outcome: 'completed',
80
+ terminalOwner: 'task',
81
+ code: 'ok',
82
+ kind: 'workflow_complete',
83
+ retryable: false,
84
+ diagnostic: { ...EMPTY_DIAGNOSTIC },
85
+ provider: null,
86
+ event: null,
87
+ category: null,
88
+ };
89
+ }
90
+
91
+ if (message.topic !== 'CLUSTER_FAILED') {
92
+ throw new Error(`unsupported terminal topic: ${message.topic}`);
93
+ }
94
+ const data = terminalData(message);
95
+ if (isExplicitTaskFailure(message, data)) {
96
+ return {
97
+ outcome: 'task_failure',
98
+ terminalOwner: 'task',
99
+ code: data.reason,
100
+ kind: 'declared_failure',
101
+ retryable: false,
102
+ diagnostic: { ...EMPTY_DIAGNOSTIC },
103
+ provider: null,
104
+ event: null,
105
+ category: null,
106
+ };
107
+ }
108
+ if (data.reason === 'provider_execution_failed') {
109
+ if (typeof data.retryable !== 'boolean') {
110
+ throw new Error('provider retryable must be boolean');
111
+ }
112
+ return {
113
+ outcome: 'provider_failure',
114
+ terminalOwner: 'provider',
115
+ code: requireClosedText(data.code, PROVIDER_CODES, 'provider code'),
116
+ kind: requireClosedText(data.kind, PROVIDER_KINDS, 'provider kind'),
117
+ retryable: data.retryable,
118
+ diagnostic: requireDiagnostic(data.diagnostic),
119
+ provider: requireClosedText(data.provider, PROVIDERS, 'provider'),
120
+ event: requireClosedText(data.event, PROVIDER_EVENTS, 'provider event'),
121
+ category: requireClosedText(data.category, PROVIDER_CATEGORIES, 'provider category'),
122
+ };
123
+ }
124
+ return {
125
+ outcome: 'engine_failure',
126
+ terminalOwner: 'engine',
127
+ code: 'engine_failed',
128
+ kind: 'declared_failure',
129
+ retryable: false,
130
+ diagnostic: { ...EMPTY_DIAGNOSTIC },
131
+ provider: null,
132
+ event: null,
133
+ category: null,
134
+ };
135
+ }
136
+
137
+ function validateStoppedAgents(agents) {
138
+ if (!Array.isArray(agents)) throw new Error('agents must be an array');
139
+ for (const agent of agents) {
140
+ const state = requireObject(agent, 'agent state');
141
+ if (state.pid !== null && state.pid !== undefined) {
142
+ throw new Error(`agent ${String(state.id)} still has a live process identity`);
143
+ }
144
+ }
145
+ }
146
+
147
+ function validateRunId(runId) {
148
+ const parts = typeof runId === 'string' ? runId.split('-') : [];
149
+ const valid =
150
+ parts.length >= 2 &&
151
+ parts.every(
152
+ (part) => part.length > 0 && [...part].every((character) => /[a-z0-9]/.test(character))
153
+ );
154
+ if (!valid) {
155
+ throw new Error('runId must be a canonical cluster id');
156
+ }
157
+ }
158
+
159
+ function buildBenchmarkResult({ runId, terminalMessages, agents }) {
160
+ validateRunId(runId);
161
+ if (!Array.isArray(terminalMessages) || terminalMessages.length !== 1) {
162
+ throw new Error('foreground run must have exactly one terminal event');
163
+ }
164
+ validateStoppedAgents(agents);
165
+ return {
166
+ schema: RESULT_SCHEMA,
167
+ runId,
168
+ ...classifyTerminal(terminalMessages[0]),
169
+ };
170
+ }
171
+
172
+ function buildCancelledResult({ runId, agents }) {
173
+ validateRunId(runId);
174
+ validateStoppedAgents(agents);
175
+ return {
176
+ schema: RESULT_SCHEMA,
177
+ runId,
178
+ outcome: 'cancelled',
179
+ terminalOwner: 'controller',
180
+ code: 'cancelled',
181
+ kind: 'controlled_cancellation',
182
+ retryable: false,
183
+ diagnostic: { ...EMPTY_DIAGNOSTIC },
184
+ provider: null,
185
+ event: null,
186
+ category: null,
187
+ };
188
+ }
189
+
190
+ function normalizeTokenEntry(value, label) {
191
+ const source = requireObject(value, label);
192
+ const entry = {};
193
+ for (const field of TOKEN_FIELDS) {
194
+ const amount = source[field] ?? 0;
195
+ if (typeof amount !== 'number' || !Number.isFinite(amount) || amount < 0) {
196
+ throw new Error(`${label}.${field} must be a finite non-negative number`);
197
+ }
198
+ entry[field] = amount;
199
+ }
200
+ return entry;
201
+ }
202
+
203
+ function buildTelemetry(runId, snapshot) {
204
+ validateRunId(runId);
205
+ const source = requireObject(snapshot, 'telemetry snapshot');
206
+ if (!Number.isSafeInteger(source.messageCount) || source.messageCount < 0) {
207
+ throw new Error('telemetry messageCount must be a non-negative safe integer');
208
+ }
209
+ const roles = requireObject(source.tokensByRole, 'tokensByRole');
210
+ const names = Object.keys(roles).sort();
211
+ if (names.length > 64) throw new Error('telemetry role count exceeds 64');
212
+ const tokensByRole = {};
213
+ for (const name of names) {
214
+ const validRoleName =
215
+ name === '_total' ||
216
+ (name.length <= 128 &&
217
+ /^[a-zA-Z0-9]$/.test(name[0] || '') &&
218
+ [...name].every((character) => /[a-zA-Z0-9_.-]/.test(character)));
219
+ if (!validRoleName) {
220
+ throw new Error('telemetry role name is invalid');
221
+ }
222
+ tokensByRole[name] = normalizeTokenEntry(roles[name], `tokensByRole.${name}`);
223
+ }
224
+ return { schema: TELEMETRY_SCHEMA, runId, messageCount: source.messageCount, tokensByRole };
225
+ }
226
+
227
+ module.exports = {
228
+ RESULT_SCHEMA,
229
+ TELEMETRY_SCHEMA,
230
+ buildBenchmarkResult,
231
+ buildCancelledResult,
232
+ buildTelemetry,
233
+ };
@@ -0,0 +1,72 @@
1
+ const { buildBenchmarkResult, buildCancelledResult } = require('./foreground-benchmark-result');
2
+ const { writeBenchmarkResultBundle } = require('./foreground-benchmark-files');
3
+
4
+ const TERMINAL_TOPICS = ['CLUSTER_COMPLETE', 'CLUSTER_FAILED'];
5
+ const VERIFIER_ELIGIBLE = new Set(['completed', 'task_failure']);
6
+ const EXIT_CODES = Object.freeze({
7
+ provider_failure: 20,
8
+ engine_failure: 21,
9
+ cancelled: 22,
10
+ });
11
+
12
+ function isForegroundStatusSettled(status) {
13
+ return (
14
+ status &&
15
+ ['stopped', 'killed'].includes(status.state) &&
16
+ status.isZombie === false &&
17
+ Array.isArray(status.agents) &&
18
+ status.agents.every((agent) => agent && (agent.pid === null || agent.pid === undefined))
19
+ );
20
+ }
21
+
22
+ function terminalMessages(cluster, clusterId) {
23
+ const messages = TERMINAL_TOPICS.flatMap((topic) =>
24
+ cluster.messageBus.query({ cluster_id: clusterId, topic })
25
+ );
26
+ return messages.sort((left, right) => {
27
+ const a = BigInt(left.sequence);
28
+ const b = BigInt(right.sequence);
29
+ if (a < b) return -1;
30
+ if (a > b) return 1;
31
+ return 0;
32
+ });
33
+ }
34
+
35
+ function requireSettledStatus(orchestrator, clusterId) {
36
+ const status = orchestrator.getStatus(clusterId);
37
+ if (!isForegroundStatusSettled(status)) {
38
+ throw new Error(`foreground cluster is not settled: ${String(status?.state || 'unavailable')}`);
39
+ }
40
+ return status;
41
+ }
42
+
43
+ function writeForegroundResult({ orchestrator, cluster, clusterId, resultPath, cancelled }) {
44
+ const status = requireSettledStatus(orchestrator, clusterId);
45
+ const terminals = terminalMessages(cluster, clusterId);
46
+ let result;
47
+ if (cancelled && terminals.length === 0) {
48
+ result = buildCancelledResult({ runId: clusterId, agents: status.agents });
49
+ } else {
50
+ result = buildBenchmarkResult({
51
+ runId: clusterId,
52
+ terminalMessages: terminals,
53
+ agents: status.agents,
54
+ });
55
+ }
56
+ const snapshot = cluster.messageBus.readSnapshot(clusterId);
57
+ return writeBenchmarkResultBundle(resultPath, result, snapshot);
58
+ }
59
+
60
+ function exitCodeForResult(result) {
61
+ if (VERIFIER_ELIGIBLE.has(result.outcome)) return 0;
62
+ const exitCode = EXIT_CODES[result.outcome];
63
+ if (exitCode === undefined) throw new Error(`unsupported result outcome: ${result.outcome}`);
64
+ return exitCode;
65
+ }
66
+
67
+ module.exports = {
68
+ exitCodeForResult,
69
+ isForegroundStatusSettled,
70
+ terminalMessages,
71
+ writeForegroundResult,
72
+ };
@@ -30,6 +30,7 @@ const {
30
30
  isUsableEnvValue,
31
31
  validateProviderEnvAuth,
32
32
  } = require('../lib/docker-config');
33
+ const { resolveProviderCredentialPaths } = require('../lib/provider-credential-path');
33
34
  const { getProvider } = require('./providers');
34
35
  const { readRepoSettings } = require('../lib/repo-settings');
35
36
  const { provisionClaudeCredentials } = require('./claude-credentials');
@@ -151,6 +152,34 @@ function expandHomePath(value) {
151
152
  return value.replace(/^~(?=\/|$)/, os.homedir());
152
153
  }
153
154
 
155
+ function appendCredentialMount(plan, mount, explicitContainerPaths, claudeContainerPath) {
156
+ if (mount.container === claudeContainerPath) {
157
+ console.warn(
158
+ `[IsolationManager] Skipping mount for ${mount.host} -> ${mount.container} ` +
159
+ '(Claude config is managed by zeroshot).'
160
+ );
161
+ return;
162
+ }
163
+
164
+ const hostPath = expandHomePath(mount.host);
165
+ let stat;
166
+ try {
167
+ stat = fs.statSync(hostPath);
168
+ } catch {
169
+ return;
170
+ }
171
+ if (hostPath.endsWith('config') && !stat.isFile()) return;
172
+
173
+ plan.mountedHosts.push(hostPath);
174
+ const mountSpec = mount.readonly
175
+ ? `${hostPath}:${mount.container}:ro`
176
+ : `${hostPath}:${mount.container}`;
177
+ plan.args.push('-v', mountSpec);
178
+ if (explicitContainerPaths.has(mount.container)) {
179
+ plan.explicitMountContainerPaths.push(mount.container);
180
+ }
181
+ }
182
+
154
183
  function pathContains(base, target) {
155
184
  const resolvedBase = path.resolve(base);
156
185
  const resolvedTarget = path.resolve(target);
@@ -325,7 +354,6 @@ class IsolationManager {
325
354
  this._removeContainerByName(containerName);
326
355
 
327
356
  workDir = await this._prepareIsolatedWorkspace(clusterId, workDir, reuseExisting);
328
-
329
357
  // The cluster config dir carries Claude credentials (via provisionClaudeCredentials) and the
330
358
  // Claude-specific AskUserQuestion-blocking hook (~/.claude/settings.json PreToolUse), which
331
359
  // only the `claude` CLI reads. Creating and mounting it for every provider — regardless of
@@ -534,33 +562,7 @@ class IsolationManager {
534
562
  const claudeContainerPath = path.posix.join(containerHome, '.claude');
535
563
 
536
564
  for (const mount of mounts) {
537
- if (mount.container === claudeContainerPath) {
538
- console.warn(
539
- `[IsolationManager] Skipping mount for ${mount.host} -> ${mount.container} ` +
540
- '(Claude config is managed by zeroshot).'
541
- );
542
- continue;
543
- }
544
-
545
- const hostPath = expandHomePath(mount.host);
546
-
547
- try {
548
- const stat = fs.statSync(hostPath);
549
- if (hostPath.endsWith('config') && !stat.isFile()) {
550
- continue;
551
- }
552
- } catch {
553
- continue;
554
- }
555
-
556
- const mountSpec = mount.readonly
557
- ? `${hostPath}:${mount.container}:ro`
558
- : `${hostPath}:${mount.container}`;
559
- plan.args.push('-v', mountSpec);
560
- plan.mountedHosts.push(hostPath);
561
- if (explicitContainerPaths.has(mount.container)) {
562
- plan.explicitMountContainerPaths.push(mount.container);
563
- }
565
+ appendCredentialMount(plan, mount, explicitContainerPaths, claudeContainerPath);
564
566
  }
565
567
 
566
568
  const { envToPass, explicitNames } = this._collectDockerEnvVars(
@@ -568,6 +570,16 @@ class IsolationManager {
568
570
  userMountConfig,
569
571
  settings
570
572
  );
573
+ const hostOnlyEnvs = new Set(
574
+ mountConfig
575
+ .filter((preset) => typeof preset === 'string')
576
+ .map((preset) => MOUNT_PRESETS[preset]?.hostEnv)
577
+ .filter((name) => typeof name === 'string')
578
+ );
579
+ for (const hostOnlyEnv of hostOnlyEnvs) {
580
+ delete envToPass[hostOnlyEnv];
581
+ explicitNames.delete(hostOnlyEnv);
582
+ }
571
583
  for (const [key, value] of Object.entries(envToPass)) {
572
584
  plan.args.push('-e', `${key}=${value}`);
573
585
  plan.forwardedEnv[key] = value;
@@ -680,7 +692,9 @@ class IsolationManager {
680
692
 
681
693
  // A mount only counts if it carries the secret (credentialInMount !== false).
682
694
  const credentialPaths = provider.getCredentialPaths ? provider.getCredentialPaths() : [];
683
- const expandedCreds = credentialPaths.map((cred) => expandHomePath(cred));
695
+ const expandedCreds = resolveProviderCredentialPaths({ ...metadata, credentialPaths }).filter(
696
+ (credPath) => fs.existsSync(credPath)
697
+ );
684
698
  const hasCredentialMount =
685
699
  docker.credentialInMount !== false &&
686
700
  mountedHosts.some((hostPath) =>
@@ -111,6 +111,35 @@ function applyModelOverride(agentConfig, modelOverride) {
111
111
  */
112
112
  const VALID_OPERATIONS = ['add_agents', 'remove_agents', 'update_agent', 'publish', 'load_config'];
113
113
 
114
+ function isValidPreparedWorktreeDescriptor(prepared, cwd) {
115
+ if (!prepared) return false;
116
+ if (typeof prepared !== 'object') return false;
117
+ if (path.resolve(String(prepared.path)) !== cwd) return false;
118
+ if (path.resolve(String(prepared.repoRoot)) !== cwd) return false;
119
+ if (typeof prepared.branch !== 'string') return false;
120
+ if (prepared.branch.length === 0) return false;
121
+ if (prepared.branch.length > 255) return false;
122
+ return /^[0-9a-f]{40}$/.test(String(prepared.baseSha));
123
+ }
124
+
125
+ function resolvePreparedWorktree(options) {
126
+ const prepared = options.preparedWorktree;
127
+ if (!prepared) return null;
128
+ const cwd = path.resolve(options.cwd || process.cwd());
129
+ if (options.worktree !== true || options.isolation) {
130
+ throw new Error('Invalid prepared worktree authority');
131
+ }
132
+ if (!isValidPreparedWorktreeDescriptor(prepared, cwd)) {
133
+ throw new Error('Invalid prepared worktree authority');
134
+ }
135
+ return Object.freeze({
136
+ path: cwd,
137
+ repoRoot: cwd,
138
+ branch: prepared.branch,
139
+ baseSha: prepared.baseSha,
140
+ });
141
+ }
142
+
114
143
  /**
115
144
  * Workflow-triggering topics that indicate cluster state progression
116
145
  * These are the topics that MATTER for resume - not AGENT_OUTPUT noise
@@ -1854,7 +1883,10 @@ class Orchestrator {
1854
1883
  let worktreeInfo = null;
1855
1884
  let isolationImage = null;
1856
1885
 
1857
- if (options.isolation) {
1886
+ if (options.preparedWorktree) {
1887
+ worktreeInfo = resolvePreparedWorktree(options);
1888
+ this._log(`[Orchestrator] Using prepared worktree: ${worktreeInfo.path}`);
1889
+ } else if (options.isolation) {
1858
1890
  // Resolve the provider first so the capability gate and the cluster's image variant (which
1859
1891
  // installs the provider CLI as a Docker-cached layer) both see the same provider. Providers
1860
1892
  // baked into the base image (e.g. Claude) resolve back to the base image unchanged.
@@ -625,6 +625,13 @@ export function shouldUseAttachableWatcher(options, providerName) {
625
625
  return false;
626
626
  }
627
627
 
628
+ // Benchmark runs are non-interactive and keep the cluster process in the foreground. Use the
629
+ // pipe watcher so task completion is observed only after stdout/stderr close; a PTY exit can
630
+ // race its final buffered output on remote runtimes and lose the terminal structured result.
631
+ if (resolveTaskExecutionContext() === 'benchmark') {
632
+ return false;
633
+ }
634
+
628
635
  // The rpc-stdio lane owns bidirectional correlated RPC over stdio itself (see
629
636
  // omp-rpc-driver.ts) and always uses rpc-watcher.js instead of the attachable PTY watcher.
630
637
  if (getProviderRegistryEntry(providerName).invoke.lane === 'rpc-stdio') {
@@ -103,7 +103,12 @@ function createCodexOutputPassthrough({ log, captureProviderSession }) {
103
103
  };
104
104
  }
105
105
 
106
- function createBoundedLinePassthrough({ log, handleLine, deferRawUntilOverflow = false }) {
106
+ function createBoundedLinePassthrough({
107
+ log,
108
+ handleLine,
109
+ deferRawUntilOverflow = false,
110
+ linePrefix = '',
111
+ }) {
107
112
  const decoder = new StringDecoder('utf8');
108
113
  let atLineStart = true;
109
114
  let lineTimestamp = null;
@@ -160,7 +165,7 @@ function createBoundedLinePassthrough({ log, handleLine, deferRawUntilOverflow =
160
165
  while (offset < text.length) {
161
166
  if (atLineStart) {
162
167
  lineTimestamp = Date.now();
163
- appendRaw(logged, `[${lineTimestamp}]`);
168
+ appendRaw(logged, `[${lineTimestamp}]${linePrefix}`);
164
169
  atLineStart = false;
165
170
  }
166
171
  const newline = text.indexOf('\n', offset);
@@ -361,6 +366,10 @@ export function createWatcherOutputRuntime({
361
366
  });
362
367
  const stderrPassthrough = createBoundedLinePassthrough({
363
368
  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] ' : '',
364
373
  handleLine: (line, timestamp) => maybeHandleFatalError(line, timestamp),
365
374
  });
366
375
 
@@ -370,7 +379,7 @@ export function createWatcherOutputRuntime({
370
379
  if (!detected) return false;
371
380
  fatalError = detected;
372
381
  if (silentJsonMode) log(`[${timestamp}]${line}\n`);
373
- log(`[${timestamp}][FATAL] ${detected}\n`);
382
+ log(`[${timestamp}][ZEROSHOT][FATAL] ${detected}\n`);
374
383
  stopProvider(timestamp);
375
384
  return true;
376
385
  }
@@ -396,13 +405,15 @@ export function createWatcherOutputRuntime({
396
405
  fatalError =
397
406
  `Provider structured output exceeded the ${MAX_WATCHER_CONTROL_RECORD_BYTES}-byte ` +
398
407
  'watcher inspection limit; complete output remains in the task log';
399
- log(`[${timestamp}][FATAL] ${fatalError}\n`);
408
+ log(`[${timestamp}][ZEROSHOT][FATAL] ${fatalError}\n`);
400
409
  stopProvider(timestamp);
401
410
  return;
402
411
  }
403
412
  captureProviderSession(line);
404
413
  if (silentJsonMode && !line.trim()) return;
405
- maybeHandleFatalError(line, timestamp);
414
+ // Pi reserves stdout for JSON lifecycle events. Error text inside an assistant message may be
415
+ // followed by an automatic retry, so only Pi stderr can prove a pre-agent startup failure.
416
+ if (providerName !== 'pi') maybeHandleFatalError(line, timestamp);
406
417
  if (captureStreamingError(line, timestamp)) return;
407
418
  if (silentJsonMode) {
408
419
  maybeCaptureStructuredOutput(line);