@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
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* This is NOT a unit test of our code (there is no production vertex code
|
|
6
6
|
* yet). It pins the exact cross-package behavior production will rely on:
|
|
7
|
-
* the REAL `ChatAnthropic` (@langchain/anthropic
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* the REAL `ChatAnthropic` (@langchain/anthropic) driving the REAL
|
|
8
|
+
* `AnthropicVertex` client (@anthropic-ai/vertex-sdk), both resolving the
|
|
9
|
+
* single override-pinned @anthropic-ai/sdk copy. If a future bump of
|
|
10
10
|
* either side changes request shaping, streaming event handling, tool-call
|
|
11
11
|
* assembly, or usage accounting across this seam, this suite fails in CI
|
|
12
|
-
* instead of production. See scripts/check-langchain-deps.sh for
|
|
13
|
-
*
|
|
12
|
+
* instead of production. See scripts/check-langchain-deps.sh for the
|
|
13
|
+
* override rationale and the single-copy invariant it guards.
|
|
14
14
|
*
|
|
15
15
|
* Determinism: zero credentials, zero network. Google auth is bypassed by
|
|
16
16
|
* injecting a fake `authClient` (the SDK's supported constructor option —
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
classifyTurnInterruption,
|
|
4
|
+
getShutdownSignalForQueue,
|
|
5
|
+
registerWorkerShutdownSignal,
|
|
6
|
+
signalWorkerShutdown,
|
|
7
|
+
unregisterWorkerShutdownSignal,
|
|
8
|
+
} from "../worker-shutdown.js";
|
|
9
|
+
|
|
10
|
+
describe("worker-shutdown signal registry", () => {
|
|
11
|
+
it("registers a live signal an activity can resolve by queue name", () => {
|
|
12
|
+
const controller = registerWorkerShutdownSignal("session:reg-1");
|
|
13
|
+
const signal = getShutdownSignalForQueue("session:reg-1");
|
|
14
|
+
expect(signal).toBe(controller.signal);
|
|
15
|
+
expect(signal?.aborted).toBe(false);
|
|
16
|
+
unregisterWorkerShutdownSignal("session:reg-1");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("aborting via signalWorkerShutdown marks the queue's signal (idempotent, unknown queues are no-ops)", () => {
|
|
20
|
+
registerWorkerShutdownSignal("session:abort-1");
|
|
21
|
+
signalWorkerShutdown("session:abort-1");
|
|
22
|
+
signalWorkerShutdown("session:abort-1");
|
|
23
|
+
expect(getShutdownSignalForQueue("session:abort-1")?.aborted).toBe(true);
|
|
24
|
+
expect(() => signalWorkerShutdown("session:never-registered")).not.toThrow();
|
|
25
|
+
unregisterWorkerShutdownSignal("session:abort-1");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("re-registering a reused queue replaces an aborted signal with a fresh one", () => {
|
|
29
|
+
// A re-created worker on a reused queue (desktop re-opens a session) must
|
|
30
|
+
// not observe the previous worker's aborted signal as its own shutdown.
|
|
31
|
+
const first = registerWorkerShutdownSignal("session:reuse-1");
|
|
32
|
+
first.abort();
|
|
33
|
+
const second = registerWorkerShutdownSignal("session:reuse-1");
|
|
34
|
+
expect(getShutdownSignalForQueue("session:reuse-1")).toBe(second.signal);
|
|
35
|
+
expect(getShutdownSignalForQueue("session:reuse-1")?.aborted).toBe(false);
|
|
36
|
+
unregisterWorkerShutdownSignal("session:reuse-1");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("unregistering removes the signal", () => {
|
|
40
|
+
registerWorkerShutdownSignal("session:gone-1");
|
|
41
|
+
unregisterWorkerShutdownSignal("session:gone-1");
|
|
42
|
+
expect(getShutdownSignalForQueue("session:gone-1")).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("classifyTurnInterruption", () => {
|
|
47
|
+
const none = {
|
|
48
|
+
heartbeatCancelled: false,
|
|
49
|
+
heartbeatWorkerShutdown: false,
|
|
50
|
+
cancellationSignalAborted: false,
|
|
51
|
+
shutdownSignalAborted: false,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
it("an uninterrupted turn is none", () => {
|
|
55
|
+
expect(classifyTurnInterruption(none)).toBe("none");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("GRACE-WINDOW GUARD (#776): an aborted shutdown signal alone stays none — a run that completed inside the drain grace window must not be failed", () => {
|
|
59
|
+
expect(
|
|
60
|
+
classifyTurnInterruption({ ...none, shutdownSignalAborted: true }),
|
|
61
|
+
).toBe("none");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("heartbeat cancellation without a shutdown signal is the orchestrator's pause", () => {
|
|
65
|
+
expect(
|
|
66
|
+
classifyTurnInterruption({ ...none, heartbeatCancelled: true }),
|
|
67
|
+
).toBe("pause");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("heartbeat cancellation WITH the shutdown signal aborted is a worker shutdown, not a pause (the #776 misclassification)", () => {
|
|
71
|
+
expect(
|
|
72
|
+
classifyTurnInterruption({
|
|
73
|
+
...none,
|
|
74
|
+
heartbeatCancelled: true,
|
|
75
|
+
shutdownSignalAborted: true,
|
|
76
|
+
}),
|
|
77
|
+
).toBe("worker-shutdown");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("the heartbeat's own workerShutdown flag classifies directly", () => {
|
|
81
|
+
expect(
|
|
82
|
+
classifyTurnInterruption({ ...none, heartbeatWorkerShutdown: true }),
|
|
83
|
+
).toBe("worker-shutdown");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("a delivered cancellation with the shutdown signal aborted is a worker shutdown (signal races the heartbeat tick)", () => {
|
|
87
|
+
expect(
|
|
88
|
+
classifyTurnInterruption({
|
|
89
|
+
...none,
|
|
90
|
+
cancellationSignalAborted: true,
|
|
91
|
+
shutdownSignalAborted: true,
|
|
92
|
+
}),
|
|
93
|
+
).toBe("worker-shutdown");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("a delivered cancellation alone (heartbeat timeout / infra cancel) is neither shutdown nor pause", () => {
|
|
97
|
+
expect(
|
|
98
|
+
classifyTurnInterruption({ ...none, cancellationSignalAborted: true }),
|
|
99
|
+
).toBe("none");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { extractZipFileEntries } from "../zip-extract.js";
|
|
2
|
+
import { extractZipFileEntries, type ZipFileEntry } from "../zip-extract.js";
|
|
3
3
|
import { buildZip } from "../../__test-utils__/zip-fixtures.js";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Decode entries for text-content assertions. The production contract is
|
|
7
|
+
* bytes (issue #683); decoding belongs to the tests that want to assert on
|
|
8
|
+
* human-readable fixtures, not to the module under test.
|
|
9
|
+
*/
|
|
10
|
+
function decoded(entries: ZipFileEntry[]): { path: string; content: string }[] {
|
|
11
|
+
return entries.map((e) => ({ path: e.path, content: new TextDecoder().decode(e.content) }));
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
// ─── extractZipFileEntries ───────────────────────────────────────────────
|
|
6
15
|
|
|
7
16
|
describe("extractZipFileEntries", () => {
|
|
@@ -12,9 +21,10 @@ describe("extractZipFileEntries", () => {
|
|
|
12
21
|
]);
|
|
13
22
|
|
|
14
23
|
const entries = await extractZipFileEntries(zip);
|
|
15
|
-
expect(entries).
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
expect(decoded(entries)).toEqual([
|
|
25
|
+
{ path: "SKILL.md", content: "# My Skill" },
|
|
26
|
+
{ path: "references/schema.md", content: "# Schema\n\nTable definitions." },
|
|
27
|
+
]);
|
|
18
28
|
});
|
|
19
29
|
|
|
20
30
|
it("extracts deflated files", async () => {
|
|
@@ -22,8 +32,7 @@ describe("extractZipFileEntries", () => {
|
|
|
22
32
|
const zip = buildZip([{ name: "notes.txt", content, method: "deflated" }]);
|
|
23
33
|
|
|
24
34
|
const entries = await extractZipFileEntries(zip);
|
|
25
|
-
expect(entries).
|
|
26
|
-
expect(entries[0]).toEqual({ path: "notes.txt", content });
|
|
35
|
+
expect(decoded(entries)).toEqual([{ path: "notes.txt", content }]);
|
|
27
36
|
});
|
|
28
37
|
|
|
29
38
|
it("skips directory entries", async () => {
|
|
@@ -123,6 +132,32 @@ describe("extractZipFileEntries", () => {
|
|
|
123
132
|
expect(entries).toHaveLength(1);
|
|
124
133
|
});
|
|
125
134
|
|
|
135
|
+
// ── Binary safety (issue #683) ──────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
it("round-trips a stored binary entry byte-identically", async () => {
|
|
138
|
+
// PNG magic followed by bytes that are not valid UTF-8 (0x89 alone, a
|
|
139
|
+
// lone continuation byte, an unpaired lead byte). A decode/encode
|
|
140
|
+
// round-trip replaces these with U+FFFD — the corruption this pins.
|
|
141
|
+
const binary = new Uint8Array([
|
|
142
|
+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xfe, 0x00, 0x80, 0xc3,
|
|
143
|
+
]);
|
|
144
|
+
const zip = buildZip([{ name: "references/diagram.png", content: binary }]);
|
|
145
|
+
|
|
146
|
+
const entries = await extractZipFileEntries(zip);
|
|
147
|
+
expect(entries).toHaveLength(1);
|
|
148
|
+
expect(new Uint8Array(entries[0].content)).toEqual(binary);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("round-trips a deflated binary entry byte-identically", async () => {
|
|
152
|
+
const binary = new Uint8Array(256);
|
|
153
|
+
for (let i = 0; i < 256; i++) binary[i] = i; // every byte value once
|
|
154
|
+
const zip = buildZip([{ name: "assets/font.woff2", content: binary, method: "deflated" }]);
|
|
155
|
+
|
|
156
|
+
const entries = await extractZipFileEntries(zip);
|
|
157
|
+
expect(entries).toHaveLength(1);
|
|
158
|
+
expect(new Uint8Array(entries[0].content)).toEqual(binary);
|
|
159
|
+
});
|
|
160
|
+
|
|
126
161
|
// ── Streaming entries (issue #450) ─────────────────────────────────────
|
|
127
162
|
|
|
128
163
|
it("extracts Go-default streaming archives (deflated, data descriptors)", async () => {
|
|
@@ -132,7 +167,7 @@ describe("extractZipFileEntries", () => {
|
|
|
132
167
|
]);
|
|
133
168
|
|
|
134
169
|
const entries = await extractZipFileEntries(zip);
|
|
135
|
-
expect(entries).toEqual([
|
|
170
|
+
expect(decoded(entries)).toEqual([
|
|
136
171
|
{ path: "SKILL.md", content: "# Streamed Skill" },
|
|
137
172
|
{ path: "references/notes.md", content: "streamed notes" },
|
|
138
173
|
]);
|
|
@@ -151,7 +186,7 @@ describe("extractZipFileEntries", () => {
|
|
|
151
186
|
]);
|
|
152
187
|
|
|
153
188
|
const entries = await extractZipFileEntries(zip);
|
|
154
|
-
expect(entries).toEqual([
|
|
189
|
+
expect(decoded(entries)).toEqual([
|
|
155
190
|
{ path: "poison.md", content: poisoned },
|
|
156
191
|
{ path: "after.md", content: "the entry after the poisoned one" },
|
|
157
192
|
]);
|
|
@@ -162,7 +197,7 @@ describe("extractZipFileEntries", () => {
|
|
|
162
197
|
const zip = buildZip([{ name: "cd-sizes.txt", content, streaming: true }]);
|
|
163
198
|
|
|
164
199
|
const entries = await extractZipFileEntries(zip);
|
|
165
|
-
expect(entries).toEqual([{ path: "cd-sizes.txt", content }]);
|
|
200
|
+
expect(decoded(entries)).toEqual([{ path: "cd-sizes.txt", content }]);
|
|
166
201
|
});
|
|
167
202
|
|
|
168
203
|
// ── Central directory edge cases ───────────────────────────────────────
|
|
@@ -173,7 +208,7 @@ describe("extractZipFileEntries", () => {
|
|
|
173
208
|
});
|
|
174
209
|
|
|
175
210
|
const entries = await extractZipFileEntries(zip);
|
|
176
|
-
expect(entries).toEqual([{ path: "a.txt", content: "aaa" }]);
|
|
211
|
+
expect(decoded(entries)).toEqual([{ path: "a.txt", content: "aaa" }]);
|
|
177
212
|
});
|
|
178
213
|
|
|
179
214
|
it("is not fooled by EOCD signature bytes inside the archive comment", async () => {
|
|
@@ -185,7 +220,7 @@ describe("extractZipFileEntries", () => {
|
|
|
185
220
|
});
|
|
186
221
|
|
|
187
222
|
const entries = await extractZipFileEntries(zip);
|
|
188
|
-
expect(entries).toEqual([{ path: "a.txt", content: "aaa" }]);
|
|
223
|
+
expect(decoded(entries)).toEqual([{ path: "a.txt", content: "aaa" }]);
|
|
189
224
|
});
|
|
190
225
|
|
|
191
226
|
it("returns empty array when the central directory is missing", async () => {
|
|
@@ -34,11 +34,25 @@ export const MAX_ARGS_PREVIEW_LENGTH = 500;
|
|
|
34
34
|
* Cursor gate path must instead use {@link buildElidedArgsPreview}, which keeps
|
|
35
35
|
* the JSON valid and preserves the salient identity fields.
|
|
36
36
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Redact secret-keyed values (see {@link SENSITIVE_ARG_KEYS}), preserving every
|
|
39
|
+
* other entry verbatim. The shared first step of both preview builders, and the
|
|
40
|
+
* shape stamped as `args` on interrupt-placeholder tool calls (issue #754's
|
|
41
|
+
* header fix): full enough for the UI's path/primary-arg extraction, never
|
|
42
|
+
* carrying a secret value.
|
|
43
|
+
*/
|
|
44
|
+
export function redactSensitiveArgs(
|
|
45
|
+
args: Record<string, unknown>,
|
|
46
|
+
): Record<string, unknown> {
|
|
47
|
+
const redacted: Record<string, unknown> = {};
|
|
39
48
|
for (const [key, value] of Object.entries(args)) {
|
|
40
|
-
|
|
49
|
+
redacted[key] = SENSITIVE_ARG_KEYS.has(key.toLowerCase()) ? "[REDACTED]" : value;
|
|
41
50
|
}
|
|
51
|
+
return redacted;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function sanitizeArgsPreview(args: Record<string, unknown>): string {
|
|
55
|
+
const sanitized = redactSensitiveArgs(args);
|
|
42
56
|
try {
|
|
43
57
|
const json = JSON.stringify(sanitized);
|
|
44
58
|
return json.length > MAX_ARGS_PREVIEW_LENGTH
|
|
@@ -56,7 +56,17 @@ export interface ArtifactStorage {
|
|
|
56
56
|
exists(key: string): Promise<boolean>;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* "none" is the explicit no-store posture: capture degrades to the deny-gate
|
|
61
|
+
* (file writes gated pre-execution, DD-22/DD-26) and offload is disabled —
|
|
62
|
+
* the same first-class absent-store state a proxy misconfig or unwritable
|
|
63
|
+
* local path resolves to, but chosen deliberately (ARTIFACT_STORAGE_TYPE=none)
|
|
64
|
+
* instead of reached by failure. The e2e file-gate stack boots with it, and
|
|
65
|
+
* an operator can use it to force gate-everything behavior. It is only valid
|
|
66
|
+
* where an absent store is: claimcheck (fail-hard by contract) refuses it at
|
|
67
|
+
* construction.
|
|
68
|
+
*/
|
|
69
|
+
export type ArtifactStorageType = "local" | "proxy" | "none";
|
|
60
70
|
|
|
61
71
|
// ── Local Backend ────────────────────────────────────────────────────
|
|
62
72
|
|
|
@@ -371,6 +381,7 @@ export function loadArtifactStorageConfig(config: Config): ArtifactStorageConfig
|
|
|
371
381
|
const type: ArtifactStorageType =
|
|
372
382
|
envType === "proxy" ? "proxy" :
|
|
373
383
|
envType === "local" ? "local" :
|
|
384
|
+
envType === "none" ? "none" :
|
|
374
385
|
config.proxyEndpoint ? "proxy" : "local";
|
|
375
386
|
|
|
376
387
|
return {
|
|
@@ -387,6 +398,14 @@ export function loadArtifactStorageConfig(config: Config): ArtifactStorageConfig
|
|
|
387
398
|
}
|
|
388
399
|
|
|
389
400
|
export function createArtifactStorage(cfg: ArtifactStorageConfig): ArtifactStorage {
|
|
401
|
+
if (cfg.type === "none") {
|
|
402
|
+
// Deliberate no-store posture. Throwing keeps both consumers honest:
|
|
403
|
+
// resolveUsableArtifactStorage catches and degrades to the deny-gate
|
|
404
|
+
// (the intent of "none"), while claimcheck's direct fail-hard call
|
|
405
|
+
// surfaces the config contradiction at boot instead of silently
|
|
406
|
+
// dropping Temporal payloads.
|
|
407
|
+
throw new Error("ARTIFACT_STORAGE_TYPE=none: artifact storage deliberately disabled");
|
|
408
|
+
}
|
|
390
409
|
if (cfg.type === "proxy") {
|
|
391
410
|
if (!cfg.proxyEndpoint) {
|
|
392
411
|
throw new Error("Proxy artifact storage requires STIGMER_PROXY_ENDPOINT");
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declared preferences (stigmer/stigmer#293, DD-002): standing free-text
|
|
3
|
+
* preferences the organization and the calling user declared once — "keep
|
|
4
|
+
* answers terse", "we deploy to us-east-1" — that every eligible execution
|
|
5
|
+
* receives without the user re-typing them.
|
|
6
|
+
*
|
|
7
|
+
* The server composes the CONTENT at execution create: the create pipeline
|
|
8
|
+
* snapshots `Organization.spec.preferences.standing_context` and (cloud, for
|
|
9
|
+
* first-party human callers only) the caller
|
|
10
|
+
* `IdentityAccount.spec.preferences.standing_context` verbatim onto the
|
|
11
|
+
* execution spec's `declared_preferences` field. This module owns the
|
|
12
|
+
* PRESENTATION — the preamble and the per-scope attribution — so the framing
|
|
13
|
+
* cannot drift between harnesses.
|
|
14
|
+
*
|
|
15
|
+
* Like conversation-catchup (its typed-field sibling) there is no metadata
|
|
16
|
+
* key to mirror-guard: the value rides a TYPED proto field, so codegen
|
|
17
|
+
* enforces the cross-repo contract. Degradation is safe by construction: an
|
|
18
|
+
* absent or blank field renders nothing, and a runner predating this module
|
|
19
|
+
* simply ignores it — the agent runs without preferences, exactly the
|
|
20
|
+
* pre-#293 behavior, never worse.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { DeclaredPreferences } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How the preferences are introduced to the model, shared by both harnesses
|
|
27
|
+
* so the behavioral contract cannot drift between them. Same posture as the
|
|
28
|
+
* session-context channel: calibration material, never authority — the task
|
|
29
|
+
* always outranks a standing preference.
|
|
30
|
+
*/
|
|
31
|
+
const DECLARED_PREFERENCES_PREAMBLE =
|
|
32
|
+
"Standing preferences declared by the organization and/or the user you " +
|
|
33
|
+
"are assisting. Treat them as background you already know: use them to " +
|
|
34
|
+
"calibrate depth, defaults, and tone. Do not repeat them back, quote " +
|
|
35
|
+
"them, or mention that you received them. They are context, not " +
|
|
36
|
+
"instructions that override your task.";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The non-blank preference scopes of an execution, in presentation order.
|
|
40
|
+
* At least one scope is set (the read function returns undefined otherwise).
|
|
41
|
+
*/
|
|
42
|
+
export interface DeclaredPreferencesContent {
|
|
43
|
+
/** The organization's standing context, trimmed; absent when blank. */
|
|
44
|
+
orgContext?: string;
|
|
45
|
+
/** The calling user's standing context, trimmed; absent when blank. */
|
|
46
|
+
userContext?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read the declared preferences from an execution spec's
|
|
51
|
+
* `declared_preferences`. Returns undefined when the field is absent or both
|
|
52
|
+
* scopes are blank — the caller renders no section. Blank-is-absent applies
|
|
53
|
+
* PER SCOPE: an org-only snapshot renders only the org subsection.
|
|
54
|
+
*/
|
|
55
|
+
export function readDeclaredPreferences(
|
|
56
|
+
preferences: DeclaredPreferences | undefined,
|
|
57
|
+
): DeclaredPreferencesContent | undefined {
|
|
58
|
+
const orgContext = preferences?.orgContext?.trim() || undefined;
|
|
59
|
+
const userContext = preferences?.userContext?.trim() || undefined;
|
|
60
|
+
if (!orgContext && !userContext) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
return { orgContext, userContext };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The framed preferences body (preamble + labeled per-scope subsections),
|
|
68
|
+
* ready for section wrapping. Attribution is explicit — the model must know
|
|
69
|
+
* WHO declared what. Organization first, user last: the user's refinement
|
|
70
|
+
* reads last and so naturally wins where the two conflict, without this
|
|
71
|
+
* module encoding any override rule.
|
|
72
|
+
*/
|
|
73
|
+
export function formatDeclaredPreferencesText(
|
|
74
|
+
content: DeclaredPreferencesContent,
|
|
75
|
+
): string {
|
|
76
|
+
const sections: string[] = [DECLARED_PREFERENCES_PREAMBLE];
|
|
77
|
+
if (content.orgContext) {
|
|
78
|
+
sections.push(`Declared by the organization:\n${content.orgContext}`);
|
|
79
|
+
}
|
|
80
|
+
if (content.userContext) {
|
|
81
|
+
sections.push(`Declared by the user:\n${content.userContext}`);
|
|
82
|
+
}
|
|
83
|
+
return sections.join("\n\n");
|
|
84
|
+
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { randomBytes, type BinaryLike } from "node:crypto";
|
|
23
|
+
import { getRunnerSecret } from "./runner-credential-store.js";
|
|
23
24
|
|
|
24
25
|
const ENV_VAR = "STIGMER_RUNNER_HITL_SECRET";
|
|
25
26
|
|
|
@@ -27,13 +28,14 @@ let cached: Buffer | undefined;
|
|
|
27
28
|
let warned = false;
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
* Return the runner's HITL master secret (
|
|
31
|
-
*
|
|
31
|
+
* Return the runner's HITL master secret (operator-configured via the
|
|
32
|
+
* {@link ENV_VAR} env var, resolved through the credential store since the
|
|
33
|
+
* #508 boot capture; else a stable per-process random fallback). Memoized.
|
|
32
34
|
*/
|
|
33
35
|
export function getRunnerHitlMasterSecret(): BinaryLike {
|
|
34
36
|
if (cached) return cached;
|
|
35
37
|
|
|
36
|
-
const fromEnv =
|
|
38
|
+
const fromEnv = getRunnerSecret(ENV_VAR);
|
|
37
39
|
if (fromEnv && fromEnv.length > 0) {
|
|
38
40
|
cached = Buffer.from(fromEnv, "utf-8");
|
|
39
41
|
return cached;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { LlmProvider } from "./llm-proxy.js";
|
|
18
|
+
import { runnerSecretsEnvView } from "./runner-credential-store.js";
|
|
18
19
|
|
|
19
20
|
// ─── Backend selection ───────────────────────────────────────────────────────
|
|
20
21
|
|
|
@@ -345,7 +346,12 @@ export function checkFoundryPrerequisites(
|
|
|
345
346
|
*/
|
|
346
347
|
export function checkDirectCredentials(
|
|
347
348
|
provider: LlmProvider,
|
|
348
|
-
|
|
349
|
+
// Credential keys live in the runner credential store after the boot
|
|
350
|
+
// capture (#508), so the default is the store view, not bare process.env.
|
|
351
|
+
// The other checks in this module keep the process.env default: they read
|
|
352
|
+
// deployment CONFIG (backend selection, regions), which is deliberately
|
|
353
|
+
// not captured.
|
|
354
|
+
env: NodeJS.ProcessEnv = runnerSecretsEnvView(),
|
|
349
355
|
): string | null {
|
|
350
356
|
if (provider === "openai") {
|
|
351
357
|
if (env.OPENAI_API_KEY?.trim()) return null;
|
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* - An empty usage list falls back to the server's default_enabled_tools.
|
|
8
8
|
* - Both empty means NO restriction — every discovered tool is available.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* duplicated resolvers — see the header of shared/mcp-resolver.ts):
|
|
10
|
+
* Resolution is shared (shared/mcp-resolver.ts threads the effective list
|
|
11
|
+
* onto every ResolvedMcpServer), but ENFORCEMENT is per-harness:
|
|
13
12
|
* - The deep-agent harness filters the discovered toolset before it reaches
|
|
14
13
|
* the model (shared/mcp-manager.ts connectMcpServers).
|
|
15
14
|
* - The Cursor harness cannot hide tools (the Cursor SDK's McpServerConfig
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
* Resolves Stigmer McpServerUsage references into an intermediate
|
|
3
3
|
* ResolvedMcpServer format.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* THE single resolver for both harnesses (oss#387 retired the Cursor
|
|
6
|
+
* harness's near-duplicate): the deep-agent harness
|
|
7
|
+
* (execute-deep-agent/setup.ts), the Cursor harness
|
|
8
|
+
* (execute-cursor/index.ts), the connect backfill
|
|
9
|
+
* (shared/connect-backfill.ts), and the discovery activity
|
|
10
|
+
* (activities/discover-mcp-server.ts, via mcpServerToResolved) all consume
|
|
11
|
+
* it. Each harness maps the result into its SDK format at the last hop:
|
|
12
|
+
* toMcpClientConfig (shared/mcp-manager.ts) for LangChain,
|
|
13
|
+
* toCursorMcpConfig (execute-cursor/cursor-mcp-config.ts) for the Cursor
|
|
14
|
+
* SDK — so a behavioral change here (like the transport guard) lands in
|
|
15
|
+
* both execution paths by construction.
|
|
12
16
|
*/
|
|
13
17
|
|
|
14
18
|
import type { McpServerUsage, ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
@@ -51,8 +55,11 @@ export interface ResolvedMcpServer {
|
|
|
51
55
|
* enabled_tools, falling back to the server's default_enabled_tools when
|
|
52
56
|
* the usage's list is empty (see shared/mcp-enabled-tools.ts). Absent when
|
|
53
57
|
* unrestricted — which keeps synthesized attachment servers (built without
|
|
54
|
-
* a usage) and the discovery path unfiltered by construction.
|
|
55
|
-
*
|
|
58
|
+
* a usage) and the discovery path unfiltered by construction. Enforcement
|
|
59
|
+
* is per-harness: the deep-agent path filters at connect time
|
|
60
|
+
* (connectMcpServers, shared/mcp-manager.ts); the Cursor SDK config cannot
|
|
61
|
+
* hide tools, so that harness denies non-enabled calls in the HITL hook
|
|
62
|
+
* (see hook-script.ts).
|
|
56
63
|
*/
|
|
57
64
|
enabledTools?: string[];
|
|
58
65
|
/**
|
|
@@ -61,11 +68,10 @@ export interface ResolvedMcpServer {
|
|
|
61
68
|
* override to its own server — a flat cross-server list is how an
|
|
62
69
|
* override once leaked onto (or silently un-gated) a same-named tool on
|
|
63
70
|
* another server. Deliberately REQUIRED, not optional: empty means "no
|
|
64
|
-
* overrides", and every construction site — including the
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* (shared/approval-policy.ts).
|
|
71
|
+
* overrides", and every construction site — including the synthesized
|
|
72
|
+
* attachments, which have no usage and therefore no layer 3 — must say so
|
|
73
|
+
* explicitly, so a forgotten site cannot compile. Consumed by
|
|
74
|
+
* mergeApprovalPolicies (shared/approval-policy.ts).
|
|
69
75
|
*/
|
|
70
76
|
toolApprovalOverrides: ToolApprovalOverride[];
|
|
71
77
|
}
|
|
@@ -202,12 +208,6 @@ export function mcpServerToResolved(
|
|
|
202
208
|
}
|
|
203
209
|
}
|
|
204
210
|
|
|
205
|
-
export function extractMcpServerSlugs(usages: McpServerUsage[]): string[] {
|
|
206
|
-
return usages
|
|
207
|
-
.map((u) => u.mcpServerRef?.slug)
|
|
208
|
-
.filter((s): s is string => !!s);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
211
|
/**
|
|
212
212
|
* Merge MCP server usages from agent (base) and session (overlay).
|
|
213
213
|
*
|