@stigmer/runner 3.12.3 → 3.12.5
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/.build-fingerprint +1 -1
- package/dist/activities/call-http.js +12 -0
- package/dist/activities/call-http.js.map +1 -1
- package/dist/activities/call-llm.d.ts +18 -0
- package/dist/activities/call-llm.js +56 -2
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/execute-cursor/agent-session-cache.d.ts +72 -0
- package/dist/activities/execute-cursor/agent-session-cache.js +186 -0
- package/dist/activities/execute-cursor/agent-session-cache.js.map +1 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
- package/dist/activities/execute-cursor/index.d.ts +10 -1
- package/dist/activities/execute-cursor/index.js +108 -57
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
- package/dist/activities/execute-cursor/prompt-builder.js +11 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/service-tier.d.ts +5 -15
- package/dist/activities/execute-cursor/service-tier.js +5 -21
- package/dist/activities/execute-cursor/service-tier.js.map +1 -1
- package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
- package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
- package/dist/activities/execute-cursor/skill-resolver.js +5 -108
- package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +9 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js +2 -1
- package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +18 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.js +20 -3
- package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +57 -5
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +31 -16
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/shell-env.d.ts +8 -5
- package/dist/activities/execute-deep-agent/shell-env.js +10 -7
- package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
- package/dist/config.js +10 -5
- package/dist/config.js.map +1 -1
- package/dist/encryption/config.js +7 -2
- package/dist/encryption/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/main.js +12 -6
- package/dist/main.js.map +1 -1
- package/dist/middleware/path-normalization.d.ts +49 -49
- package/dist/middleware/path-normalization.js +73 -84
- package/dist/middleware/path-normalization.js.map +1 -1
- package/dist/middleware/types.d.ts +3 -3
- package/dist/payload-codecs.js +2 -1
- package/dist/payload-codecs.js.map +1 -1
- package/dist/runner-manager.d.ts +1 -1
- package/dist/runner-manager.js +42 -22
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +30 -6
- package/dist/runner.js.map +1 -1
- package/dist/shared/args-preview.d.ts +8 -0
- package/dist/shared/args-preview.js +14 -3
- package/dist/shared/args-preview.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +11 -1
- package/dist/shared/artifact-storage.js +10 -1
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/declared-preferences.d.ts +47 -0
- package/dist/shared/declared-preferences.js +64 -0
- package/dist/shared/declared-preferences.js.map +1 -0
- package/dist/shared/fingerprint-secret.d.ts +3 -2
- package/dist/shared/fingerprint-secret.js +5 -3
- package/dist/shared/fingerprint-secret.js.map +1 -1
- package/dist/shared/llm-backend.js +8 -1
- package/dist/shared/llm-backend.js.map +1 -1
- package/dist/shared/mcp-enabled-tools.d.ts +2 -3
- package/dist/shared/mcp-enabled-tools.js +2 -3
- package/dist/shared/mcp-enabled-tools.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +20 -15
- package/dist/shared/mcp-resolver.js +11 -12
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/model-client.d.ts +15 -0
- package/dist/shared/model-client.js +57 -13
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/placeholder-resolver.d.ts +9 -2
- package/dist/shared/placeholder-resolver.js +9 -2
- package/dist/shared/placeholder-resolver.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +26 -46
- package/dist/shared/plan-mode-permissions.js +27 -55
- package/dist/shared/plan-mode-permissions.js.map +1 -1
- package/dist/shared/registry-endpoint.d.ts +5 -0
- package/dist/shared/registry-endpoint.js +7 -1
- package/dist/shared/registry-endpoint.js.map +1 -1
- package/dist/shared/runner-credential-keys.d.ts +26 -1
- package/dist/shared/runner-credential-keys.js +34 -1
- package/dist/shared/runner-credential-keys.js.map +1 -1
- package/dist/shared/runner-credential-store.d.ts +77 -0
- package/dist/shared/runner-credential-store.js +111 -0
- package/dist/shared/runner-credential-store.js.map +1 -0
- package/dist/shared/service-tier.d.ts +55 -0
- package/dist/shared/service-tier.js +67 -0
- package/dist/shared/service-tier.js.map +1 -0
- package/dist/shared/skill-mount.d.ts +89 -0
- package/dist/shared/skill-mount.js +142 -0
- package/dist/shared/skill-mount.js.map +1 -0
- package/dist/shared/skill-writer.d.ts +28 -26
- package/dist/shared/skill-writer.js +79 -102
- package/dist/shared/skill-writer.js.map +1 -1
- package/dist/shared/worker-shutdown.d.ts +67 -0
- package/dist/shared/worker-shutdown.js +79 -0
- package/dist/shared/worker-shutdown.js.map +1 -0
- package/dist/shared/workspace/types.d.ts +3 -2
- package/dist/shared/zip-extract.d.ts +10 -3
- package/dist/shared/zip-extract.js +10 -3
- package/dist/shared/zip-extract.js.map +1 -1
- package/dist/workflow-engine/loader.js +38 -1
- package/dist/workflow-engine/loader.js.map +1 -1
- package/dist/workflow-engine/tasks/call-function.d.ts +14 -0
- package/dist/workflow-engine/tasks/call-function.js +49 -5
- package/dist/workflow-engine/tasks/call-function.js.map +1 -1
- package/dist/workflow-engine/tasks/human-input.js +23 -2
- package/dist/workflow-engine/tasks/human-input.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +6 -0
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/engine-core.js +36 -8
- package/dist/workflows/engine-core.js.map +1 -1
- package/package.json +15 -11
- package/src/activities/__tests__/call-http.test.ts +36 -0
- package/src/activities/__tests__/call-llm.test.ts +77 -0
- package/src/activities/call-http.ts +17 -0
- package/src/activities/call-llm.ts +78 -2
- package/src/activities/execute-cursor/__tests__/agent-session-cache.test.ts +220 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
- package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
- package/src/activities/execute-cursor/__tests__/service-tier.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
- package/src/activities/execute-cursor/agent-session-cache.ts +229 -0
- package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
- package/src/activities/execute-cursor/index.ts +129 -55
- package/src/activities/execute-cursor/prompt-builder.ts +29 -2
- package/src/activities/execute-cursor/service-tier.ts +5 -29
- package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
- package/src/activities/execute-cursor/skill-resolver.ts +10 -137
- package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +11 -8
- package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
- package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
- package/src/activities/execute-deep-agent/index.ts +57 -5
- package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
- package/src/activities/execute-deep-agent/setup.ts +36 -19
- package/src/activities/execute-deep-agent/shell-env.ts +10 -7
- package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
- package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
- package/src/config.ts +10 -5
- package/src/encryption/config.ts +8 -2
- package/src/index.ts +1 -1
- package/src/main.ts +16 -6
- package/src/middleware/__tests__/path-normalization.test.ts +32 -32
- package/src/middleware/path-normalization.ts +78 -90
- package/src/middleware/types.ts +3 -3
- package/src/payload-codecs.ts +2 -1
- package/src/runner-manager.ts +55 -22
- package/src/runner.ts +39 -6
- package/src/shared/__tests__/artifact-storage.test.ts +28 -0
- package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
- package/src/shared/__tests__/declared-preferences.test.ts +109 -0
- package/src/shared/__tests__/foundry-seam.test.ts +5 -5
- package/src/shared/__tests__/model-client.test.ts +99 -0
- package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
- package/src/shared/__tests__/runner-credential-store.test.ts +155 -0
- package/src/shared/__tests__/skill-mount.test.ts +238 -0
- package/src/shared/__tests__/skill-writer.test.ts +151 -128
- package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
- package/src/shared/__tests__/vertex-seam.test.ts +5 -5
- package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
- package/src/shared/__tests__/zip-extract.test.ts +46 -11
- package/src/shared/args-preview.ts +17 -3
- package/src/shared/artifact-storage.ts +20 -1
- package/src/shared/declared-preferences.ts +84 -0
- package/src/shared/fingerprint-secret.ts +5 -3
- package/src/shared/llm-backend.ts +7 -1
- package/src/shared/mcp-enabled-tools.ts +2 -3
- package/src/shared/mcp-resolver.ts +20 -20
- package/src/shared/model-client.ts +76 -13
- package/src/shared/placeholder-resolver.ts +9 -2
- package/src/shared/plan-mode-permissions.ts +27 -58
- package/src/shared/registry-endpoint.ts +9 -1
- package/src/shared/runner-credential-keys.ts +36 -1
- package/src/shared/runner-credential-store.ts +115 -0
- package/src/shared/service-tier.ts +78 -0
- package/src/shared/skill-mount.ts +179 -0
- package/src/shared/skill-writer.ts +96 -130
- package/src/shared/worker-shutdown.ts +99 -0
- package/src/shared/workspace/types.ts +3 -2
- package/src/shared/zip-extract.ts +14 -7
- package/src/workflow-engine/__tests__/golden-execution.test.ts +20 -1
- package/src/workflow-engine/__tests__/loader.test.ts +51 -0
- package/src/workflow-engine/__tests__/tasks/call-function.test.ts +105 -0
- package/src/workflow-engine/__tests__/tasks/human-input.test.ts +117 -0
- package/src/workflow-engine/loader.ts +46 -1
- package/src/workflow-engine/tasks/call-function.ts +74 -13
- package/src/workflow-engine/tasks/human-input.ts +33 -5
- package/src/workflow-engine/types.ts +6 -0
- package/src/workflows/engine-core.ts +39 -8
- package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
- package/dist/activities/execute-cursor/connect-backfill.js +0 -27
- package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
- package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
- package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
- package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
- package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
- package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
- package/src/activities/execute-cursor/connect-backfill.ts +0 -52
- package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
- package/src/activities/execute-cursor/placeholder-resolver.ts +0 -109
|
@@ -35,36 +35,47 @@ async function argsSeenByHandler(
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
describe("normalizeWorkspacePathArg", () => {
|
|
38
|
-
it("maps a workspace-relative path to its
|
|
39
|
-
expect(normalizeWorkspacePathArg("src/main.py", ROOT)).toBe(
|
|
38
|
+
it("maps a workspace-relative path to its virtual-absolute form", () => {
|
|
39
|
+
expect(normalizeWorkspacePathArg("src/main.py", ROOT)).toBe("/src/main.py");
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it("strips a leading ./ before
|
|
43
|
-
expect(normalizeWorkspacePathArg("./notes.md", ROOT)).toBe(
|
|
42
|
+
it("strips a leading ./ before rooting", () => {
|
|
43
|
+
expect(normalizeWorkspacePathArg("./notes.md", ROOT)).toBe("/notes.md");
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
it("handles multi-workspace entry-relative paths", () => {
|
|
47
47
|
expect(normalizeWorkspacePathArg("entry-1/src/main.py", ROOT)).toBe(
|
|
48
|
-
|
|
48
|
+
"/entry-1/src/main.py",
|
|
49
49
|
);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it("resolves interior .. segments that stay inside the root", () => {
|
|
53
|
-
expect(normalizeWorkspacePathArg("src/../notes.md", ROOT)).toBe(
|
|
53
|
+
expect(normalizeWorkspacePathArg("src/../notes.md", ROOT)).toBe("/notes.md");
|
|
54
|
+
// Same repair on an already-virtual path: the virtual resolver rejects
|
|
55
|
+
// `..` outright, so the safe interior collapse happens at this seam.
|
|
56
|
+
expect(normalizeWorkspacePathArg("/src/../notes.md", ROOT)).toBe("/notes.md");
|
|
54
57
|
});
|
|
55
58
|
|
|
56
|
-
it("
|
|
59
|
+
it("maps a REAL-absolute in-root path to its virtual form (legacy-dialect compatibility)", () => {
|
|
60
|
+
expect(normalizeWorkspacePathArg(`${ROOT}/src/main.py`, ROOT)).toBe("/src/main.py");
|
|
61
|
+
// The root itself is the virtual root.
|
|
62
|
+
expect(normalizeWorkspacePathArg(ROOT, ROOT)).toBe("/");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("leaves canonical virtual-absolute paths untouched", () => {
|
|
66
|
+
// In the virtual dialect these are already canonical names — "/etc/hosts"
|
|
67
|
+
// means the WORKSPACE's etc/hosts, not the host file.
|
|
57
68
|
expect(normalizeWorkspacePathArg("/etc/hosts", ROOT)).toBeUndefined();
|
|
58
|
-
expect(normalizeWorkspacePathArg(`${ROOT}/src/main.py`, ROOT)).toBeUndefined();
|
|
59
69
|
expect(normalizeWorkspacePathArg("/", ROOT)).toBeUndefined();
|
|
60
70
|
});
|
|
61
71
|
|
|
62
72
|
it("refuses to rewrite relatives that escape the workspace root", () => {
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
73
|
+
// posix.normalize would silently swallow a leading ".." on an absolute
|
|
74
|
+
// path — rewriting the call onto a DIFFERENT in-root file. The escape is
|
|
75
|
+
// detected in relative form and left raw for the upstream refusal.
|
|
66
76
|
expect(normalizeWorkspacePathArg("../sibling/secret.txt", ROOT)).toBeUndefined();
|
|
67
77
|
expect(normalizeWorkspacePathArg("src/../../escape.txt", ROOT)).toBeUndefined();
|
|
78
|
+
expect(normalizeWorkspacePathArg("/src/../../escape.txt", ROOT)).toBeUndefined();
|
|
68
79
|
});
|
|
69
80
|
|
|
70
81
|
it("leaves ~-carrying paths untouched (upstream refuses them either way)", () => {
|
|
@@ -84,7 +95,7 @@ describe("createPathNormalizationMiddleware", () => {
|
|
|
84
95
|
file_path: "src/main.py",
|
|
85
96
|
content: "x",
|
|
86
97
|
});
|
|
87
|
-
expect(args.file_path).toBe(
|
|
98
|
+
expect(args.file_path).toBe("/src/main.py");
|
|
88
99
|
// Sibling args ride along untouched.
|
|
89
100
|
expect(args.content).toBe("x");
|
|
90
101
|
}
|
|
@@ -93,27 +104,26 @@ describe("createPathNormalizationMiddleware", () => {
|
|
|
93
104
|
it("rewrites the base path on ls/glob/grep", async () => {
|
|
94
105
|
for (const tool of ["ls", "glob", "grep"]) {
|
|
95
106
|
const args = await argsSeenByHandler(tool, { path: "src", pattern: "**/*.py" });
|
|
96
|
-
expect(args.path).toBe(
|
|
107
|
+
expect(args.path).toBe("/src");
|
|
97
108
|
// The pattern is never a path — byte-untouched.
|
|
98
109
|
expect(args.pattern).toBe("**/*.py");
|
|
99
110
|
}
|
|
100
111
|
});
|
|
101
112
|
|
|
102
|
-
it("passes
|
|
113
|
+
it("passes canonical virtual paths through byte-untouched", async () => {
|
|
103
114
|
const args = await argsSeenByHandler("read_file", { file_path: "/etc/hosts" });
|
|
104
115
|
expect(args.file_path).toBe("/etc/hosts");
|
|
105
116
|
});
|
|
106
117
|
|
|
107
|
-
it("
|
|
108
|
-
// Deliberate reversal of the
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// already knows.
|
|
118
|
+
it("leaves an omitted base path alone — the tools' '/' default IS the workspace root now", async () => {
|
|
119
|
+
// Deliberate reversal of the #528 injection: under the legacy backend the
|
|
120
|
+
// schema default "/" meant the OS ROOT, so the middleware had to fill the
|
|
121
|
+
// omission. Under the virtual root (issue #754) the default already means
|
|
122
|
+
// the workspace root — filling it would just duplicate the tool's own
|
|
123
|
+
// behavior.
|
|
114
124
|
for (const tool of ["ls", "glob", "grep"]) {
|
|
115
125
|
const args = await argsSeenByHandler(tool, { pattern: "TOKEN" });
|
|
116
|
-
expect(args.path).
|
|
126
|
+
expect(args.path).toBeUndefined();
|
|
117
127
|
expect(args.pattern).toBe("TOKEN");
|
|
118
128
|
}
|
|
119
129
|
});
|
|
@@ -125,16 +135,6 @@ describe("createPathNormalizationMiddleware", () => {
|
|
|
125
135
|
expect(args.file_path).toBeUndefined();
|
|
126
136
|
});
|
|
127
137
|
|
|
128
|
-
it("leaves an explicit '/' untouched on the dir tools — the rules answer it honestly", async () => {
|
|
129
|
-
// The model asked for the OS root; under plan mode's read boundary the
|
|
130
|
-
// honest answer is the rules' denial, not silently substituted workspace
|
|
131
|
-
// contents (the #429 never-convert-a-refusal-into-an-allowance doctrine).
|
|
132
|
-
for (const tool of ["ls", "glob", "grep"]) {
|
|
133
|
-
const args = await argsSeenByHandler(tool, { path: "/" });
|
|
134
|
-
expect(args.path).toBe("/");
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
138
|
it("never touches tools outside the built-in filesystem set", async () => {
|
|
139
139
|
// An MCP tool could plausibly carry a same-named arg with different
|
|
140
140
|
// semantics; only the six built-in names are rewritten.
|
|
@@ -1,55 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* (issues #429, #528).
|
|
2
|
+
* Canonical virtual-path normalization for the native harness's built-in
|
|
3
|
+
* filesystem tools (issues #429, #528, #754).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the shape the multi-workspace prompt explicitly mandates, and one models
|
|
11
|
-
* routinely choose in single-workspace sessions — fails with
|
|
12
|
-
* `path must be absolute` instead of just working, burning tool rounds until
|
|
13
|
-
* the model adapts by switching to absolute paths.
|
|
5
|
+
* The native harness speaks ONE path dialect end to end: the VIRTUAL ROOT,
|
|
6
|
+
* where "/" denotes the workspace root (backends are constructed with
|
|
7
|
+
* `virtualMode: true` — see execute-deep-agent/cas-capture-backend.ts). This
|
|
8
|
+
* middleware is the dialect-repair seam at the model boundary. It rewrites,
|
|
9
|
+
* on the deepagents built-in filesystem tools only:
|
|
14
10
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
11
|
+
* - workspace-relative paths ("src/x.py", "./notes.md") to their virtual
|
|
12
|
+
* absolute form ("/src/x.py") — deepagents' permission validation refuses
|
|
13
|
+
* non-absolute shapes on rule-bearing graphs (plan mode), and the prompt
|
|
14
|
+
* explicitly mandates entry-relative paths, so without this seam a
|
|
15
|
+
* prompt-compliant call would die with `path must be absolute`;
|
|
16
|
+
* - REAL-absolute paths under the workspace root ("{root}/src/x.py") to
|
|
17
|
+
* their virtual form ("/src/x.py") — the compatibility mapping for the
|
|
18
|
+
* pre-#754 dialect, where tool results and transcripts surfaced real
|
|
19
|
+
* filesystem paths that a model may still echo back;
|
|
20
|
+
* - interior `..` segments that stay inside the root ("src/../notes.md" →
|
|
21
|
+
* "/notes.md") — the virtual resolver rejects `..` outright, so a safe
|
|
22
|
+
* interior collapse is repaired here rather than burning a tool round.
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* (the same first-turn degradation class #429 fixed). An EXPLICIT "/" is
|
|
29
|
-
* deliberately NOT rewritten: the model asked for the OS root, and the
|
|
30
|
-
* honest answer under plan mode's read boundary is the rules' denial —
|
|
31
|
-
* silently substituting workspace contents would be an answer to a
|
|
32
|
-
* different question.
|
|
24
|
+
* Nothing becomes newly reachable: escaping relatives ("../x") and
|
|
25
|
+
* `~`-carrying paths are left raw so the upstream refusal keeps speaking,
|
|
26
|
+
* and every rewritten path is workspace-confined by the virtual resolver
|
|
27
|
+
* regardless. The middleware converts false errors into correct behavior —
|
|
28
|
+
* never a refusal into an allowance.
|
|
33
29
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* injection
|
|
42
|
-
* to the workspace, granting nothing the rules would refuse.)
|
|
30
|
+
* Installed on EVERY native graph (parent and sub-agent, act and plan mode)
|
|
31
|
+
* so all downstream consumers — the approval gate's capturability checks,
|
|
32
|
+
* the CAS observer, otel spans — observe one canonical dialect. The pre-#754
|
|
33
|
+
* version was installed only beside permission rules and also injected the
|
|
34
|
+
* workspace root when `ls`/`glob`/`grep` omitted their path (the tools'
|
|
35
|
+
* schema default "/" was the OS ROOT under the legacy backend); under the
|
|
36
|
+
* virtual root that default already MEANS the workspace root, so the
|
|
37
|
+
* injection is retired.
|
|
43
38
|
*
|
|
44
39
|
* Tool matching is by bare built-in name, the house doctrine (an MCP server
|
|
45
40
|
* is not expected to shadow a built-in name — see shared/tool-kind.ts); the
|
|
46
41
|
* rewrite touches only the tool's path-bearing argument, never glob/grep
|
|
47
42
|
* patterns. Install FIRST in the stack so every downstream middleware
|
|
48
|
-
*
|
|
43
|
+
* observes canonical paths.
|
|
49
44
|
*/
|
|
50
45
|
|
|
51
|
-
import { isAbsolute, relative } from "node:path";
|
|
52
|
-
import { resolveWorkspacePath } from "../shared/file-change.js";
|
|
46
|
+
import { isAbsolute, relative, posix } from "node:path";
|
|
53
47
|
import type { StigmerMiddleware } from "./types.js";
|
|
54
48
|
|
|
55
49
|
/**
|
|
@@ -66,55 +60,65 @@ const PATH_ARG_BY_TOOL: ReadonlyMap<string, string> = new Map([
|
|
|
66
60
|
["edit_file", "file_path"],
|
|
67
61
|
]);
|
|
68
62
|
|
|
69
|
-
/**
|
|
70
|
-
* The tools whose path argument is optional with an OS-root ("/") schema
|
|
71
|
-
* default. When the model omits it, this middleware supplies the workspace
|
|
72
|
-
* root instead (issue #528). The file tools are deliberately excluded: an
|
|
73
|
-
* absent `file_path` is a genuine model error, and the tool's own input
|
|
74
|
-
* validation gives the better message.
|
|
75
|
-
*/
|
|
76
|
-
const DIR_DEFAULTING_TOOLS: ReadonlySet<string> = new Set(["ls", "glob", "grep"]);
|
|
77
|
-
|
|
78
63
|
export interface PathNormalizationConfig {
|
|
79
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* The REAL workspace root the graph's backend is rooted at — used only for
|
|
66
|
+
* the legacy-dialect compatibility mapping (real-absolute in-root paths →
|
|
67
|
+
* virtual). The virtual rewrite itself needs no root.
|
|
68
|
+
*/
|
|
80
69
|
readonly rootDir: string;
|
|
81
70
|
}
|
|
82
71
|
|
|
83
72
|
/**
|
|
84
|
-
* Rewrite `raw` to its
|
|
85
|
-
* value must be left untouched
|
|
86
|
-
* the
|
|
73
|
+
* Rewrite `raw` to its canonical virtual-absolute form, or return undefined
|
|
74
|
+
* when the value must be left untouched. Exported for direct unit testing of
|
|
75
|
+
* the mapping table.
|
|
76
|
+
*
|
|
77
|
+
* Left raw (undefined): empty strings, `~`-carrying paths (refused upstream
|
|
78
|
+
* in every mode), escaping relatives ("../x" — the refusal is the honest
|
|
79
|
+
* answer), real-absolute paths OUTSIDE the root (virtual dialect: they name
|
|
80
|
+
* an in-workspace path that simply does not exist — resolution answers
|
|
81
|
+
* honestly), and paths already in canonical virtual form.
|
|
87
82
|
*/
|
|
88
83
|
export function normalizeWorkspacePathArg(
|
|
89
84
|
raw: string,
|
|
90
85
|
rootDir: string,
|
|
91
86
|
): string | undefined {
|
|
92
|
-
if (raw.length === 0
|
|
93
|
-
// Upstream validation refuses `~` segments
|
|
94
|
-
//
|
|
95
|
-
// therefore the honest refusal) intact.
|
|
87
|
+
if (raw.length === 0) return undefined;
|
|
88
|
+
// Upstream validation refuses `~` segments in every position, and a rewrite
|
|
89
|
+
// could not make such a call succeed — leave the honest refusal intact.
|
|
96
90
|
if (raw.split("/").includes("~")) return undefined;
|
|
97
91
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
92
|
+
// Legacy-dialect compatibility: a REAL absolute path under the workspace
|
|
93
|
+
// root maps to its virtual form. Any other absolute path is already a
|
|
94
|
+
// virtual-dialect name — reduced to relative form for canonicalization.
|
|
95
|
+
let candidate: string;
|
|
96
|
+
if (isAbsolute(raw)) {
|
|
97
|
+
const rel = relative(rootDir, raw);
|
|
98
|
+
if (rel === "") return "/";
|
|
99
|
+
candidate =
|
|
100
|
+
rel && !rel.startsWith("..") && !isAbsolute(rel)
|
|
101
|
+
? rel
|
|
102
|
+
: raw.replace(/^\/+/, "");
|
|
103
|
+
} else {
|
|
104
|
+
candidate = raw;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Canonicalize in RELATIVE form first: posix.normalize keeps a leading
|
|
108
|
+
// ".." on relative paths but silently swallows it on absolute ones, and a
|
|
109
|
+
// swallowed escape would rewrite the call onto a DIFFERENT in-root path —
|
|
110
|
+
// a refusal converted into an allowance. Detect the escape while it is
|
|
111
|
+
// still visible, and leave it raw for the upstream refusal.
|
|
112
|
+
const normalizedRel = posix.normalize(candidate);
|
|
113
|
+
if (normalizedRel === ".." || normalizedRel.startsWith("../")) return undefined;
|
|
114
|
+
|
|
115
|
+
const virtual = normalizedRel === "." ? "/" : `/${normalizedRel}`;
|
|
116
|
+
return virtual === raw ? undefined : virtual;
|
|
108
117
|
}
|
|
109
118
|
|
|
110
119
|
/**
|
|
111
|
-
* Create
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* Installed only on graphs that carry filesystem permission rules — derive
|
|
115
|
-
* the install condition from the same expression that supplies the rules
|
|
116
|
-
* (setup.ts / compileSubagents) so the rules and their normalization shim
|
|
117
|
-
* cannot drift apart.
|
|
120
|
+
* Create the dialect-repair middleware (see module header). Stateless per
|
|
121
|
+
* request; safe on every graph.
|
|
118
122
|
*/
|
|
119
123
|
export function createPathNormalizationMiddleware(
|
|
120
124
|
config: PathNormalizationConfig,
|
|
@@ -129,22 +133,6 @@ export function createPathNormalizationMiddleware(
|
|
|
129
133
|
if (!argKey) return handler(request);
|
|
130
134
|
|
|
131
135
|
const raw = request.toolCall.args[argKey];
|
|
132
|
-
|
|
133
|
-
// Absent base directory on ls/glob/grep: the middleware sees the
|
|
134
|
-
// model's raw args, BEFORE the tool's zod parse applies the "/"
|
|
135
|
-
// (OS root) schema default — so the omission must be filled here,
|
|
136
|
-
// where the workspace root is known. An explicit "/" is not this
|
|
137
|
-
// case and flows through to an honest rule denial (header doctrine).
|
|
138
|
-
if (raw == null && DIR_DEFAULTING_TOOLS.has(request.toolCall.name)) {
|
|
139
|
-
return handler({
|
|
140
|
-
...request,
|
|
141
|
-
toolCall: {
|
|
142
|
-
...request.toolCall,
|
|
143
|
-
args: { ...request.toolCall.args, [argKey]: rootDir },
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
136
|
if (typeof raw !== "string") return handler(request);
|
|
149
137
|
|
|
150
138
|
const normalized = normalizeWorkspacePathArg(raw, rootDir);
|
package/src/middleware/types.ts
CHANGED
|
@@ -117,9 +117,9 @@ export interface MiddlewareStackConfig {
|
|
|
117
117
|
readonly otelSpans?: Partial<OtelSpansConfig>;
|
|
118
118
|
readonly approvalGate?: ApprovalGateConfig | null;
|
|
119
119
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
120
|
+
* Virtual-dialect path normalization (issues #429/#754). Set on every
|
|
121
|
+
* native graph: the backends are virtual-rooted, so every graph carries
|
|
122
|
+
* the repair seam that keeps model-supplied paths canonical.
|
|
123
123
|
*/
|
|
124
124
|
readonly pathNormalization?: PathNormalizationConfig | null;
|
|
125
125
|
}
|
package/src/payload-codecs.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import type { PayloadCodec } from "@temporalio/common";
|
|
16
16
|
import type { Config } from "./config.js";
|
|
17
17
|
import type { BootstrapKeyMaterial } from "./encryption/config.js";
|
|
18
|
+
import { getRunnerSecret } from "./shared/runner-credential-store.js";
|
|
18
19
|
|
|
19
20
|
export async function createPayloadCodecs(
|
|
20
21
|
config: Config,
|
|
@@ -30,7 +31,7 @@ export async function createPayloadCodecs(
|
|
|
30
31
|
const encryptionConfig = loadPayloadEncryptionConfig(bootstrapKeys);
|
|
31
32
|
if (encryptionConfig) {
|
|
32
33
|
codecs.push(new EncryptionPayloadCodec(encryptionConfig));
|
|
33
|
-
const source =
|
|
34
|
+
const source = getRunnerSecret("STIGMER_PAYLOAD_ENCRYPTION_KEY") ? "env" : "bootstrap";
|
|
34
35
|
console.log(
|
|
35
36
|
`[runner] Payload encryption enabled (source=${source}, ` +
|
|
36
37
|
`key_id=${encryptionConfig.primary.keyId}` +
|
package/src/runner-manager.ts
CHANGED
|
@@ -32,6 +32,11 @@ import type { WorkerActivities } from "./worker.js";
|
|
|
32
32
|
import { resolveWorkflowSource, OTEL_WORKFLOW_INTERCEPTOR_MODULE } from "./workflow-source.js";
|
|
33
33
|
import { resolveRunnerBootstrap, refreshRunnerAccessToken } from "./bootstrap.js";
|
|
34
34
|
import { assertLlmBackendsPreflight } from "./preflight.js";
|
|
35
|
+
import {
|
|
36
|
+
captureRunnerSecrets,
|
|
37
|
+
getRunnerSecret,
|
|
38
|
+
setRunnerSecret,
|
|
39
|
+
} from "./shared/runner-credential-store.js";
|
|
35
40
|
import { createRunnerTokenCoordinator } from "./runner-token-coordinator.js";
|
|
36
41
|
// Per-task-queue in-flight activity tracking lives in ./in-flight.ts so the
|
|
37
42
|
// activity interceptor (no manager-closure handle) and unit tests can reach it.
|
|
@@ -44,6 +49,10 @@ import {
|
|
|
44
49
|
setQueueDrainCallback,
|
|
45
50
|
forgetQueue,
|
|
46
51
|
} from "./in-flight.js";
|
|
52
|
+
import {
|
|
53
|
+
registerWorkerShutdownSignal,
|
|
54
|
+
unregisterWorkerShutdownSignal,
|
|
55
|
+
} from "./shared/worker-shutdown.js";
|
|
47
56
|
|
|
48
57
|
const SESSION_QUEUE_PREFIX = "session:";
|
|
49
58
|
const WFEXEC_QUEUE_PREFIX = "wfexec:";
|
|
@@ -52,16 +61,10 @@ const WFEXEC_QUEUE_PREFIX = "wfexec:";
|
|
|
52
61
|
// Mirrors SessionDispatchService's convention: lowercase kind, colon, id.
|
|
53
62
|
const POOL_CONTROL_QUEUE_PREFIX = "sandbox:";
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*/
|
|
60
|
-
const _shutdownSignalRegistry = new Map<string, AbortSignal>();
|
|
61
|
-
|
|
62
|
-
export function getShutdownSignalForQueue(taskQueue: string): AbortSignal | undefined {
|
|
63
|
-
return _shutdownSignalRegistry.get(taskQueue);
|
|
64
|
-
}
|
|
64
|
+
// Re-export for existing importers; the registry itself lives in
|
|
65
|
+
// shared/worker-shutdown.ts so the static runner (runner.ts) and the
|
|
66
|
+
// activities can share it without importing this manager module.
|
|
67
|
+
export { getShutdownSignalForQueue } from "./shared/worker-shutdown.js";
|
|
65
68
|
|
|
66
69
|
export interface RunnerManagerOptions {
|
|
67
70
|
/**
|
|
@@ -214,6 +217,12 @@ export async function createStigmerRunnerManager(
|
|
|
214
217
|
): Promise<StigmerRunnerManager> {
|
|
215
218
|
validateManagerOptions(options);
|
|
216
219
|
|
|
220
|
+
// Take custody of runner secrets BEFORE anything else can read them from
|
|
221
|
+
// env — and before any agent code could spawn with them (#508). Runs here
|
|
222
|
+
// (not only in main.ts) because this factory is a public library boot door:
|
|
223
|
+
// in-process embedders like the desktop never execute main.ts.
|
|
224
|
+
captureRunnerSecrets();
|
|
225
|
+
|
|
217
226
|
const { registerStigmerDeepagentsProfiles } = await import(
|
|
218
227
|
"./activities/execute-deep-agent/deepagents-profiles.js"
|
|
219
228
|
);
|
|
@@ -365,7 +374,7 @@ export async function createStigmerRunnerManager(
|
|
|
365
374
|
if (
|
|
366
375
|
!bootstrap.payloadEncryption &&
|
|
367
376
|
bootstrap.runnerAccessToken &&
|
|
368
|
-
!
|
|
377
|
+
!getRunnerSecret("STIGMER_PAYLOAD_ENCRYPTION_KEY")
|
|
369
378
|
) {
|
|
370
379
|
console.warn(
|
|
371
380
|
"[runner-manager] Server minted a runner token but returned no payload " +
|
|
@@ -400,15 +409,12 @@ export async function createStigmerRunnerManager(
|
|
|
400
409
|
|
|
401
410
|
const sessions = new Map<string, ManagedSession>();
|
|
402
411
|
const workflowExecutions = new Map<string, ManagedSession>();
|
|
403
|
-
const shutdownSignals = new Map<string, AbortController>();
|
|
404
412
|
// At most one per process: a pool member IS its control worker's identity.
|
|
405
413
|
let poolControl: { taskQueue: string; managed: ManagedSession } | null = null;
|
|
406
414
|
let shuttingDown = false;
|
|
407
415
|
|
|
408
416
|
async function createWorkerOnQueue(taskQueue: string): Promise<ManagedSession> {
|
|
409
|
-
const shutdownController =
|
|
410
|
-
shutdownSignals.set(taskQueue, shutdownController);
|
|
411
|
-
_shutdownSignalRegistry.set(taskQueue, shutdownController.signal);
|
|
417
|
+
const shutdownController = registerWorkerShutdownSignal(taskQueue);
|
|
412
418
|
|
|
413
419
|
const worker = await Worker.create({
|
|
414
420
|
connection,
|
|
@@ -470,8 +476,7 @@ export async function createStigmerRunnerManager(
|
|
|
470
476
|
managed.worker.shutdown();
|
|
471
477
|
await managed.runPromise;
|
|
472
478
|
registry.delete(id);
|
|
473
|
-
|
|
474
|
-
_shutdownSignalRegistry.delete(taskQueue);
|
|
479
|
+
unregisterWorkerShutdownSignal(taskQueue);
|
|
475
480
|
forgetQueue(taskQueue);
|
|
476
481
|
console.log(`[runner-manager] Removed ${kind} ${id} (active=${registry.size})`);
|
|
477
482
|
}
|
|
@@ -530,6 +535,13 @@ export async function createStigmerRunnerManager(
|
|
|
530
535
|
await removeManaged(
|
|
531
536
|
sessions, sessionId, SESSION_QUEUE_PREFIX + sessionId, "session",
|
|
532
537
|
);
|
|
538
|
+
// The session is done on this host — release its parked agent (and
|
|
539
|
+
// the executor + MCP subprocesses the lease pins) immediately rather
|
|
540
|
+
// than waiting out the idle TTL (#215).
|
|
541
|
+
const { evictSessionAgent } = await import(
|
|
542
|
+
"./activities/execute-cursor/agent-session-cache.js"
|
|
543
|
+
);
|
|
544
|
+
evictSessionAgent(sessionId);
|
|
533
545
|
},
|
|
534
546
|
|
|
535
547
|
activeSessions(): string[] {
|
|
@@ -585,12 +597,11 @@ export async function createStigmerRunnerManager(
|
|
|
585
597
|
// proxy credential follows it: only when no token has been minted (so the
|
|
586
598
|
// pre-mint lockstep is preserved), never once the runner owns a minted
|
|
587
599
|
// token. See runner-token-coordinator.ts and the staleness changelogs.
|
|
600
|
+
// The credential store is the second leg of the pair (it replaced the
|
|
601
|
+
// process.env.STIGMER_TOKEN write, #508): per-call readers like
|
|
602
|
+
// call-llm and the registry headers resolve the current token there.
|
|
588
603
|
tokenRef.current = token;
|
|
589
|
-
|
|
590
|
-
process.env.STIGMER_TOKEN = token;
|
|
591
|
-
} else {
|
|
592
|
-
delete process.env.STIGMER_TOKEN;
|
|
593
|
-
}
|
|
604
|
+
setRunnerSecret("STIGMER_TOKEN", token);
|
|
594
605
|
tokenCoordinator.onControlPlaneTokenChanged(token);
|
|
595
606
|
console.log("[runner-manager] Auth token updated");
|
|
596
607
|
},
|
|
@@ -603,6 +614,22 @@ export async function createStigmerRunnerManager(
|
|
|
603
614
|
`[runner-manager] Shutting down ${totalWorkers} workers (${sessions.size} sessions, ${workflowExecutions.size} workflow executions)...`,
|
|
604
615
|
);
|
|
605
616
|
|
|
617
|
+
// Mark every queue's shutdown signal BEFORE draining, so an in-flight
|
|
618
|
+
// activity cancelled by the drain classifies it as a worker shutdown
|
|
619
|
+
// rather than a user pause (issue #776 — a SIGTERM'd pool member used
|
|
620
|
+
// to persist EXECUTION_PAUSED and let raw Temporal drain text reach
|
|
621
|
+
// status.error). Aborting is classification-only: it stops nothing;
|
|
622
|
+
// worker.shutdown() below still owns the drain. This is deliberately
|
|
623
|
+
// NOT done in teardownManaged — single-worker teardowns only run once
|
|
624
|
+
// the queue is idle, and aborting there is the old view-close bug.
|
|
625
|
+
for (const session of sessions.values()) {
|
|
626
|
+
session.shutdownController.abort();
|
|
627
|
+
}
|
|
628
|
+
for (const execution of workflowExecutions.values()) {
|
|
629
|
+
execution.shutdownController.abort();
|
|
630
|
+
}
|
|
631
|
+
poolControl?.managed.shutdownController.abort();
|
|
632
|
+
|
|
606
633
|
const shutdownPromises = [
|
|
607
634
|
...Array.from(sessions.entries()).map(
|
|
608
635
|
async ([id, session]) => {
|
|
@@ -629,6 +656,12 @@ export async function createStigmerRunnerManager(
|
|
|
629
656
|
}
|
|
630
657
|
|
|
631
658
|
await Promise.all(shutdownPromises);
|
|
659
|
+
// Workers are drained — release every parked session agent so their
|
|
660
|
+
// executor leases dispose (stdio MCP subprocesses die with them) (#215).
|
|
661
|
+
const { closeAllCachedAgents } = await import(
|
|
662
|
+
"./activities/execute-cursor/agent-session-cache.js"
|
|
663
|
+
);
|
|
664
|
+
closeAllCachedAgents();
|
|
632
665
|
sessions.clear();
|
|
633
666
|
workflowExecutions.clear();
|
|
634
667
|
poolControl = null;
|
package/src/runner.ts
CHANGED
|
@@ -20,6 +20,10 @@ import { DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS, DEFAULT_CURSOR_STREAM_STALL_TI
|
|
|
20
20
|
import type { WorkerActivities } from "./worker.js";
|
|
21
21
|
import { resolveRunnerBootstrap } from "./bootstrap.js";
|
|
22
22
|
import { assertLlmBackendsPreflight } from "./preflight.js";
|
|
23
|
+
import {
|
|
24
|
+
captureRunnerSecrets,
|
|
25
|
+
setRunnerSecret,
|
|
26
|
+
} from "./shared/runner-credential-store.js";
|
|
23
27
|
import { markBoot, emitRunnerBootTiming } from "./shared/cold-start-timing.js";
|
|
24
28
|
|
|
25
29
|
/**
|
|
@@ -118,11 +122,12 @@ export interface StigmerRunner {
|
|
|
118
122
|
* Wire up self-renewal for a static cloud sandbox's control-plane credential
|
|
119
123
|
* (see sandbox-token-renewal.ts for the model). The applied token reaches
|
|
120
124
|
* every consumer: activity gRPC clients read {@code tokenRef} per request,
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* ref per call, and the two
|
|
124
|
-
* a static runner has no
|
|
125
|
-
* proxy credential, so its
|
|
125
|
+
* per-call sites (call-llm, registry-endpoint headers) resolve it through
|
|
126
|
+
* the runner credential store (which replaced the process.env.STIGMER_TOKEN
|
|
127
|
+
* channel, #508), artifact storage resolves the ref per call, and the two
|
|
128
|
+
* Cursor SDK interceptors are updated directly — a static runner has no
|
|
129
|
+
* {@code RunnerTokenCoordinator} minting a separate proxy credential, so its
|
|
130
|
+
* x-stigmer-auth IS this token.
|
|
126
131
|
*/
|
|
127
132
|
async function startStaticSandboxTokenRenewal(
|
|
128
133
|
config: Config,
|
|
@@ -157,7 +162,9 @@ async function startStaticSandboxTokenRenewal(
|
|
|
157
162
|
client.getRunnerScopedToken({ renewal: true }, currentToken),
|
|
158
163
|
applyToken: (token) => {
|
|
159
164
|
tokenRef.current = token;
|
|
160
|
-
process.env.STIGMER_TOKEN
|
|
165
|
+
// Store write replaced the process.env.STIGMER_TOKEN write (#508) —
|
|
166
|
+
// same per-call readers (call-llm, registry headers), no env exposure.
|
|
167
|
+
setRunnerSecret("STIGMER_TOKEN", token);
|
|
161
168
|
updateInterceptorToken(token);
|
|
162
169
|
updateHttp2InterceptorToken(token);
|
|
163
170
|
},
|
|
@@ -190,6 +197,12 @@ export async function createStigmerRunner(
|
|
|
190
197
|
): Promise<StigmerRunner> {
|
|
191
198
|
validateOptions(options);
|
|
192
199
|
|
|
200
|
+
// Take custody of runner secrets BEFORE anything else can read them from
|
|
201
|
+
// env — and before any agent code could spawn with them (#508). Runs here
|
|
202
|
+
// (not only in main.ts) because this factory is a public library boot door
|
|
203
|
+
// for in-process embedders.
|
|
204
|
+
captureRunnerSecrets();
|
|
205
|
+
|
|
193
206
|
const { registerStigmerDeepagentsProfiles } = await import(
|
|
194
207
|
"./activities/execute-deep-agent/deepagents-profiles.js"
|
|
195
208
|
);
|
|
@@ -311,6 +324,16 @@ export async function createStigmerRunner(
|
|
|
311
324
|
// unaffected — their env-injected platform key wins inside the loader.
|
|
312
325
|
const payloadCodecs = await createPayloadCodecs(config, coordinates.payloadEncryption);
|
|
313
326
|
|
|
327
|
+
// Register the queue's shutdown signal BEFORE the worker can poll, so an
|
|
328
|
+
// activity interrupted by shutdown() below classifies the cancellation as a
|
|
329
|
+
// worker shutdown, not a user pause (issue #776). Static mode historically
|
|
330
|
+
// never registered one, leaving CLI daemons with the same misclassification
|
|
331
|
+
// the desktop manager had.
|
|
332
|
+
const { registerWorkerShutdownSignal } = await import(
|
|
333
|
+
"./shared/worker-shutdown.js"
|
|
334
|
+
);
|
|
335
|
+
const shutdownController = registerWorkerShutdownSignal(config.taskQueue);
|
|
336
|
+
|
|
314
337
|
const { startWorker } = await import("./worker.js");
|
|
315
338
|
const worker = await startWorker({ config, activities, payloadCodecs });
|
|
316
339
|
markBoot("worker_created");
|
|
@@ -327,9 +350,19 @@ export async function createStigmerRunner(
|
|
|
327
350
|
emitRunnerBootTiming({ task_queue: config.taskQueue, mode: config.mode });
|
|
328
351
|
await worker.run();
|
|
329
352
|
console.log("Worker stopped");
|
|
353
|
+
// The worker has drained — release any parked session agent so its
|
|
354
|
+
// executor lease disposes with the process (#215).
|
|
355
|
+
const { closeAllCachedAgents } = await import(
|
|
356
|
+
"./activities/execute-cursor/agent-session-cache.js"
|
|
357
|
+
);
|
|
358
|
+
closeAllCachedAgents();
|
|
330
359
|
},
|
|
331
360
|
shutdown() {
|
|
332
361
|
tokenRenewal?.stop();
|
|
362
|
+
// Classification first, drain second: an in-flight activity cancelled
|
|
363
|
+
// by the drain must observe the signal already aborted (see
|
|
364
|
+
// shared/worker-shutdown.ts for the ownership contract).
|
|
365
|
+
shutdownController.abort();
|
|
333
366
|
worker.shutdown();
|
|
334
367
|
},
|
|
335
368
|
};
|