salmon-loop 0.2.3 → 0.2.16
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/argv/headless-detection.js +27 -0
- package/dist/cli/chat-flow.js +11 -0
- package/dist/cli/chat.js +161 -24
- package/dist/cli/commands/chat.js +30 -24
- package/dist/cli/commands/context.js +15 -3
- package/dist/cli/commands/flow-mode.js +63 -0
- package/dist/cli/commands/help-format.js +12 -0
- package/dist/cli/commands/registry.js +6 -7
- package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
- package/dist/cli/commands/run/config-resolution.js +30 -24
- package/dist/cli/commands/run/early-errors.js +23 -0
- package/dist/cli/commands/run/handler.js +131 -44
- package/dist/cli/commands/run/headless-error-writer.js +8 -0
- package/dist/cli/commands/run/loop-params.js +3 -0
- package/dist/cli/commands/run/mode.js +2 -5
- package/dist/cli/commands/run/parse-options.js +18 -2
- package/dist/cli/commands/run/persist-session.js +10 -1
- package/dist/cli/commands/run/preflight.js +10 -0
- package/dist/cli/commands/run/reporter-factory.js +4 -0
- package/dist/cli/commands/run/runtime-llm.js +38 -11
- package/dist/cli/commands/run/runtime-options.js +2 -2
- package/dist/cli/commands/run/validate-options.js +0 -5
- package/dist/cli/commands/run/verbose.js +2 -7
- package/dist/cli/commands/serve.js +117 -90
- package/dist/cli/commands/tool-names.js +78 -78
- package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/json-protocol.js +37 -0
- package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/protocol-metadata.js +22 -0
- package/dist/cli/headless/stream-json-protocol.js +34 -1
- package/dist/cli/index.js +6 -4
- package/dist/cli/locales/en.js +32 -6
- package/dist/cli/program-bootstrap.js +14 -4
- package/dist/cli/program-commands.js +9 -1
- package/dist/cli/program-options.js +1 -0
- package/dist/cli/reporters/anthropic-stream.js +7 -1
- package/dist/cli/reporters/json.js +4 -0
- package/dist/cli/reporters/stream-json.js +17 -2
- package/dist/cli/run-cli.js +5 -3
- package/dist/cli/slash/runtime.js +30 -15
- package/dist/cli/ui/components/CommandInput.js +7 -3
- package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
- package/dist/cli/utils/command-option-source.js +13 -0
- package/dist/cli/utils/output-format.js +6 -0
- package/dist/cli/utils/resolve-cli-config.js +98 -0
- package/dist/cli/utils/verbose-level.js +8 -0
- package/dist/cli/utils/verify-resolver.js +8 -4
- package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
- package/dist/core/adapters/fs/file-adapter.js +6 -0
- package/dist/core/adapters/fs/filesystem.js +2 -1
- package/dist/core/adapters/git/git-adapter.js +78 -1
- package/dist/core/benchmark/patch-artifact.js +124 -0
- package/dist/core/benchmark/swe-bench.js +25 -0
- package/dist/core/config/load.js +39 -18
- package/dist/core/config/merge.js +27 -0
- package/dist/core/config/paths.js +24 -5
- package/dist/core/config/resolve-llm.js +12 -0
- package/dist/core/config/resolve.js +7 -5
- package/dist/core/config/resolvers/server.js +0 -6
- package/dist/core/config/validate.js +94 -21
- package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
- package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
- package/dist/core/context/keywords.js +18 -4
- package/dist/core/context/service-deps.js +2 -2
- package/dist/core/context/service.js +8 -0
- package/dist/core/context/steps/context-gather.js +38 -0
- package/dist/core/context/summarization/summarizer.js +55 -12
- package/dist/core/context/targeting/target-resolver.js +4 -4
- package/dist/core/extensions/index.js +23 -5
- package/dist/core/extensions/paths.js +31 -0
- package/dist/core/extensions/schemas.js +8 -5
- package/dist/core/facades/cli-chat.js +6 -2
- package/dist/core/facades/cli-command-chat.js +2 -1
- package/dist/core/facades/cli-command-tool-names.js +2 -0
- package/dist/core/facades/cli-context.js +1 -0
- package/dist/core/facades/cli-observability.js +1 -1
- package/dist/core/facades/cli-run-handler.js +4 -2
- package/dist/core/facades/cli-run-persist-session.js +1 -0
- package/dist/core/facades/cli-serve.js +2 -4
- package/dist/core/facades/cli-utils-worktree.js +1 -1
- package/dist/core/failure/diagnostics.js +53 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
- package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
- package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +173 -7
- package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
- package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
- package/dist/core/grizzco/steps/answer.js +13 -14
- package/dist/core/grizzco/steps/autopilot.js +396 -0
- package/dist/core/grizzco/steps/cache-sharing.js +29 -0
- package/dist/core/grizzco/steps/explore.js +37 -21
- package/dist/core/grizzco/steps/generateReview.js +2 -5
- package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
- package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
- package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
- package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
- package/dist/core/grizzco/steps/patch.js +105 -146
- package/dist/core/grizzco/steps/plan.js +101 -25
- package/dist/core/grizzco/steps/preflight.js +5 -3
- package/dist/core/grizzco/steps/request-assembly.js +78 -0
- package/dist/core/grizzco/steps/research.js +39 -36
- package/dist/core/grizzco/steps/tool-runtime.js +47 -0
- package/dist/core/grizzco/steps/verify-shared.js +23 -0
- package/dist/core/grizzco/steps/verify.js +13 -21
- package/dist/core/intent/chat-intent.js +0 -4
- package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
- package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
- package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
- package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
- package/dist/core/llm/ai-sdk/request-params.js +74 -1
- package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
- package/dist/core/llm/ai-sdk.js +112 -27
- package/dist/core/llm/capabilities.js +12 -0
- package/dist/core/llm/contracts/repair.js +36 -30
- package/dist/core/llm/errors.js +83 -2
- package/dist/core/llm/message-composition.js +7 -22
- package/dist/core/llm/phase-router.js +29 -10
- package/dist/core/llm/redact.js +28 -3
- package/dist/core/llm/registry.js +2 -0
- package/dist/core/llm/request-augmentation.js +55 -0
- package/dist/core/llm/request-envelope.js +334 -0
- package/dist/core/llm/shared-request-assembly.js +35 -0
- package/dist/core/llm/stream-utils.js +13 -4
- package/dist/core/llm/utils.js +18 -29
- package/dist/core/memory/relevant-retrieval.js +144 -0
- package/dist/core/observability/logger.js +11 -2
- package/dist/core/patch/diff.js +1 -0
- package/dist/core/prompts/registry.js +39 -2
- package/dist/core/prompts/runtime.js +50 -12
- package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
- package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
- package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
- package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
- package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
- package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
- package/dist/core/prompts/templates/system/main_system.hbs +4 -16
- package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
- package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
- package/dist/core/prompts/templates/system/research_system.hbs +2 -0
- package/dist/core/protocols/a2a/agent-card.js +3 -2
- package/dist/core/protocols/a2a/sdk/executor.js +8 -6
- package/dist/core/protocols/a2a/sdk/server.js +0 -1
- package/dist/core/protocols/acp/formal-agent.js +221 -55
- package/dist/core/protocols/acp/handlers.js +5 -1
- package/dist/core/protocols/acp/permission-provider.js +21 -1
- package/dist/core/protocols/shared/execution-request.js +24 -0
- package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
- package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
- package/dist/core/public-capabilities/projections.js +29 -0
- package/dist/core/public-capabilities/registry.js +26 -0
- package/dist/core/public-capabilities/types.js +2 -0
- package/dist/core/runtime/agent-server-runtime.js +47 -43
- package/dist/core/runtime/execution-profile.js +67 -0
- package/dist/core/session/artifact-state.js +160 -0
- package/dist/core/session/compaction/index.js +183 -0
- package/dist/core/session/compaction/microcompact.js +78 -0
- package/dist/core/session/compaction/tracking.js +48 -0
- package/dist/core/session/compaction/types.js +11 -0
- package/dist/core/session/compression.js +12 -4
- package/dist/core/session/manager.js +247 -10
- package/dist/core/session/pruning-strategy.js +55 -9
- package/dist/core/session/replacement-preview-provider.js +24 -0
- package/dist/core/session/replacement-state.js +131 -0
- package/dist/core/session/resume-repair/pipeline.js +79 -0
- package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
- package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
- package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
- package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
- package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
- package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
- package/dist/core/session/resume-repair/types.js +2 -0
- package/dist/core/session/summary-sync.js +164 -13
- package/dist/core/session/token-tracker.js +6 -0
- package/dist/core/skills/audit.js +34 -0
- package/dist/core/skills/bridge.js +84 -7
- package/dist/core/skills/discovery.js +94 -0
- package/dist/core/skills/feature-flags.js +52 -0
- package/dist/core/skills/index.js +1 -1
- package/dist/core/skills/loader.js +195 -20
- package/dist/core/skills/parser.js +296 -24
- package/dist/core/skills/permissions.js +117 -0
- package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
- package/dist/core/skills/runtime/SkillRunner.js +240 -61
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
- package/dist/core/strata/layers/worktree.js +70 -13
- package/dist/core/strata/runtime/synchronizer.js +29 -2
- package/dist/core/streaming/stream-assembler.js +75 -31
- package/dist/core/sub-agent/context-snapshot.js +156 -0
- package/dist/core/sub-agent/core/loop.js +1 -1
- package/dist/core/sub-agent/core/manager.js +119 -20
- package/dist/core/sub-agent/dispatch-policy.js +29 -0
- package/dist/core/sub-agent/prefix-consistency.js +48 -0
- package/dist/core/sub-agent/registry-defaults.js +4 -0
- package/dist/core/sub-agent/tools/task-spawn.js +79 -2
- package/dist/core/sub-agent/types.js +134 -5
- package/dist/core/tools/audit.js +13 -4
- package/dist/core/tools/builtin/ast-grep.js +1 -1
- package/dist/core/tools/builtin/ast.js +1 -1
- package/dist/core/tools/builtin/benchmark.js +360 -0
- package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
- package/dist/core/tools/builtin/code-search/executor.js +6 -1
- package/dist/core/tools/builtin/code-search/spec.js +26 -2
- package/dist/core/tools/builtin/fs.js +256 -23
- package/dist/core/tools/builtin/git.js +2 -2
- package/dist/core/tools/builtin/index.js +51 -2
- package/dist/core/tools/builtin/interaction.js +8 -1
- package/dist/core/tools/builtin/plan.js +37 -15
- package/dist/core/tools/builtin/shell.js +1 -1
- package/dist/core/tools/loader.js +39 -16
- package/dist/core/tools/mapper.js +17 -3
- package/dist/core/tools/parallel/scheduler.js +35 -4
- package/dist/core/tools/permissions/permission-rules.js +5 -10
- package/dist/core/tools/policy.js +6 -1
- package/dist/core/tools/recoverable-tool-errors.js +10 -0
- package/dist/core/tools/router.js +24 -6
- package/dist/core/tools/session.js +458 -48
- package/dist/core/tools/tool-visibility.js +62 -0
- package/dist/core/tools/types.js +9 -1
- package/dist/core/types/execution.js +4 -0
- package/dist/core/types/flow-mode.js +8 -0
- package/dist/core/utils/path.js +52 -0
- package/dist/core/verification/runner.js +4 -1
- package/dist/interfaces/cli/task-runner.js +4 -3
- package/dist/languages/typescript/index.js +4 -1
- package/dist/locales/en.js +87 -2
- package/dist/utils/eol.js +1 -1
- package/package.json +15 -8
- package/scripts/fix-es-abstract-compat.js +77 -0
- package/dist/core/runtime/fastify-server-bundle.js +0 -26
- package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
- package/dist/core/runtime/sidecar-paths.js +0 -47
- package/dist/core/runtime/sidecar-route-catalog.js +0 -103
|
@@ -2,12 +2,15 @@ import { getExitCode } from '../runtime/exit-codes.js';
|
|
|
2
2
|
import { normalizeStopReason } from './normalized-events.js';
|
|
3
3
|
export class StreamAssembler {
|
|
4
4
|
clock;
|
|
5
|
+
deferToolRequestsUntilExecutionInput;
|
|
5
6
|
streams = new Map();
|
|
6
7
|
canonicalTextStreams = new Set();
|
|
7
8
|
canonicalClosedTextStreams = new Set();
|
|
8
9
|
toolCallStates = new Map();
|
|
9
10
|
constructor(options = {}) {
|
|
10
11
|
this.clock = options.clock ?? (() => new Date());
|
|
12
|
+
this.deferToolRequestsUntilExecutionInput =
|
|
13
|
+
options.deferToolRequestsUntilExecutionInput ?? false;
|
|
11
14
|
}
|
|
12
15
|
push(event) {
|
|
13
16
|
if (event.type === 'llm.responses.event') {
|
|
@@ -27,33 +30,42 @@ export class StreamAssembler {
|
|
|
27
30
|
return [];
|
|
28
31
|
}
|
|
29
32
|
this.canonicalTextStreams.delete(event.streamId);
|
|
30
|
-
return
|
|
33
|
+
return [
|
|
34
|
+
...this.flushPendingToolRequests(event.streamId),
|
|
35
|
+
...this.handleTextEnd(event.streamId, event.timestamp, event.finishReason),
|
|
36
|
+
];
|
|
31
37
|
}
|
|
32
38
|
if (event.type === 'tool.call.start') {
|
|
33
39
|
const out = [];
|
|
34
40
|
const st = this.getToolCallState(event.callId);
|
|
35
41
|
if (!st.requestStarted) {
|
|
36
42
|
st.requestStarted = true;
|
|
37
|
-
|
|
43
|
+
st.request = {
|
|
38
44
|
type: 'normalized.tool_request_start',
|
|
39
45
|
callId: event.callId,
|
|
40
46
|
toolName: event.toolName,
|
|
41
47
|
phase: event.phase,
|
|
42
48
|
round: event.round,
|
|
49
|
+
...(event.input === undefined ? {} : { input: event.input }),
|
|
43
50
|
timestamp: event.timestamp,
|
|
44
|
-
}
|
|
51
|
+
};
|
|
45
52
|
}
|
|
53
|
+
else if (event.input !== undefined && st.request && st.request.input === undefined) {
|
|
54
|
+
st.request = { ...st.request, input: event.input };
|
|
55
|
+
}
|
|
56
|
+
out.push(...this.emitPendingToolRequest(st));
|
|
46
57
|
if (!st.requestEnded) {
|
|
47
58
|
st.requestEnded = true;
|
|
48
|
-
|
|
59
|
+
st.requestEnd = {
|
|
49
60
|
type: 'normalized.tool_request_end',
|
|
50
61
|
callId: event.callId,
|
|
51
62
|
toolName: event.toolName,
|
|
52
63
|
phase: event.phase,
|
|
53
64
|
round: event.round,
|
|
54
65
|
timestamp: event.timestamp,
|
|
55
|
-
}
|
|
66
|
+
};
|
|
56
67
|
}
|
|
68
|
+
out.push(...this.emitPendingToolRequestEnd(st));
|
|
57
69
|
if (st.executionStarted)
|
|
58
70
|
return out;
|
|
59
71
|
st.executionStarted = true;
|
|
@@ -73,26 +85,28 @@ export class StreamAssembler {
|
|
|
73
85
|
const st = this.getToolCallState(event.callId);
|
|
74
86
|
if (!st.requestStarted) {
|
|
75
87
|
st.requestStarted = true;
|
|
76
|
-
|
|
88
|
+
st.request = {
|
|
77
89
|
type: 'normalized.tool_request_start',
|
|
78
90
|
callId: event.callId,
|
|
79
91
|
toolName: event.toolName,
|
|
80
92
|
phase: event.phase,
|
|
81
93
|
round: event.round,
|
|
82
94
|
timestamp: event.timestamp,
|
|
83
|
-
}
|
|
95
|
+
};
|
|
84
96
|
}
|
|
97
|
+
out.push(...this.emitPendingToolRequest(st));
|
|
85
98
|
if (!st.requestEnded) {
|
|
86
99
|
st.requestEnded = true;
|
|
87
|
-
|
|
100
|
+
st.requestEnd = {
|
|
88
101
|
type: 'normalized.tool_request_end',
|
|
89
102
|
callId: event.callId,
|
|
90
103
|
toolName: event.toolName,
|
|
91
104
|
phase: event.phase,
|
|
92
105
|
round: event.round,
|
|
93
106
|
timestamp: event.timestamp,
|
|
94
|
-
}
|
|
107
|
+
};
|
|
95
108
|
}
|
|
109
|
+
out.push(...this.emitPendingToolRequestEnd(st));
|
|
96
110
|
out.push({
|
|
97
111
|
type: 'normalized.tool_call_end',
|
|
98
112
|
callId: event.callId,
|
|
@@ -211,7 +225,10 @@ export class StreamAssembler {
|
|
|
211
225
|
return [];
|
|
212
226
|
this.canonicalClosedTextStreams.add(event.streamId);
|
|
213
227
|
this.canonicalTextStreams.delete(event.streamId);
|
|
214
|
-
return
|
|
228
|
+
return [
|
|
229
|
+
...this.flushPendingToolRequests(event.streamId),
|
|
230
|
+
...this.handleTextEnd(event.streamId, event.timestamp, undefined),
|
|
231
|
+
];
|
|
215
232
|
}
|
|
216
233
|
if (isOutputItemAddedMessageEvent(event.event) ||
|
|
217
234
|
isContentPartAddedOutputTextEvent(event.event)) {
|
|
@@ -223,7 +240,10 @@ export class StreamAssembler {
|
|
|
223
240
|
return [];
|
|
224
241
|
this.canonicalClosedTextStreams.add(event.streamId);
|
|
225
242
|
this.canonicalTextStreams.delete(event.streamId);
|
|
226
|
-
return
|
|
243
|
+
return [
|
|
244
|
+
...this.flushPendingToolRequests(event.streamId),
|
|
245
|
+
...this.handleTextEnd(event.streamId, event.timestamp, undefined),
|
|
246
|
+
];
|
|
227
247
|
}
|
|
228
248
|
if (isOutputItemAddedFunctionCallEvent(event.event)) {
|
|
229
249
|
if (!event.phase || typeof event.round !== 'number')
|
|
@@ -231,38 +251,38 @@ export class StreamAssembler {
|
|
|
231
251
|
const callId = event.event.item.call_id;
|
|
232
252
|
const toolName = event.event.item.name;
|
|
233
253
|
const st = this.getToolCallState(callId);
|
|
254
|
+
st.streamId ??= event.streamId;
|
|
234
255
|
if (st.requestStarted)
|
|
235
256
|
return [];
|
|
236
257
|
st.requestStarted = true;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
];
|
|
258
|
+
st.request = {
|
|
259
|
+
type: 'normalized.tool_request_start',
|
|
260
|
+
callId,
|
|
261
|
+
toolName,
|
|
262
|
+
phase: event.phase,
|
|
263
|
+
round: event.round,
|
|
264
|
+
timestamp: event.timestamp,
|
|
265
|
+
};
|
|
266
|
+
return this.deferToolRequestsUntilExecutionInput ? [] : this.emitPendingToolRequest(st);
|
|
247
267
|
}
|
|
248
268
|
if (isOutputItemDoneFunctionCallEvent(event.event)) {
|
|
249
269
|
if (!event.phase || typeof event.round !== 'number')
|
|
250
270
|
return [];
|
|
251
271
|
const callId = event.event.item.call_id;
|
|
252
272
|
const st = this.getToolCallState(callId);
|
|
273
|
+
st.streamId ??= event.streamId;
|
|
253
274
|
if (st.requestEnded)
|
|
254
275
|
return [];
|
|
255
276
|
st.requestEnded = true;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
];
|
|
277
|
+
st.requestEnd = {
|
|
278
|
+
type: 'normalized.tool_request_end',
|
|
279
|
+
callId,
|
|
280
|
+
toolName: event.event.item.name,
|
|
281
|
+
phase: event.phase,
|
|
282
|
+
round: event.round,
|
|
283
|
+
timestamp: event.timestamp,
|
|
284
|
+
};
|
|
285
|
+
return this.emitPendingToolRequestEnd(st);
|
|
266
286
|
}
|
|
267
287
|
return [];
|
|
268
288
|
}
|
|
@@ -274,10 +294,34 @@ export class StreamAssembler {
|
|
|
274
294
|
requestStarted: false,
|
|
275
295
|
requestEnded: false,
|
|
276
296
|
executionStarted: false,
|
|
297
|
+
emittedRequest: false,
|
|
298
|
+
emittedRequestEnd: false,
|
|
277
299
|
};
|
|
278
300
|
this.toolCallStates.set(callId, created);
|
|
279
301
|
return created;
|
|
280
302
|
}
|
|
303
|
+
emitPendingToolRequest(st) {
|
|
304
|
+
if (st.emittedRequest || !st.request)
|
|
305
|
+
return [];
|
|
306
|
+
st.emittedRequest = true;
|
|
307
|
+
return [st.request];
|
|
308
|
+
}
|
|
309
|
+
emitPendingToolRequestEnd(st) {
|
|
310
|
+
if (st.emittedRequestEnd || !st.requestEnd || !st.emittedRequest)
|
|
311
|
+
return [];
|
|
312
|
+
st.emittedRequestEnd = true;
|
|
313
|
+
return [st.requestEnd];
|
|
314
|
+
}
|
|
315
|
+
flushPendingToolRequests(streamId) {
|
|
316
|
+
const out = [];
|
|
317
|
+
for (const st of this.toolCallStates.values()) {
|
|
318
|
+
if (st.streamId !== streamId)
|
|
319
|
+
continue;
|
|
320
|
+
out.push(...this.emitPendingToolRequest(st));
|
|
321
|
+
out.push(...this.emitPendingToolRequestEnd(st));
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
281
325
|
}
|
|
282
326
|
function isOutputTextDeltaEvent(event) {
|
|
283
327
|
return (event.type === 'response.output_text.delta' &&
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { normalizeToolResultReplacementState, } from '../session/replacement-state.js';
|
|
2
|
+
import { SUB_AGENT_CONTEXT_SNAPSHOT_VERSION, SUB_AGENT_CONTEXT_SNAPSHOT_FIELD_SEMANTICS, } from './types.js';
|
|
3
|
+
function deepClone(value) {
|
|
4
|
+
if (typeof structuredClone === 'function') {
|
|
5
|
+
return structuredClone(value);
|
|
6
|
+
}
|
|
7
|
+
return JSON.parse(JSON.stringify(value));
|
|
8
|
+
}
|
|
9
|
+
function cloneArtifactHandle(artifact) {
|
|
10
|
+
if (!artifact)
|
|
11
|
+
return undefined;
|
|
12
|
+
return {
|
|
13
|
+
handle: artifact.handle,
|
|
14
|
+
mimeType: artifact.mimeType,
|
|
15
|
+
sha256: artifact.sha256,
|
|
16
|
+
size: artifact.size,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function cloneConversationContext(messages) {
|
|
20
|
+
if (!Array.isArray(messages) || messages.length === 0)
|
|
21
|
+
return undefined;
|
|
22
|
+
return messages.map((message) => {
|
|
23
|
+
const cloned = {
|
|
24
|
+
role: message.role,
|
|
25
|
+
content: message.content,
|
|
26
|
+
};
|
|
27
|
+
if (message.name !== undefined)
|
|
28
|
+
cloned.name = message.name;
|
|
29
|
+
if (message.reasoning_content !== undefined)
|
|
30
|
+
cloned.reasoning_content = message.reasoning_content;
|
|
31
|
+
if (message.tool_call_id !== undefined)
|
|
32
|
+
cloned.tool_call_id = message.tool_call_id;
|
|
33
|
+
if (Array.isArray(message.tool_calls)) {
|
|
34
|
+
cloned.tool_calls = deepClone(message.tool_calls);
|
|
35
|
+
}
|
|
36
|
+
return cloned;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function cloneToolCallingAudit(entries) {
|
|
40
|
+
if (!Array.isArray(entries) || entries.length === 0)
|
|
41
|
+
return undefined;
|
|
42
|
+
return entries.map((entry) => deepClone(entry));
|
|
43
|
+
}
|
|
44
|
+
function cloneArtifactHints(hints) {
|
|
45
|
+
if (!hints)
|
|
46
|
+
return undefined;
|
|
47
|
+
const verifyArtifact = cloneArtifactHandle(hints.verifyArtifact);
|
|
48
|
+
const subAgentPatchArtifacts = hints.subAgentPatchArtifacts?.map((artifact) => cloneArtifactHandle(artifact));
|
|
49
|
+
const subAgentAuditArtifacts = hints.subAgentAuditArtifacts?.map((artifact) => cloneArtifactHandle(artifact));
|
|
50
|
+
const recentReadArtifacts = hints.recentReadArtifacts?.map((item) => ({
|
|
51
|
+
path: item.path,
|
|
52
|
+
artifact: cloneArtifactHandle(item.artifact),
|
|
53
|
+
}));
|
|
54
|
+
const toolResultPreviewArtifacts = hints.toolResultPreviewArtifacts?.map((item) => ({
|
|
55
|
+
label: item.label,
|
|
56
|
+
artifact: cloneArtifactHandle(item.artifact),
|
|
57
|
+
}));
|
|
58
|
+
if (!verifyArtifact &&
|
|
59
|
+
!subAgentPatchArtifacts?.length &&
|
|
60
|
+
!subAgentAuditArtifacts?.length &&
|
|
61
|
+
!recentReadArtifacts?.length &&
|
|
62
|
+
!toolResultPreviewArtifacts?.length) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
verifyArtifact,
|
|
67
|
+
subAgentPatchArtifacts,
|
|
68
|
+
subAgentAuditArtifacts,
|
|
69
|
+
recentReadArtifacts,
|
|
70
|
+
toolResultPreviewArtifacts,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function cloneReplacementState(state) {
|
|
74
|
+
const normalized = normalizeToolResultReplacementState(state);
|
|
75
|
+
if (!normalized)
|
|
76
|
+
return undefined;
|
|
77
|
+
return {
|
|
78
|
+
schemaVersion: normalized.schemaVersion,
|
|
79
|
+
entries: Object.fromEntries(Object.entries(normalized.entries).map(([key, value]) => [
|
|
80
|
+
key,
|
|
81
|
+
{
|
|
82
|
+
toolResultId: value.toolResultId,
|
|
83
|
+
decision: value.decision,
|
|
84
|
+
preview: value.preview,
|
|
85
|
+
frozenAt: value.frozenAt,
|
|
86
|
+
sourceArtifactHandle: value.sourceArtifactHandle,
|
|
87
|
+
identityVersion: value.identityVersion,
|
|
88
|
+
hashAlgorithm: value.hashAlgorithm,
|
|
89
|
+
},
|
|
90
|
+
])),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function hasAnySnapshotData(snapshot) {
|
|
94
|
+
return Boolean(snapshot.conversationContext ||
|
|
95
|
+
snapshot.artifactHints ||
|
|
96
|
+
snapshot.toolCallingAudit ||
|
|
97
|
+
snapshot.replacementState ||
|
|
98
|
+
snapshot.planRuntime ||
|
|
99
|
+
snapshot.cacheSharing);
|
|
100
|
+
}
|
|
101
|
+
function normalizeSnapshotVersion(snapshot) {
|
|
102
|
+
const version = snapshot.version ?? SUB_AGENT_CONTEXT_SNAPSHOT_VERSION;
|
|
103
|
+
if (version !== SUB_AGENT_CONTEXT_SNAPSHOT_VERSION) {
|
|
104
|
+
throw new Error(`Unsupported sub-agent context snapshot version: ${version}`);
|
|
105
|
+
}
|
|
106
|
+
return version;
|
|
107
|
+
}
|
|
108
|
+
function assertSupportedSnapshotFields(snapshot) {
|
|
109
|
+
const supportedFields = new Set([
|
|
110
|
+
'version',
|
|
111
|
+
...Object.keys(SUB_AGENT_CONTEXT_SNAPSHOT_FIELD_SEMANTICS),
|
|
112
|
+
]);
|
|
113
|
+
const unknownFields = Object.keys(snapshot).filter((key) => !supportedFields.has(key));
|
|
114
|
+
if (unknownFields.length > 0) {
|
|
115
|
+
throw new Error(`Unsupported sub-agent context snapshot fields: ${unknownFields.sort().join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Applies the Stage 5 protocol:
|
|
120
|
+
* - mutable runtime state is cloned by default
|
|
121
|
+
* - session infrastructure metadata remains shared by reference
|
|
122
|
+
*/
|
|
123
|
+
export function cloneSubAgentContextSnapshot(snapshot) {
|
|
124
|
+
if (!snapshot)
|
|
125
|
+
return undefined;
|
|
126
|
+
assertSupportedSnapshotFields(snapshot);
|
|
127
|
+
const version = normalizeSnapshotVersion(snapshot);
|
|
128
|
+
const cloned = {
|
|
129
|
+
version,
|
|
130
|
+
conversationContext: cloneConversationContext(snapshot.conversationContext),
|
|
131
|
+
artifactHints: cloneArtifactHints(snapshot.artifactHints),
|
|
132
|
+
toolCallingAudit: cloneToolCallingAudit(snapshot.toolCallingAudit),
|
|
133
|
+
replacementState: cloneReplacementState(snapshot.replacementState),
|
|
134
|
+
planRuntime: snapshot.planRuntime,
|
|
135
|
+
cacheSharing: snapshot.cacheSharing,
|
|
136
|
+
};
|
|
137
|
+
if (!hasAnySnapshotData(cloned)) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
return cloned;
|
|
141
|
+
}
|
|
142
|
+
export function mergeSubAgentContextSnapshot(requestSnapshot, runtimeSnapshot) {
|
|
143
|
+
const normalizedRequestVersion = requestSnapshot && normalizeSnapshotVersion(requestSnapshot);
|
|
144
|
+
const normalizedRuntimeVersion = runtimeSnapshot && normalizeSnapshotVersion(runtimeSnapshot);
|
|
145
|
+
const merged = {
|
|
146
|
+
version: normalizedRuntimeVersion ?? normalizedRequestVersion ?? SUB_AGENT_CONTEXT_SNAPSHOT_VERSION,
|
|
147
|
+
conversationContext: runtimeSnapshot?.conversationContext ?? requestSnapshot?.conversationContext,
|
|
148
|
+
artifactHints: runtimeSnapshot?.artifactHints ?? requestSnapshot?.artifactHints,
|
|
149
|
+
toolCallingAudit: runtimeSnapshot?.toolCallingAudit ?? requestSnapshot?.toolCallingAudit,
|
|
150
|
+
replacementState: runtimeSnapshot?.replacementState ?? requestSnapshot?.replacementState,
|
|
151
|
+
planRuntime: runtimeSnapshot?.planRuntime ?? requestSnapshot?.planRuntime,
|
|
152
|
+
cacheSharing: runtimeSnapshot?.cacheSharing ?? requestSnapshot?.cacheSharing,
|
|
153
|
+
};
|
|
154
|
+
return cloneSubAgentContextSnapshot(merged);
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=context-snapshot.js.map
|
|
@@ -25,7 +25,7 @@ export class SmallfryLoop {
|
|
|
25
25
|
* Run the recursive loop based on the stratagem.
|
|
26
26
|
*/
|
|
27
27
|
async execute(initCtx) {
|
|
28
|
-
getLogger().
|
|
28
|
+
getLogger().debug(`[SmallfryLoop] ${text.smallfry.status.working} (${this.profile.name})`);
|
|
29
29
|
let pipeline = Pipeline.of(initCtx);
|
|
30
30
|
// Dynamic Phase Injection based on Stratagem
|
|
31
31
|
pipeline = pipeline.step('PREFLIGHT', runPreflight);
|
|
@@ -3,11 +3,15 @@ import { text } from '../../../locales/index.js';
|
|
|
3
3
|
import { createFileSystemAdapter } from '../../adapters/fs/index.js';
|
|
4
4
|
import * as fs from '../../adapters/fs/node-fs.js';
|
|
5
5
|
import { GitAdapter } from '../../adapters/git/git-adapter.js';
|
|
6
|
+
import { recordAuditEvent } from '../../observability/audit-trail.js';
|
|
6
7
|
import { getLogger } from '../../observability/logger.js';
|
|
7
8
|
import { FileStateResolver } from '../../strata/layers/file-state-resolver.js';
|
|
8
9
|
import { RuntimeEnvironment } from '../../strata/runtime/environment.js';
|
|
9
10
|
import { ErrorType } from '../../types/index.js';
|
|
10
11
|
import { ArtifactStore } from '../artifacts/store.js';
|
|
12
|
+
import { cloneSubAgentContextSnapshot } from '../context-snapshot.js';
|
|
13
|
+
import { isReadOnlySubAgentContext, resolveSubAgentDryRun } from '../dispatch-policy.js';
|
|
14
|
+
import { validateSharedPrefixConsistency } from '../prefix-consistency.js';
|
|
11
15
|
import { getSubAgentRegistry } from '../registry.js';
|
|
12
16
|
import { SmallfryLoop } from './loop.js';
|
|
13
17
|
/**
|
|
@@ -33,12 +37,39 @@ export class SubAgentManager {
|
|
|
33
37
|
* Spawns a new sub-agent and monitors its execution.
|
|
34
38
|
*/
|
|
35
39
|
async execute(request) {
|
|
36
|
-
const
|
|
40
|
+
const normalizedRequest = request.session_target === 'shared'
|
|
41
|
+
? (() => {
|
|
42
|
+
const consistency = validateSharedPrefixConsistency({
|
|
43
|
+
requestSnapshot: request.contextSnapshot,
|
|
44
|
+
runtimeSnapshot: this.ctx.contextSnapshot,
|
|
45
|
+
});
|
|
46
|
+
if (consistency.compatible)
|
|
47
|
+
return request;
|
|
48
|
+
recordAuditEvent('sub_agent.shared.prefix_consistency_failed', {
|
|
49
|
+
metric: 'shared_fallback_rate',
|
|
50
|
+
fallbackMode: 'isolated',
|
|
51
|
+
reason: consistency.reason,
|
|
52
|
+
expected: consistency.expected,
|
|
53
|
+
actual: consistency.actual,
|
|
54
|
+
}, {
|
|
55
|
+
source: 'smallfry',
|
|
56
|
+
severity: 'medium',
|
|
57
|
+
scope: 'session',
|
|
58
|
+
phase: this.ctx.phase,
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
...request,
|
|
62
|
+
session_target: 'isolated',
|
|
63
|
+
contextSnapshot: undefined,
|
|
64
|
+
};
|
|
65
|
+
})()
|
|
66
|
+
: request;
|
|
67
|
+
const profile = this.deps.registry.get(normalizedRequest.agent_ref);
|
|
37
68
|
if (!profile) {
|
|
38
|
-
return this.fail(
|
|
69
|
+
return this.fail(normalizedRequest.agent_ref, text.smallfry.errors.profileNotFound(normalizedRequest.agent_ref), 'LOOP_FAILED');
|
|
39
70
|
}
|
|
40
71
|
const agentId = `smallfry-${randomBytes(4).toString('hex')}`;
|
|
41
|
-
const currentDepth =
|
|
72
|
+
const currentDepth = normalizedRequest.recursionDepth || 0;
|
|
42
73
|
const MAX_RECURSION_DEPTH = 2;
|
|
43
74
|
if (currentDepth >= MAX_RECURSION_DEPTH) {
|
|
44
75
|
const msg = text.smallfry.errors.recursionLimitExceeded(currentDepth, MAX_RECURSION_DEPTH);
|
|
@@ -47,7 +78,7 @@ export class SubAgentManager {
|
|
|
47
78
|
}
|
|
48
79
|
this.activeAgents.set(agentId, { profile, status: 'hiring' });
|
|
49
80
|
this.controller.registerAgent(agentId, profile, 'hiring');
|
|
50
|
-
getLogger().
|
|
81
|
+
getLogger().debug(`[SubAgentManager] ${text.smallfry.status.spawning} (ID: ${agentId}, Role: ${profile.role})`);
|
|
51
82
|
const llm = this.ctx.llm;
|
|
52
83
|
if (!llm) {
|
|
53
84
|
const msg = text.smallfry.errors.dispatchMissingRuntimeLlm;
|
|
@@ -59,7 +90,12 @@ export class SubAgentManager {
|
|
|
59
90
|
if (this.controller.isStopRequested(agentId)) {
|
|
60
91
|
throw new Error('Stop requested before launching Smallfry');
|
|
61
92
|
}
|
|
62
|
-
const
|
|
93
|
+
const effectiveDryRun = resolveSubAgentDryRun({
|
|
94
|
+
parentDryRun: this.ctx.dryRun,
|
|
95
|
+
flowMode: this.ctx.flowMode,
|
|
96
|
+
phase: this.ctx.phase,
|
|
97
|
+
});
|
|
98
|
+
const runtimeEnv = await this.setupIsolatedEnvironment(normalizedRequest, llm, agentId, effectiveDryRun);
|
|
63
99
|
try {
|
|
64
100
|
const workspace = runtimeEnv.workspace;
|
|
65
101
|
const activePath = workspace.workPath;
|
|
@@ -68,21 +104,23 @@ export class SubAgentManager {
|
|
|
68
104
|
const flowMode = 'patch';
|
|
69
105
|
const fsAdapter = createFileSystemAdapter(flowMode);
|
|
70
106
|
// 2. Construct InitCtx for the smallfry
|
|
71
|
-
const initCtx = {
|
|
107
|
+
const initCtx = this.applyContextSnapshot(normalizedRequest.contextSnapshot, {
|
|
72
108
|
workspace: {
|
|
73
109
|
workPath: activePath,
|
|
74
110
|
baseRepoPath: workspace.baseRepoPath,
|
|
75
111
|
strategy: workspace.strategy,
|
|
76
112
|
},
|
|
77
113
|
options: {
|
|
78
|
-
instruction:
|
|
114
|
+
instruction: normalizedRequest.task,
|
|
79
115
|
repoPath: activePath,
|
|
80
|
-
dryRun:
|
|
81
|
-
contextFiles:
|
|
116
|
+
dryRun: effectiveDryRun,
|
|
117
|
+
contextFiles: normalizedRequest.contextFiles || [],
|
|
82
118
|
llm,
|
|
83
119
|
recursionDepth: currentDepth + 1, // Increment depth for child
|
|
84
|
-
allowedToolNames: this.filterAllowedTools(profile.allowedTools),
|
|
85
|
-
timeoutMs:
|
|
120
|
+
allowedToolNames: this.filterAllowedTools(profile.allowedTools, this.ctx.phase),
|
|
121
|
+
timeoutMs: normalizedRequest.timeout_seconds
|
|
122
|
+
? normalizedRequest.timeout_seconds * 1000
|
|
123
|
+
: profile.timeoutMs,
|
|
86
124
|
},
|
|
87
125
|
mode: flowMode,
|
|
88
126
|
fs: fsAdapter,
|
|
@@ -100,7 +138,7 @@ export class SubAgentManager {
|
|
|
100
138
|
},
|
|
101
139
|
fileStateResolver: resolver,
|
|
102
140
|
shadowInitialRef: runtimeEnv?.initialSnapshotHash || 'HEAD',
|
|
103
|
-
};
|
|
141
|
+
});
|
|
104
142
|
// 3. Launch the "Little Fry"
|
|
105
143
|
const subLoop = new SmallfryLoop(profile);
|
|
106
144
|
const result = await subLoop.execute(initCtx);
|
|
@@ -133,6 +171,23 @@ export class SubAgentManager {
|
|
|
133
171
|
async spawn(request) {
|
|
134
172
|
return this.execute(request);
|
|
135
173
|
}
|
|
174
|
+
applyContextSnapshot(snapshot, initCtx) {
|
|
175
|
+
const normalized = cloneSubAgentContextSnapshot(snapshot);
|
|
176
|
+
if (!normalized)
|
|
177
|
+
return initCtx;
|
|
178
|
+
return {
|
|
179
|
+
...initCtx,
|
|
180
|
+
cacheSharing: normalized.cacheSharing ?? initCtx.cacheSharing,
|
|
181
|
+
planRuntime: normalized.planRuntime ?? initCtx.planRuntime,
|
|
182
|
+
toolCallingAudit: normalized.toolCallingAudit ?? initCtx.toolCallingAudit,
|
|
183
|
+
replacementState: normalized.replacementState ?? initCtx.replacementState,
|
|
184
|
+
artifactHints: normalized.artifactHints ?? initCtx.artifactHints,
|
|
185
|
+
options: {
|
|
186
|
+
...initCtx.options,
|
|
187
|
+
conversationContext: normalized.conversationContext ?? initCtx.options.conversationContext,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
136
191
|
updateStatus(id, status) {
|
|
137
192
|
const entry = this.activeAgents.get(id);
|
|
138
193
|
if (entry) {
|
|
@@ -154,13 +209,29 @@ export class SubAgentManager {
|
|
|
154
209
|
errorType: ErrorType.UNKNOWN,
|
|
155
210
|
};
|
|
156
211
|
}
|
|
157
|
-
async setupIsolatedEnvironment(request, llm, agentId) {
|
|
212
|
+
async setupIsolatedEnvironment(request, llm, agentId, effectiveDryRun) {
|
|
213
|
+
if (isReadOnlySubAgentContext({
|
|
214
|
+
flowMode: this.ctx.flowMode,
|
|
215
|
+
phase: this.ctx.phase,
|
|
216
|
+
}) &&
|
|
217
|
+
request.session_target !== 'isolated') {
|
|
218
|
+
recordAuditEvent('sub_agent.dispatch.read_only_forced_isolated', {
|
|
219
|
+
requestedSessionTarget: request.session_target,
|
|
220
|
+
effectiveSessionTarget: 'isolated',
|
|
221
|
+
}, {
|
|
222
|
+
source: 'smallfry',
|
|
223
|
+
severity: 'low',
|
|
224
|
+
scope: 'session',
|
|
225
|
+
phase: this.ctx.phase,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
158
228
|
const baseRepoPath = this.ctx.persistenceRoot || this.ctx.repoRoot;
|
|
159
229
|
const options = {
|
|
160
230
|
instruction: request.task,
|
|
161
231
|
repoPath: baseRepoPath,
|
|
162
232
|
llm,
|
|
163
|
-
|
|
233
|
+
// CRITICAL SAFETY: read-only model phases force sub-agent dryRun.
|
|
234
|
+
dryRun: effectiveDryRun,
|
|
164
235
|
verify: undefined,
|
|
165
236
|
strategy: 'worktree',
|
|
166
237
|
contextFiles: request.contextFiles,
|
|
@@ -187,15 +258,20 @@ export class SubAgentManager {
|
|
|
187
258
|
}
|
|
188
259
|
async persistArtifacts(agentId, result) {
|
|
189
260
|
const patch = result.finalPatch;
|
|
190
|
-
|
|
191
|
-
|
|
261
|
+
const { finalPatch: _ignored, ...rest } = result;
|
|
262
|
+
const auditArtifact = await this.persistAuditArtifact(rest.auditPath);
|
|
263
|
+
if (!patch || typeof patch !== 'string') {
|
|
264
|
+
return {
|
|
265
|
+
...rest,
|
|
266
|
+
auditPath: auditArtifact?.handle ?? rest.auditPath,
|
|
267
|
+
auditArtifact: auditArtifact ?? rest.auditArtifact,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
192
270
|
const saved = await this.deps.artifactStore.saveText({
|
|
193
271
|
content: patch,
|
|
194
272
|
mimeType: 'text/x-diff',
|
|
195
273
|
fileExt: 'patch',
|
|
196
274
|
});
|
|
197
|
-
const { finalPatch: _ignored, ...rest } = result;
|
|
198
|
-
const auditArtifact = await this.persistAuditArtifact(rest.auditPath);
|
|
199
275
|
return {
|
|
200
276
|
...rest,
|
|
201
277
|
auditPath: auditArtifact?.handle ?? rest.auditPath,
|
|
@@ -203,7 +279,7 @@ export class SubAgentManager {
|
|
|
203
279
|
patchArtifact: saved,
|
|
204
280
|
};
|
|
205
281
|
}
|
|
206
|
-
filterAllowedTools(allowed) {
|
|
282
|
+
filterAllowedTools(allowed, phase) {
|
|
207
283
|
const safeReadOnlyTools = new Set([
|
|
208
284
|
'agent_dispatch',
|
|
209
285
|
'code.search',
|
|
@@ -213,7 +289,30 @@ export class SubAgentManager {
|
|
|
213
289
|
'git.cat',
|
|
214
290
|
'artifact.read',
|
|
215
291
|
]);
|
|
216
|
-
|
|
292
|
+
const readOnlyPlanTools = new Set(['plan.init', 'plan.read', 'plan.update']);
|
|
293
|
+
const readOnlyPhase = isReadOnlySubAgentContext({
|
|
294
|
+
flowMode: this.ctx.flowMode,
|
|
295
|
+
phase,
|
|
296
|
+
});
|
|
297
|
+
if (!readOnlyPhase) {
|
|
298
|
+
return allowed;
|
|
299
|
+
}
|
|
300
|
+
const filtered = allowed.filter((name) => safeReadOnlyTools.has(name) || (readOnlyPhase && readOnlyPlanTools.has(name)));
|
|
301
|
+
if (readOnlyPhase) {
|
|
302
|
+
const removed = allowed.filter((name) => !filtered.includes(name));
|
|
303
|
+
if (removed.length > 0) {
|
|
304
|
+
recordAuditEvent('sub_agent.dispatch.read_only_tool_guard_filtered', {
|
|
305
|
+
removedTools: removed,
|
|
306
|
+
retainedTools: filtered,
|
|
307
|
+
}, {
|
|
308
|
+
source: 'smallfry',
|
|
309
|
+
severity: 'medium',
|
|
310
|
+
scope: 'session',
|
|
311
|
+
phase,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return filtered;
|
|
217
316
|
}
|
|
218
317
|
async persistAuditArtifact(auditPath) {
|
|
219
318
|
if (!auditPath || typeof auditPath !== 'string')
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resolveExecutionProfile } from '../runtime/execution-profile.js';
|
|
2
|
+
import { Phase } from '../types/runtime.js';
|
|
3
|
+
const READ_ONLY_MODEL_PHASE_SET = new Set([Phase.EXPLORE, Phase.PLAN, Phase.PATCH]);
|
|
4
|
+
/**
|
|
5
|
+
* Read-only model phases in which sub-agent dispatch must never cause workspace mutation.
|
|
6
|
+
*/
|
|
7
|
+
export function isReadOnlyModelPhase(phase) {
|
|
8
|
+
return phase !== undefined && READ_ONLY_MODEL_PHASE_SET.has(phase);
|
|
9
|
+
}
|
|
10
|
+
export function isReadOnlySubAgentContext({ flowMode, phase }) {
|
|
11
|
+
if (!flowMode) {
|
|
12
|
+
return isReadOnlyModelPhase(phase);
|
|
13
|
+
}
|
|
14
|
+
const profile = resolveExecutionProfile(flowMode);
|
|
15
|
+
if (profile.driver === 'agent') {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return isReadOnlyModelPhase(phase);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* In read-only model phases, sub-agent execution must run in dry-run mode regardless of parent value.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveSubAgentDryRun({ parentDryRun, flowMode, phase, }) {
|
|
24
|
+
if (isReadOnlySubAgentContext({ flowMode, phase })) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return parentDryRun;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=dispatch-policy.js.map
|