@stigmer/runner 3.12.4 → 3.12.6
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-agent.js +19 -6
- package/dist/activities/call-agent.js.map +1 -1
- 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/error-classifier.d.ts +9 -0
- package/dist/activities/execute-cursor/error-classifier.js +30 -1
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +10 -1
- package/dist/activities/execute-cursor/index.js +62 -36
- 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 +38 -29
- package/dist/activities/execute-cursor/service-tier.js +92 -63
- 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-cursor/usage-accumulator.d.ts +16 -2
- package/dist/activities/execute-cursor/usage-accumulator.js +12 -2
- package/dist/activities/execute-cursor/usage-accumulator.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 +17 -16
- package/dist/activities/execute-deep-agent/setup.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/index.d.ts +1 -1
- package/dist/index.js +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/runner-manager.d.ts +1 -1
- package/dist/runner-manager.js +22 -15
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +11 -0
- 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/caller-identity.d.ts +10 -7
- package/dist/shared/caller-identity.js +10 -7
- package/dist/shared/caller-identity.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/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/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/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/thinking-mode.d.ts +35 -0
- package/dist/shared/thinking-mode.js +43 -0
- package/dist/shared/thinking-mode.js.map +1 -0
- 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/workflow-engine/loader.js +82 -6
- package/dist/workflow-engine/loader.js.map +1 -1
- package/dist/workflow-engine/tasks/human-input.d.ts +2 -1
- package/dist/workflow-engine/tasks/human-input.js +31 -3
- package/dist/workflow-engine/tasks/human-input.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +17 -3
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/human-input-orchestrator.d.ts +4 -0
- package/dist/workflows/human-input-orchestrator.js +13 -0
- package/dist/workflows/human-input-orchestrator.js.map +1 -1
- package/package.json +15 -11
- package/src/activities/call-agent.ts +20 -6
- 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__/error-classifier-billing.test.ts +67 -0
- package/src/activities/execute-cursor/__tests__/service-tier.test.ts +67 -7
- package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
- package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +33 -1
- package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
- package/src/activities/execute-cursor/error-classifier.ts +42 -1
- package/src/activities/execute-cursor/index.ts +78 -41
- package/src/activities/execute-cursor/prompt-builder.ts +29 -2
- package/src/activities/execute-cursor/service-tier.ts +94 -63
- package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
- package/src/activities/execute-cursor/skill-resolver.ts +10 -137
- package/src/activities/execute-cursor/usage-accumulator.ts +11 -1
- 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 +21 -19
- package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
- package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
- package/src/index.ts +1 -1
- 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/runner-manager.ts +26 -16
- package/src/runner.ts +14 -0
- 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__/plan-mode-permissions.test.ts +15 -44
- 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/args-preview.ts +17 -3
- package/src/shared/artifact-storage.ts +20 -1
- package/src/shared/caller-identity.ts +10 -7
- package/src/shared/declared-preferences.ts +84 -0
- package/src/shared/mcp-enabled-tools.ts +2 -3
- package/src/shared/mcp-resolver.ts +20 -20
- package/src/shared/placeholder-resolver.ts +9 -2
- package/src/shared/plan-mode-permissions.ts +27 -58
- package/src/shared/skill-mount.ts +179 -0
- package/src/shared/skill-writer.ts +96 -130
- package/src/shared/thinking-mode.ts +52 -0
- package/src/shared/worker-shutdown.ts +99 -0
- package/src/shared/workspace/types.ts +3 -2
- package/src/workflow-engine/__tests__/loader.test.ts +95 -0
- package/src/workflow-engine/__tests__/tasks/human-input.test.ts +156 -0
- package/src/workflow-engine/loader.ts +97 -6
- package/src/workflow-engine/tasks/human-input.ts +41 -6
- package/src/workflow-engine/types.ts +18 -3
- package/src/workflows/human-input-orchestrator.ts +20 -2
- 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
|
@@ -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/runner-manager.ts
CHANGED
|
@@ -49,6 +49,10 @@ import {
|
|
|
49
49
|
setQueueDrainCallback,
|
|
50
50
|
forgetQueue,
|
|
51
51
|
} from "./in-flight.js";
|
|
52
|
+
import {
|
|
53
|
+
registerWorkerShutdownSignal,
|
|
54
|
+
unregisterWorkerShutdownSignal,
|
|
55
|
+
} from "./shared/worker-shutdown.js";
|
|
52
56
|
|
|
53
57
|
const SESSION_QUEUE_PREFIX = "session:";
|
|
54
58
|
const WFEXEC_QUEUE_PREFIX = "wfexec:";
|
|
@@ -57,16 +61,10 @@ const WFEXEC_QUEUE_PREFIX = "wfexec:";
|
|
|
57
61
|
// Mirrors SessionDispatchService's convention: lowercase kind, colon, id.
|
|
58
62
|
const POOL_CONTROL_QUEUE_PREFIX = "sandbox:";
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
*/
|
|
65
|
-
const _shutdownSignalRegistry = new Map<string, AbortSignal>();
|
|
66
|
-
|
|
67
|
-
export function getShutdownSignalForQueue(taskQueue: string): AbortSignal | undefined {
|
|
68
|
-
return _shutdownSignalRegistry.get(taskQueue);
|
|
69
|
-
}
|
|
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";
|
|
70
68
|
|
|
71
69
|
export interface RunnerManagerOptions {
|
|
72
70
|
/**
|
|
@@ -411,15 +409,12 @@ export async function createStigmerRunnerManager(
|
|
|
411
409
|
|
|
412
410
|
const sessions = new Map<string, ManagedSession>();
|
|
413
411
|
const workflowExecutions = new Map<string, ManagedSession>();
|
|
414
|
-
const shutdownSignals = new Map<string, AbortController>();
|
|
415
412
|
// At most one per process: a pool member IS its control worker's identity.
|
|
416
413
|
let poolControl: { taskQueue: string; managed: ManagedSession } | null = null;
|
|
417
414
|
let shuttingDown = false;
|
|
418
415
|
|
|
419
416
|
async function createWorkerOnQueue(taskQueue: string): Promise<ManagedSession> {
|
|
420
|
-
const shutdownController =
|
|
421
|
-
shutdownSignals.set(taskQueue, shutdownController);
|
|
422
|
-
_shutdownSignalRegistry.set(taskQueue, shutdownController.signal);
|
|
417
|
+
const shutdownController = registerWorkerShutdownSignal(taskQueue);
|
|
423
418
|
|
|
424
419
|
const worker = await Worker.create({
|
|
425
420
|
connection,
|
|
@@ -481,8 +476,7 @@ export async function createStigmerRunnerManager(
|
|
|
481
476
|
managed.worker.shutdown();
|
|
482
477
|
await managed.runPromise;
|
|
483
478
|
registry.delete(id);
|
|
484
|
-
|
|
485
|
-
_shutdownSignalRegistry.delete(taskQueue);
|
|
479
|
+
unregisterWorkerShutdownSignal(taskQueue);
|
|
486
480
|
forgetQueue(taskQueue);
|
|
487
481
|
console.log(`[runner-manager] Removed ${kind} ${id} (active=${registry.size})`);
|
|
488
482
|
}
|
|
@@ -620,6 +614,22 @@ export async function createStigmerRunnerManager(
|
|
|
620
614
|
`[runner-manager] Shutting down ${totalWorkers} workers (${sessions.size} sessions, ${workflowExecutions.size} workflow executions)...`,
|
|
621
615
|
);
|
|
622
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
|
+
|
|
623
633
|
const shutdownPromises = [
|
|
624
634
|
...Array.from(sessions.entries()).map(
|
|
625
635
|
async ([id, session]) => {
|
package/src/runner.ts
CHANGED
|
@@ -324,6 +324,16 @@ export async function createStigmerRunner(
|
|
|
324
324
|
// unaffected — their env-injected platform key wins inside the loader.
|
|
325
325
|
const payloadCodecs = await createPayloadCodecs(config, coordinates.payloadEncryption);
|
|
326
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
|
+
|
|
327
337
|
const { startWorker } = await import("./worker.js");
|
|
328
338
|
const worker = await startWorker({ config, activities, payloadCodecs });
|
|
329
339
|
markBoot("worker_created");
|
|
@@ -349,6 +359,10 @@ export async function createStigmerRunner(
|
|
|
349
359
|
},
|
|
350
360
|
shutdown() {
|
|
351
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();
|
|
352
366
|
worker.shutdown();
|
|
353
367
|
},
|
|
354
368
|
};
|
|
@@ -618,6 +618,22 @@ describe("createArtifactStorage", () => {
|
|
|
618
618
|
};
|
|
619
619
|
expect(() => createArtifactStorage(cfg)).toThrow("STIGMER_TOKEN");
|
|
620
620
|
});
|
|
621
|
+
|
|
622
|
+
it("throws for the deliberate 'none' posture — resolveUsable degrades it, claimcheck fail-hards", async () => {
|
|
623
|
+
const cfg: ArtifactStorageConfig = {
|
|
624
|
+
type: "none",
|
|
625
|
+
localPath: "/tmp/artifacts",
|
|
626
|
+
localServeUrl: "http://localhost:7235",
|
|
627
|
+
proxyEndpoint: null,
|
|
628
|
+
proxyAuthToken: null,
|
|
629
|
+
};
|
|
630
|
+
expect(() => createArtifactStorage(cfg)).toThrow(/deliberately disabled/i);
|
|
631
|
+
// The capture/offload resolver treats it as the first-class absent store:
|
|
632
|
+
// deny-gate mode, not a crash.
|
|
633
|
+
await expect(
|
|
634
|
+
resolveUsableArtifactStorage(cfg, { executionId: "exec-none" }),
|
|
635
|
+
).resolves.toBeUndefined();
|
|
636
|
+
});
|
|
621
637
|
});
|
|
622
638
|
|
|
623
639
|
describe("loadArtifactStorageConfig", () => {
|
|
@@ -680,6 +696,18 @@ describe("loadArtifactStorageConfig", () => {
|
|
|
680
696
|
expect(cfg.proxyAuthToken).toBe("tok");
|
|
681
697
|
});
|
|
682
698
|
|
|
699
|
+
it("honors ARTIFACT_STORAGE_TYPE=none even when a proxy endpoint is configured", () => {
|
|
700
|
+
// "none" is a deliberate operator/e2e posture — it must beat the
|
|
701
|
+
// storage-follows-transport default exactly like the other overrides.
|
|
702
|
+
process.env.ARTIFACT_STORAGE_TYPE = "none";
|
|
703
|
+
const cfg = loadArtifactStorageConfig({
|
|
704
|
+
...baseConfig,
|
|
705
|
+
proxyEndpoint: "https://proxy.example.com",
|
|
706
|
+
stigmerToken: "tok",
|
|
707
|
+
});
|
|
708
|
+
expect(cfg.type).toBe("none");
|
|
709
|
+
});
|
|
710
|
+
|
|
683
711
|
it("respects ARTIFACT_STORAGE_TYPE override", () => {
|
|
684
712
|
process.env.ARTIFACT_STORAGE_TYPE = "proxy";
|
|
685
713
|
const cfg = loadArtifactStorageConfig({
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Characterization test for the ChatAnthropic `createClient` ->
|
|
3
3
|
* AnthropicBedrock seam — the integration the T04 bedrock backend adapter
|
|
4
4
|
* is built on. Sibling of vertex-seam.test.ts; same rules: this pins REAL
|
|
5
|
-
* cross-package behavior (`@langchain/anthropic`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* of in production. See scripts/check-langchain-deps.sh for
|
|
9
|
-
*
|
|
5
|
+
* cross-package behavior (`@langchain/anthropic` driving
|
|
6
|
+
* `@anthropic-ai/bedrock-sdk`, both resolving the single override-pinned
|
|
7
|
+
* @anthropic-ai/sdk copy), so a future bump of either side fails here in
|
|
8
|
+
* CI instead of in production. See scripts/check-langchain-deps.sh for
|
|
9
|
+
* the override rationale and the single-copy invariant it guards.
|
|
10
10
|
*
|
|
11
11
|
* Determinism: zero live credentials, zero network. SigV4 signing runs for
|
|
12
12
|
* real — it is pure HMAC over static test keys passed through the SDK's
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the declared-preferences module (stigmer/stigmer#293,
|
|
3
|
+
* DD-002). Like conversation-catchup there is no string key to mirror-guard —
|
|
4
|
+
* the value rides the typed `AgentExecutionSpec.declared_preferences` proto
|
|
5
|
+
* field, so codegen enforces the cross-repo contract. What IS pinned here:
|
|
6
|
+
* the per-scope blank-is-absent read semantics (the server stamps the field
|
|
7
|
+
* on EVERY eligible create, usually with blank scopes), the per-scope
|
|
8
|
+
* attribution, and the framing's behavioral contract.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect } from "vitest";
|
|
12
|
+
import { create } from "@bufbuild/protobuf";
|
|
13
|
+
import { DeclaredPreferencesSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
formatDeclaredPreferencesText,
|
|
17
|
+
readDeclaredPreferences,
|
|
18
|
+
} from "../declared-preferences.js";
|
|
19
|
+
|
|
20
|
+
const ORG_CONTEXT = "We deploy to us-east-1. All costs in USD.";
|
|
21
|
+
const USER_CONTEXT = "Keep answers terse. Prefer Go examples.";
|
|
22
|
+
|
|
23
|
+
describe("readDeclaredPreferences", () => {
|
|
24
|
+
it("reads both scopes when both are set", () => {
|
|
25
|
+
const preferences = create(DeclaredPreferencesSchema, {
|
|
26
|
+
orgContext: ORG_CONTEXT,
|
|
27
|
+
userContext: USER_CONTEXT,
|
|
28
|
+
});
|
|
29
|
+
expect(readDeclaredPreferences(preferences)).toEqual({
|
|
30
|
+
orgContext: ORG_CONTEXT,
|
|
31
|
+
userContext: USER_CONTEXT,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("answers undefined when the field is absent (pre-#293 executions)", () => {
|
|
36
|
+
expect(readDeclaredPreferences(undefined)).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("answers undefined when both scopes are blank — the server stamps the field on every eligible create", () => {
|
|
40
|
+
const preferences = create(DeclaredPreferencesSchema, {
|
|
41
|
+
orgContext: " ",
|
|
42
|
+
userContext: "",
|
|
43
|
+
});
|
|
44
|
+
expect(readDeclaredPreferences(preferences)).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("blank-is-absent applies per scope: an org-only snapshot carries no user scope", () => {
|
|
48
|
+
const preferences = create(DeclaredPreferencesSchema, {
|
|
49
|
+
orgContext: ORG_CONTEXT,
|
|
50
|
+
userContext: " ",
|
|
51
|
+
});
|
|
52
|
+
expect(readDeclaredPreferences(preferences)).toEqual({
|
|
53
|
+
orgContext: ORG_CONTEXT,
|
|
54
|
+
userContext: undefined,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("trims surrounding whitespace from each scope", () => {
|
|
59
|
+
const preferences = create(DeclaredPreferencesSchema, {
|
|
60
|
+
orgContext: " org facts ",
|
|
61
|
+
userContext: "\nuser facts\n",
|
|
62
|
+
});
|
|
63
|
+
expect(readDeclaredPreferences(preferences)).toEqual({
|
|
64
|
+
orgContext: "org facts",
|
|
65
|
+
userContext: "user facts",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("formatDeclaredPreferencesText", () => {
|
|
71
|
+
it("frames the preferences as already-known, non-announced background", () => {
|
|
72
|
+
const framed = formatDeclaredPreferencesText({
|
|
73
|
+
orgContext: ORG_CONTEXT,
|
|
74
|
+
userContext: USER_CONTEXT,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(framed).toContain("Standing preferences declared by");
|
|
78
|
+
expect(framed).toContain("Do not repeat them back");
|
|
79
|
+
expect(framed).toContain("not instructions that override your task");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("attributes each scope explicitly, organization first, user last", () => {
|
|
83
|
+
const framed = formatDeclaredPreferencesText({
|
|
84
|
+
orgContext: ORG_CONTEXT,
|
|
85
|
+
userContext: USER_CONTEXT,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const org = framed.indexOf("Declared by the organization:");
|
|
89
|
+
const user = framed.indexOf("Declared by the user:");
|
|
90
|
+
expect(org).toBeGreaterThan(-1);
|
|
91
|
+
expect(user).toBeGreaterThan(org);
|
|
92
|
+
expect(framed).toContain(ORG_CONTEXT);
|
|
93
|
+
expect(framed.endsWith(USER_CONTEXT)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("renders only the org subsection for an org-only snapshot", () => {
|
|
97
|
+
const framed = formatDeclaredPreferencesText({ orgContext: ORG_CONTEXT });
|
|
98
|
+
|
|
99
|
+
expect(framed).toContain("Declared by the organization:");
|
|
100
|
+
expect(framed).not.toContain("Declared by the user:");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("renders only the user subsection for a user-only snapshot", () => {
|
|
104
|
+
const framed = formatDeclaredPreferencesText({ userContext: USER_CONTEXT });
|
|
105
|
+
|
|
106
|
+
expect(framed).not.toContain("Declared by the organization:");
|
|
107
|
+
expect(framed).toContain("Declared by the user:");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* AnthropicFoundry seam — the integration the T05 foundry backend adapter
|
|
4
4
|
* is built on. Sibling of vertex-seam.test.ts and bedrock-seam.test.ts;
|
|
5
5
|
* same rules: this pins REAL cross-package behavior (`@langchain/anthropic`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* fails here in CI instead of in production. See
|
|
9
|
-
* scripts/check-langchain-deps.sh for
|
|
10
|
-
*
|
|
6
|
+
* driving `@anthropic-ai/foundry-sdk`, both resolving the single
|
|
7
|
+
* override-pinned @anthropic-ai/sdk copy), so a future bump of either
|
|
8
|
+
* side fails here in CI instead of in production. See
|
|
9
|
+
* scripts/check-langchain-deps.sh for the override rationale and the
|
|
10
|
+
* single-copy invariant it guards.
|
|
11
11
|
*
|
|
12
12
|
* Determinism: zero live credentials, zero network. API-key auth is a
|
|
13
13
|
* static test key; Entra ID auth is a fake `azureADTokenProvider` (the
|
|
@@ -1,58 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit pins for the plan-mode rule builder
|
|
2
|
+
* Unit pins for the plan-mode rule builder.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Since issue #754 the rules carry ONLY plan mode's write-deny: the read
|
|
5
|
+
* boundary is structural (virtual-rooted backends confine every path to the
|
|
6
|
+
* workspace), so there is no workspace-root glob, no escaping, and no rule
|
|
7
|
+
* ordering left to protect. The single rule's SHAPE is still the security
|
|
8
|
+
* boundary for writes — "/**" must stay the match-all virtual pattern and
|
|
9
|
+
* the operation class must stay `write` (deepagents evaluates
|
|
10
|
+
* first-match-wins with a permissive default, so reads flow by omission).
|
|
11
|
+
* End-to-end behavior is pinned against the real deepagents runtime in
|
|
10
12
|
* execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import { describe, it, expect } from "vitest";
|
|
14
|
-
import {
|
|
15
|
-
buildPlanModePermissions,
|
|
16
|
-
escapeGlobLiteral,
|
|
17
|
-
} from "../plan-mode-permissions.js";
|
|
16
|
+
import { buildPlanModePermissions } from "../plan-mode-permissions.js";
|
|
18
17
|
|
|
19
18
|
describe("buildPlanModePermissions", () => {
|
|
20
|
-
it("builds the policy in
|
|
21
|
-
expect(buildPlanModePermissions(
|
|
22
|
-
{ operations: ["read"], paths: ["/ws/session-1/repo/**"] },
|
|
23
|
-
{ operations: ["read"], paths: ["/**"], mode: "deny" },
|
|
19
|
+
it("builds exactly the write-deny policy in the virtual dialect", () => {
|
|
20
|
+
expect(buildPlanModePermissions()).toEqual([
|
|
24
21
|
{ operations: ["write"], paths: ["/**"], mode: "deny" },
|
|
25
22
|
]);
|
|
26
23
|
});
|
|
27
24
|
|
|
28
|
-
it("
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// trailing-slash root would silently match nothing.
|
|
32
|
-
const [allow] = buildPlanModePermissions("/ws//session-1/repo/");
|
|
33
|
-
expect(allow.paths).toEqual(["/ws/session-1/repo/**"]);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("glob-escapes the root so special characters match literally", () => {
|
|
37
|
-
const [allow] = buildPlanModePermissions("/Users/x/My (work) [v2]");
|
|
38
|
-
expect(allow.paths).toEqual(["/Users/x/My \\(work\\) \\[v2\\]/**"]);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe("escapeGlobLiteral", () => {
|
|
43
|
-
it("escapes every micromatch metacharacter", () => {
|
|
44
|
-
expect(escapeGlobLiteral("a*b?c(d)e[f]g{h}i!j+k@l")).toBe(
|
|
45
|
-
"a\\*b\\?c\\(d\\)e\\[f\\]g\\{h\\}i\\!j\\+k\\@l",
|
|
46
|
-
);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("escapes backslashes themselves", () => {
|
|
50
|
-
expect(escapeGlobLiteral("a\\b")).toBe("a\\\\b");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("leaves plain path characters alone", () => {
|
|
54
|
-
expect(escapeGlobLiteral("/ws/session-1/repo.dir")).toBe(
|
|
55
|
-
"/ws/session-1/repo.dir",
|
|
56
|
-
);
|
|
25
|
+
it("carries no read rules — the read boundary is structural, not policy", () => {
|
|
26
|
+
const operations = buildPlanModePermissions().flatMap((rule) => rule.operations);
|
|
27
|
+
expect(operations).not.toContain("read");
|
|
57
28
|
});
|
|
58
29
|
});
|