@the-open-engine/zeroshot 6.35.3 → 6.37.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 (68) hide show
  1. package/cli/index.js +99 -29
  2. package/lib/agent-cli-provider/adapters/omp.js +1 -1
  3. package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
  4. package/lib/hosted-target/adapter-types.d.cts +0 -8
  5. package/lib/hosted-target/adapter-types.d.mts +0 -8
  6. package/lib/hosted-target/adapter-types.d.ts +0 -8
  7. package/lib/hosted-target/index.d.cts +1 -1
  8. package/lib/hosted-target/index.d.mts +1 -1
  9. package/lib/hosted-target/index.d.ts +1 -1
  10. package/lib/hosted-target/retry-executor.d.cts +1 -1
  11. package/lib/hosted-target/retry-executor.d.mts +1 -1
  12. package/lib/hosted-target/retry-executor.d.ts +1 -1
  13. package/lib/hosted-target/target-adapter.d.cts +1 -1
  14. package/lib/hosted-target/target-adapter.d.mts +1 -1
  15. package/lib/hosted-target/target-adapter.d.ts +1 -1
  16. package/lib/hosted-target/zero-cloud-v1-adapter.cjs +0 -29
  17. package/lib/hosted-target/zero-cloud-v1-adapter.d.cts +0 -2
  18. package/lib/hosted-target/zero-cloud-v1-adapter.d.mts +0 -2
  19. package/lib/hosted-target/zero-cloud-v1-adapter.d.ts +0 -2
  20. package/lib/hosted-target/zero-cloud-v1-adapter.mjs +0 -29
  21. package/lib/target/discovery-sections.cjs +2 -21
  22. package/lib/target/discovery-sections.d.cts +0 -2
  23. package/lib/target/discovery-sections.d.mts +0 -2
  24. package/lib/target/discovery-sections.d.ts +0 -2
  25. package/lib/target/discovery-sections.js +2 -21
  26. package/lib/target/discovery-sections.mjs +3 -22
  27. package/lib/target/discovery-validation.cjs +4 -28
  28. package/lib/target/discovery-validation.d.cts +0 -10
  29. package/lib/target/discovery-validation.d.mts +0 -10
  30. package/lib/target/discovery-validation.d.ts +0 -10
  31. package/lib/target/discovery-validation.js +4 -28
  32. package/lib/target/discovery-validation.mjs +4 -27
  33. package/lib/target/discovery.cjs +0 -1
  34. package/lib/target/discovery.d.cts +0 -3
  35. package/lib/target/discovery.d.mts +0 -3
  36. package/lib/target/discovery.d.ts +0 -3
  37. package/lib/target/discovery.js +0 -1
  38. package/lib/target/discovery.mjs +1 -2
  39. package/lib/target/index.d.ts +1 -1
  40. package/lib/target/run-intent-discovery.cjs +3 -1
  41. package/lib/target/run-intent-discovery.js +3 -1
  42. package/lib/target/run-intent-discovery.mjs +4 -2
  43. package/npm-shrinkwrap.json +2 -2
  44. package/package.json +1 -1
  45. package/src/agent/agent-lifecycle.js +25 -76
  46. package/src/agent/agent-liveness-poll.js +93 -0
  47. package/src/agent/agent-task-executor.js +19 -3
  48. package/src/agent-cli-provider/adapters/omp.ts +1 -1
  49. package/src/foreground-benchmark-files.js +81 -0
  50. package/src/foreground-benchmark-result.js +233 -0
  51. package/src/foreground-benchmark-run.js +72 -0
  52. package/src/hosted-target/adapter-types.ts +0 -14
  53. package/src/hosted-target/index.ts +0 -1
  54. package/src/hosted-target/retry-executor.ts +12 -14
  55. package/src/hosted-target/target-adapter.ts +1 -5
  56. package/src/hosted-target/zero-cloud-v1-adapter.ts +0 -37
  57. package/src/target/discovery-sections.ts +3 -28
  58. package/src/target/discovery-validation.ts +15 -61
  59. package/src/target/discovery.ts +1 -9
  60. package/src/target/index.ts +0 -1
  61. package/src/target/run-intent-discovery.ts +3 -2
  62. package/task-lib/runner.js +7 -0
  63. package/lib/hosted-target/runtime-install.cjs +0 -45
  64. package/lib/hosted-target/runtime-install.d.cts +0 -14
  65. package/lib/hosted-target/runtime-install.d.mts +0 -14
  66. package/lib/hosted-target/runtime-install.d.ts +0 -14
  67. package/lib/hosted-target/runtime-install.mjs +0 -42
  68. package/src/hosted-target/runtime-install.ts +0 -83
