@the-open-engine/zeroshot 6.17.0 → 6.19.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.
- package/cli/index.js +11 -14
- package/lib/agent-cli-provider/adapters/claude.d.ts +2 -2
- package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +30 -0
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.d.ts +2 -2
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +37 -0
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/gemini.d.ts +2 -2
- package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/gemini.js +115 -4
- package/lib/agent-cli-provider/adapters/gemini.js.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode.d.ts +2 -2
- package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode.js +108 -0
- package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
- package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-options.js +12 -0
- package/lib/agent-cli-provider/contract-options.js.map +1 -1
- package/lib/agent-cli-provider/index.d.ts +2 -2
- package/lib/agent-cli-provider/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/index.js +2 -1
- package/lib/agent-cli-provider/index.js.map +1 -1
- package/lib/agent-cli-provider/omp-release.d.ts +15 -0
- package/lib/agent-cli-provider/omp-release.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-release.js +50 -0
- package/lib/agent-cli-provider/omp-release.js.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-protocol.d.ts +38 -0
- package/lib/agent-cli-provider/omp-rpc-protocol.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp-rpc-protocol.js +267 -0
- package/lib/agent-cli-provider/omp-rpc-protocol.js.map +1 -0
- package/lib/agent-cli-provider/provider-registry.d.ts +35 -8
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +29 -1
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +36 -5
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +30 -3
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster/errors.cjs +4 -1
- package/lib/cluster/errors.d.ts +2 -0
- package/lib/cluster/errors.mjs +2 -0
- package/lib/cluster/index.cjs +8 -1
- package/lib/cluster/index.d.ts +4 -1
- package/lib/cluster/index.mjs +4 -1
- package/lib/cluster/json-source.cjs +61 -0
- package/lib/cluster/json-source.d.ts +4 -0
- package/lib/cluster/json-source.mjs +55 -0
- package/lib/cluster/payload-value.cjs +76 -0
- package/lib/cluster/payload-value.d.ts +7 -0
- package/lib/cluster/payload-value.mjs +72 -0
- package/lib/cluster/validators.cjs +21 -0
- package/lib/cluster/validators.d.ts +4 -1
- package/lib/cluster/validators.mjs +19 -1
- package/lib/provider-names.js +10 -0
- package/lib/settings.js +6 -3
- package/lib/setup-plan.js +5 -5
- package/lib/start-cluster.js +3 -3
- package/package.json +2 -2
- package/src/agent/agent-lifecycle.js +4 -4
- package/src/agent/agent-task-executor.js +201 -416
- package/src/agent/output-extraction.js +39 -14
- package/src/agent/output-reformatter.js +154 -111
- package/src/agent/pr-verification.js +4 -1
- package/src/agent-cli-provider/adapters/claude.ts +39 -2
- package/src/agent-cli-provider/adapters/codex.ts +56 -2
- package/src/agent-cli-provider/adapters/gemini.ts +102 -6
- package/src/agent-cli-provider/adapters/opencode.ts +85 -2
- package/src/agent-cli-provider/contract-options.ts +16 -0
- package/src/agent-cli-provider/index.ts +4 -0
- package/src/agent-cli-provider/omp-release.ts +63 -0
- package/src/agent-cli-provider/omp-rpc-protocol.ts +396 -0
- package/src/agent-cli-provider/provider-registry.ts +52 -4
- package/src/agent-cli-provider/single-agent-runtime.ts +52 -5
- package/src/agent-cli-provider/types.ts +36 -2
- package/src/agent-wrapper.js +5 -5
- package/src/claude-task-runner.js +5 -3
- package/src/cluster/errors.ts +1 -0
- package/src/cluster/index.ts +4 -0
- package/src/cluster/json-source.ts +71 -0
- package/src/cluster/payload-value.ts +84 -0
- package/src/cluster/validators.ts +31 -2
- package/src/config-validator.js +3 -2
- package/src/isolation-manager.js +6 -2
- package/src/orchestrator.js +36 -16
- package/src/preflight.js +2 -1
- package/task-lib/attachable-watcher.js +1 -0
- package/task-lib/command-spec-cleanup.js +57 -11
- package/task-lib/commands/run.js +1 -0
- package/task-lib/provider-session-capture.js +8 -0
- package/task-lib/runner.js +9 -6
- package/task-lib/watcher.js +1 -0
|
@@ -9,6 +9,8 @@ import type {
|
|
|
9
9
|
ProviderDocsMetadata,
|
|
10
10
|
ProviderInvokeSpec,
|
|
11
11
|
ProviderRegistryEntry,
|
|
12
|
+
StructuredOutputProviderRegistryEntry,
|
|
13
|
+
UnstructuredOutputProviderRegistryEntry,
|
|
12
14
|
} from './provider-registry';
|
|
13
15
|
|
|
14
16
|
export type ProviderId = (typeof providerIds)[number];
|
|
@@ -27,8 +29,9 @@ export type {
|
|
|
27
29
|
ProviderDocsMetadata,
|
|
28
30
|
ProviderInvokeSpec,
|
|
29
31
|
ProviderRegistryEntry,
|
|
32
|
+
StructuredOutputProviderRegistryEntry,
|
|
33
|
+
UnstructuredOutputProviderRegistryEntry,
|
|
30
34
|
};
|
|
31
|
-
|
|
32
35
|
export interface AgentCliProviderHelperMetadata {
|
|
33
36
|
readonly packageName: '@the-open-engine/zeroshot';
|
|
34
37
|
readonly buildOutputDir: 'lib/agent-cli-provider';
|
|
@@ -105,6 +108,9 @@ export interface ClaudeCliFeatures extends BaseCliFeatures {
|
|
|
105
108
|
readonly supportsSettings: boolean;
|
|
106
109
|
readonly supportsMcpConfig: boolean;
|
|
107
110
|
readonly supportsResume: boolean;
|
|
111
|
+
readonly supportsTools: boolean;
|
|
112
|
+
readonly supportsStrictMcpConfig: boolean;
|
|
113
|
+
readonly supportsNoSessionPersistence: boolean;
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
export interface CodexCliFeatures extends BaseCliFeatures {
|
|
@@ -118,6 +124,11 @@ export interface CodexCliFeatures extends BaseCliFeatures {
|
|
|
118
124
|
readonly supportsSkipGitRepoCheck: boolean;
|
|
119
125
|
readonly supportsResume: boolean;
|
|
120
126
|
readonly supportsWebSearch: boolean;
|
|
127
|
+
readonly supportsSandbox: boolean;
|
|
128
|
+
readonly supportsEphemeral: boolean;
|
|
129
|
+
readonly supportsIgnoreUserConfig: boolean;
|
|
130
|
+
readonly supportsIgnoreRules: boolean;
|
|
131
|
+
readonly supportsStrictConfig: boolean;
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
export interface GeminiCliFeatures extends BaseCliFeatures {
|
|
@@ -126,6 +137,7 @@ export interface GeminiCliFeatures extends BaseCliFeatures {
|
|
|
126
137
|
readonly supportsAutoApprove: boolean;
|
|
127
138
|
readonly supportsCwd: boolean;
|
|
128
139
|
readonly supportsModel: boolean;
|
|
140
|
+
readonly supportsAdminPolicy: boolean;
|
|
129
141
|
}
|
|
130
142
|
|
|
131
143
|
export interface OpencodeCliFeatures extends BaseCliFeatures {
|
|
@@ -138,6 +150,8 @@ export interface OpencodeCliFeatures extends BaseCliFeatures {
|
|
|
138
150
|
readonly supportsAutoApprove: false;
|
|
139
151
|
readonly supportsResume: boolean;
|
|
140
152
|
readonly supportsWebSearch: boolean;
|
|
153
|
+
readonly supportsAgent: boolean;
|
|
154
|
+
readonly supportsRecoveryIsolation: boolean;
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
export interface PiCliFeatures extends BaseCliFeatures {
|
|
@@ -215,6 +229,9 @@ export interface CliFeatureOverrides {
|
|
|
215
229
|
readonly supportsIncludePartials?: boolean;
|
|
216
230
|
readonly supportsVerbose?: boolean;
|
|
217
231
|
readonly supportsModel?: boolean;
|
|
232
|
+
readonly supportsTools?: boolean;
|
|
233
|
+
readonly supportsStrictMcpConfig?: boolean;
|
|
234
|
+
readonly supportsNoSessionPersistence?: boolean;
|
|
218
235
|
readonly supportsEffort?: boolean;
|
|
219
236
|
readonly supportsSettings?: boolean;
|
|
220
237
|
readonly supportsJson?: boolean;
|
|
@@ -223,12 +240,20 @@ export interface CliFeatureOverrides {
|
|
|
223
240
|
readonly supportsCwd?: boolean;
|
|
224
241
|
readonly supportsConfigOverride?: boolean;
|
|
225
242
|
readonly supportsSkipGitRepoCheck?: boolean;
|
|
243
|
+
readonly supportsSandbox?: boolean;
|
|
244
|
+
readonly supportsEphemeral?: boolean;
|
|
245
|
+
readonly supportsIgnoreUserConfig?: boolean;
|
|
246
|
+
readonly supportsIgnoreRules?: boolean;
|
|
247
|
+
readonly supportsStrictConfig?: boolean;
|
|
226
248
|
readonly supportsVariant?: boolean;
|
|
249
|
+
readonly supportsAdminPolicy?: boolean;
|
|
227
250
|
readonly supportsJsonMode?: boolean;
|
|
228
251
|
readonly supportsNoSession?: boolean;
|
|
229
252
|
readonly supportsNoExtensions?: boolean;
|
|
230
253
|
readonly supportsNoSkills?: boolean;
|
|
231
254
|
readonly supportsNoPromptTemplates?: boolean;
|
|
255
|
+
readonly supportsAgent?: boolean;
|
|
256
|
+
readonly supportsRecoveryIsolation?: boolean;
|
|
232
257
|
readonly supportsNoContextFiles?: boolean;
|
|
233
258
|
readonly supportsNoApprove?: boolean;
|
|
234
259
|
readonly supportsModeJson?: boolean;
|
|
@@ -262,7 +287,7 @@ export interface CleanupMetadata {
|
|
|
262
287
|
readonly kind: 'temp-file' | 'temp-directory';
|
|
263
288
|
readonly provider: ProviderId;
|
|
264
289
|
readonly path: string;
|
|
265
|
-
readonly reason: 'output-schema' | 'settings-overlay';
|
|
290
|
+
readonly reason: 'output-schema' | 'settings-overlay' | 'admin-policy' | 'isolated-config';
|
|
266
291
|
}
|
|
267
292
|
|
|
268
293
|
export interface WarningMetadata {
|
|
@@ -312,6 +337,8 @@ export interface BuildProviderCommandOptions {
|
|
|
312
337
|
// one variadic `--mcp-config` followed by file paths; adapters such as Copilot accept repeated
|
|
313
338
|
// flags with inline JSON so configs survive container path translation.
|
|
314
339
|
readonly mcpConfig?: readonly string[];
|
|
340
|
+
/** Internal profile for provider-neutral structured-output correction turns. */
|
|
341
|
+
readonly structuredOutputRecovery?: boolean;
|
|
315
342
|
}
|
|
316
343
|
|
|
317
344
|
export interface TextEvent {
|
|
@@ -415,6 +442,13 @@ export interface ProviderAdapter {
|
|
|
415
442
|
classifyError(error: unknown): ErrorClassification;
|
|
416
443
|
}
|
|
417
444
|
|
|
445
|
+
export interface StructuredOutputRecoveryAdapter extends ProviderAdapter {
|
|
446
|
+
buildStructuredOutputRecoveryCommand(
|
|
447
|
+
context: string,
|
|
448
|
+
options?: BuildProviderCommandOptions
|
|
449
|
+
): CommandSpec;
|
|
450
|
+
}
|
|
451
|
+
|
|
418
452
|
export class InvalidProviderModelError extends Error {
|
|
419
453
|
readonly permanent = true;
|
|
420
454
|
|
package/src/agent-wrapper.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
const LogicEngine = require('./logic-engine');
|
|
14
14
|
const { validateAgentConfig } = require('./agent/agent-config');
|
|
15
15
|
const { loadSettings, validateModelAgainstMax, VALID_MODELS } = require('../lib/settings');
|
|
16
|
-
const { normalizeProviderName } = require('../lib/provider-names');
|
|
16
|
+
const { normalizeProviderName, getDefaultProviderId } = require('../lib/provider-names');
|
|
17
17
|
const { getProvider } = require('./providers');
|
|
18
18
|
const { buildContext } = require('./agent/agent-context-builder');
|
|
19
19
|
const { collectQueuedGuidance } = require('./agent/guidance-queue');
|
|
@@ -160,9 +160,9 @@ class AgentWrapper {
|
|
|
160
160
|
this.config.provider ||
|
|
161
161
|
clusterConfig.defaultProvider ||
|
|
162
162
|
settings.defaultProvider ||
|
|
163
|
-
|
|
163
|
+
getDefaultProviderId();
|
|
164
164
|
|
|
165
|
-
return normalizeProviderName(resolved) ||
|
|
165
|
+
return normalizeProviderName(resolved) || getDefaultProviderId();
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
_resolveModelSpec() {
|
|
@@ -586,8 +586,8 @@ class AgentWrapper {
|
|
|
586
586
|
* Falls back to reformatting if extraction fails
|
|
587
587
|
* @private
|
|
588
588
|
*/
|
|
589
|
-
_parseResultOutput(output) {
|
|
590
|
-
return parseResultOutput(this, output);
|
|
589
|
+
_parseResultOutput(output, options = {}) {
|
|
590
|
+
return parseResultOutput(this, output, options);
|
|
591
591
|
}
|
|
592
592
|
|
|
593
593
|
/**
|
|
@@ -9,7 +9,7 @@ const { spawn, spawnSync } = require('child_process');
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const TaskRunner = require('./task-runner');
|
|
11
11
|
const { loadSettings } = require('../lib/settings');
|
|
12
|
-
const { normalizeProviderName } = require('../lib/provider-names');
|
|
12
|
+
const { normalizeProviderName, getDefaultProviderId } = require('../lib/provider-names');
|
|
13
13
|
const { getProvider } = require('./providers');
|
|
14
14
|
const { prependWorktreeToolBinToEnv } = require('./worktree-tooling-env');
|
|
15
15
|
const { applyDarwinKeychainBoundaryToEnv } = require('./darwin-keychain-boundary');
|
|
@@ -200,7 +200,9 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
200
200
|
} = options;
|
|
201
201
|
|
|
202
202
|
const settings = loadSettings();
|
|
203
|
-
const providerName = normalizeProviderName(
|
|
203
|
+
const providerName = normalizeProviderName(
|
|
204
|
+
provider || settings.defaultProvider || getDefaultProviderId()
|
|
205
|
+
);
|
|
204
206
|
const { providerModule, providerSettings, levelOverrides } = this._getProviderContext(
|
|
205
207
|
providerName,
|
|
206
208
|
settings
|
|
@@ -736,7 +738,7 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
736
738
|
rejectCallerSuppliedModelProvenance(options);
|
|
737
739
|
const {
|
|
738
740
|
agentId = 'unknown',
|
|
739
|
-
provider =
|
|
741
|
+
provider = getDefaultProviderId(),
|
|
740
742
|
model = null,
|
|
741
743
|
modelLevel = null,
|
|
742
744
|
modelSpec: explicitModelSpec = null,
|
package/src/cluster/errors.ts
CHANGED
|
@@ -13,6 +13,7 @@ export class ClusterInternalError extends ClusterError {}
|
|
|
13
13
|
export class ClusterProtocolError extends ClusterError {}
|
|
14
14
|
export class ClusterTransportError extends ClusterError {}
|
|
15
15
|
export class ClusterTimeoutError extends ClusterError {}
|
|
16
|
+
export class ClusterRequestError extends ClusterError {}
|
|
16
17
|
|
|
17
18
|
export function requestAbortError(method: string): DOMException {
|
|
18
19
|
return new DOMException(
|
package/src/cluster/index.ts
CHANGED
|
@@ -5,11 +5,15 @@ export {
|
|
|
5
5
|
ClusterError,
|
|
6
6
|
ClusterInternalError,
|
|
7
7
|
ClusterProtocolError,
|
|
8
|
+
ClusterRequestError,
|
|
8
9
|
ClusterRpcError,
|
|
9
10
|
ClusterStateError,
|
|
10
11
|
ClusterTimeoutError,
|
|
11
12
|
ClusterTransportError,
|
|
12
13
|
} from './errors.js';
|
|
14
|
+
export { assertGraphProfile, assertGraphProfileSupported, assertGraphSpec } from './validators.js';
|
|
15
|
+
export * from './payload-value.js';
|
|
16
|
+
export * from './json-source.js';
|
|
13
17
|
export { CONNECTION_TRANSITIONS, PROTOCOL_DIAGNOSTIC_CAPACITY, Connection } from './connection.js';
|
|
14
18
|
export type {
|
|
15
19
|
CallOptions,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ClusterRequestError } from './errors.js';
|
|
2
|
+
import { MAX_FRAME_BYTES } from './generated/protocol.js';
|
|
3
|
+
|
|
4
|
+
export const MAX_REQUEST_BYTES = MAX_FRAME_BYTES;
|
|
5
|
+
|
|
6
|
+
export function decodeBoundedJson(bytes: Uint8Array): unknown {
|
|
7
|
+
if (bytes.length > MAX_REQUEST_BYTES) {
|
|
8
|
+
throw new ClusterRequestError(
|
|
9
|
+
`request payload of ${bytes.length} bytes exceeds the ${MAX_REQUEST_BYTES} byte limit`,
|
|
10
|
+
'OVERSIZED_JSON',
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
let text: string;
|
|
14
|
+
try {
|
|
15
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
16
|
+
} catch {
|
|
17
|
+
throw new ClusterRequestError('request payload is not valid UTF-8', 'INVALID_UTF8');
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(text) as unknown;
|
|
21
|
+
} catch {
|
|
22
|
+
throw new ClusterRequestError('request payload is not valid JSON', 'MALFORMED_JSON');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Callers resolve `--graph`/`--input` specifiers ('-' vs. a file path) to a byte
|
|
27
|
+
// source themselves; opening the filesystem stream stays outside src/cluster so this
|
|
28
|
+
// module keeps no host-specific dependency (not even the `NodeJS` ambient namespace,
|
|
29
|
+
// which isn't resolvable from a consumer's declaration-only install) beyond the
|
|
30
|
+
// standard async-iterable protocol every Node Readable already implements — mirrors
|
|
31
|
+
// how WebSocketLike is injected instead of importing 'ws' directly, see socket.ts.
|
|
32
|
+
export async function readBoundedSource(
|
|
33
|
+
source: AsyncIterable<Uint8Array | string>,
|
|
34
|
+
): Promise<Uint8Array> {
|
|
35
|
+
const encoder = new TextEncoder();
|
|
36
|
+
const chunks: Uint8Array[] = [];
|
|
37
|
+
let total = 0;
|
|
38
|
+
for await (const chunk of source) {
|
|
39
|
+
const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : new Uint8Array(chunk);
|
|
40
|
+
total += bytes.length;
|
|
41
|
+
if (total > MAX_REQUEST_BYTES) {
|
|
42
|
+
throw new ClusterRequestError(
|
|
43
|
+
`request payload exceeds the ${MAX_REQUEST_BYTES} byte limit`,
|
|
44
|
+
'OVERSIZED_JSON',
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
chunks.push(bytes);
|
|
48
|
+
}
|
|
49
|
+
const assembled = new Uint8Array(total);
|
|
50
|
+
let offset = 0;
|
|
51
|
+
for (const bytes of chunks) {
|
|
52
|
+
assembled.set(bytes, offset);
|
|
53
|
+
offset += bytes.length;
|
|
54
|
+
}
|
|
55
|
+
return assembled;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function assertDistinctRequestSources(
|
|
59
|
+
graph: string,
|
|
60
|
+
input: string | undefined,
|
|
61
|
+
): asserts input is string {
|
|
62
|
+
if (input === undefined) {
|
|
63
|
+
throw new ClusterRequestError('--input is required for every hosted run', 'MISSING_INPUT');
|
|
64
|
+
}
|
|
65
|
+
if (graph === '-' && input === '-') {
|
|
66
|
+
throw new ClusterRequestError(
|
|
67
|
+
'graph and input cannot both read from stdin',
|
|
68
|
+
'AMBIGUOUS_STDIN_SOURCE',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { PayloadType } from './generated/protocol.js';
|
|
2
|
+
import { ClusterRequestError } from './errors.js';
|
|
3
|
+
|
|
4
|
+
export type InputValidationIssue = {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly code: 'TYPE_MISMATCH' | 'MISSING_REQUIRED_FIELD' | 'UNKNOWN_FIELD' | 'UNKNOWN_ENUM_LABEL';
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type InternalIssue = InputValidationIssue & { readonly expectedKind: string };
|
|
10
|
+
|
|
11
|
+
function isJsonInteger(value: unknown): boolean {
|
|
12
|
+
return typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function evaluate(type: PayloadType, value: unknown, path: string): InternalIssue | null {
|
|
16
|
+
switch (type.kind) {
|
|
17
|
+
case 'null':
|
|
18
|
+
return value === null ? null : { path, code: 'TYPE_MISMATCH', expectedKind: 'null' };
|
|
19
|
+
case 'boolean':
|
|
20
|
+
return typeof value === 'boolean' ? null : { path, code: 'TYPE_MISMATCH', expectedKind: 'boolean' };
|
|
21
|
+
case 'integer':
|
|
22
|
+
return isJsonInteger(value) ? null : { path, code: 'TYPE_MISMATCH', expectedKind: 'integer' };
|
|
23
|
+
case 'number':
|
|
24
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
25
|
+
? null
|
|
26
|
+
: { path, code: 'TYPE_MISMATCH', expectedKind: 'number' };
|
|
27
|
+
case 'string':
|
|
28
|
+
return typeof value === 'string' ? null : { path, code: 'TYPE_MISMATCH', expectedKind: 'string' };
|
|
29
|
+
case 'enum':
|
|
30
|
+
if (typeof value !== 'string') return { path, code: 'TYPE_MISMATCH', expectedKind: 'enum' };
|
|
31
|
+
return type.values.includes(value)
|
|
32
|
+
? null
|
|
33
|
+
: { path, code: 'UNKNOWN_ENUM_LABEL', expectedKind: 'enum' };
|
|
34
|
+
case 'array': {
|
|
35
|
+
if (!Array.isArray(value)) return { path, code: 'TYPE_MISMATCH', expectedKind: 'array' };
|
|
36
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
37
|
+
const issue = evaluate(type.items, value[index], `${path}/${index}`);
|
|
38
|
+
if (issue) return issue;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
case 'record': {
|
|
43
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
44
|
+
return { path, code: 'TYPE_MISMATCH', expectedKind: 'record' };
|
|
45
|
+
}
|
|
46
|
+
const record = value as Readonly<Record<string, unknown>>;
|
|
47
|
+
const fieldNames = Object.keys(type.fields).sort();
|
|
48
|
+
for (const name of fieldNames) {
|
|
49
|
+
const field = type.fields[name];
|
|
50
|
+
if (!field) continue;
|
|
51
|
+
const fieldPath = `${path}/${name}`;
|
|
52
|
+
if (Object.prototype.hasOwnProperty.call(record, name)) {
|
|
53
|
+
const issue = evaluate(field.type, record[name], fieldPath);
|
|
54
|
+
if (issue) return issue;
|
|
55
|
+
} else if (field.required) {
|
|
56
|
+
return { path: fieldPath, code: 'MISSING_REQUIRED_FIELD', expectedKind: field.type.kind };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const declared = new Set(fieldNames);
|
|
60
|
+
const unknownField = Object.keys(record).sort().find((name) => !declared.has(name));
|
|
61
|
+
return unknownField
|
|
62
|
+
? { path: `${path}/${unknownField}`, code: 'UNKNOWN_FIELD', expectedKind: 'record' }
|
|
63
|
+
: null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function firstInputValidationIssue(
|
|
69
|
+
type: PayloadType,
|
|
70
|
+
value: unknown,
|
|
71
|
+
path = '',
|
|
72
|
+
): InputValidationIssue | null {
|
|
73
|
+
const issue = evaluate(type, value, path);
|
|
74
|
+
return issue ? { path: issue.path, code: issue.code } : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function assertInputValue(type: PayloadType, value: unknown): void {
|
|
78
|
+
const issue = evaluate(type, value, '');
|
|
79
|
+
if (!issue) return;
|
|
80
|
+
throw new ClusterRequestError(
|
|
81
|
+
`input value at '${issue.path || '/'}' failed validation: ${issue.code}, expected ${issue.expectedKind}`,
|
|
82
|
+
'INVALID_INPUT',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
METHOD_RESULT_DEFINITIONS,
|
|
5
5
|
PROTOCOL_VERSION,
|
|
6
6
|
} from './generated/protocol.js';
|
|
7
|
-
import type { ClusterMethod } from './generated/protocol.js';
|
|
7
|
+
import type { ClusterMethod, GraphProfile, GraphSpec, ServerCapabilities } from './generated/protocol.js';
|
|
8
8
|
import { CLUSTER_PROTOCOL_SCHEMA } from './generated/protocol-schema.js';
|
|
9
|
-
import { ClusterProtocolError } from './errors.js';
|
|
9
|
+
import { ClusterProtocolError, ClusterRequestError } from './errors.js';
|
|
10
10
|
|
|
11
11
|
const ajv = new Ajv2020({
|
|
12
12
|
allErrors: true,
|
|
@@ -54,3 +54,32 @@ export function assertMethodResult(method: ClusterMethod, value: unknown): void
|
|
|
54
54
|
throw error;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
function assertRequestDefinition(definition: string, value: unknown, code: string): void {
|
|
59
|
+
const validate = validatorFor(definition);
|
|
60
|
+
if (validate(value)) return;
|
|
61
|
+
const details = (validate.errors ?? []).map((error) =>
|
|
62
|
+
`${error.instancePath || '/'} ${error.message ?? 'is invalid'}`
|
|
63
|
+
).join('; ');
|
|
64
|
+
throw new ClusterRequestError(`${definition} validation failed: ${details}`, code);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function assertGraphSpec(value: unknown): asserts value is GraphSpec {
|
|
68
|
+
assertRequestDefinition('GraphSpec', value, 'INVALID_GRAPH');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function assertGraphProfile(value: unknown): asserts value is GraphProfile {
|
|
72
|
+
assertRequestDefinition('GraphProfile', value, 'INVALID_GRAPH_PROFILE');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function assertGraphProfileSupported(
|
|
76
|
+
profile: GraphProfile,
|
|
77
|
+
capabilities: ServerCapabilities,
|
|
78
|
+
): void {
|
|
79
|
+
if (!capabilities.graphProfiles?.includes(profile)) {
|
|
80
|
+
throw new ClusterRequestError(
|
|
81
|
+
`graph profile ${profile} is not among the server's advertised graphProfiles`,
|
|
82
|
+
'UNSUPPORTED_GRAPH_PROFILE',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/config-validator.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
const { loadSettings } = require('../lib/settings');
|
|
15
15
|
const {
|
|
16
16
|
VALID_PROVIDERS,
|
|
17
|
+
getDefaultProviderId,
|
|
17
18
|
normalizeProviderName,
|
|
18
19
|
providerSupportsCapability,
|
|
19
20
|
} = require('../lib/provider-names');
|
|
@@ -1987,8 +1988,8 @@ function resolveProviderName(agent, config, settings) {
|
|
|
1987
1988
|
agent.provider ||
|
|
1988
1989
|
config.defaultProvider ||
|
|
1989
1990
|
settings.defaultProvider ||
|
|
1990
|
-
|
|
1991
|
-
return normalizeProviderName(resolved) ||
|
|
1991
|
+
getDefaultProviderId();
|
|
1992
|
+
return normalizeProviderName(resolved) || getDefaultProviderId();
|
|
1992
1993
|
}
|
|
1993
1994
|
|
|
1994
1995
|
function validateProviderLevel(provider, requestedLevel, minLevel, maxLevel) {
|
package/src/isolation-manager.js
CHANGED
|
@@ -16,7 +16,11 @@ const os = require('os');
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const { loadSettings } = require('../lib/settings');
|
|
18
18
|
const { CLAUDE_AUTH_ENV_VARS, resolveClaudeAuth } = require('../lib/settings/claude-auth');
|
|
19
|
-
const {
|
|
19
|
+
const {
|
|
20
|
+
normalizeProviderName,
|
|
21
|
+
getProviderMetadata,
|
|
22
|
+
getDefaultProviderId,
|
|
23
|
+
} = require('../lib/provider-names');
|
|
20
24
|
const {
|
|
21
25
|
MOUNT_PRESETS,
|
|
22
26
|
resolveMounts,
|
|
@@ -236,7 +240,7 @@ class IsolationManager {
|
|
|
236
240
|
|
|
237
241
|
const settings = loadSettings();
|
|
238
242
|
const providerName = normalizeProviderName(
|
|
239
|
-
config.provider || settings.defaultProvider ||
|
|
243
|
+
config.provider || settings.defaultProvider || getDefaultProviderId()
|
|
240
244
|
);
|
|
241
245
|
const containerHome = config.containerHome || settings.dockerContainerHome || '/root';
|
|
242
246
|
|
package/src/orchestrator.js
CHANGED
|
@@ -46,7 +46,7 @@ const { generateName } = require('./name-generator');
|
|
|
46
46
|
const configValidator = require('./config-validator');
|
|
47
47
|
const TemplateResolver = require('./template-resolver');
|
|
48
48
|
const { loadSettings } = require('../lib/settings');
|
|
49
|
-
const { normalizeProviderName } = require('../lib/provider-names');
|
|
49
|
+
const { normalizeProviderName, getDefaultProviderId } = require('../lib/provider-names');
|
|
50
50
|
const { resolveRunPlan } = require('../lib/run-plan');
|
|
51
51
|
const { isProcessRunning } = require('../lib/process-liveness');
|
|
52
52
|
const { getProvider } = require('./providers');
|
|
@@ -270,6 +270,7 @@ class Orchestrator {
|
|
|
270
270
|
|
|
271
271
|
// Track if orchestrator is closed (prevents _saveClusters race conditions during cleanup)
|
|
272
272
|
this.closed = false;
|
|
273
|
+
this._conductorWatchdogs = new Set();
|
|
273
274
|
|
|
274
275
|
// Track if clusters are loaded (for lazy loading pattern)
|
|
275
276
|
this._clustersLoaded = options.skipLoad === true;
|
|
@@ -312,8 +313,8 @@ class Orchestrator {
|
|
|
312
313
|
clusterConfig.forceProvider ||
|
|
313
314
|
clusterConfig.defaultProvider ||
|
|
314
315
|
settings.defaultProvider ||
|
|
315
|
-
|
|
316
|
-
return normalizeProviderName(resolved) ||
|
|
316
|
+
getDefaultProviderId();
|
|
317
|
+
return normalizeProviderName(resolved) || getDefaultProviderId();
|
|
317
318
|
}
|
|
318
319
|
|
|
319
320
|
/**
|
|
@@ -1654,6 +1655,28 @@ class Orchestrator {
|
|
|
1654
1655
|
const timeoutMs = 30000;
|
|
1655
1656
|
let watchdogTimer = null;
|
|
1656
1657
|
let completedAt = null;
|
|
1658
|
+
const watchdog = {
|
|
1659
|
+
clear: () => {
|
|
1660
|
+
if (!watchdogTimer) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
clearTimeout(watchdogTimer);
|
|
1664
|
+
watchdogTimer = null;
|
|
1665
|
+
this._conductorWatchdogs.delete(watchdog);
|
|
1666
|
+
const elapsed = completedAt ? Date.now() - completedAt : 0;
|
|
1667
|
+
this._log(
|
|
1668
|
+
`✅ CLUSTER_OPERATIONS received (${elapsed}ms after conductor completed) - watchdog cleared`
|
|
1669
|
+
);
|
|
1670
|
+
},
|
|
1671
|
+
dispose: () => {
|
|
1672
|
+
if (watchdogTimer) {
|
|
1673
|
+
clearTimeout(watchdogTimer);
|
|
1674
|
+
watchdogTimer = null;
|
|
1675
|
+
}
|
|
1676
|
+
this._conductorWatchdogs.delete(watchdog);
|
|
1677
|
+
},
|
|
1678
|
+
};
|
|
1679
|
+
this._conductorWatchdogs.add(watchdog);
|
|
1657
1680
|
|
|
1658
1681
|
this._subscribeToClusterTopic(messageBus, clusterId, 'AGENT_LIFECYCLE', (message) => {
|
|
1659
1682
|
const event = message.content?.data?.event;
|
|
@@ -1666,6 +1689,11 @@ class Orchestrator {
|
|
|
1666
1689
|
);
|
|
1667
1690
|
|
|
1668
1691
|
watchdogTimer = setTimeout(() => {
|
|
1692
|
+
watchdogTimer = null;
|
|
1693
|
+
this._conductorWatchdogs.delete(watchdog);
|
|
1694
|
+
if (this.closed || messageBus._closed) {
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1669
1697
|
const clusterOps = messageBus.query({
|
|
1670
1698
|
cluster_id: clusterId,
|
|
1671
1699
|
topic: 'CLUSTER_OPERATIONS',
|
|
@@ -1700,19 +1728,7 @@ class Orchestrator {
|
|
|
1700
1728
|
}
|
|
1701
1729
|
});
|
|
1702
1730
|
|
|
1703
|
-
return
|
|
1704
|
-
clear: () => {
|
|
1705
|
-
if (!watchdogTimer) {
|
|
1706
|
-
return;
|
|
1707
|
-
}
|
|
1708
|
-
clearTimeout(watchdogTimer);
|
|
1709
|
-
watchdogTimer = null;
|
|
1710
|
-
const elapsed = completedAt ? Date.now() - completedAt : 0;
|
|
1711
|
-
this._log(
|
|
1712
|
-
`✅ CLUSTER_OPERATIONS received (${elapsed}ms after conductor completed) - watchdog cleared`
|
|
1713
|
-
);
|
|
1714
|
-
},
|
|
1715
|
-
};
|
|
1731
|
+
return watchdog;
|
|
1716
1732
|
}
|
|
1717
1733
|
|
|
1718
1734
|
_registerClusterOperationsHandler(
|
|
@@ -2414,6 +2430,10 @@ class Orchestrator {
|
|
|
2414
2430
|
return;
|
|
2415
2431
|
}
|
|
2416
2432
|
this.closed = true;
|
|
2433
|
+
for (const watchdog of this._conductorWatchdogs) {
|
|
2434
|
+
watchdog.dispose();
|
|
2435
|
+
}
|
|
2436
|
+
this._conductorWatchdogs.clear();
|
|
2417
2437
|
|
|
2418
2438
|
for (const cluster of this.clusters.values()) {
|
|
2419
2439
|
if (typeof cluster.snapshotter?.stop === 'function') {
|
package/src/preflight.js
CHANGED
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
const { loadSettings, getClaudeCommand } = require('../lib/settings.js');
|
|
22
22
|
const {
|
|
23
23
|
VALID_PROVIDERS,
|
|
24
|
+
getDefaultProviderId,
|
|
24
25
|
getProviderMetadata,
|
|
25
26
|
normalizeProviderName,
|
|
26
27
|
resolveProviderCommand,
|
|
@@ -597,7 +598,7 @@ async function runPreflight(options = {}) {
|
|
|
597
598
|
};
|
|
598
599
|
}
|
|
599
600
|
const providerName = normalizeProviderName(
|
|
600
|
-
options.provider || settings.defaultProvider ||
|
|
601
|
+
options.provider || settings.defaultProvider || getDefaultProviderId()
|
|
601
602
|
);
|
|
602
603
|
|
|
603
604
|
const providerResult = validateProvider(providerName, options);
|
|
@@ -111,6 +111,7 @@ const providerSessionCapture = createProviderSessionCapture({
|
|
|
111
111
|
requestedSessionId: persistedTask?.requestedResumeSessionId || null,
|
|
112
112
|
initialSessionId: persistedTask?.sessionId || null,
|
|
113
113
|
initialSessionIdConflict: persistedTask?.sessionIdConflict === true,
|
|
114
|
+
disabled: config.structuredOutputRecovery === true,
|
|
114
115
|
});
|
|
115
116
|
let outputBuffer = '';
|
|
116
117
|
|
|
@@ -14,6 +14,10 @@ const CLEANUP_METADATA_KEYS = ['kind', 'path', 'provider', 'reason'];
|
|
|
14
14
|
const SCHEMA_DIRECTORY_PATTERN = /^zeroshot-schema-[A-Za-z0-9_-]+$/u;
|
|
15
15
|
const SCHEMA_FILE_PATTERN =
|
|
16
16
|
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.json$/u;
|
|
17
|
+
const POLICY_DIRECTORY_PATTERN = /^zeroshot-gemini-policy-[A-Za-z0-9_-]+$/u;
|
|
18
|
+
const POLICY_FILE_PATTERN =
|
|
19
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.toml$/u;
|
|
20
|
+
const OPENCODE_CONFIG_DIRECTORY_PATTERN = /^zeroshot-opencode-config-[A-Za-z0-9_-]+$/u;
|
|
17
21
|
|
|
18
22
|
function assertClosedCleanupMetadata(cleanupPath, metadata) {
|
|
19
23
|
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
|
@@ -59,6 +63,37 @@ function createCleanupPlan(commandSpec) {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
function assertOwnedTempDirectory(cleanupPath, metadata) {
|
|
66
|
+
const isOpenCodeConfig =
|
|
67
|
+
metadata.provider === 'opencode' && metadata.reason === 'isolated-config';
|
|
68
|
+
if (isOpenCodeConfig) {
|
|
69
|
+
const canonical =
|
|
70
|
+
isAbsolute(cleanupPath) &&
|
|
71
|
+
resolve(cleanupPath) === cleanupPath &&
|
|
72
|
+
dirname(cleanupPath) === resolve(tmpdir()) &&
|
|
73
|
+
OPENCODE_CONFIG_DIRECTORY_PATTERN.test(basename(cleanupPath));
|
|
74
|
+
if (!canonical) {
|
|
75
|
+
throw new Error(`Refusing unowned temporary directory cleanup: ${cleanupPath}`);
|
|
76
|
+
}
|
|
77
|
+
let stat;
|
|
78
|
+
try {
|
|
79
|
+
stat = lstatSync(cleanupPath);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error?.code === 'ENOENT') return true;
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
const tempRoot = realpathSync(tmpdir());
|
|
85
|
+
const realDirectory = realpathSync(cleanupPath);
|
|
86
|
+
if (
|
|
87
|
+
stat.isSymbolicLink() ||
|
|
88
|
+
!stat.isDirectory() ||
|
|
89
|
+
dirname(realDirectory) !== tempRoot ||
|
|
90
|
+
basename(realDirectory) !== basename(cleanupPath)
|
|
91
|
+
) {
|
|
92
|
+
throw new Error(`Refusing unowned temporary directory cleanup: ${cleanupPath}`);
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
62
97
|
if (
|
|
63
98
|
metadata.provider !== 'claude' ||
|
|
64
99
|
metadata.reason !== 'settings-overlay' ||
|
|
@@ -79,22 +114,33 @@ function assertOwnedTempDirectory(cleanupPath, metadata) {
|
|
|
79
114
|
}
|
|
80
115
|
|
|
81
116
|
function assertCanonicalSchemaPath(cleanupPath, metadata) {
|
|
117
|
+
const isCodexSchema =
|
|
118
|
+
metadata.kind === 'temp-file' &&
|
|
119
|
+
metadata.provider === 'codex' &&
|
|
120
|
+
metadata.reason === 'output-schema';
|
|
121
|
+
const isGeminiPolicy =
|
|
122
|
+
metadata.kind === 'temp-file' &&
|
|
123
|
+
metadata.provider === 'gemini' &&
|
|
124
|
+
metadata.reason === 'admin-policy';
|
|
82
125
|
if (
|
|
83
|
-
|
|
84
|
-
metadata.provider !== 'codex' ||
|
|
85
|
-
metadata.reason !== 'output-schema' ||
|
|
126
|
+
(!isCodexSchema && !isGeminiPolicy) ||
|
|
86
127
|
!isAbsolute(cleanupPath) ||
|
|
87
128
|
resolve(cleanupPath) !== cleanupPath
|
|
88
129
|
) {
|
|
89
|
-
throw new Error(
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Refusing unowned ${isGeminiPolicy ? 'admin-policy' : 'output-schema'} cleanup: ${cleanupPath}`
|
|
132
|
+
);
|
|
90
133
|
}
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
134
|
+
const ownedDirectory = dirname(cleanupPath);
|
|
135
|
+
const matchesOwnedName = isCodexSchema
|
|
136
|
+
? SCHEMA_DIRECTORY_PATTERN.test(basename(ownedDirectory)) &&
|
|
137
|
+
SCHEMA_FILE_PATTERN.test(basename(cleanupPath))
|
|
138
|
+
: POLICY_DIRECTORY_PATTERN.test(basename(ownedDirectory)) &&
|
|
139
|
+
POLICY_FILE_PATTERN.test(basename(cleanupPath));
|
|
140
|
+
if (dirname(ownedDirectory) !== resolve(tmpdir()) || !matchesOwnedName) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Refusing non-canonical ${isGeminiPolicy ? 'admin-policy' : 'output-schema'} cleanup: ${cleanupPath}`
|
|
143
|
+
);
|
|
98
144
|
}
|
|
99
145
|
}
|
|
100
146
|
|
package/task-lib/commands/run.js
CHANGED
|
@@ -40,6 +40,7 @@ export async function runTask(prompt, options = {}) {
|
|
|
40
40
|
jsonSchema,
|
|
41
41
|
mcpConfig: options.mcpConfig,
|
|
42
42
|
silentJsonOutput,
|
|
43
|
+
structuredOutputRecovery: options.structuredOutputRecovery === true,
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
console.log(chalk.green(`\n✓ Task spawned: ${chalk.cyan(task.id)}`));
|