salmon-loop 0.3.2 → 0.4.1
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/dist/cli/authorization/non-interactive.js +9 -13
- package/dist/cli/chat.js +12 -6
- package/dist/cli/commands/allowlist.js +1 -1
- package/dist/cli/commands/chat.js +13 -13
- package/dist/cli/commands/parallel.js +1 -1
- package/dist/cli/commands/run/handler.js +6 -3
- package/dist/cli/commands/run/loop-params.js +1 -0
- package/dist/cli/commands/run/parse-options.js +14 -26
- package/dist/cli/commands/run/runtime-llm.js +15 -12
- package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
- package/dist/cli/reporters/standard.js +2 -3
- package/dist/cli/reporters/stream-json.js +2 -1
- package/dist/cli/slash/runtime.js +2 -2
- package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
- package/dist/cli/ui/hooks/useLoopState.js +1 -1
- package/dist/core/ast/parser.js +18 -9
- package/dist/core/config/schema.js +738 -0
- package/dist/core/config/validate.js +11 -922
- package/dist/core/context/gatherers/ast-gatherer.js +4 -12
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +3 -0
- package/dist/core/context/service.js +8 -0
- package/dist/core/context/token/encoding-registry.js +7 -6
- package/dist/core/extensions/index.js +48 -3
- package/dist/core/extensions/load.js +3 -2
- package/dist/core/extensions/merge.js +5 -1
- package/dist/core/extensions/paths.js +6 -0
- package/dist/core/extensions/schemas.js +21 -0
- package/dist/core/facades/cli-command-chat.js +2 -0
- package/dist/core/facades/cli-run-handler.js +1 -0
- package/dist/core/facades/cli-utils-serialize.js +2 -0
- package/dist/core/grizzco/dsl/llm-strategy.js +3 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -10
- package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +5 -4
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
- package/dist/core/grizzco/runtime/apply-back-runtime.js +2 -1
- package/dist/core/grizzco/services/registry.js +18 -0
- package/dist/core/grizzco/steps/audit.js +20 -10
- package/dist/core/grizzco/steps/display-report.js +4 -11
- package/dist/core/grizzco/steps/explore.js +9 -2
- package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
- package/dist/core/grizzco/steps/patch.js +1 -0
- package/dist/core/grizzco/steps/plan.js +58 -49
- package/dist/core/grizzco/steps/tool-runtime.js +3 -0
- package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
- package/dist/core/llm/ai-sdk/message-mapper.js +24 -18
- package/dist/core/llm/ai-sdk/request-params.js +1 -3
- package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
- package/dist/core/llm/ai-sdk/retry-classifier.js +6 -4
- package/dist/core/llm/contracts/repair.js +16 -8
- package/dist/core/llm/errors.js +13 -10
- package/dist/core/llm/output-policy.js +8 -0
- package/dist/core/llm/redact.js +1 -3
- package/dist/core/llm/sub-agent-factory.js +48 -0
- package/dist/core/llm/tool-calling-stub.js +48 -0
- package/dist/core/llm/utils.js +17 -6
- package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
- package/dist/core/mcp/bridge/tool-bridge.js +5 -14
- package/dist/core/mcp/client/connection-manager.js +3 -2
- package/dist/core/mcp/host/sampling-provider.js +1 -1
- package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
- package/dist/core/memory/relevant-retrieval.js +6 -4
- package/dist/core/observability/authorization-decisions.js +13 -12
- package/dist/core/observability/error-mapping.js +2 -1
- package/dist/core/observability/token-usage.js +5 -4
- package/dist/core/plugin/loader.js +5 -4
- package/dist/core/prompts/registry.js +11 -29
- package/dist/core/protocols/a2a/sdk/server.js +2 -3
- package/dist/core/protocols/acp/formal-agent.js +10 -4
- package/dist/core/protocols/acp/stdio-server.js +6 -6
- package/dist/core/runtime/agent-server-runtime.js +3 -2
- package/dist/core/runtime/initialize.js +70 -6
- package/dist/core/session/compaction/index.js +4 -3
- package/dist/core/session/manager.js +24 -37
- package/dist/core/session/token-tracker.js +18 -7
- package/dist/core/skills/parser.js +3 -2
- package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
- package/dist/core/skills/runtime/SkillRunner.js +5 -2
- package/dist/core/slash/steps/slash-execute.js +7 -5
- package/dist/core/slash/strategy.js +1 -1
- package/dist/core/strata/layers/worktree.js +7 -9
- package/dist/core/strata/runtime/synchronizer.js +10 -9
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
- package/dist/core/structured-output/json-schema-validator.js +1 -13
- package/dist/core/sub-agent/context-snapshot.js +12 -6
- package/dist/core/sub-agent/controller.js +70 -1
- package/dist/core/sub-agent/core/loop.js +25 -3
- package/dist/core/sub-agent/core/manager.js +319 -116
- package/dist/core/sub-agent/registry-defaults.js +12 -0
- package/dist/core/sub-agent/registry.js +8 -0
- package/dist/core/sub-agent/team.js +98 -0
- package/dist/core/sub-agent/tools/task-await.js +109 -0
- package/dist/core/sub-agent/tools/task-spawn.js +49 -7
- package/dist/core/sub-agent/tools/team.js +92 -0
- package/dist/core/sub-agent/types.js +11 -2
- package/dist/core/tools/budget.js +4 -11
- package/dist/core/tools/builtin/code-search/executor.js +46 -43
- package/dist/core/tools/builtin/fs.js +14 -6
- package/dist/core/tools/builtin/index.js +41 -107
- package/dist/core/tools/builtin/interaction.js +13 -15
- package/dist/core/tools/builtin/proposal.js +11 -2
- package/dist/core/tools/capability/executor.js +5 -5
- package/dist/core/tools/headless-payload.js +1 -3
- package/dist/core/tools/mapper.js +8 -42
- package/dist/core/tools/parallel/persistence.js +17 -5
- package/dist/core/tools/parallel/scheduler.js +23 -21
- package/dist/core/tools/permissions/permission-rules.js +66 -114
- package/dist/core/tools/plugins/loader.js +4 -3
- package/dist/core/tools/router.js +24 -53
- package/dist/core/tools/session.js +54 -97
- package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
- package/dist/core/tools/tool-visibility.js +2 -1
- package/dist/core/tools/types.js +10 -0
- package/dist/core/utils/error.js +79 -0
- package/dist/core/utils/serialize.js +63 -0
- package/dist/core/utils/zod.js +29 -0
- package/dist/core/workspace/capabilities.js +3 -2
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
- package/dist/locales/en.js +2 -1
- package/package.json +1 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { agentAwaitTaskSpec } from '../../sub-agent/tools/task-await.js';
|
|
1
2
|
import { subAgentTaskSpec } from '../../sub-agent/tools/task-spawn.js';
|
|
3
|
+
import { agentTeamSpec } from '../../sub-agent/tools/team.js';
|
|
4
|
+
import { defineTool } from '../types.js';
|
|
2
5
|
import { artifactReadSpec, executeArtifactRead } from './artifact.js';
|
|
3
6
|
import { astGrepSpec, executeAstGrep } from './ast-grep.js';
|
|
4
7
|
import { astDefsRefsSpec, executeAstDefsRefs } from './ast.js';
|
|
@@ -15,116 +18,47 @@ import { shellExecSpec, executeShellExec } from './shell.js';
|
|
|
15
18
|
import { verifyRunSpec, executeVerifyRun } from './verify.js';
|
|
16
19
|
import { workspaceInfoSpec, executeWorkspaceInfo } from './workspace.js';
|
|
17
20
|
/**
|
|
18
|
-
* Registers all builtin tools into the provided registry
|
|
21
|
+
* Registers all builtin tools into the provided registry.
|
|
22
|
+
* Uses defineTool() to pair specs with executors type-safely.
|
|
19
23
|
*/
|
|
20
24
|
export function registerAllBuiltins(registry) {
|
|
21
|
-
//
|
|
25
|
+
// Sub-agent tools (already self-contained)
|
|
22
26
|
registry.register(subAgentTaskSpec);
|
|
23
|
-
registry.register(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
registry.register(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
registry.register(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
registry.register(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
registry.register(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
registry.register(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
registry.register(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
registry.register(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
registry.register(
|
|
57
|
-
|
|
58
|
-
executor: executeGitDiffCheck,
|
|
59
|
-
});
|
|
60
|
-
registry.register({
|
|
61
|
-
...gitApplyCheckSpec,
|
|
62
|
-
executor: executeGitApplyCheck,
|
|
63
|
-
});
|
|
64
|
-
registry.register({
|
|
65
|
-
...benchmarkReportSpec,
|
|
66
|
-
executor: executeBenchmarkReport,
|
|
67
|
-
});
|
|
68
|
-
registry.register({
|
|
69
|
-
...sweBenchLoadInstanceSpec,
|
|
70
|
-
executor: executeSweBenchLoadInstance,
|
|
71
|
-
});
|
|
72
|
-
registry.register({
|
|
73
|
-
...sweBenchWritePredictionSpec,
|
|
74
|
-
executor: executeSweBenchWritePrediction,
|
|
75
|
-
});
|
|
76
|
-
registry.register({
|
|
77
|
-
...sweBenchSubmitPredictionsSpec,
|
|
78
|
-
executor: executeSweBenchSubmitPredictions,
|
|
79
|
-
});
|
|
80
|
-
registry.register({
|
|
81
|
-
...sweBenchGetReportSpec,
|
|
82
|
-
executor: executeSweBenchGetReport,
|
|
83
|
-
});
|
|
84
|
-
registry.register({
|
|
85
|
-
...fsReadFileSpec,
|
|
86
|
-
executor: executeFsReadFile,
|
|
87
|
-
});
|
|
88
|
-
registry.register({
|
|
89
|
-
...codeReadSpec,
|
|
90
|
-
executor: executeFsReadFile,
|
|
91
|
-
});
|
|
92
|
-
registry.register({
|
|
93
|
-
...fsListSpec,
|
|
94
|
-
executor: executeFsList,
|
|
95
|
-
});
|
|
96
|
-
registry.register({
|
|
97
|
-
...fsListDirectorySpec,
|
|
98
|
-
executor: executeFsListDirectory,
|
|
99
|
-
});
|
|
100
|
-
registry.register({
|
|
101
|
-
...fsListFilesSpec,
|
|
102
|
-
executor: executeFsListFiles,
|
|
103
|
-
});
|
|
104
|
-
registry.register({
|
|
105
|
-
...astGrepSpec,
|
|
106
|
-
executor: executeAstGrep,
|
|
107
|
-
});
|
|
108
|
-
registry.register({
|
|
109
|
-
...verifyRunSpec,
|
|
110
|
-
executor: executeVerifyRun,
|
|
111
|
-
});
|
|
112
|
-
registry.register({
|
|
113
|
-
...shellExecSpec,
|
|
114
|
-
executor: executeShellExec,
|
|
115
|
-
});
|
|
116
|
-
registry.register({
|
|
117
|
-
...fsWriteFileSpec,
|
|
118
|
-
executor: executeFsWriteFile,
|
|
119
|
-
});
|
|
120
|
-
registry.register({
|
|
121
|
-
...fsCreateDirectorySpec,
|
|
122
|
-
executor: executeFsCreateDirectory,
|
|
123
|
-
});
|
|
124
|
-
registry.register({
|
|
125
|
-
...fsDeleteFileSpec,
|
|
126
|
-
executor: executeFsDeleteFile,
|
|
127
|
-
});
|
|
27
|
+
registry.register(agentAwaitTaskSpec);
|
|
28
|
+
registry.register(agentTeamSpec);
|
|
29
|
+
// Artifact & knowledge
|
|
30
|
+
registry.register(defineTool(artifactReadSpec, executeArtifactRead));
|
|
31
|
+
registry.register(defineTool(updateKnowledgeSpec, executeUpdateKnowledge));
|
|
32
|
+
registry.register(defineTool(workspaceInfoSpec, executeWorkspaceInfo));
|
|
33
|
+
registry.register(defineTool(proposalApplySpec, executeProposalApply));
|
|
34
|
+
// Code search & AST
|
|
35
|
+
registry.register(defineTool(CodeSearchSpec, codeSearchExecutor));
|
|
36
|
+
registry.register(defineTool(astDefsRefsSpec, executeAstDefsRefs));
|
|
37
|
+
registry.register(defineTool(astGrepSpec, executeAstGrep));
|
|
38
|
+
// Git
|
|
39
|
+
registry.register(defineTool(gitCatSpec, executeGitCat));
|
|
40
|
+
registry.register(defineTool(gitStatusSpec, executeGitStatus));
|
|
41
|
+
registry.register(defineTool(gitDiffCheckSpec, executeGitDiffCheck));
|
|
42
|
+
registry.register(defineTool(gitApplyCheckSpec, executeGitApplyCheck));
|
|
43
|
+
// Benchmark / SWE-bench
|
|
44
|
+
registry.register(defineTool(benchmarkReportSpec, executeBenchmarkReport));
|
|
45
|
+
registry.register(defineTool(sweBenchLoadInstanceSpec, executeSweBenchLoadInstance));
|
|
46
|
+
registry.register(defineTool(sweBenchWritePredictionSpec, executeSweBenchWritePrediction));
|
|
47
|
+
registry.register(defineTool(sweBenchSubmitPredictionsSpec, executeSweBenchSubmitPredictions));
|
|
48
|
+
registry.register(defineTool(sweBenchGetReportSpec, executeSweBenchGetReport));
|
|
49
|
+
// Filesystem
|
|
50
|
+
registry.register(defineTool(fsReadFileSpec, executeFsReadFile));
|
|
51
|
+
registry.register(defineTool(codeReadSpec, executeFsReadFile));
|
|
52
|
+
registry.register(defineTool(fsListSpec, executeFsList));
|
|
53
|
+
registry.register(defineTool(fsListDirectorySpec, executeFsListDirectory));
|
|
54
|
+
registry.register(defineTool(fsListFilesSpec, executeFsListFiles));
|
|
55
|
+
registry.register(defineTool(fsWriteFileSpec, executeFsWriteFile));
|
|
56
|
+
registry.register(defineTool(fsCreateDirectorySpec, executeFsCreateDirectory));
|
|
57
|
+
registry.register(defineTool(fsDeleteFileSpec, executeFsDeleteFile));
|
|
58
|
+
// Execution
|
|
59
|
+
registry.register(defineTool(verifyRunSpec, executeVerifyRun));
|
|
60
|
+
registry.register(defineTool(shellExecSpec, executeShellExec));
|
|
61
|
+
// Plan & interaction
|
|
128
62
|
registry.register(planInitSpec);
|
|
129
63
|
registry.register(planReadSpec);
|
|
130
64
|
registry.register(planUpdateSpec);
|
|
@@ -75,28 +75,26 @@ export const askUserSpec = {
|
|
|
75
75
|
outputSchema: askUserOutputSchema,
|
|
76
76
|
executor: async (input, ctx) => {
|
|
77
77
|
if (ctx.agentKind === 'subagent') {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
throw Object.assign(new Error(text.tools.askUserSubagentBlocked), {
|
|
79
|
+
code: 'ASK_USER_SUBAGENT_BLOCKED',
|
|
80
|
+
});
|
|
81
81
|
}
|
|
82
82
|
if (!ctx.userInputProvider) {
|
|
83
|
-
const err = new Error(text.tools.askUserRequired);
|
|
84
83
|
const inputRequired = buildInputRequired(input);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
throw Object.assign(new Error(text.tools.askUserRequired), {
|
|
85
|
+
code: 'INTERRUPT_REQUIRED',
|
|
86
|
+
interrupt: {
|
|
87
|
+
type: 'awaiting_input',
|
|
88
|
+
reason: inputRequired.reason ?? 'clarification',
|
|
89
|
+
prompt: inputRequired.prompt,
|
|
90
|
+
data: { inputRequired },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
93
|
}
|
|
94
94
|
const output = await ctx.userInputProvider.askUser(input, { signal: ctx.signal });
|
|
95
95
|
const validationError = validateAnswers(input, output.answers);
|
|
96
96
|
if (validationError) {
|
|
97
|
-
|
|
98
|
-
err.code = 'INVALID_OUTPUT';
|
|
99
|
-
throw err;
|
|
97
|
+
throw Object.assign(new Error(validationError), { code: 'INVALID_OUTPUT' });
|
|
100
98
|
}
|
|
101
99
|
return { questions: input.questions, answers: output.answers };
|
|
102
100
|
},
|
|
@@ -14,6 +14,7 @@ import { getRejectionsDir } from '../../runtime/paths.js';
|
|
|
14
14
|
import { FileStateResolver } from '../../strata/layers/file-state-resolver.js';
|
|
15
15
|
import { ArtifactStore } from '../../sub-agent/artifacts/store.js';
|
|
16
16
|
import { Phase } from '../../types/runtime.js';
|
|
17
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
17
18
|
function bootstrapRegistry() {
|
|
18
19
|
if (!registry.has('remote_lock'))
|
|
19
20
|
registry.register(new MockLockService());
|
|
@@ -48,7 +49,7 @@ export const proposalApplySpec = {
|
|
|
48
49
|
// challenge-response authorization without violating the execution contract.
|
|
49
50
|
allowedPhases: [Phase.VERIFY],
|
|
50
51
|
summarizeArgsForAuthorization: async (args, _ctx) => {
|
|
51
|
-
const handle = args
|
|
52
|
+
const handle = isRecord(args) && typeof args.handle === 'string' ? args.handle : undefined;
|
|
52
53
|
if (!handle)
|
|
53
54
|
return undefined;
|
|
54
55
|
const read = await ArtifactStore.readText(handle);
|
|
@@ -99,7 +100,15 @@ export async function executeProposalApply(input, ctx) {
|
|
|
99
100
|
for (const op of operations) {
|
|
100
101
|
const fileState = stateMap.get(op.path);
|
|
101
102
|
const fileInfo = fileState
|
|
102
|
-
? {
|
|
103
|
+
? {
|
|
104
|
+
path: fileState.path,
|
|
105
|
+
status: fileState.status,
|
|
106
|
+
isBinary: fileState.isBinary,
|
|
107
|
+
isSymlink: fileState.isSymlink,
|
|
108
|
+
isIgnored: fileState.isIgnored,
|
|
109
|
+
hasConflict: fileState.status === FileStatus.CONFLICT,
|
|
110
|
+
size: fileState.size,
|
|
111
|
+
}
|
|
103
112
|
: {
|
|
104
113
|
path: op.path,
|
|
105
114
|
status: FileStatus.CLEAN,
|
|
@@ -75,10 +75,10 @@ export async function runWithFallback(backends, input, ctx, opts) {
|
|
|
75
75
|
throw new Error(`All backends failed for capability. Tried: ${JSON.stringify(meta.tried)}`);
|
|
76
76
|
}
|
|
77
77
|
function createBackendError(backendId, fail, meta) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
return Object.assign(new Error(`Backend ${backendId} failed: [${fail.code}] ${fail.message}`), {
|
|
79
|
+
backendId,
|
|
80
|
+
failCode: fail.code,
|
|
81
|
+
meta,
|
|
82
|
+
});
|
|
83
83
|
}
|
|
84
84
|
//# sourceMappingURL=executor.js.map
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { redactValue } from '../llm/redact.js';
|
|
2
|
-
|
|
3
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
-
}
|
|
2
|
+
import { isRecord } from '../utils/serialize.js';
|
|
5
3
|
function limitValue(value, params) {
|
|
6
4
|
if (params.depth >= params.maxDepth)
|
|
7
5
|
return '[Truncated]';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { unwrapZodSchema } from '../utils/zod.js';
|
|
2
3
|
function formatToolExamplesForDescription(spec) {
|
|
3
4
|
if (!Array.isArray(spec.examples) || spec.examples.length === 0)
|
|
4
5
|
return '';
|
|
@@ -13,37 +14,8 @@ function formatToolExamplesForDescription(spec) {
|
|
|
13
14
|
function toolDescriptionForModel(spec) {
|
|
14
15
|
return `${spec.description}${formatToolExamplesForDescription(spec)}`;
|
|
15
16
|
}
|
|
16
|
-
function unwrapForSchemaGeneration(schema) {
|
|
17
|
-
let current = schema;
|
|
18
|
-
for (let depth = 0; depth < 20; depth++) {
|
|
19
|
-
const ZodEffects = z.ZodEffects;
|
|
20
|
-
if (typeof ZodEffects === 'function' && current instanceof ZodEffects) {
|
|
21
|
-
current = current._def.schema;
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (current instanceof z.ZodPipe) {
|
|
25
|
-
// z.preprocess in Zod v4 produces a ZodPipe(in=ZodTransform, out=<schema>).
|
|
26
|
-
current = current._def.out;
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (current instanceof z.ZodOptional) {
|
|
30
|
-
current = current._def.innerType;
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (current instanceof z.ZodNullable) {
|
|
34
|
-
current = current._def.innerType;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (current instanceof z.ZodDefault) {
|
|
38
|
-
current = current._def.innerType;
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
return current;
|
|
44
|
-
}
|
|
45
17
|
function zodToOpenApi3(schema) {
|
|
46
|
-
const unwrapped =
|
|
18
|
+
const unwrapped = unwrapZodSchema(schema);
|
|
47
19
|
const description = unwrapped.description;
|
|
48
20
|
if (unwrapped instanceof z.ZodObject) {
|
|
49
21
|
const shape = unwrapped.shape;
|
|
@@ -64,34 +36,28 @@ function zodToOpenApi3(schema) {
|
|
|
64
36
|
return out;
|
|
65
37
|
}
|
|
66
38
|
if (unwrapped instanceof z.ZodArray) {
|
|
67
|
-
const items = zodToOpenApi3(unwrapped.
|
|
39
|
+
const items = zodToOpenApi3(unwrapped.element);
|
|
68
40
|
const out = { type: 'array', items };
|
|
69
41
|
if (description)
|
|
70
42
|
out.description = description;
|
|
71
43
|
return out;
|
|
72
44
|
}
|
|
73
45
|
if (unwrapped instanceof z.ZodEnum) {
|
|
74
|
-
const options = unwrapped.options
|
|
75
|
-
|
|
76
|
-
if (Array.isArray(options)) {
|
|
77
|
-
values = options.map(String);
|
|
78
|
-
}
|
|
79
|
-
else if (options && typeof options === 'object') {
|
|
80
|
-
values = Object.values(options).map(String);
|
|
81
|
-
}
|
|
46
|
+
const options = unwrapped.options;
|
|
47
|
+
const values = options.map(String);
|
|
82
48
|
const out = values.length > 0 ? { type: 'string', enum: values } : { type: 'string' };
|
|
83
49
|
if (description)
|
|
84
50
|
out.description = description;
|
|
85
51
|
return out;
|
|
86
52
|
}
|
|
87
53
|
if (unwrapped instanceof z.ZodLiteral) {
|
|
88
|
-
const out = { const: unwrapped.
|
|
54
|
+
const out = { const: unwrapped.value };
|
|
89
55
|
if (description)
|
|
90
56
|
out.description = description;
|
|
91
57
|
return out;
|
|
92
58
|
}
|
|
93
59
|
if (unwrapped instanceof z.ZodUnion) {
|
|
94
|
-
const options = unwrapped.
|
|
60
|
+
const options = unwrapped.options;
|
|
95
61
|
const out = { oneOf: options.map((o) => zodToOpenApi3(o)) };
|
|
96
62
|
if (description)
|
|
97
63
|
out.description = description;
|
|
@@ -110,7 +76,7 @@ function zodToOpenApi3(schema) {
|
|
|
110
76
|
return out;
|
|
111
77
|
}
|
|
112
78
|
if (unwrapped instanceof z.ZodNumber) {
|
|
113
|
-
const isInt =
|
|
79
|
+
const isInt = unwrapped.isInt;
|
|
114
80
|
const out = { type: isInt ? 'integer' : 'number' };
|
|
115
81
|
if (description)
|
|
116
82
|
out.description = description;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { syncFs as fs } from '../../adapters/fs/node-fs.js';
|
|
3
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
4
|
+
function isPersistedPlanState(value) {
|
|
5
|
+
return (isRecord(value) &&
|
|
6
|
+
isRecord(value.plan) &&
|
|
7
|
+
isRecord(value.result) &&
|
|
8
|
+
typeof value.updatedAt === 'string');
|
|
9
|
+
}
|
|
3
10
|
/**
|
|
4
11
|
* Persistence layer for parallel execution plans.
|
|
5
12
|
* Supports saving, loading, and listing plans from the .salmonloop/parallel directory.
|
|
@@ -41,10 +48,13 @@ export class PlanPersistence {
|
|
|
41
48
|
const filePath = path.join(this.getPersistenceDir(repoRoot), `${planId}.json`);
|
|
42
49
|
try {
|
|
43
50
|
const content = await fs.readFile(filePath, 'utf8');
|
|
44
|
-
|
|
51
|
+
const parsed = JSON.parse(content);
|
|
52
|
+
if (!isPersistedPlanState(parsed))
|
|
53
|
+
return null;
|
|
54
|
+
return parsed;
|
|
45
55
|
}
|
|
46
56
|
catch (_error) {
|
|
47
|
-
if (_error.code === 'ENOENT') {
|
|
57
|
+
if (isRecord(_error) && _error.code === 'ENOENT') {
|
|
48
58
|
return null;
|
|
49
59
|
}
|
|
50
60
|
throw _error;
|
|
@@ -63,7 +73,9 @@ export class PlanPersistence {
|
|
|
63
73
|
for (const file of jsonFiles) {
|
|
64
74
|
try {
|
|
65
75
|
const content = await fs.readFile(path.join(dir, file), 'utf8');
|
|
66
|
-
|
|
76
|
+
const parsed = JSON.parse(content);
|
|
77
|
+
if (isPersistedPlanState(parsed))
|
|
78
|
+
states.push(parsed);
|
|
67
79
|
}
|
|
68
80
|
catch (_error) {
|
|
69
81
|
// Skip malformed or unreadable files
|
|
@@ -73,7 +85,7 @@ export class PlanPersistence {
|
|
|
73
85
|
return states;
|
|
74
86
|
}
|
|
75
87
|
catch (_error) {
|
|
76
|
-
if (_error.code === 'ENOENT') {
|
|
88
|
+
if (isRecord(_error) && _error.code === 'ENOENT') {
|
|
77
89
|
return [];
|
|
78
90
|
}
|
|
79
91
|
throw _error;
|
|
@@ -117,7 +129,7 @@ export class PlanPersistence {
|
|
|
117
129
|
await fs.unlink(filePath);
|
|
118
130
|
}
|
|
119
131
|
catch (_error) {
|
|
120
|
-
if (_error.code !== 'ENOENT') {
|
|
132
|
+
if (isRecord(_error) && _error.code !== 'ENOENT') {
|
|
121
133
|
throw _error;
|
|
122
134
|
}
|
|
123
135
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Phase } from '../../types/runtime.js';
|
|
2
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
1
3
|
import { isRecoverableToolInputErrorCode } from '../recoverable-tool-errors.js';
|
|
2
4
|
import { IsolationManager } from './isolation.js';
|
|
3
5
|
import { resolveArgsWithResults } from './resolve-args.js';
|
|
@@ -14,8 +16,7 @@ export class ParallelScheduler {
|
|
|
14
16
|
tryResolveSpec(node) {
|
|
15
17
|
if (node.spec)
|
|
16
18
|
return node.spec;
|
|
17
|
-
const
|
|
18
|
-
const spec = typeof router.getSpec === 'function' ? router.getSpec(node.toolName) : undefined;
|
|
19
|
+
const spec = this.router.getSpec?.(node.toolName);
|
|
19
20
|
if (!spec)
|
|
20
21
|
return undefined;
|
|
21
22
|
node.spec = spec;
|
|
@@ -28,6 +29,8 @@ export class ParallelScheduler {
|
|
|
28
29
|
return parsed.success ? parsed.data : args;
|
|
29
30
|
}
|
|
30
31
|
shouldFallbackFromComputeResources(spec, args, error) {
|
|
32
|
+
if (!spec.inputSchema || typeof spec.inputSchema.safeParse !== 'function')
|
|
33
|
+
return false;
|
|
31
34
|
const parsed = spec.inputSchema.safeParse(args);
|
|
32
35
|
if (parsed.success)
|
|
33
36
|
return false;
|
|
@@ -35,9 +38,7 @@ export class ParallelScheduler {
|
|
|
35
38
|
if (issueCode === 'invalid_type' || issueCode === 'invalid_union' || issueCode === 'custom') {
|
|
36
39
|
return true;
|
|
37
40
|
}
|
|
38
|
-
const errorCode = typeof error === '
|
|
39
|
-
? error.code
|
|
40
|
-
: undefined;
|
|
41
|
+
const errorCode = isRecord(error) && typeof error.code === 'string' ? error.code : undefined;
|
|
41
42
|
return isRecoverableToolInputErrorCode(errorCode);
|
|
42
43
|
}
|
|
43
44
|
deriveDefaultResources(spec, ctx) {
|
|
@@ -139,7 +140,7 @@ export class ParallelScheduler {
|
|
|
139
140
|
try {
|
|
140
141
|
const spec = this.tryResolveSpec(node);
|
|
141
142
|
if (!spec) {
|
|
142
|
-
const phase = typeof baseCtx.phase === 'string' ? baseCtx.phase : undefined;
|
|
143
|
+
const phase = isRecord(baseCtx) && typeof baseCtx.phase === 'string' ? baseCtx.phase : undefined;
|
|
143
144
|
const toolResult = {
|
|
144
145
|
id: nodeId,
|
|
145
146
|
toolName: node.toolName,
|
|
@@ -185,15 +186,14 @@ export class ParallelScheduler {
|
|
|
185
186
|
const resolvedArgs = resolveArgsWithResults(node.args, nodeResults);
|
|
186
187
|
const normalizedArgs = this.normalizeArgsForSpec(spec, resolvedArgs);
|
|
187
188
|
// 1.5 Deferred authorization preflight (avoid holding locks while waiting for user)
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
: null;
|
|
189
|
+
const phase = isRecord(baseCtx) && typeof baseCtx.phase === 'string' ? baseCtx.phase : Phase.EXPLORE;
|
|
190
|
+
const preflight = (await this.router.preflightDeferredAuthorization?.({
|
|
191
|
+
id: nodeId,
|
|
192
|
+
phase,
|
|
193
|
+
toolName: node.toolName,
|
|
194
|
+
args: normalizedArgs,
|
|
195
|
+
ctx: baseCtx,
|
|
196
|
+
})) ?? null;
|
|
197
197
|
if (preflight?.kind === 'pending') {
|
|
198
198
|
nodeStates.set(nodeId, 'BLOCKED_APPROVAL');
|
|
199
199
|
const approval = {
|
|
@@ -250,7 +250,7 @@ export class ParallelScheduler {
|
|
|
250
250
|
const runStart = Date.now();
|
|
251
251
|
const result = await this.router.call({
|
|
252
252
|
id: nodeId,
|
|
253
|
-
phase
|
|
253
|
+
phase,
|
|
254
254
|
toolName: node.toolName,
|
|
255
255
|
args: normalizedArgs,
|
|
256
256
|
ctx: isolatedEnv
|
|
@@ -275,7 +275,9 @@ export class ParallelScheduler {
|
|
|
275
275
|
toolName: node.toolName,
|
|
276
276
|
riskLevel: spec.riskLevel,
|
|
277
277
|
message: result.error.message || 'Approval required',
|
|
278
|
-
confirmToken: result.error
|
|
278
|
+
confirmToken: isRecord(result.error)
|
|
279
|
+
? result.error.confirmToken
|
|
280
|
+
: undefined,
|
|
279
281
|
};
|
|
280
282
|
nodeResults[nodeId] = {
|
|
281
283
|
status: 'BLOCKED_APPROVAL',
|
|
@@ -307,18 +309,18 @@ export class ParallelScheduler {
|
|
|
307
309
|
catch (e) {
|
|
308
310
|
nodeStates.set(nodeId, 'FAILED');
|
|
309
311
|
const error = e instanceof Error
|
|
310
|
-
? { code: 'EXECUTION_ERROR', message: e.message,
|
|
311
|
-
: { code: 'EXECUTION_ERROR', message: String(e) };
|
|
312
|
+
? { code: 'EXECUTION_ERROR', message: e.message, retryable: false }
|
|
313
|
+
: { code: 'EXECUTION_ERROR', message: String(e), retryable: false };
|
|
312
314
|
const toolResult = {
|
|
313
315
|
id: nodeId,
|
|
314
316
|
toolName: node.toolName,
|
|
315
317
|
source: 'builtin',
|
|
316
318
|
status: 'error',
|
|
317
|
-
error
|
|
319
|
+
error,
|
|
318
320
|
};
|
|
319
321
|
nodeResults[nodeId] = {
|
|
320
322
|
status: 'FAILED',
|
|
321
|
-
error
|
|
323
|
+
error,
|
|
322
324
|
toolResult,
|
|
323
325
|
timing: { lockWaitMs: 0, runMs: 0 },
|
|
324
326
|
};
|