@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
|
@@ -25,8 +25,25 @@
|
|
|
25
25
|
* records its before-bytes at authorization time instead (`captureDeleteBefore`,
|
|
26
26
|
* issue #303). A delete via shell (`rm`) stays on the approval gate as always.
|
|
27
27
|
*
|
|
28
|
+
* VIRTUAL ROOT — THE ONE PATH DIALECT (issue #754)
|
|
29
|
+
* ------------------------------------------------
|
|
30
|
+
* Every backend here is constructed with `virtualMode: true`: a leading "/"
|
|
31
|
+
* denotes the WORKSPACE ROOT, traversal (`..`, `~`) is rejected, and every
|
|
32
|
+
* resolution stays inside `rootDir` by construction. This is the same dialect
|
|
33
|
+
* the rest of the harness already speaks — the system prompt's path-resolution
|
|
34
|
+
* directive, `resolveWorkspacePath(..., virtualRoot=true)` in the CAS observer
|
|
35
|
+
* and turn-boundary capture, `InlinePublisher.normalizePath`, and the approval
|
|
36
|
+
* gate's capturability checks. Before this flag, deepagents' legacy default
|
|
37
|
+
* passed absolute paths through to the REAL filesystem: a `write_file("/tmp/x")`
|
|
38
|
+
* escaped the session workspace onto the host AND escaped review entirely
|
|
39
|
+
* (auto-approved as capture-mode flow, but the observer/boundary/publisher all
|
|
40
|
+
* looked inside the workspace and found nothing). Read-side, the same legacy
|
|
41
|
+
* pass-through was why plan mode needed rule-based read fencing at all. Do not
|
|
42
|
+
* remove this flag: workspace confinement is structural, not policy.
|
|
43
|
+
*
|
|
28
44
|
* @since File-Change HITL Redesign (Phase 3 — CAS deep-agent wiring); sub-agent
|
|
29
|
-
* gitignored capture parity (Session 26, DD-19); shell restore (issue #248)
|
|
45
|
+
* gitignored capture parity (Session 26, DD-19); shell restore (issue #248);
|
|
46
|
+
* virtual-root confinement (issue #754)
|
|
30
47
|
*/
|
|
31
48
|
|
|
32
49
|
import { FilesystemBackend, LocalShellBackend } from "deepagents";
|
|
@@ -111,12 +128,12 @@ export async function createCasCaptureBackend(
|
|
|
111
128
|
|
|
112
129
|
if (shellEnv !== undefined) {
|
|
113
130
|
const backend = new CasCaptureShellBackend(
|
|
114
|
-
{ rootDir, env: shellEnv },
|
|
131
|
+
{ rootDir, virtualMode: true, env: shellEnv },
|
|
115
132
|
{ observer },
|
|
116
133
|
);
|
|
117
134
|
await backend.initialize();
|
|
118
135
|
return backend;
|
|
119
136
|
}
|
|
120
137
|
|
|
121
|
-
return new CasCaptureFilesystemBackend({ rootDir }, { observer });
|
|
138
|
+
return new CasCaptureFilesystemBackend({ rootDir, virtualMode: true }, { observer });
|
|
122
139
|
}
|
|
@@ -78,10 +78,33 @@ import { stampFlowedFileEditRows, stampFlowedSubAgentFileEditRows } from "./stam
|
|
|
78
78
|
import { deriveTurnCommandProvenance } from "./command-provenance.js";
|
|
79
79
|
import { describeExecutionError } from "../../shared/model-error.js";
|
|
80
80
|
import { inferProvider, type LlmProvider } from "../../shared/llm-proxy.js";
|
|
81
|
+
import { getShutdownSignalForQueue } from "../../shared/worker-shutdown.js";
|
|
81
82
|
|
|
82
83
|
/** The harness id stamped on the deep-agent's file-review ledger events. */
|
|
83
84
|
const DEEP_AGENT_HARNESS_ID = "deep-agent";
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* The worker-shutdown terminal status (#776). A shutdown is not a pause: the
|
|
88
|
+
* execution will not resume on this worker, so persisting PAUSED would strand
|
|
89
|
+
* the user with a lie. The copy is byte-identical to the Cursor harness's
|
|
90
|
+
* worker-shutdown branch so every downstream status.error consumer (the Go and
|
|
91
|
+
* Java workflow fallbacks, the cloud channel decision table) keys on ONE shape.
|
|
92
|
+
*/
|
|
93
|
+
function buildWorkerShutdownStatus(): AgentExecutionStatus {
|
|
94
|
+
return create(AgentExecutionStatusSchema, {
|
|
95
|
+
phase: ExecutionPhase.EXECUTION_FAILED,
|
|
96
|
+
error: "Execution interrupted: runner worker was shut down. Retry or resume.",
|
|
97
|
+
completedAt: utcTimestamp(),
|
|
98
|
+
messages: [
|
|
99
|
+
create(AgentMessageSchema, {
|
|
100
|
+
type: MessageType.MESSAGE_SYSTEM,
|
|
101
|
+
content: "Execution interrupted: the runner worker was shut down while the agent was still running. You can retry or resume.",
|
|
102
|
+
timestamp: utcTimestamp(),
|
|
103
|
+
}),
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
85
108
|
/**
|
|
86
109
|
* Best-effort provider inference for error-message wording. inferProvider
|
|
87
110
|
* throws on unrecognized names; an error path must never throw over a label.
|
|
@@ -115,6 +138,11 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
115
138
|
): Promise<unknown> => {
|
|
116
139
|
const { executionId, threadId, turnSeq } = normalizeActivityInput(arg0, arg1);
|
|
117
140
|
activityStarted();
|
|
141
|
+
// Worker-shutdown classification channel (#776): the pause paths below
|
|
142
|
+
// consult this signal to distinguish "my worker is draining" (SIGTERM,
|
|
143
|
+
// desktop quit) from "the orchestrator cancelled me" (a real user
|
|
144
|
+
// pause). See shared/worker-shutdown.ts for the ownership contract.
|
|
145
|
+
const shutdownSignal = getShutdownSignalForQueue(Context.current().info.taskQueue);
|
|
118
146
|
let setup: SetupResult | null = null;
|
|
119
147
|
// Exclusive turn lock on the workspace working tree — held across the
|
|
120
148
|
// entire tree-mutating window (decision reconcile, agent writes,
|
|
@@ -567,6 +595,13 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
567
595
|
|
|
568
596
|
if (result.terminalStatus) {
|
|
569
597
|
if (initialStatus.phase === ExecutionPhase.EXECUTION_PAUSED) {
|
|
598
|
+
// The stream loop marks PAUSED for ANY platform cancellation; a
|
|
599
|
+
// worker shutdown is not a pause and must not persist as one (#776).
|
|
600
|
+
if (shutdownSignal?.aborted) {
|
|
601
|
+
console.log(`[ExecuteDeepAgent] Cancelled (worker shutdown) for execution ${executionId}: events=${result.eventsProcessed}`);
|
|
602
|
+
await persistStatus(client, executionId, buildWorkerShutdownStatus(), { offload: statusOffload }).catch(() => {});
|
|
603
|
+
throw new CancelledFailure("Activity cancelled (worker shutdown, not user pause)");
|
|
604
|
+
}
|
|
570
605
|
await persistStatus(client, executionId, initialStatus, { offload: statusOffload });
|
|
571
606
|
console.log(`[ExecuteDeepAgent] Paused for execution ${executionId}: events=${result.eventsProcessed}`);
|
|
572
607
|
throw new CancelledFailure("Activity paused by orchestrator");
|
|
@@ -617,15 +652,20 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
617
652
|
policyEngineVersion: intr.policySource ? POLICY_ENGINE_VERSION : "",
|
|
618
653
|
});
|
|
619
654
|
|
|
620
|
-
// Capture a sanitized args preview
|
|
621
|
-
//
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
|
|
655
|
+
// Capture a sanitized args preview AND the redacted args object
|
|
656
|
+
// while the graph is paused, so the approval UI renders the
|
|
657
|
+
// proposed change before the tool runs — and the row header can
|
|
658
|
+
// extract its filename-first path from `args` like every other
|
|
659
|
+
// row (issue #754: a placeholder without args rendered a
|
|
660
|
+
// pathless "Write" header). Args are correlated from the
|
|
661
|
+
// AI-message tool call in graph state (the single source of
|
|
662
|
+
// truth).
|
|
663
|
+
const { argsPreview, args } = captureApprovalArtifacts({
|
|
625
664
|
toolCallId: intr.toolCallId,
|
|
626
665
|
messages: aiMessages,
|
|
627
666
|
});
|
|
628
667
|
if (argsPreview) toolCall.argsPreview = argsPreview;
|
|
668
|
+
if (args) toolCall.args = args as typeof toolCall.args;
|
|
629
669
|
|
|
630
670
|
aiMsg.toolCalls.push(toolCall);
|
|
631
671
|
}
|
|
@@ -749,6 +789,13 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
749
789
|
|
|
750
790
|
} catch (err: unknown) {
|
|
751
791
|
if (err instanceof CancelledFailure) {
|
|
792
|
+
// Worker shutdown is infrastructure failure, not pause — the caught
|
|
793
|
+
// CancelledFailure is itself the interruption evidence (#776).
|
|
794
|
+
if (shutdownSignal?.aborted) {
|
|
795
|
+
console.log(`[ExecuteDeepAgent] Cancelled (worker shutdown) for execution ${executionId}`);
|
|
796
|
+
await persistStatus(client, executionId, buildWorkerShutdownStatus()).catch(() => {});
|
|
797
|
+
throw err;
|
|
798
|
+
}
|
|
752
799
|
console.log(`[ExecuteDeepAgent] Cancelled (pause) for execution ${executionId}`);
|
|
753
800
|
const pausedStatus = create(AgentExecutionStatusSchema, {
|
|
754
801
|
phase: ExecutionPhase.EXECUTION_PAUSED,
|
|
@@ -758,6 +805,11 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
758
805
|
}
|
|
759
806
|
|
|
760
807
|
if (Context.current().cancellationSignal.aborted) {
|
|
808
|
+
if (shutdownSignal?.aborted) {
|
|
809
|
+
console.log(`[ExecuteDeepAgent] Error during worker-shutdown cancellation for ${executionId}: ${err}`);
|
|
810
|
+
await persistStatus(client, executionId, buildWorkerShutdownStatus()).catch(() => {});
|
|
811
|
+
throw new CancelledFailure("Activity cancelled (worker shutdown, not user pause)");
|
|
812
|
+
}
|
|
761
813
|
console.log(`[ExecuteDeepAgent] Error during cancellation for ${executionId}, treating as pause: ${err}`);
|
|
762
814
|
const pausedStatus = create(AgentExecutionStatusSchema, {
|
|
763
815
|
phase: ExecutionPhase.EXECUTION_PAUSED,
|
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
type SenderIdentity,
|
|
18
18
|
} from "../../shared/sender-identity.js";
|
|
19
19
|
import { formatSessionContextText } from "../../shared/session-context.js";
|
|
20
|
+
import {
|
|
21
|
+
formatDeclaredPreferencesText,
|
|
22
|
+
type DeclaredPreferencesContent,
|
|
23
|
+
} from "../../shared/declared-preferences.js";
|
|
20
24
|
import {
|
|
21
25
|
visionDisclosureLines,
|
|
22
26
|
type NotViewableEntry,
|
|
@@ -166,6 +170,14 @@ export interface PromptBuilderInput {
|
|
|
166
170
|
* context is standing session state, like skills.
|
|
167
171
|
*/
|
|
168
172
|
sessionContext?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Platform-declared standing preferences (stigmer/stigmer#293): the org's
|
|
175
|
+
* and user's standing context, server-snapshotted onto the execution
|
|
176
|
+
* spec's `declared_preferences` at create. Injected on EVERY turn like
|
|
177
|
+
* the bridge — the native system prompt is rebuilt per invocation, so an
|
|
178
|
+
* edited preference reaches the very next turn.
|
|
179
|
+
*/
|
|
180
|
+
declaredPreferences?: DeclaredPreferencesContent;
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
// The prompt renders the injector's own result type — a local structural twin
|
|
@@ -229,6 +241,16 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
|
229
241
|
formatSenderIdentityText(input.senderIdentity);
|
|
230
242
|
}
|
|
231
243
|
|
|
244
|
+
// Platform-declared standing facts precede embedder-supplied context
|
|
245
|
+
// (DD-002 D3): both are standing background, but the declared preferences
|
|
246
|
+
// are platform-authored while session context is the embedder's overlay —
|
|
247
|
+
// the more specific overlay reads later and naturally refines.
|
|
248
|
+
if (input.declaredPreferences) {
|
|
249
|
+
prompt +=
|
|
250
|
+
"\n\n## Declared preferences\n\n" +
|
|
251
|
+
formatDeclaredPreferencesText(input.declaredPreferences);
|
|
252
|
+
}
|
|
253
|
+
|
|
232
254
|
// Standing facts about the user (session context) come before the
|
|
233
255
|
// carried conversation (bridge): the bridge may refer back to them.
|
|
234
256
|
if (input.sessionContext) {
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
resolveCallerIdentity,
|
|
33
33
|
} from "../../shared/caller-identity.js";
|
|
34
34
|
import { readSessionContext } from "../../shared/session-context.js";
|
|
35
|
+
import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
|
|
35
36
|
import { connectMcpServers, type McpConnectionResult } from "../../shared/mcp-manager.js";
|
|
36
37
|
import { mergeMcpServerUsages, resolveMcpServers } from "../../shared/mcp-resolver.js";
|
|
37
38
|
import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
|
|
@@ -88,12 +89,9 @@ import type { ToolApprovalCategory } from "../../shared/tool-kind.js";
|
|
|
88
89
|
import {
|
|
89
90
|
mergeSkillRefs,
|
|
90
91
|
fetchSkillsByRefs,
|
|
91
|
-
|
|
92
|
-
computeSkillPaths,
|
|
93
|
-
checkSkillIntegrity,
|
|
92
|
+
mountSkills,
|
|
94
93
|
generatePromptSection,
|
|
95
94
|
generateAlsoAvailableSection,
|
|
96
|
-
fetchSkillArtifacts,
|
|
97
95
|
} from "../../shared/skill-writer.js";
|
|
98
96
|
import { filterSkills, SKILL_COUNT_THRESHOLD } from "../../shared/skill-relevance.js";
|
|
99
97
|
import { injectAttachments } from "./attachment-injector.js";
|
|
@@ -478,8 +476,11 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
478
476
|
const skills = await fetchSkillsByRefs(client, skillRefs);
|
|
479
477
|
|
|
480
478
|
if (skills.length > 0) {
|
|
481
|
-
|
|
482
|
-
|
|
479
|
+
// platformDir is an invariant of this path: provisionWorkspace below
|
|
480
|
+
// threads ensurePlatformDir into every backend it constructs.
|
|
481
|
+
const { paths: skillPaths } = await mountSkills(
|
|
482
|
+
client, skills, workspaceBackend.platformDir!,
|
|
483
|
+
);
|
|
483
484
|
|
|
484
485
|
const userMessage = execution.spec!.message || "";
|
|
485
486
|
const skillNames = skills.map(s => s.spec?.name || s.metadata?.slug || "unknown");
|
|
@@ -573,6 +574,9 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
573
574
|
contextBridge: readContextBridge(session.spec!.metadata),
|
|
574
575
|
senderIdentity: readSenderIdentity(session.spec!.metadata),
|
|
575
576
|
sessionContext: readSessionContext(session.spec!.metadata),
|
|
577
|
+
declaredPreferences: readDeclaredPreferences(
|
|
578
|
+
execution.spec!.declaredPreferences,
|
|
579
|
+
),
|
|
576
580
|
});
|
|
577
581
|
|
|
578
582
|
// Step 9: Construct the LLM model. Resolution to the provider API id
|
|
@@ -606,16 +610,13 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
606
610
|
const execConfig = execution.spec!.executionConfig;
|
|
607
611
|
const isPlanMode = execConfig?.interactionMode === InteractionMode.PLAN;
|
|
608
612
|
const shellEnv = isPlanMode ? undefined : buildShellEnv(envResult.mergedEnvVars);
|
|
609
|
-
// Plan mode's filesystem permission rules, hoisted once: the parent graph
|
|
610
|
-
// every sub-agent graph
|
|
611
|
-
//
|
|
612
|
-
//
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
|
|
616
|
-
const planModePermissions = isPlanMode
|
|
617
|
-
? buildPlanModePermissions(workspaceBackend.rootDir)
|
|
618
|
-
: undefined;
|
|
613
|
+
// Plan mode's filesystem permission rules, hoisted once: the parent graph
|
|
614
|
+
// and every sub-agent graph carry this single value. The rules are the
|
|
615
|
+
// write-deny half of plan mode only — the read boundary is structural
|
|
616
|
+
// (virtual-rooted backends, issue #754), and the path-normalization shim
|
|
617
|
+
// is no longer tied to the rules: every native graph installs it (see
|
|
618
|
+
// buildMiddlewareStack below) so the whole harness speaks one dialect.
|
|
619
|
+
const planModePermissions = isPlanMode ? buildPlanModePermissions() : undefined;
|
|
619
620
|
|
|
620
621
|
const toolServerMap = new Map<string, string>();
|
|
621
622
|
if (mcpConnection) {
|
|
@@ -729,9 +730,10 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
729
730
|
} : null,
|
|
730
731
|
otelSpans: { toolServerMap },
|
|
731
732
|
approvalGate: approvalGateConfig,
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
733
|
+
// Every graph, every mode (issue #754): the dialect-repair seam that
|
|
734
|
+
// keeps model-supplied paths canonical for the virtual-rooted backend
|
|
735
|
+
// and every downstream consumer (gate, capture, spans).
|
|
736
|
+
pathNormalization: { rootDir: workspaceBackend.rootDir },
|
|
735
737
|
});
|
|
736
738
|
timing.mark("build_middleware");
|
|
737
739
|
|
|
@@ -42,8 +42,7 @@ import { SubAgentGate } from "../../shared/subagent-gate.js";
|
|
|
42
42
|
import { isModelRegistered } from "../../shared/model-registry.js";
|
|
43
43
|
import {
|
|
44
44
|
fetchSkillsByRefs,
|
|
45
|
-
|
|
46
|
-
writeSkills,
|
|
45
|
+
mountSkills,
|
|
47
46
|
generatePromptSection,
|
|
48
47
|
} from "../../shared/skill-writer.js";
|
|
49
48
|
|
|
@@ -493,6 +492,10 @@ export function resolveSubagentSkillPrompt(
|
|
|
493
492
|
* (approval-gated by the sub-agent's own middleware). Absent (plan mode),
|
|
494
493
|
* the backend is filesystem-only — read-only by construction, matching the
|
|
495
494
|
* parent.
|
|
495
|
+
*
|
|
496
|
+
* Every variant is virtual-rooted (`virtualMode: true`) like the parent's —
|
|
497
|
+
* see the cas-capture-backend.ts header (issue #754): workspace confinement
|
|
498
|
+
* is structural on every graph, sub-agents included.
|
|
496
499
|
*/
|
|
497
500
|
async function buildSubagentBackend(opts: {
|
|
498
501
|
readonly workspaceRootDir: string;
|
|
@@ -508,11 +511,12 @@ async function buildSubagentBackend(opts: {
|
|
|
508
511
|
}
|
|
509
512
|
|
|
510
513
|
if (opts.shellEnv === undefined) {
|
|
511
|
-
return new FilesystemBackend({ rootDir: opts.workspaceRootDir });
|
|
514
|
+
return new FilesystemBackend({ rootDir: opts.workspaceRootDir, virtualMode: true });
|
|
512
515
|
}
|
|
513
516
|
|
|
514
517
|
const shellBackend = new LocalShellBackend({
|
|
515
518
|
rootDir: opts.workspaceRootDir,
|
|
519
|
+
virtualMode: true,
|
|
516
520
|
env: opts.shellEnv,
|
|
517
521
|
});
|
|
518
522
|
await shellBackend.initialize();
|
|
@@ -554,17 +558,14 @@ export async function compileSubagents(
|
|
|
554
558
|
// Structural coupling (DD-19): a sub-agent gate flows gitignored writes into
|
|
555
559
|
// CAS iff a CAS observer backs that sub-agent's filesystem backend. Deriving
|
|
556
560
|
// both from the same `casObserver` makes "unobserved unreviewable bytes"
|
|
557
|
-
// impossible by construction.
|
|
558
|
-
// (issue #
|
|
559
|
-
//
|
|
560
|
-
// prompt-compliant relative paths from dying in rule validation.
|
|
561
|
+
// impossible by construction. Path normalization is unconditional
|
|
562
|
+
// (issue #754): every graph speaks the virtual dialect, so every graph
|
|
563
|
+
// carries the repair seam — matching the parent's composition.
|
|
561
564
|
const middleware = buildSubAgentMiddleware({
|
|
562
565
|
costCap: opts.costCap,
|
|
563
566
|
approvalGate: opts.approvalGate,
|
|
564
567
|
captureIgnored: !!opts.casObserver,
|
|
565
|
-
|
|
566
|
-
? { pathNormalization: { rootDir: opts.workspaceRootDir } }
|
|
567
|
-
: {}),
|
|
568
|
+
pathNormalization: { rootDir: opts.workspaceRootDir },
|
|
568
569
|
});
|
|
569
570
|
|
|
570
571
|
const modelName = spec.model ?? opts.parentModelName;
|
|
@@ -679,8 +680,14 @@ export async function transformAndCompileSubagents(
|
|
|
679
680
|
const skills = await fetchSkillsByRefs(skillClient, refs);
|
|
680
681
|
|
|
681
682
|
if (skills.length > 0) {
|
|
682
|
-
|
|
683
|
-
|
|
683
|
+
// Runs after the parent's setup step 7b mounted its skills, so any
|
|
684
|
+
// skill shared by parent and sub-agent is a cache hit here — the
|
|
685
|
+
// hash-keyed marker turns the old double download into a no-op.
|
|
686
|
+
// platformDir is an invariant: provisionWorkspace threads
|
|
687
|
+
// ensurePlatformDir into every backend it constructs.
|
|
688
|
+
const { paths: skillPaths } = await mountSkills(
|
|
689
|
+
skillClient, skills, workspaceBackend.platformDir!,
|
|
690
|
+
);
|
|
684
691
|
|
|
685
692
|
for (const skill of skills) {
|
|
686
693
|
const slug = (skill as { metadata?: { slug?: string } }).metadata?.slug;
|
|
@@ -75,9 +75,9 @@ export interface SubAgentMiddlewareOptions {
|
|
|
75
75
|
*/
|
|
76
76
|
readonly captureIgnored?: boolean;
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
79
|
-
* sub-agent
|
|
80
|
-
*
|
|
78
|
+
* Virtual-dialect path normalization (issues #429/#754). Present on every
|
|
79
|
+
* sub-agent graph — the backends are virtual-rooted, so every graph
|
|
80
|
+
* carries the repair seam, matching the parent composition.
|
|
81
81
|
*/
|
|
82
82
|
readonly pathNormalization?: PathNormalizationConfig;
|
|
83
83
|
}
|
package/src/index.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* import { createStigmerRunner } from '@stigmer/runner';
|
|
15
15
|
*
|
|
16
16
|
* const runner = await createStigmerRunner({
|
|
17
|
-
* taskQueue: '
|
|
17
|
+
* taskQueue: 'stigmer_runner',
|
|
18
18
|
* temporalAddress: 'localhost:7233',
|
|
19
19
|
* stigmerEndpoint: 'http://localhost:7234',
|
|
20
20
|
* });
|
|
@@ -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.
|