@stigmer/runner 3.11.0 → 3.12.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/README.md +2 -0
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -0
- package/dist/activities/discover-mcp-server.js +6 -4
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/emit-event.d.ts +14 -2
- package/dist/activities/emit-event.js +52 -17
- package/dist/activities/emit-event.js.map +1 -1
- package/dist/activities/execute-cursor/attachment-resolver.d.ts +11 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +8 -0
- package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
- package/dist/activities/execute-cursor/blueprint-resolver.js +0 -1
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/error-classifier.d.ts +19 -0
- package/dist/activities/execute-cursor/error-classifier.js +34 -3
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +52 -12
- package/dist/activities/execute-cursor/index.js +141 -67
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/model-pricing-data.d.ts +2 -0
- package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
- package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -11
- package/dist/activities/execute-cursor/prompt-builder.js +57 -10
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
- package/dist/activities/execute-cursor/turn-recovery.js +193 -0
- package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +11 -0
- package/dist/activities/execute-deep-agent/attachment-injector.js +7 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +0 -15
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -2
- package/dist/activities/execute-deep-agent/mcp-gate.js +0 -1
- package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -8
- package/dist/activities/execute-deep-agent/prompt-builder.js +14 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +0 -10
- package/dist/activities/execute-deep-agent/setup.js +22 -49
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
- package/dist/activities/execute-deep-agent/shell-env.js +7 -9
- package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +7 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +17 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js +14 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +8 -5
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/activities/run-command.d.ts +5 -2
- package/dist/activities/run-command.js +22 -10
- package/dist/activities/run-command.js.map +1 -1
- package/dist/activities/run-env.d.ts +40 -0
- package/dist/activities/run-env.js +66 -0
- package/dist/activities/run-env.js.map +1 -0
- package/dist/bootstrap.js +5 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +83 -26
- package/dist/client/stigmer-client.js +88 -24
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/config.d.ts +6 -5
- package/dist/config.js.map +1 -1
- package/dist/encryption/config.d.ts +41 -9
- package/dist/encryption/config.js +63 -23
- package/dist/encryption/config.js.map +1 -1
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +7 -0
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/path-normalization.d.ts +57 -0
- package/dist/middleware/path-normalization.js +109 -0
- package/dist/middleware/path-normalization.js.map +1 -0
- package/dist/middleware/types.d.ts +8 -0
- package/dist/payload-codecs.d.ts +2 -1
- package/dist/payload-codecs.js +7 -3
- package/dist/payload-codecs.js.map +1 -1
- package/dist/runner-manager.js +17 -3
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +7 -3
- package/dist/runner.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +34 -1
- package/dist/shared/artifact-storage.js +53 -10
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/attachment-download-urls.d.ts +62 -0
- package/dist/shared/attachment-download-urls.js +79 -0
- package/dist/shared/attachment-download-urls.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +1 -1
- package/dist/shared/channel-attachment.js +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +4 -20
- package/dist/shared/checkpointer/http-saver.js +15 -57
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/conversation-attachment.d.ts +2 -3
- package/dist/shared/conversation-attachment.js +2 -3
- package/dist/shared/conversation-attachment.js.map +1 -1
- package/dist/shared/conversation-catchup.d.ts +9 -2
- package/dist/shared/conversation-catchup.js +39 -6
- package/dist/shared/conversation-catchup.js.map +1 -1
- package/dist/shared/grpc-retry.d.ts +5 -1
- package/dist/shared/grpc-retry.js +5 -1
- package/dist/shared/grpc-retry.js.map +1 -1
- package/dist/shared/http-retry.d.ts +51 -9
- package/dist/shared/http-retry.js +60 -9
- package/dist/shared/http-retry.js.map +1 -1
- package/dist/shared/mcp-manager.d.ts +8 -4
- package/dist/shared/mcp-manager.js +30 -4
- package/dist/shared/mcp-manager.js.map +1 -1
- package/dist/shared/mcp-schema-sanitizer.d.ts +70 -0
- package/dist/shared/mcp-schema-sanitizer.js +197 -0
- package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
- package/dist/shared/mcp-transport-guard.d.ts +2 -2
- package/dist/shared/mcp-transport-guard.js +2 -2
- package/dist/shared/model-client.d.ts +6 -0
- package/dist/shared/model-client.js +14 -3
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/model-pricing-data.d.ts +2 -0
- package/dist/shared/model-pricing-data.js +13 -3
- package/dist/shared/model-pricing-data.js.map +1 -1
- package/dist/shared/model-registry.js +3 -2
- package/dist/shared/model-registry.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +7 -0
- package/dist/shared/plan-mode-permissions.js +7 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -1
- package/dist/shared/registry-endpoint.d.ts +20 -0
- package/dist/shared/registry-endpoint.js +25 -0
- package/dist/shared/registry-endpoint.js.map +1 -1
- package/dist/shared/runner-credential-keys.d.ts +24 -0
- package/dist/shared/runner-credential-keys.js +47 -0
- package/dist/shared/runner-credential-keys.js.map +1 -0
- package/dist/shared/synthesized-attachment.d.ts +4 -4
- package/dist/shared/synthesized-attachment.js +4 -4
- package/dist/workflow-engine/resolve.d.ts +19 -1
- package/dist/workflow-engine/resolve.js +37 -2
- package/dist/workflow-engine/resolve.js.map +1 -1
- package/dist/workflows/connect-mcp-server.js +2 -0
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +3 -4
- package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
- package/src/__test-utils__/fake-artifact-storage.ts +10 -1
- package/src/__test-utils__/vitest-global-setup.ts +50 -0
- package/src/__tests__/bootstrap.test.ts +43 -0
- package/src/__tests__/encryption-codec.test.ts +53 -0
- package/src/__tests__/preflight.test.ts +15 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +39 -1
- package/src/activities/__tests__/error-classifier.test.ts +94 -2
- package/src/activities/__tests__/run-command.test.ts +221 -0
- package/src/activities/discover-mcp-server.ts +16 -2
- package/src/activities/emit-event.ts +65 -21
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +63 -3
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +173 -31
- package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
- package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
- package/src/activities/execute-cursor/attachment-resolver.ts +24 -0
- package/src/activities/execute-cursor/blueprint-resolver.ts +1 -10
- package/src/activities/execute-cursor/error-classifier.ts +40 -3
- package/src/activities/execute-cursor/index.ts +175 -74
- package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
- package/src/activities/execute-cursor/prompt-builder.ts +89 -22
- package/src/activities/execute-cursor/turn-recovery.ts +208 -0
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +105 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -2
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +2 -7
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +174 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +71 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/shell-env.test.ts +24 -10
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +22 -17
- package/src/activities/execute-deep-agent/attachment-injector.ts +20 -1
- package/src/activities/execute-deep-agent/index.ts +0 -19
- package/src/activities/execute-deep-agent/mcp-gate.ts +0 -3
- package/src/activities/execute-deep-agent/prompt-builder.ts +27 -15
- package/src/activities/execute-deep-agent/setup.ts +22 -73
- package/src/activities/execute-deep-agent/shell-env.ts +8 -9
- package/src/activities/execute-deep-agent/subagent-transformer.ts +7 -1
- package/src/activities/execute-deep-agent/subagent-wiring.ts +26 -3
- package/src/activities/hydrate-workflow-execution.ts +8 -5
- package/src/activities/run-command.ts +25 -12
- package/src/activities/run-env.ts +79 -0
- package/src/bootstrap.ts +5 -0
- package/src/client/__tests__/stigmer-client.test.ts +104 -5
- package/src/client/stigmer-client.ts +128 -29
- package/src/config.ts +6 -5
- package/src/encryption/config.ts +87 -23
- package/src/middleware/__tests__/path-normalization.test.ts +140 -0
- package/src/middleware/index.ts +8 -0
- package/src/middleware/path-normalization.ts +125 -0
- package/src/middleware/types.ts +11 -0
- package/src/payload-codecs.ts +8 -2
- package/src/runner-manager.ts +21 -3
- package/src/runner.ts +7 -3
- package/src/shared/__tests__/artifact-storage.test.ts +154 -2
- package/src/shared/__tests__/attachment-download-urls.test.ts +70 -0
- package/src/shared/__tests__/channel-attachment.test.ts +2 -2
- package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
- package/src/shared/__tests__/http-retry.test.ts +5 -4
- package/src/shared/__tests__/mcp-manager.test.ts +53 -13
- package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
- package/src/shared/__tests__/model-client.test.ts +46 -0
- package/src/shared/__tests__/model-pricing.test.ts +62 -2
- package/src/shared/__tests__/model-registry.test.ts +22 -0
- package/src/shared/__tests__/synthesized-attachment.test.ts +19 -41
- package/src/shared/artifact-storage.ts +86 -10
- package/src/shared/attachment-download-urls.ts +98 -0
- package/src/shared/channel-attachment.ts +1 -1
- package/src/shared/checkpointer/http-saver.ts +15 -60
- package/src/shared/conversation-attachment.ts +2 -3
- package/src/shared/conversation-catchup.ts +39 -6
- package/src/shared/grpc-retry.ts +5 -1
- package/src/shared/http-retry.ts +98 -9
- package/src/shared/mcp-manager.ts +35 -4
- package/src/shared/mcp-schema-sanitizer.ts +224 -0
- package/src/shared/mcp-transport-guard.ts +2 -2
- package/src/shared/model-client.ts +21 -3
- package/src/shared/model-pricing-data.ts +22 -3
- package/src/shared/model-registry.ts +7 -2
- package/src/shared/plan-mode-permissions.ts +7 -0
- package/src/shared/registry-endpoint.ts +27 -0
- package/src/shared/runner-credential-keys.ts +46 -0
- package/src/shared/synthesized-attachment.ts +4 -4
- package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
- package/src/workflow-engine/resolve.ts +48 -2
- package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
- package/src/workflows/connect-mcp-server.ts +2 -0
- package/src/workflows/types.ts +8 -0
- package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
- package/dist/activities/execute-cursor/mcp-config.js +0 -39
- package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
- package/dist/shared/datastore-attachment.d.ts +0 -99
- package/dist/shared/datastore-attachment.js +0 -184
- package/dist/shared/datastore-attachment.js.map +0 -1
- package/src/activities/execute-cursor/mcp-config.ts +0 -66
- package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +0 -104
- package/src/shared/__tests__/datastore-attachment.test.ts +0 -293
- package/src/shared/datastore-attachment.ts +0 -209
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { buildShellEnv, SHELL_ENV_DENYLIST } from "../shell-env.js";
|
|
3
|
+
import { RUNNER_CREDENTIAL_ENV_KEYS } from "../../../shared/runner-credential-keys.js";
|
|
3
4
|
|
|
4
5
|
describe("buildShellEnv", () => {
|
|
5
|
-
it("strips runner
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
};
|
|
6
|
+
it("strips every runner credential from the base env", () => {
|
|
7
|
+
// Plant a value for every name in the source-of-truth list, so a key
|
|
8
|
+
// added to runner-credential-keys.ts is covered here automatically.
|
|
9
|
+
const base: NodeJS.ProcessEnv = { PATH: "/usr/bin", HOME: "/home/runner" };
|
|
10
|
+
for (const key of RUNNER_CREDENTIAL_ENV_KEYS) {
|
|
11
|
+
base[key] = `leaked-${key}`;
|
|
12
|
+
}
|
|
13
13
|
|
|
14
14
|
const env = buildShellEnv({}, base);
|
|
15
15
|
|
|
16
16
|
expect(env.PATH).toBe("/usr/bin");
|
|
17
17
|
expect(env.HOME).toBe("/home/runner");
|
|
18
|
-
for (const key of
|
|
19
|
-
expect(env[key]).toBeUndefined();
|
|
18
|
+
for (const key of RUNNER_CREDENTIAL_ENV_KEYS) {
|
|
19
|
+
expect(env[key], `runner credential '${key}' must not reach the shell env`).toBeUndefined();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("denies the full credential list, not just the original three (issue #385)", () => {
|
|
24
|
+
// Pins the five names #385 added. The parameterized test above would
|
|
25
|
+
// pass even if the list regressed to three; this one cannot.
|
|
26
|
+
for (const key of [
|
|
27
|
+
"ANTHROPIC_API_KEY",
|
|
28
|
+
"OPENAI_API_KEY",
|
|
29
|
+
"STIGMER_AUTH_TOKEN",
|
|
30
|
+
"ANTHROPIC_FOUNDRY_API_KEY",
|
|
31
|
+
"AWS_BEARER_TOKEN_BEDROCK",
|
|
32
|
+
]) {
|
|
33
|
+
expect(SHELL_ENV_DENYLIST, `'${key}' must be in the shell denylist`).toContain(key);
|
|
20
34
|
}
|
|
21
35
|
});
|
|
22
36
|
|
|
@@ -83,12 +83,10 @@ describe("plan-mode sub-agent filesystem permissions (issue #255)", () => {
|
|
|
83
83
|
// One turn, three tool calls: both mutation tools must be denied by the
|
|
84
84
|
// permission rules; the read must pass through untouched (the deny rule
|
|
85
85
|
// covers the `write` operation class only).
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
// paths with a validation error before any rule or backend runs — a
|
|
91
|
-
// different denial, but still nothing written.
|
|
86
|
+
// Absolute workspace paths — the canonical shape after enforcement
|
|
87
|
+
// canonicalization, and what the single-workspace prompt's prose (the
|
|
88
|
+
// absolute rootDir) steers models toward. The workspace-relative shape
|
|
89
|
+
// is pinned separately below (issue #429).
|
|
92
90
|
const script: ScriptSelector = () => ({
|
|
93
91
|
toolCalls: [
|
|
94
92
|
{ name: "write_file", args: { file_path: join(root, "dist/out.txt"), content: "new file" }, id: "c_write" },
|
|
@@ -143,19 +141,20 @@ describe("plan-mode sub-agent filesystem permissions (issue #255)", () => {
|
|
|
143
141
|
expect(observer.before.size).toBe(1);
|
|
144
142
|
});
|
|
145
143
|
|
|
146
|
-
it("relative-path
|
|
147
|
-
// The
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
// relative
|
|
154
|
-
//
|
|
155
|
-
//
|
|
144
|
+
it("relative-path writes are refused by the RULES, and relative reads flow (issue #429)", async () => {
|
|
145
|
+
// The multi-workspace prompt mandates workspace-relative paths, and
|
|
146
|
+
// models routinely choose them in single-workspace sessions too. Before
|
|
147
|
+
// the #429 fix, deepagents' permission canonicalization refused the
|
|
148
|
+
// shape outright — every relative call, reads included, died with
|
|
149
|
+
// "path must be absolute" before any rule ran. compileSubagents now
|
|
150
|
+
// derives a path-normalization shim from the same `permissions` it
|
|
151
|
+
// bakes into the graph, so the relative shape reaches the rules in
|
|
152
|
+
// canonical form: writes get the honest plan-mode denial, reads just
|
|
153
|
+
// work. Read-only-by-construction (issue #255) holds throughout.
|
|
156
154
|
const script: ScriptSelector = () => ({
|
|
157
155
|
toolCalls: [
|
|
158
156
|
{ name: "write_file", args: { file_path: "dist/out.txt", content: "new file" }, id: "c_write_rel" },
|
|
157
|
+
{ name: "read_file", args: { file_path: "notes.md" }, id: "c_read_rel" },
|
|
159
158
|
],
|
|
160
159
|
done: "worker done",
|
|
161
160
|
});
|
|
@@ -166,8 +165,14 @@ describe("plan-mode sub-agent filesystem permissions (issue #255)", () => {
|
|
|
166
165
|
{ configurable: { thread_id: "t3" }, recursionLimit: 50 },
|
|
167
166
|
)) as { messages: BaseMessage[] };
|
|
168
167
|
|
|
169
|
-
|
|
168
|
+
const writeResult = toolResultById(result.messages, "c_write_rel");
|
|
169
|
+
expect(writeResult).toMatch(/permission denied for write/i);
|
|
170
|
+
expect(writeResult).not.toMatch(/path must be absolute/i);
|
|
170
171
|
await expect(access(join(root, "dist/out.txt"))).rejects.toThrow();
|
|
171
172
|
expect(observer.before.size).toBe(0);
|
|
173
|
+
|
|
174
|
+
const readResult = toolResultById(result.messages, "c_read_rel");
|
|
175
|
+
expect(readResult).toContain("PLAN_MODE_README_TOKEN");
|
|
176
|
+
expect(readResult).not.toMatch(/path must be absolute/i);
|
|
172
177
|
});
|
|
173
178
|
});
|
|
@@ -27,6 +27,7 @@ import { posix } from "node:path";
|
|
|
27
27
|
import type { Attachment } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
28
28
|
import type { WorkspaceBackend } from "../../shared/workspace/types.js";
|
|
29
29
|
import type { ArtifactStorage } from "../../shared/artifact-storage.js";
|
|
30
|
+
import { mintAttachmentDownloadUrl } from "../../shared/attachment-download-urls.js";
|
|
30
31
|
import { allocateUniqueName } from "../../shared/attachment-naming.js";
|
|
31
32
|
import type {
|
|
32
33
|
VisionBudget,
|
|
@@ -66,6 +67,17 @@ export interface InjectedFile {
|
|
|
66
67
|
* Attachments that were never image-shaped carry neither field.
|
|
67
68
|
*/
|
|
68
69
|
readonly visionDegraded?: VisionDegradedReason;
|
|
70
|
+
/**
|
|
71
|
+
* A download URL for the attachment's stored object, listed in the system
|
|
72
|
+
* prompt's Input Files section so the agent can hand the file to a tool
|
|
73
|
+
* whose backend cannot read the sandbox filesystem (issue #532). Minted
|
|
74
|
+
* whenever the attachment has a `storageKey` and a usable storage; never
|
|
75
|
+
* present on extracted ZIP entries (no attachment-level object). Absent
|
|
76
|
+
* when there is nothing to mint or the mint failed (non-fatal — policy,
|
|
77
|
+
* degrade log, and prompt wording live in
|
|
78
|
+
* shared/attachment-download-urls.ts).
|
|
79
|
+
*/
|
|
80
|
+
readonly downloadUrl?: string;
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
export interface InjectAttachmentsOptions {
|
|
@@ -300,7 +312,10 @@ export async function injectAttachments(opts: InjectAttachmentsOptions): Promise
|
|
|
300
312
|
);
|
|
301
313
|
// A renamed mount DIR is visible through every extracted path; the
|
|
302
314
|
// entries themselves were not renamed, so they carry no renamedFrom
|
|
303
|
-
// (per-file disclosure would misattribute the rename).
|
|
315
|
+
// (per-file disclosure would misattribute the rename). Extracted
|
|
316
|
+
// entries also carry no downloadUrl: the stored object is the ZIP, not
|
|
317
|
+
// any listed file, and a URL to bytes that differ from the listing
|
|
318
|
+
// would be a false promise.
|
|
304
319
|
injectedFiles.push(...extracted);
|
|
305
320
|
} else {
|
|
306
321
|
await backend.writeFileBuffer(mountPath, content);
|
|
@@ -313,6 +328,9 @@ export async function injectAttachments(opts: InjectAttachmentsOptions): Promise
|
|
|
313
328
|
// the vision budget before they go out of scope (the sniff decides
|
|
314
329
|
// eligibility; the budget owns every size/count rule).
|
|
315
330
|
const vision = visionBudget?.offer(filename, attachment.contentType, content);
|
|
331
|
+
const downloadUrl = await mintAttachmentDownloadUrl(
|
|
332
|
+
storage, attachment.storageKey, filename,
|
|
333
|
+
);
|
|
316
334
|
injectedFiles.push({
|
|
317
335
|
filename,
|
|
318
336
|
path: mountPath,
|
|
@@ -320,6 +338,7 @@ export async function injectAttachments(opts: InjectAttachmentsOptions): Promise
|
|
|
320
338
|
...(renamedFrom !== undefined ? { renamedFrom } : {}),
|
|
321
339
|
...(vision?.kind === "accepted" ? { vision: vision.image } : {}),
|
|
322
340
|
...(vision?.kind === "degraded" ? { visionDegraded: vision.reason } : {}),
|
|
341
|
+
...(downloadUrl !== undefined ? { downloadUrl } : {}),
|
|
323
342
|
});
|
|
324
343
|
}
|
|
325
344
|
}
|
|
@@ -32,7 +32,6 @@ import {
|
|
|
32
32
|
} from "../../shared/workspace/stigmer-link.js";
|
|
33
33
|
import type { ToolOutputOffloadContext } from "../../shared/status-offload.js";
|
|
34
34
|
import { publishPlanArtifact } from "../../shared/plan-artifact.js";
|
|
35
|
-
import { formatDatastoreDegradationNotice } from "../../shared/datastore-attachment.js";
|
|
36
35
|
import { classifyTool } from "../../shared/tool-kind.js";
|
|
37
36
|
import {
|
|
38
37
|
POLICY_ENGINE_VERSION,
|
|
@@ -153,24 +152,6 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
153
152
|
? seedStatusFromExecution(setup.execution)
|
|
154
153
|
: create(AgentExecutionStatusSchema, {});
|
|
155
154
|
|
|
156
|
-
// Datastore degradation disclosure, operator half (issue #325): the
|
|
157
|
-
// prompt already tells the agent to disclose the outage; this makes
|
|
158
|
-
// it visible on the execution record without reading a transcript.
|
|
159
|
-
// Appended to initialStatus BEFORE streaming (not via a status
|
|
160
|
-
// builder): both the v2 and v3 stream paths construct their own
|
|
161
|
-
// builder around this same proto and only ever append messages, so
|
|
162
|
-
// this row survives whichever path runs and rides every persist.
|
|
163
|
-
if (setup.datastoreToolsMissing.length > 0) {
|
|
164
|
-
initialStatus.messages.push(create(AgentMessageSchema, {
|
|
165
|
-
type: MessageType.MESSAGE_SYSTEM,
|
|
166
|
-
content: formatDatastoreDegradationNotice(
|
|
167
|
-
setup.agent.spec?.datastoreUsages?.length ?? 0,
|
|
168
|
-
setup.datastoreToolsMissing,
|
|
169
|
-
),
|
|
170
|
-
timestamp: utcTimestamp(),
|
|
171
|
-
}));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
155
|
const statusBuilder = new StatusBuilder(executionId, initialStatus);
|
|
175
156
|
|
|
176
157
|
statusBuilder.setApprovalProvider({
|
|
@@ -17,8 +17,6 @@
|
|
|
17
17
|
export interface McpToolSources {
|
|
18
18
|
/** Declared MCP server usages (agent spec + session spec). */
|
|
19
19
|
readonly mcpServerUsageCount: number;
|
|
20
|
-
/** Declared datastore usages (the records attachment, T05). */
|
|
21
|
-
readonly datastoreUsageCount: number;
|
|
22
20
|
/** Serving proactive-messaging channels (the channels attachment, DD-006). */
|
|
23
21
|
readonly channelMessagingCount: number;
|
|
24
22
|
/** The serving channel id when this session IS a live channel
|
|
@@ -30,7 +28,6 @@ export interface McpToolSources {
|
|
|
30
28
|
export function shouldConnectMcp(sources: McpToolSources): boolean {
|
|
31
29
|
return (
|
|
32
30
|
sources.mcpServerUsageCount > 0 ||
|
|
33
|
-
sources.datastoreUsageCount > 0 ||
|
|
34
31
|
sources.channelMessagingCount > 0 ||
|
|
35
32
|
sources.conversationChannelId !== undefined
|
|
36
33
|
);
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
visionDisclosureLines,
|
|
22
22
|
type NotViewableEntry,
|
|
23
23
|
} from "../../shared/attachment-vision.js";
|
|
24
|
+
import {
|
|
25
|
+
downloadUrlDisclosureLine,
|
|
26
|
+
type DownloadUrlKind,
|
|
27
|
+
} from "../../shared/attachment-download-urls.js";
|
|
24
28
|
import { PLAN_MODE_DIRECTIVE } from "../../shared/plan-mode-prompt.js";
|
|
25
29
|
import {
|
|
26
30
|
buildImplementPlanDirective,
|
|
@@ -99,14 +103,6 @@ export interface PromptBuilderInput {
|
|
|
99
103
|
provisionResults: ProvisionResult[];
|
|
100
104
|
containerRoot: string;
|
|
101
105
|
skillsPromptSection: string;
|
|
102
|
-
/**
|
|
103
|
-
* The datastores section (shared/datastore-attachment.ts
|
|
104
|
-
* formatDatastoresSection): `<available_datastores>` when the connected
|
|
105
|
-
* record-tool roster is complete, `<unavailable_datastores>` when setup's
|
|
106
|
-
* reconciliation found record tools missing (issue #325). Empty when the
|
|
107
|
-
* agent uses no datastores.
|
|
108
|
-
*/
|
|
109
|
-
datastoresPromptSection?: string;
|
|
110
106
|
/**
|
|
111
107
|
* The `<available_channel_templates>` section
|
|
112
108
|
* (shared/channel-attachment.ts formatChannelTemplatesSection); absent
|
|
@@ -122,6 +118,12 @@ export interface PromptBuilderInput {
|
|
|
122
118
|
* Rendered inside the Input Files section.
|
|
123
119
|
*/
|
|
124
120
|
vision?: VisionPromptInfo;
|
|
121
|
+
/**
|
|
122
|
+
* What kind of URL the turn's storage backend mints (issue #532) — keys
|
|
123
|
+
* the Input Files section's hand-off wording (attachment-download-urls.ts).
|
|
124
|
+
* One turn-level fact: all attachments ride the one configured storage.
|
|
125
|
+
*/
|
|
126
|
+
downloadUrlKind?: DownloadUrlKind;
|
|
125
127
|
/**
|
|
126
128
|
* The execution's interaction mode. PLAN appends the shared plan-mode
|
|
127
129
|
* directive so the model knows the turn's deliverable is a plan document.
|
|
@@ -201,10 +203,6 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
|
201
203
|
prompt += input.skillsPromptSection;
|
|
202
204
|
}
|
|
203
205
|
|
|
204
|
-
if (input.datastoresPromptSection) {
|
|
205
|
-
prompt += "\n\n" + input.datastoresPromptSection;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
206
|
if (input.channelTemplatesPromptSection) {
|
|
209
207
|
prompt += "\n\n" + input.channelTemplatesPromptSection;
|
|
210
208
|
}
|
|
@@ -220,7 +218,9 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
|
220
218
|
}
|
|
221
219
|
|
|
222
220
|
if (input.injectedFiles.length > 0) {
|
|
223
|
-
prompt += buildInjectedFilesSection(
|
|
221
|
+
prompt += buildInjectedFilesSection(
|
|
222
|
+
input.injectedFiles, input.vision, input.downloadUrlKind,
|
|
223
|
+
);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
if (input.senderIdentity) {
|
|
@@ -401,6 +401,7 @@ function buildReferencedFilesSection(
|
|
|
401
401
|
function buildInjectedFilesSection(
|
|
402
402
|
files: readonly InjectedFile[],
|
|
403
403
|
vision?: VisionPromptInfo,
|
|
404
|
+
downloadUrlKind?: DownloadUrlKind,
|
|
404
405
|
): string {
|
|
405
406
|
let section = "\n\n## Input Files\n\n";
|
|
406
407
|
section +=
|
|
@@ -416,12 +417,23 @@ function buildInjectedFilesSection(
|
|
|
416
417
|
const sizeInfo = ` (${f.sizeBytes} bytes)`;
|
|
417
418
|
// A duplicate-renamed file (attachment-naming.ts) discloses its original
|
|
418
419
|
// name so the agent can connect "the two report.pdfs" in the user's
|
|
419
|
-
// message to distinct files on disk.
|
|
420
|
+
// message to distinct files on disk. A file with a minted download URL
|
|
421
|
+
// (attachment-download-urls.ts) lists it beside the path for the remote
|
|
422
|
+
// hand-off story.
|
|
420
423
|
const renameInfo =
|
|
421
424
|
f.renamedFrom !== undefined
|
|
422
425
|
? ` (renamed from duplicate '${f.renamedFrom}')`
|
|
423
426
|
: "";
|
|
424
|
-
|
|
427
|
+
const urlInfo =
|
|
428
|
+
f.downloadUrl !== undefined ? ` — download URL: ${f.downloadUrl}` : "";
|
|
429
|
+
section += `- \`${f.path}\`${sizeInfo}${renameInfo}${urlInfo}\n`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// The URL hand-off line (shared wording, attachment-download-urls.ts)
|
|
433
|
+
// renders only when some listed file actually carries a URL — its wording
|
|
434
|
+
// keys on what kind of URL the storage backend mints.
|
|
435
|
+
if (downloadUrlKind !== undefined && files.some((f) => f.downloadUrl !== undefined)) {
|
|
436
|
+
section += "\n" + downloadUrlDisclosureLine(downloadUrlKind) + "\n";
|
|
425
437
|
}
|
|
426
438
|
|
|
427
439
|
// The vision lines (shared wording, attachment-vision.ts) tell the model
|
|
@@ -36,12 +36,6 @@ import { connectMcpServers, type McpConnectionResult } from "../../shared/mcp-ma
|
|
|
36
36
|
import { mergeMcpServerUsages, resolveMcpServers } from "../../shared/mcp-resolver.js";
|
|
37
37
|
import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
|
|
38
38
|
import { backfillMcpServersIfNeeded } from "../../shared/connect-backfill.js";
|
|
39
|
-
import {
|
|
40
|
-
DATASTORE_ATTACHMENT_SLUG,
|
|
41
|
-
formatDatastoresSection,
|
|
42
|
-
missingRecordTools,
|
|
43
|
-
synthesizeDatastoreAttachment,
|
|
44
|
-
} from "../../shared/datastore-attachment.js";
|
|
45
39
|
import {
|
|
46
40
|
discoverChannelMessaging,
|
|
47
41
|
formatChannelTemplatesSection,
|
|
@@ -129,16 +123,6 @@ export interface SetupResult {
|
|
|
129
123
|
readonly session: Session;
|
|
130
124
|
readonly workspaceBackend: WorkspaceBackend;
|
|
131
125
|
readonly mcpConnection: McpConnectionResult | null;
|
|
132
|
-
/**
|
|
133
|
-
* Record tools declared via datastore_usages but absent from the connected
|
|
134
|
-
* stigmer-records roster this turn (issue #325). Empty = healthy (also when
|
|
135
|
-
* the agent uses no datastores). Non-empty selects the degraded prompt
|
|
136
|
-
* section in Step 8, and the activity surfaces it to operators as a
|
|
137
|
-
* MESSAGE_SYSTEM row — the vision-facts pattern: setup computes the honest
|
|
138
|
-
* struct, the caller discloses. Recomputed every turn, so a healed outage
|
|
139
|
-
* stops disclosing on the next turn.
|
|
140
|
-
*/
|
|
141
|
-
readonly datastoreToolsMissing: readonly string[];
|
|
142
126
|
readonly mergedEnvVars: Record<string, string>;
|
|
143
127
|
readonly secretKeys: ReadonlySet<string>;
|
|
144
128
|
readonly modelName: string;
|
|
@@ -369,7 +353,6 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
369
353
|
agent.spec!.mcpServerUsages || [],
|
|
370
354
|
session.spec!.mcpServerUsages || [],
|
|
371
355
|
);
|
|
372
|
-
const datastoreUsages = agent.spec!.datastoreUsages || [];
|
|
373
356
|
|
|
374
357
|
// The synthesized attachments' credential story (DD-006 D4): the
|
|
375
358
|
// exchanged token authenticates the discovery reads per-call (the
|
|
@@ -410,7 +393,6 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
410
393
|
let resolvedMcpServers: Awaited<ReturnType<typeof resolveMcpServers>> | null = null;
|
|
411
394
|
if (shouldConnectMcp({
|
|
412
395
|
mcpServerUsageCount: mcpServerUsages.length,
|
|
413
|
-
datastoreUsageCount: datastoreUsages.length,
|
|
414
396
|
channelMessagingCount: channelMessaging.length,
|
|
415
397
|
conversationChannelId,
|
|
416
398
|
})) {
|
|
@@ -444,25 +426,9 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
444
426
|
envResult.secretKeys,
|
|
445
427
|
);
|
|
446
428
|
|
|
447
|
-
// The
|
|
448
|
-
// backfill so the destructiveHint tightener can never gate
|
|
449
|
-
// approval maps keep it approval-free by construction
|
|
450
|
-
// shared/datastore-attachment.ts).
|
|
451
|
-
if (datastoreUsages.length > 0) {
|
|
452
|
-
const attachment = synthesizeDatastoreAttachment(datastoreUsages, {
|
|
453
|
-
bridgeEndpoint: config.mcpBridgeEndpoint,
|
|
454
|
-
credential: attachmentCredential,
|
|
455
|
-
backendEndpoint: config.stigmerBackendEndpoint,
|
|
456
|
-
});
|
|
457
|
-
if (attachment) {
|
|
458
|
-
backfilledServers = injectSynthesizedAttachment(
|
|
459
|
-
backfilledServers, attachment, "datastore records",
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// The channel messaging attachment (DD-006 D7/D8) — the records
|
|
465
|
-
// attachment's twin, injected under the same after-backfill rule.
|
|
429
|
+
// The channel messaging attachment (DD-006 D7/D8) — injected AFTER
|
|
430
|
+
// resolve + backfill so the destructiveHint tightener can never gate
|
|
431
|
+
// it; empty approval maps keep it approval-free by construction.
|
|
466
432
|
if (channelMessaging.length > 0) {
|
|
467
433
|
const attachment = synthesizeChannelAttachment(channelMessaging, {
|
|
468
434
|
bridgeEndpoint: config.mcpBridgeEndpoint,
|
|
@@ -477,8 +443,8 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
477
443
|
}
|
|
478
444
|
|
|
479
445
|
// The conversation participation attachment (DD-008 D-c) — the
|
|
480
|
-
//
|
|
481
|
-
// answers undefined with no bridge endpoint by design (see
|
|
446
|
+
// channel attachment's sibling, same after-backfill rule. HTTP-only:
|
|
447
|
+
// synthesize answers undefined with no bridge endpoint by design (see
|
|
482
448
|
// shared/conversation-attachment.ts).
|
|
483
449
|
const conversationAttachment = synthesizeConversationAttachment(conversationChannelId, {
|
|
484
450
|
bridgeEndpoint: config.mcpBridgeEndpoint,
|
|
@@ -585,37 +551,12 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
585
551
|
}
|
|
586
552
|
timing.mark("inject_attachments");
|
|
587
553
|
|
|
588
|
-
// Reconcile the declared datastores against the record tools actually
|
|
589
|
-
// connected (issue #325): the records roster always serves all five
|
|
590
|
-
// tools, so any absence means the store is degraded, and the prompt
|
|
591
|
-
// must say so instead of promising tools the agent does not have. The
|
|
592
|
-
// connected roster is post-connect truth — when usages exist, MCP
|
|
593
|
-
// connect always ran above (a thrown connect already failed the run).
|
|
594
|
-
const datastoreToolsMissing = datastoreUsages.length > 0 && mcpConnection
|
|
595
|
-
? missingRecordTools(
|
|
596
|
-
(mcpConnection.serverToolMap[DATASTORE_ATTACHMENT_SLUG] ?? [])
|
|
597
|
-
.map((t) => t.name),
|
|
598
|
-
)
|
|
599
|
-
: [];
|
|
600
|
-
if (datastoreToolsMissing.length > 0) {
|
|
601
|
-
console.warn(
|
|
602
|
-
`[setup] Datastore record tools degraded: declared ` +
|
|
603
|
-
`${datastoreUsages.length} datastore usage(s) but the ` +
|
|
604
|
-
`'${DATASTORE_ATTACHMENT_SLUG}' roster is missing ` +
|
|
605
|
-
`[${datastoreToolsMissing.join(", ")}] — the prompt discloses the ` +
|
|
606
|
-
`outage instead of the available-datastores section.`,
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
554
|
// Step 8: Build enhanced system prompt
|
|
611
555
|
const systemPrompt = buildEnhancedSystemPrompt({
|
|
612
556
|
instructions,
|
|
613
557
|
provisionResults,
|
|
614
558
|
containerRoot: workspaceBackend.rootDir,
|
|
615
559
|
skillsPromptSection,
|
|
616
|
-
datastoresPromptSection: datastoreUsages.length > 0
|
|
617
|
-
? formatDatastoresSection(datastoreUsages, datastoreToolsMissing)
|
|
618
|
-
: undefined,
|
|
619
560
|
// "" (nothing sendable) threads as undefined: the tool alone still
|
|
620
561
|
// serves text sends inside a 24-hour window (DD-006 D6).
|
|
621
562
|
channelTemplatesPromptSection: channelMessaging.length > 0
|
|
@@ -625,6 +566,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
625
566
|
workspaceRoot: workspaceBackend.rootDir,
|
|
626
567
|
injectedFiles,
|
|
627
568
|
vision: visionPromptInfo,
|
|
569
|
+
downloadUrlKind: artifactStorage?.downloadUrlKind,
|
|
628
570
|
interactionMode: execution.spec!.executionConfig?.interactionMode,
|
|
629
571
|
buildFromPlan: execution.spec!.executionConfig?.buildFromPlan,
|
|
630
572
|
contextBridge: readContextBridge(session.spec!.metadata),
|
|
@@ -635,17 +577,14 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
635
577
|
// Step 9: Construct the LLM model. Resolution to the provider API id
|
|
636
578
|
// happens inside buildChatModel; modelName stays the registry id for
|
|
637
579
|
// pricing, the native-thinking heuristic, and sub-agent inheritance.
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
|
|
641
|
-
Number.parseInt(process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS ?? "", 10);
|
|
642
|
-
const requestTimeoutMs = parsedTimeoutMs > 0 ? parsedTimeoutMs : undefined;
|
|
580
|
+
// The operator's STIGMER_LLM_REQUEST_TIMEOUT_MS bound is applied inside
|
|
581
|
+
// buildChatModel (#468) — the sub-agent modelFactory below silently
|
|
582
|
+
// dropped it when each caller parsed the env itself.
|
|
643
583
|
const { model } = await buildChatModel({
|
|
644
584
|
modelName,
|
|
645
585
|
proxyEndpoint: config.proxyEndpoint ?? undefined,
|
|
646
586
|
stigmerToken: config.stigmerToken ?? undefined,
|
|
647
587
|
headerScope: { executionId },
|
|
648
|
-
timeoutMs: requestTimeoutMs,
|
|
649
588
|
});
|
|
650
589
|
timing.mark("build_model");
|
|
651
590
|
|
|
@@ -655,6 +594,12 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
655
594
|
const execConfig = execution.spec!.executionConfig;
|
|
656
595
|
const isPlanMode = execConfig?.interactionMode === InteractionMode.PLAN;
|
|
657
596
|
const shellEnv = isPlanMode ? undefined : buildShellEnv(envResult.mergedEnvVars);
|
|
597
|
+
// Plan mode's filesystem permission rules, hoisted once: the parent graph,
|
|
598
|
+
// every sub-agent graph, AND the path-normalization middleware (issue #429)
|
|
599
|
+
// all derive from this single value, so a rule-bearing graph can never miss
|
|
600
|
+
// the shim that keeps prompt-compliant relative paths from dying in rule
|
|
601
|
+
// validation (`path must be absolute`).
|
|
602
|
+
const planModePermissions = isPlanMode ? PLAN_MODE_PERMISSIONS : undefined;
|
|
658
603
|
|
|
659
604
|
const toolServerMap = new Map<string, string>();
|
|
660
605
|
if (mcpConnection) {
|
|
@@ -760,6 +705,9 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
760
705
|
} : null,
|
|
761
706
|
otelSpans: { toolServerMap },
|
|
762
707
|
approvalGate: approvalGateConfig,
|
|
708
|
+
pathNormalization: planModePermissions
|
|
709
|
+
? { rootDir: workspaceBackend.rootDir }
|
|
710
|
+
: null,
|
|
763
711
|
});
|
|
764
712
|
timing.mark("build_middleware");
|
|
765
713
|
|
|
@@ -823,7 +771,9 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
823
771
|
// Plan mode's deny-all-writes rule, mirrored onto every sub-agent graph
|
|
824
772
|
// (issue #255) — pre-built CompiledSubAgents never inherit the parent's
|
|
825
773
|
// permissions, so read-only-by-construction must be baked in here.
|
|
826
|
-
|
|
774
|
+
// compileSubagents derives each sub-agent's path-normalization shim
|
|
775
|
+
// from this same value (issue #429).
|
|
776
|
+
...(planModePermissions ? { permissions: planModePermissions } : {}),
|
|
827
777
|
});
|
|
828
778
|
timing.mark("compile_subagents");
|
|
829
779
|
}
|
|
@@ -857,7 +807,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
857
807
|
middleware: middleware as any,
|
|
858
808
|
subagents: compiledSubagents ?? undefined,
|
|
859
809
|
...(responseFormat ? { responseFormat } : {}),
|
|
860
|
-
...(
|
|
810
|
+
...(planModePermissions ? { permissions: planModePermissions } : {}),
|
|
861
811
|
} as Parameters<typeof createDeepAgent>[0]);
|
|
862
812
|
|
|
863
813
|
// Step 11: Prepare invocation input and config. The conversation catchup
|
|
@@ -928,7 +878,6 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
928
878
|
session,
|
|
929
879
|
workspaceBackend,
|
|
930
880
|
mcpConnection,
|
|
931
|
-
datastoreToolsMissing,
|
|
932
881
|
mergedEnvVars: envResult.mergedEnvVars,
|
|
933
882
|
secretKeys: envResult.secretKeys,
|
|
934
883
|
modelName,
|
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
* never once at process start.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
];
|
|
9
|
+
import { RUNNER_CREDENTIAL_ENV_KEYS } from "../../shared/runner-credential-keys.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Runner-internal keys that must never reach agent shell commands: every
|
|
13
|
+
* credential the runner holds for its own outbound calls (issue #385). The
|
|
14
|
+
* names — and the rule for adding one — live in runner-credential-keys.ts.
|
|
15
|
+
*/
|
|
16
|
+
export const SHELL_ENV_DENYLIST: readonly string[] = RUNNER_CREDENTIAL_ENV_KEYS;
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* Build the environment map passed to deepagents' LocalShellBackend.
|
|
@@ -554,11 +554,17 @@ export async function compileSubagents(
|
|
|
554
554
|
// Structural coupling (DD-19): a sub-agent gate flows gitignored writes into
|
|
555
555
|
// CAS iff a CAS observer backs that sub-agent's filesystem backend. Deriving
|
|
556
556
|
// both from the same `casObserver` makes "unobserved unreviewable bytes"
|
|
557
|
-
// impossible by construction.
|
|
557
|
+
// impossible by construction. Same idiom for path normalization
|
|
558
|
+
// (issue #429): derived from the same `permissions` baked into the graph
|
|
559
|
+
// below, so a rule-bearing graph always carries the shim that keeps
|
|
560
|
+
// prompt-compliant relative paths from dying in rule validation.
|
|
558
561
|
const middleware = buildSubAgentMiddleware({
|
|
559
562
|
costCap: opts.costCap,
|
|
560
563
|
approvalGate: opts.approvalGate,
|
|
561
564
|
captureIgnored: !!opts.casObserver,
|
|
565
|
+
...(opts.permissions?.length
|
|
566
|
+
? { pathNormalization: { rootDir: opts.workspaceRootDir } }
|
|
567
|
+
: {}),
|
|
562
568
|
});
|
|
563
569
|
|
|
564
570
|
const modelName = spec.model ?? opts.parentModelName;
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* Sub-agent middleware composition for ExecuteDeepAgent.
|
|
3
3
|
*
|
|
4
4
|
* Each sub-agent gets its own middleware stack with:
|
|
5
|
+
* - Path normalization (issue #429), FIRST and only on permission-rule-
|
|
6
|
+
* bearing graphs (plan mode) — workspace-relative paths are rewritten to
|
|
7
|
+
* workspace-absolute before deepagents' permission validation refuses
|
|
8
|
+
* them, exactly as on the parent. `compileSubagents` derives it from the
|
|
9
|
+
* same `permissions` option it bakes into the graph, keeping the rules
|
|
10
|
+
* and their normalization shim coupled.
|
|
5
11
|
* - Fresh loop detection (independent cycle tracking)
|
|
6
12
|
* - Fresh tool truncation (same limits as parent)
|
|
7
13
|
* - Periodic execution budget (interval=30, max=4 advisories)
|
|
@@ -27,8 +33,13 @@
|
|
|
27
33
|
* the live HITL bypass this wiring closes.
|
|
28
34
|
*/
|
|
29
35
|
|
|
30
|
-
import type {
|
|
36
|
+
import type {
|
|
37
|
+
StigmerMiddleware,
|
|
38
|
+
ToolTruncationConfig,
|
|
39
|
+
PathNormalizationConfig,
|
|
40
|
+
} from "../../middleware/types.js";
|
|
31
41
|
import type { CostCapMiddleware } from "../../middleware/index.js";
|
|
42
|
+
import { createPathNormalizationMiddleware } from "../../middleware/path-normalization.js";
|
|
32
43
|
import { createLoopDetectionMiddleware } from "../../middleware/loop-detection.js";
|
|
33
44
|
import { createToolTruncationMiddleware } from "../../middleware/tool-truncation.js";
|
|
34
45
|
import { createExecutionBudgetMiddleware } from "../../middleware/execution-budget.js";
|
|
@@ -59,14 +70,22 @@ export interface SubAgentMiddlewareOptions {
|
|
|
59
70
|
* unobserved backend would apply unreviewable bytes.
|
|
60
71
|
*/
|
|
61
72
|
readonly captureIgnored?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Workspace-relative path normalization (issue #429). Present iff this
|
|
75
|
+
* sub-agent's graph carries filesystem permission rules — the caller
|
|
76
|
+
* derives it from the same `permissions` value it bakes into the graph.
|
|
77
|
+
*/
|
|
78
|
+
readonly pathNormalization?: PathNormalizationConfig;
|
|
62
79
|
}
|
|
63
80
|
|
|
64
81
|
/**
|
|
65
82
|
* Build the middleware stack for a single sub-agent.
|
|
66
83
|
*
|
|
67
84
|
* Returns an ordered array mirroring the parent composition:
|
|
68
|
-
* loop detection → execution budget (periodic) →
|
|
69
|
-
* [approval gate] → cost cap view → error hints.
|
|
85
|
+
* [path normalization] → loop detection → execution budget (periodic) →
|
|
86
|
+
* tool truncation → [approval gate] → cost cap view → error hints.
|
|
87
|
+
* Normalization is outermost so everything downstream observes canonical
|
|
88
|
+
* workspace-absolute paths (matching the parent). The gate sits before the
|
|
70
89
|
* cost-cap view so an approval pause happens before budget accounting, and
|
|
71
90
|
* error hints come after the gate — both matching the parent order
|
|
72
91
|
* (…→ truncation → graceful-stop → approval gate → cost cap → error hints …),
|
|
@@ -77,6 +96,10 @@ export function buildSubAgentMiddleware(
|
|
|
77
96
|
): StigmerMiddleware[] {
|
|
78
97
|
const stack: StigmerMiddleware[] = [];
|
|
79
98
|
|
|
99
|
+
if (options.pathNormalization) {
|
|
100
|
+
stack.push(createPathNormalizationMiddleware(options.pathNormalization));
|
|
101
|
+
}
|
|
102
|
+
|
|
80
103
|
stack.push(createLoopDetectionMiddleware({ enabled: true }));
|
|
81
104
|
|
|
82
105
|
stack.push(createExecutionBudgetMiddleware({
|
|
@@ -265,11 +265,14 @@ async function fetchAndFlattenEnv(
|
|
|
265
265
|
client: StigmerClient,
|
|
266
266
|
executionId: string,
|
|
267
267
|
): Promise<Record<string, unknown>> {
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
268
|
+
// The runner acquires a token scoped to this workflow execution so the
|
|
269
|
+
// server's decrypt gate binds the read: a desktop runner exchanges its
|
|
270
|
+
// bootstrap credential (#156, hard error on failure — the bootstrap
|
|
271
|
+
// credential no longer decrypts since stigmer-cloud#218, so proceeding
|
|
272
|
+
// would hydrate redacted placeholders), and an OSS runner asks the
|
|
273
|
+
// credential-less best-effort exchange (oss#535 — the OSS server redacts
|
|
274
|
+
// EC reads by default and mints execution-scoped tokens). No-op for cloud
|
|
275
|
+
// sandbox runners, whose ambient credential is already scoped.
|
|
273
276
|
const scopedToken = await client.acquireScopedRunnerToken({
|
|
274
277
|
workflowExecutionId: executionId,
|
|
275
278
|
});
|