@@ -0,0 +1,93 @@
1
+ const { isPlatformSupported } = require('./agent-stuck-detector');
2
+
3
+ function hasRecoverableTask(agent) {
4
+ return (
5
+ Boolean(agent.currentTask) ||
6
+ Boolean(agent.isolation?.enabled && agent.currentTaskId) ||
7
+ agent.nestedExecutions?.hasActive === true
8
+ );
9
+ }
10
+
11
+ function handlePendingTermination(agent, settings, now, attemptTermination) {
12
+ if (!agent.livenessTerminationContext) return false;
13
+ if (now >= agent.livenessTerminationRetryAt) attemptTermination(agent, settings);
14
+ return true;
15
+ }
16
+
17
+ function taskTiming(agent, now) {
18
+ const taskStartedAt = agent.taskStartedAt || agent.lastOutputTime || now;
19
+ const lastOutputTime = agent.lastOutputTime || taskStartedAt;
20
+ return {
21
+ taskRuntime: now - taskStartedAt,
22
+ timeSinceLastOutput: now - lastOutputTime,
23
+ lastOutputTime,
24
+ };
25
+ }
26
+
27
+ function handleTaskTimeout(context, timing) {
28
+ const { agent, settings, configuredTimeout, beginTermination } = context;
29
+ if (!configuredTimeout || timing.taskRuntime < configuredTimeout) return false;
30
+ beginTermination(
31
+ agent,
32
+ settings,
33
+ `Task timed out after ${configuredTimeout}ms`,
34
+ 'AGENT_TASK_TIMEOUT',
35
+ {
36
+ taskId: agent.currentTaskId,
37
+ taskRuntime: timing.taskRuntime,
38
+ timeout: configuredTimeout,
39
+ }
40
+ );
41
+ return true;
42
+ }
43
+
44
+ function publishStaleWarning(context, timing) {
45
+ const { agent, staleDuration, warningsBeforeKill } = context;
46
+ agent.consecutiveStaleWarnings += 1;
47
+ agent._publishLifecycle('AGENT_STALE_WARNING', {
48
+ taskId: agent.currentTaskId,
49
+ timeSinceLastOutput: timing.timeSinceLastOutput,
50
+ staleDuration,
51
+ lastOutputTime: timing.lastOutputTime,
52
+ consecutiveWarnings: agent.consecutiveStaleWarnings,
53
+ warningsBeforeKill,
54
+ processDiagnosticsAvailable: isPlatformSupported(),
55
+ analysis: `Provider produced no output for ${timing.timeSinceLastOutput}ms`,
56
+ });
57
+ }
58
+
59
+ function terminateForInactivity(context, timing) {
60
+ const { agent, settings, staleDuration, beginTermination } = context;
61
+ beginTermination(
62
+ agent,
63
+ settings,
64
+ `Provider produced no output for ${timing.timeSinceLastOutput}ms`,
65
+ 'PROVIDER_INACTIVITY_TIMEOUT',
66
+ {
67
+ taskId: agent.currentTaskId,
68
+ timeSinceLastOutput: timing.timeSinceLastOutput,
69
+ staleDuration,
70
+ consecutiveWarnings: agent.consecutiveStaleWarnings,
71
+ }
72
+ );
73
+ }
74
+
75
+ function createLivenessPoll(context) {
76
+ const { agent, settings, staleDuration, warningsBeforeKill, attemptTermination } = context;
77
+ return () => {
78
+ if (!hasRecoverableTask(agent) || agent.livenessTerminationStarted) return;
79
+ const now = Date.now();
80
+ if (handlePendingTermination(agent, settings, now, attemptTermination)) return;
81
+ const timing = taskTiming(agent, now);
82
+ if (handleTaskTimeout(context, timing)) return;
83
+ if (timing.timeSinceLastOutput < staleDuration) {
84
+ agent.consecutiveStaleWarnings = 0;
85
+ return;
86
+ }
87
+ publishStaleWarning(context, timing);
88
+ if (agent.consecutiveStaleWarnings < warningsBeforeKill) return;
89
+ terminateForInactivity(context, timing);
90
+ };
91
+ }
92
+
93
+ module.exports = { createLivenessPoll };
@@ -1866,6 +1866,19 @@ function finalizeLogFollow(agent, state) {
1866
1866
  }
1867
1867
  }
1868
1868
 
1869
+ function finishHostLogCapture(agent, state, pollLogFile, processNewContent, broadcastLine) {
1870
+ // A task status can become terminal before the watcher has flushed its final
1871
+ // filesystem write (observed on Modal). Stop the timers first, then take one
1872
+ // authoritative catch-up read and finish any final UTF-8/non-newline record.
1873
+ finalizeLogFollow(agent, state);
1874
+ pollLogFile();
1875
+ const decoderTail = state.logDecoder.end();
1876
+ if (decoderTail) processNewContent(decoderTail);
1877
+ if (state.lineBuffer.byteLength > 0) {
1878
+ completeLogRecord(state.lineBuffer, broadcastLine, true);
1879
+ }
1880
+ }
1881
+
1869
1882
  function settleHostStatusFailure({ agent, providerName, state, resolve, text, data, error }) {
1870
1883
  if (state.resolved) return;
1871
1884
  state.resolved = true;
@@ -1986,6 +1999,7 @@ function handleStatusCompletion({
1986
1999
  state,
1987
2000
  stdout,
1988
2001
  pollLogFile,
2002
+ finishLogCapture,
1989
2003
  resolve,
1990
2004
  reject,
1991
2005
  }) {
@@ -2012,7 +2026,7 @@ function handleStatusCompletion({
2012
2026
  if (state.resolved) return;
2013
2027
  state.resolved = true;
2014
2028
 
2015
- finalizeLogFollow(agent, state);
2029
+ finishLogCapture();
2016
2030
  flushAgentOutput(agent, providerName, state);
2017
2031
 
2018
2032
  buildCompletionResult({
@@ -2066,14 +2080,12 @@ function createLogFollower({
2066
2080
  const state = createLogFollowState();
2067
2081
  state.skipStructuredResultCheck = skipStructuredResultCheck;
2068
2082
  state.nested = nested;
2069
-
2070
2083
  state.logFilePath = lookupLogFilePath(ctPath, taskId);
2071
2084
  if (state.logFilePath) {
2072
2085
  agent._log(`📋 Agent ${agent.id}: Following ct logs for ${taskId}`);
2073
2086
  } else {
2074
2087
  agent._log(`⏳ Agent ${agent.id}: Waiting for log file...`);
2075
2088
  }
2076
-
2077
2089
  const broadcastLine = (line) => broadcastAgentLine({ agent, providerName, state, line });
2078
2090
  const processNewContent = (content) => appendContentToBuffer(state, content, broadcastLine);
2079
2091
  const pollLogFile = () =>
@@ -2085,6 +2097,8 @@ function createLogFollower({
2085
2097
  state,
2086
2098
  onNewContent: processNewContent,
2087
2099
  });
2100
+ const finishLogCapture = () =>
2101
+ finishHostLogCapture(agent, state, pollLogFile, processNewContent, broadcastLine);
2088
2102
 
2089
2103
  state.pollInterval = setInterval(pollLogFile, 300);
2090
2104
 
@@ -2120,6 +2134,7 @@ function createLogFollower({
2120
2134
  state,
2121
2135
  stdout,
2122
2136
  pollLogFile,
2137
+ finishLogCapture,
2123
2138
  resolve,
2124
2139
  reject,
2125
2140
  });
@@ -3546,6 +3561,7 @@ module.exports = {
3546
3561
  consumeIsolatedTailChunk,
3547
3562
  flushAgentOutput,
3548
3563
  flushIsolatedOutput,
3564
+ finishHostLogCapture,
3549
3565
  CONTROL_PLANE_OUTPUT_LIMITS: Object.freeze({
3550
3566
  maxBytes: MAX_CONTROL_PLANE_OUTPUT_BYTES,
3551
3567
  maxRecords: MAX_CONTROL_PLANE_OUTPUT_RECORDS,
@@ -81,7 +81,7 @@ function detectCliFeatures(helpText?: string | null, versionText?: string | null
81
81
  return {
82
82
  provider: 'omp',
83
83
  versionMatches: VERSION_TOKEN_PATTERN.test(version),
84
- supportsRpcMode: /(^|\s)rpc(\s|$)/m.test(help),
84
+ supportsRpcMode: /(?<![A-Za-z0-9_-])rpc(?![A-Za-z0-9_-])/.test(help),
85
85
  supportsConfig: /--config\b/.test(help),
86
86
  supportsModel: /--model\b/.test(help),
87
87
  supportsThinking: /--thinking\b/.test(help),
@@ -0,0 +1,81 @@
1
+ const crypto = require('node:crypto');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+
5
+ const { buildTelemetry } = require('./foreground-benchmark-result');
6
+
7
+ function serialized(value) {
8
+ return Buffer.from(`${JSON.stringify(value)}\n`, 'utf8');
9
+ }
10
+
11
+ function atomicWriteNew(targetPath, content) {
12
+ const directory = path.dirname(targetPath);
13
+ const temporary = path.join(
14
+ directory,
15
+ `.${path.basename(targetPath)}.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`
16
+ );
17
+ let descriptor;
18
+ let published = false;
19
+ try {
20
+ descriptor = fs.openSync(temporary, 'wx', 0o600);
21
+ fs.writeFileSync(descriptor, content);
22
+ fs.fsyncSync(descriptor);
23
+ fs.closeSync(descriptor);
24
+ descriptor = undefined;
25
+ fs.linkSync(temporary, targetPath);
26
+ published = true;
27
+ fs.unlinkSync(temporary);
28
+ const directoryDescriptor = fs.openSync(directory, fs.constants.O_RDONLY);
29
+ try {
30
+ fs.fsyncSync(directoryDescriptor);
31
+ } finally {
32
+ fs.closeSync(directoryDescriptor);
33
+ }
34
+ } catch (error) {
35
+ error.atomicTargetPublished = published;
36
+ throw error;
37
+ } finally {
38
+ if (descriptor !== undefined) fs.closeSync(descriptor);
39
+ try {
40
+ fs.unlinkSync(temporary);
41
+ } catch {
42
+ // Preserve the primary write error; a leftover randomized temp is non-authoritative.
43
+ }
44
+ }
45
+ }
46
+
47
+ function removeOrphanTelemetry(telemetryPath, primaryError) {
48
+ try {
49
+ fs.unlinkSync(telemetryPath);
50
+ } catch (cleanupError) {
51
+ if (cleanupError.code !== 'ENOENT') primaryError.cleanupError = cleanupError;
52
+ }
53
+ }
54
+
55
+ function writeBenchmarkResultBundle(resultPath, result, snapshot) {
56
+ if (typeof resultPath !== 'string' || resultPath.length === 0) {
57
+ throw new Error('result path must be non-empty text');
58
+ }
59
+ const resolvedResult = path.resolve(resultPath);
60
+ const telemetryPath = `${resolvedResult}.telemetry.json`;
61
+ const telemetry = buildTelemetry(result.runId, snapshot);
62
+ const telemetryBytes = serialized(telemetry);
63
+ atomicWriteNew(telemetryPath, telemetryBytes);
64
+ const receipt = {
65
+ ...result,
66
+ telemetry: {
67
+ artifact: path.basename(telemetryPath),
68
+ byteLength: telemetryBytes.length,
69
+ sha256: crypto.createHash('sha256').update(telemetryBytes).digest('hex'),
70
+ },
71
+ };
72
+ try {
73
+ atomicWriteNew(resolvedResult, serialized(receipt));
74
+ } catch (error) {
75
+ if (!error.atomicTargetPublished) removeOrphanTelemetry(telemetryPath, error);
76
+ throw error;
77
+ }
78
+ return receipt;
79
+ }
80
+
81
+ module.exports = { writeBenchmarkResultBundle };
@@ -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
+ };
@@ -12,13 +12,6 @@ import type {
12
12
  TargetAccessTokenProvider,
13
13
  } from './types.js';
14
14
 
15
- export type CredentialInstallCapability =
16
- | { readonly supported: false }
17
- | {
18
- readonly supported: true;
19
- readonly descriptor: NonNullable<TargetDiscoveryDescriptor['credentialInstall']>;
20
- };
21
-
22
15
  export interface TargetAdapter {
23
16
  allocate(req: AllocateRequest, signal?: AbortSignal): Promise<Capsule>;
24
17
  list(req?: ListRequest, signal?: AbortSignal): Promise<CapsuleListPage>;
@@ -26,13 +19,6 @@ export interface TargetAdapter {
26
19
  terminate(capsuleId: string, signal?: AbortSignal): Promise<Capsule>;
27
20
  limits(signal?: AbortSignal): Promise<CapsuleLimits>;
28
21
  access(capsuleId: string, signal?: AbortSignal): Promise<CapsuleAccess>;
29
- installRuntime(
30
- capsuleId: string,
31
- runtime: unknown,
32
- accessToken: string,
33
- signal?: AbortSignal
34
- ): Promise<void>;
35
- readonly credentialInstall: CredentialInstallCapability;
36
22
  }
37
23
 
38
24
  export interface CreateTargetAdapterOptions {
@@ -2,7 +2,6 @@ export {
2
2
  createTargetAdapter,
3
3
  type TargetAdapter,
4
4
  type CreateTargetAdapterOptions,
5
- type CredentialInstallCapability,
6
5
  } from './target-adapter.js';
7
6
  export {
8
7
  TargetAdapterError,
@@ -2,14 +2,7 @@ import { MAX_RETRY_ELAPSED_MS } from './bounds.js';
2
2
  import { TargetAdapterError } from './errors.js';
3
3
  import type { Clock, RetryPolicy } from './types.js';
4
4
 
5
- export type TargetOperation =
6
- | 'allocate'
7
- | 'list'
8
- | 'inspect'
9
- | 'terminate'
10
- | 'limits'
11
- | 'access'
12
- | 'installRuntime';
5
+ export type TargetOperation = 'allocate' | 'list' | 'inspect' | 'terminate' | 'limits' | 'access';
13
6
 
14
7
  function throwIfAborted(signal?: AbortSignal): void {
15
8
  if (signal?.aborted) {
@@ -22,10 +15,16 @@ async function wait(delayMs: number, signal?: AbortSignal): Promise<void> {
22
15
  if (delayMs <= 0) return;
23
16
  await new Promise<void>((resolve, reject) => {
24
17
  const timer = setTimeout(resolve, delayMs);
25
- signal?.addEventListener('abort', () => {
26
- clearTimeout(timer);
27
- reject(signal.reason ?? new globalThis.DOMException('The operation was aborted', 'AbortError'));
28
- }, { once: true });
18
+ signal?.addEventListener(
19
+ 'abort',
20
+ () => {
21
+ clearTimeout(timer);
22
+ reject(
23
+ signal.reason ?? new globalThis.DOMException('The operation was aborted', 'AbortError')
24
+ );
25
+ },
26
+ { once: true }
27
+ );
29
28
  });
30
29
  }
31
30
 
@@ -41,12 +40,11 @@ function validRetryDelay(retry: boolean, delayMs: number, remaining: number): bo
41
40
  return retry && Number.isFinite(delayMs) && delayMs >= 0 && delayMs < remaining;
42
41
  }
43
42
 
44
-
45
43
  export async function withTargetRetry<T>(
46
44
  operation: TargetOperation,
47
45
  effect: () => Promise<T>,
48
46
  signal: AbortSignal | undefined,
49
- context: RetryContext,
47
+ context: RetryContext
50
48
  ): Promise<T> {
51
49
  const retrySafe = operation !== 'access';
52
50
  const started = context.clock.now();
@@ -1,10 +1,6 @@
1
1
  import { ZeroCloudV1TargetAdapter } from './zero-cloud-v1-adapter.js';
2
2
  import type { CreateTargetAdapterOptions, TargetAdapter } from './adapter-types.js';
3
- export type {
4
- CreateTargetAdapterOptions,
5
- CredentialInstallCapability,
6
- TargetAdapter,
7
- } from './adapter-types.js';
3
+ export type { CreateTargetAdapterOptions, TargetAdapter } from './adapter-types.js';
8
4
 
9
5
  export function createTargetAdapter(options: CreateTargetAdapterOptions): TargetAdapter {
10
6
  if (options.descriptor.adapter.majorVersion !== 1) {