@sema-agent/core 2.12.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-transcript-tool.d.ts +1 -0
- package/dist/agents/agent-transcript-tool.js +1 -1
- package/dist/agents/cascade.d.ts +4 -0
- package/dist/agents/cascade.js +54 -9
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +101 -2
- package/dist/agents/retain-ledger.d.ts +18 -2
- package/dist/agents/retain-ledger.js +37 -8
- package/dist/agents/roster-store.d.ts +4 -0
- package/dist/agents/roster-store.js +22 -1
- package/dist/agents/teacher.js +4 -2
- package/dist/agents/team.js +7 -4
- package/dist/agents/verify.d.ts +15 -2
- package/dist/agents/verify.js +36 -1
- package/dist/bin/sema-tb.js +2 -2
- package/dist/brain/circuit-breaker.d.ts +1 -0
- package/dist/brain/circuit-breaker.js +26 -9
- package/dist/brain/degrading.js +13 -1
- package/dist/brain/failover.js +2 -0
- package/dist/brain/repetition.js +10 -1
- package/dist/brain/routing.js +5 -1
- package/dist/brain/status-sink.js +5 -1
- package/dist/brain/stream-engine.js +4 -1
- package/dist/brain/tool-call-repair.js +32 -14
- package/dist/core/auto-compaction.js +17 -7
- package/dist/core/auto-promote.js +2 -1
- package/dist/core/checkpoint-store.d.ts +1 -0
- package/dist/core/checkpoint-store.js +28 -10
- package/dist/core/consolidate-scope.js +4 -3
- package/dist/core/context-edit.js +3 -0
- package/dist/core/context-guard.js +3 -0
- package/dist/core/git-worktree-env.d.ts +6 -1
- package/dist/core/git-worktree-env.js +17 -1
- package/dist/core/hooks.d.ts +1 -0
- package/dist/core/hooks.js +40 -2
- package/dist/core/lsp-diagnostics.js +13 -7
- package/dist/core/lsp-protocol.d.ts +1 -1
- package/dist/core/lsp-protocol.js +20 -8
- package/dist/core/lsp-session.d.ts +12 -2
- package/dist/core/lsp-session.js +114 -49
- package/dist/core/lsp.d.ts +13 -1
- package/dist/core/lsp.js +64 -9
- package/dist/core/mcp.d.ts +1 -0
- package/dist/core/mcp.js +4 -3
- package/dist/core/memory-engine/data-plane.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +2 -1
- package/dist/core/memory-engine/engine.js +82 -7
- package/dist/core/memory-engine/file-backend.d.ts +1 -0
- package/dist/core/memory-engine/file-backend.js +13 -3
- package/dist/core/memory-engine/layout.js +12 -1
- package/dist/core/memory.js +3 -0
- package/dist/core/permission-rules.d.ts +2 -1
- package/dist/core/permission-rules.js +24 -3
- package/dist/core/runner/active-skill-scope.js +34 -6
- package/dist/core/runner/assemble-result.d.ts +1 -1
- package/dist/core/runner/assemble-result.js +0 -1
- package/dist/core/runner/prepare-task.js +23 -3
- package/dist/core/runner/runtask.js +48 -30
- package/dist/core/runner/tool-disclosure.d.ts +1 -0
- package/dist/core/runner/tool-disclosure.js +26 -7
- package/dist/core/runner/usage-accounting.d.ts +6 -0
- package/dist/core/runner/usage-accounting.js +9 -8
- package/dist/core/secret-env.js +5 -1
- package/dist/core/sensitive-path-policy.js +9 -3
- package/dist/core/session-store.js +15 -4
- package/dist/core/skill-tool-specifier.d.ts +8 -0
- package/dist/core/skill-tool-specifier.js +58 -0
- package/dist/core/skills-directory.d.ts +1 -1
- package/dist/core/skills-directory.js +16 -4
- package/dist/core/store-contracts/session-repo-contract.js +40 -0
- package/dist/core/task-registry-monitor.js +7 -1
- package/dist/core/tool-policy.js +65 -17
- package/dist/core/trace.d.ts +1 -0
- package/dist/core/types.d.ts +3 -5
- package/dist/core/with-retry.js +0 -1
- package/dist/engine/compaction/compaction.d.ts +1 -0
- package/dist/engine/compaction/compaction.js +14 -5
- package/dist/engine/lsp/frame-decoder.js +6 -3
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +103 -26
- package/dist/engine/lsp/stdio-lsp-transport.js +16 -7
- package/dist/index.d.ts +1 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/orchestration/workflow-governance.js +43 -9
- package/dist/orchestration/workflow-sandbox-conformance.js +85 -51
- package/dist/orchestration/workflow-script-store.js +34 -4
- package/dist/orchestration/workflow.js +10 -6
- package/dist/prompt-assembly/artifact-store.d.ts +2 -0
- package/dist/prompt-assembly/artifact-store.js +39 -24
- package/dist/prompt-assembly/assemble.js +38 -7
- package/dist/prompt-assembly/epoch.js +19 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/explain.js +1 -1
- package/dist/prompt-assembly/tool-catalog.js +16 -1
- package/dist/prompt-assembly/turn-snapshot.js +4 -1
- package/dist/stores/file/fs-atomic.d.ts +3 -0
- package/dist/stores/file/fs-atomic.js +53 -8
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +7 -0
- package/dist/stores/file/session-store.d.ts +2 -0
- package/dist/stores/file/session-store.js +28 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
- package/dist/tools/fs/bash-readonly-classifier.js +57 -8
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +93 -5
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.js +55 -9
- package/dist/tools/scheduler-tools.js +9 -3
- package/dist/tools/web.d.ts +8 -2
- package/dist/tools/web.js +46 -17
- package/dist/tools/worktree.d.ts +2 -0
- package/dist/tools/worktree.js +233 -143
- package/package.json +5 -2
package/dist/internal/llm.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { createAssistantMessageEventStream, stripEngineMetadata } from "../engine/llm/index.js";
|
|
2
|
-
export type { AnthropicMessagesCompat, AssistantMessage, AssistantMessageDiagnostic, AssistantMessageEvent, Context, DocumentContent, ImageContent, Message, Model, ResilienceOptions, SimpleStreamOptions, StallTimeouts, StopReason, StreamFn, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage, Usage, UserMessage, } from "../engine/llm/index.js";
|
|
2
|
+
export type { AnthropicMessagesCompat, AssistantMessage, AssistantMessageDiagnostic, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, Model, ResilienceOptions, SimpleStreamOptions, StallTimeouts, StopReason, StreamFn, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage, Usage, UserMessage, } from "../engine/llm/index.js";
|
|
@@ -37,6 +37,32 @@ function minDefined(...vals) {
|
|
|
37
37
|
const xs = vals.filter((v) => typeof v === "number" && Number.isFinite(v));
|
|
38
38
|
return xs.length ? Math.min(...xs) : undefined;
|
|
39
39
|
}
|
|
40
|
+
function minPositiveDefined(...vals) {
|
|
41
|
+
const xs = vals.filter((v) => typeof v === "number" && Number.isFinite(v) && v > 0);
|
|
42
|
+
return xs.length ? Math.min(...xs) : undefined;
|
|
43
|
+
}
|
|
44
|
+
const NON_POSITIVE_LIMIT_MSG = (field, what) => `agent(spec): \`limits.${field}\` must be a POSITIVE number — a value of 0 or less DISABLES the ${what} ` +
|
|
45
|
+
`downstream (it is a sentinel, not a tighter ceiling), and a workflow script may only tighten a limit, never remove it`;
|
|
46
|
+
function pickImageInput(el, i) {
|
|
47
|
+
const at = `agent(spec): \`images[${i}]\``;
|
|
48
|
+
if (typeof el !== "object" || el === null || Array.isArray(el)) {
|
|
49
|
+
throw new WorkflowScriptError(`${at} must be an object — either { url } or { data, mimeType }`);
|
|
50
|
+
}
|
|
51
|
+
const o = el;
|
|
52
|
+
if ("url" in o) {
|
|
53
|
+
if (typeof o.url !== "string" || o.url.length === 0) {
|
|
54
|
+
throw new WorkflowScriptError(`${at}.url must be a non-empty string`);
|
|
55
|
+
}
|
|
56
|
+
return { url: o.url };
|
|
57
|
+
}
|
|
58
|
+
if (typeof o.data !== "string") {
|
|
59
|
+
throw new WorkflowScriptError(`${at}.data must be a base64 string (or supply { url } instead)`);
|
|
60
|
+
}
|
|
61
|
+
if (typeof o.mimeType !== "string" || o.mimeType.length === 0) {
|
|
62
|
+
throw new WorkflowScriptError(`${at}.mimeType must be a non-empty string, e.g. "image/png"`);
|
|
63
|
+
}
|
|
64
|
+
return { data: o.data, mimeType: o.mimeType };
|
|
65
|
+
}
|
|
40
66
|
function pickWhitelist(scriptSpec) {
|
|
41
67
|
if (typeof scriptSpec !== "object" || scriptSpec === null || Array.isArray(scriptSpec)) {
|
|
42
68
|
throw new WorkflowScriptError("agent(spec): spec must be an object with at least an `objective` string");
|
|
@@ -60,7 +86,7 @@ function pickWhitelist(scriptSpec) {
|
|
|
60
86
|
if (s.images !== undefined) {
|
|
61
87
|
if (!Array.isArray(s.images))
|
|
62
88
|
throw new WorkflowScriptError("agent(spec): `images` must be an array");
|
|
63
|
-
safe.images = s.images;
|
|
89
|
+
safe.images = s.images.map(pickImageInput);
|
|
64
90
|
}
|
|
65
91
|
if (s.limits !== undefined) {
|
|
66
92
|
if (typeof s.limits !== "object" || s.limits === null || Array.isArray(s.limits)) {
|
|
@@ -71,11 +97,15 @@ function pickWhitelist(scriptSpec) {
|
|
|
71
97
|
if (l.maxTurns !== undefined) {
|
|
72
98
|
if (typeof l.maxTurns !== "number" || !Number.isFinite(l.maxTurns))
|
|
73
99
|
throw new WorkflowScriptError("agent(spec): `limits.maxTurns` must be a number");
|
|
100
|
+
if (l.maxTurns <= 0)
|
|
101
|
+
throw new WorkflowScriptError(NON_POSITIVE_LIMIT_MSG("maxTurns", "turn cap"));
|
|
74
102
|
limits.maxTurns = l.maxTurns;
|
|
75
103
|
}
|
|
76
104
|
if (l.timeoutSec !== undefined) {
|
|
77
105
|
if (typeof l.timeoutSec !== "number" || !Number.isFinite(l.timeoutSec))
|
|
78
106
|
throw new WorkflowScriptError("agent(spec): `limits.timeoutSec` must be a number");
|
|
107
|
+
if (l.timeoutSec <= 0)
|
|
108
|
+
throw new WorkflowScriptError(NON_POSITIVE_LIMIT_MSG("timeoutSec", "wall-clock window"));
|
|
79
109
|
limits.timeoutSec = l.timeoutSec;
|
|
80
110
|
}
|
|
81
111
|
safe.limits = limits;
|
|
@@ -116,20 +146,24 @@ function clampResourceLimits(safe, base, caps) {
|
|
|
116
146
|
}
|
|
117
147
|
const requestedTimeoutSec = safe.limits?.timeoutSec;
|
|
118
148
|
const requestedMaxTurns = safe.limits?.maxTurns;
|
|
119
|
-
const timeoutSec =
|
|
120
|
-
const maxTurns =
|
|
149
|
+
const timeoutSec = minPositiveDefined(safe.limits?.timeoutSec, base.limits?.timeoutSec, caps?.perAgentTimeoutSec);
|
|
150
|
+
const maxTurns = minPositiveDefined(safe.limits?.maxTurns, base.limits?.maxTurns, caps?.childMaxTurns);
|
|
121
151
|
if (timeoutSec !== undefined && timeoutSec !== requestedTimeoutSec) {
|
|
122
152
|
notes.push({ field: "timeoutSec", requested: requestedTimeoutSec, applied: timeoutSec });
|
|
123
153
|
}
|
|
124
154
|
if (maxTurns !== undefined && maxTurns !== requestedMaxTurns) {
|
|
125
155
|
notes.push({ field: "maxTurns", requested: requestedMaxTurns, applied: maxTurns });
|
|
126
156
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
const baseOtherLimits = { ...base.limits };
|
|
158
|
+
delete baseOtherLimits.maxTurns;
|
|
159
|
+
delete baseOtherLimits.timeoutSec;
|
|
160
|
+
const rebuilt = {
|
|
161
|
+
...baseOtherLimits,
|
|
162
|
+
...(maxTurns !== undefined ? { maxTurns } : {}),
|
|
163
|
+
...(timeoutSec !== undefined ? { timeoutSec } : {}),
|
|
164
|
+
};
|
|
165
|
+
if (base.limits !== undefined || Object.keys(rebuilt).length > 0)
|
|
166
|
+
safe.limits = rebuilt;
|
|
133
167
|
return notes;
|
|
134
168
|
}
|
|
135
169
|
export function buildGovernedChildSpec(scriptSpec, baseline, models, caps, onResourceClamp) {
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
const META = "export const meta = { name: 'conformance', description: 'sandbox escape probes' }\n";
|
|
3
|
+
const WITNESS_FN = "__probeWitness";
|
|
4
|
+
const WITNESS_PRELUDE = `function ${WITNESS_FN}(v) {\n` +
|
|
5
|
+
` if (v === null || v === undefined) return null\n` +
|
|
6
|
+
` var t = typeof v\n` +
|
|
7
|
+
` if (t === 'function') return { hostCapabilityReached: true, via: 'function' }\n` +
|
|
8
|
+
` if (t !== 'object') return null\n` +
|
|
9
|
+
` var names = ['pid', 'env', 'version', 'mainModule', 'binding']\n` +
|
|
10
|
+
` var hit = []\n` +
|
|
11
|
+
` for (var i = 0; i < names.length; i++) { try { if (names[i] in v) hit.push(names[i]) } catch (err) { } }\n` +
|
|
12
|
+
` return hit.length > 0 ? { hostCapabilityReached: true, via: 'object', keys: hit } : null\n` +
|
|
13
|
+
`}\n`;
|
|
3
14
|
const ESCAPE_BODIES = [
|
|
4
|
-
`return agent.constructor.constructor("return typeof process !== 'undefined' ? process : null")()`,
|
|
5
|
-
`return log.constructor.constructor("return typeof process !== 'undefined' ? process : null")()`,
|
|
6
|
-
`return budget.constructor.constructor("return typeof process !== 'undefined' ? process : null")()`,
|
|
7
|
-
`return ({}).constructor.constructor("return typeof process !== 'undefined' ? process : null")()`,
|
|
8
|
-
`return args.constructor.constructor("return typeof require !== 'undefined' ? require : null")()`,
|
|
9
|
-
`const r = await agent({ objective: 'x' }); return (r ?? {}).constructor.constructor("return typeof require !== 'undefined' ? require : null")()`,
|
|
10
|
-
`return (typeof process !== 'undefined' ? process : null)`,
|
|
11
|
-
`return (typeof require !== 'undefined' ? require : null)`,
|
|
12
|
-
`return (typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null`,
|
|
15
|
+
`return ${WITNESS_FN}(agent.constructor.constructor("return typeof process !== 'undefined' ? process : null")())`,
|
|
16
|
+
`return ${WITNESS_FN}(log.constructor.constructor("return typeof process !== 'undefined' ? process : null")())`,
|
|
17
|
+
`return ${WITNESS_FN}(budget.constructor.constructor("return typeof process !== 'undefined' ? process : null")())`,
|
|
18
|
+
`return ${WITNESS_FN}(({}).constructor.constructor("return typeof process !== 'undefined' ? process : null")())`,
|
|
19
|
+
`return ${WITNESS_FN}(args.constructor.constructor("return typeof require !== 'undefined' ? require : null")())`,
|
|
20
|
+
`const r = await agent({ objective: 'x' }); return ${WITNESS_FN}((r ?? {}).constructor.constructor("return typeof require !== 'undefined' ? require : null")())`,
|
|
21
|
+
`return ${WITNESS_FN}(typeof process !== 'undefined' ? process : null)`,
|
|
22
|
+
`return ${WITNESS_FN}(typeof require !== 'undefined' ? require : null)`,
|
|
23
|
+
`return ${WITNESS_FN}((typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null)`,
|
|
13
24
|
];
|
|
14
25
|
const ERROR_PATH_BODIES = [
|
|
15
|
-
`try { await agent({ objective: 'x' }); return null } catch (e) { return e.constructor.constructor("return typeof process !== 'undefined' ? process : null")() }`,
|
|
16
|
-
`try { await parallel([]); return null } catch (e) { return e.constructor.constructor("return typeof require !== 'undefined' ? require : null")() }`,
|
|
17
|
-
`try { await agent({}); return null } catch (e) { return (e && e.constructor && e.constructor.constructor) ? e.constructor.constructor("return (typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null")() : null }`,
|
|
26
|
+
`try { await agent({ objective: 'x' }); return null } catch (e) { return ${WITNESS_FN}(e.constructor.constructor("return typeof process !== 'undefined' ? process : null")()) }`,
|
|
27
|
+
`try { await parallel([]); return null } catch (e) { return ${WITNESS_FN}(e.constructor.constructor("return typeof require !== 'undefined' ? require : null")()) }`,
|
|
28
|
+
`try { await agent({}); return null } catch (e) { return ${WITNESS_FN}((e && e.constructor && e.constructor.constructor) ? e.constructor.constructor("return (typeof globalThis !== 'undefined' && globalThis.process) ? globalThis.process : null")() : null) }`,
|
|
18
29
|
];
|
|
19
30
|
const POLLUTION_BODIES = [
|
|
20
31
|
`return JSON.parse('{"__proto__":{"polluted":1}}')`,
|
|
@@ -24,9 +35,9 @@ const POLLUTION_BODIES = [
|
|
|
24
35
|
`return [JSON.parse('{"__proto__":{"polluted":1}}')]`,
|
|
25
36
|
];
|
|
26
37
|
const SIGNAL_BODIES = [
|
|
27
|
-
`return (typeof signal !== 'undefined' && signal) ? signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null`,
|
|
28
|
-
`return (typeof input !== 'undefined' && input && input.signal) ? input.signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null`,
|
|
29
|
-
`try { return (typeof signal !== 'undefined' && signal) ? (signal.addEventListener ? signal.addEventListener.constructor("return typeof process !== 'undefined' ? process : null")() : null) : null } catch (e) { return null }`,
|
|
38
|
+
`return ${WITNESS_FN}((typeof signal !== 'undefined' && signal) ? signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null)`,
|
|
39
|
+
`return ${WITNESS_FN}((typeof input !== 'undefined' && input && input.signal) ? input.signal.constructor.constructor("return typeof process !== 'undefined' ? process : null")() : null)`,
|
|
40
|
+
`try { return ${WITNESS_FN}((typeof signal !== 'undefined' && signal) ? (signal.addEventListener ? signal.addEventListener.constructor("return typeof process !== 'undefined' ? process : null")() : null) : null) } catch (e) { return null }`,
|
|
30
41
|
];
|
|
31
42
|
function benignPrimitives() {
|
|
32
43
|
return {
|
|
@@ -84,6 +95,49 @@ function looksLikeHostCapability(v) {
|
|
|
84
95
|
}
|
|
85
96
|
return false;
|
|
86
97
|
}
|
|
98
|
+
function witnessReportsHostCapability(v) {
|
|
99
|
+
return typeof v === "object" && v !== null && v.hostCapabilityReached === true;
|
|
100
|
+
}
|
|
101
|
+
function reachedHostCapability(v) {
|
|
102
|
+
return looksLikeHostCapability(v) || witnessReportsHostCapability(v);
|
|
103
|
+
}
|
|
104
|
+
const CANARY_MARKER = "workflow-conformance-canary";
|
|
105
|
+
let canarySeq = 0;
|
|
106
|
+
const CANARY_BODY = `const c = args.canary\n` +
|
|
107
|
+
`return { marker: '${CANARY_MARKER}', nonce: c.nonce, sum: c.x + c.y, joined: ['${CANARY_MARKER}', c.nonce].join(':') }`;
|
|
108
|
+
async function runLivenessCanary(runner) {
|
|
109
|
+
const nonce = `n${++canarySeq}`;
|
|
110
|
+
const scriptArgs = { canary: { nonce, x: 17, y: 25 } };
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
const out = await runner.run({ scriptSource: META + CANARY_BODY, primitives: benignPrimitives(), scriptArgs });
|
|
114
|
+
result = out.result;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
return { ok: false, detail: `run() REJECTED with: ${err instanceof Error ? err.message : String(err)}` };
|
|
118
|
+
}
|
|
119
|
+
const expected = { marker: CANARY_MARKER, nonce, sum: 42, joined: `${CANARY_MARKER}:${nonce}` };
|
|
120
|
+
try {
|
|
121
|
+
assert.deepEqual(result, expected);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return { ok: false, detail: `run() returned ${JSON.stringify(result)} — expected ${JSON.stringify(expected)}` };
|
|
125
|
+
}
|
|
126
|
+
return { ok: true, detail: "" };
|
|
127
|
+
}
|
|
128
|
+
async function runProbeBody(runner, scriptSource, primitives, family, signal) {
|
|
129
|
+
try {
|
|
130
|
+
const out = await runner.run({ scriptSource, primitives, scriptArgs: { probe: true }, signal });
|
|
131
|
+
return { executed: true, result: out.result };
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
const live = await runLivenessCanary(runner);
|
|
135
|
+
assert.ok(live.ok, `${family}: the probe threw AND the runner then failed the liveness canary — this runner is BROKEN, ` +
|
|
136
|
+
`not blocking, so nothing about its membrane was proven. probe: ${scriptSource.slice(META.length)} | ` +
|
|
137
|
+
`probe threw: ${err instanceof Error ? err.message : String(err)} | canary: ${live.detail}`);
|
|
138
|
+
return { executed: false };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
87
141
|
export async function assertWorkflowPrimitivesWiring(runner) {
|
|
88
142
|
assert.equal(runner.safeForUntrustedScripts, true, "wiring conformance targets a HARD runner (safeForUntrustedScripts must be true)");
|
|
89
143
|
const hostSeen = { phaseCalls: [], scopedBodyRan: false, logs: [] };
|
|
@@ -144,54 +198,34 @@ export async function assertWorkflowPrimitivesWiring(runner) {
|
|
|
144
198
|
}
|
|
145
199
|
export async function assertWorkflowSandboxConformance(runner) {
|
|
146
200
|
assert.equal(runner.safeForUntrustedScripts, true, "conformance is only meaningful for a HARD runner (safeForUntrustedScripts must be true)");
|
|
201
|
+
const live = await runLivenessCanary(runner);
|
|
202
|
+
assert.ok(live.ok, `POSITIVE CONTROL FAILED — this runner did not execute the canary script and return its value, so the ` +
|
|
203
|
+
`escape battery below cannot certify anything about it (a runner that runs nothing produces the same ` +
|
|
204
|
+
`"nothing leaked" results as a perfect sandbox). ${live.detail}`);
|
|
147
205
|
for (const escBody of ESCAPE_BODIES) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const out = await runner.run({ scriptSource, primitives: benignPrimitives(), scriptArgs: { probe: true } });
|
|
152
|
-
result = out.result;
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
206
|
+
const out = await runProbeBody(runner, META + WITNESS_PRELUDE + escBody, benignPrimitives(), "sandbox escape");
|
|
207
|
+
if (!out.executed)
|
|
155
208
|
continue;
|
|
156
|
-
}
|
|
157
|
-
assert.equal(looksLikeHostCapability(result), false, `sandbox escape REACHED a host capability via: ${escBody}`);
|
|
209
|
+
assert.equal(reachedHostCapability(out.result), false, `sandbox escape REACHED a host capability via: ${escBody}`);
|
|
158
210
|
}
|
|
159
211
|
for (const errBody of ERROR_PATH_BODIES) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const out = await runner.run({ scriptSource, primitives: throwingPrimitives(), scriptArgs: { probe: true } });
|
|
164
|
-
result = out.result;
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
212
|
+
const out = await runProbeBody(runner, META + WITNESS_PRELUDE + errBody, throwingPrimitives(), "error-path");
|
|
213
|
+
if (!out.executed)
|
|
167
214
|
continue;
|
|
168
|
-
}
|
|
169
|
-
assert.equal(looksLikeHostCapability(result), false, `error-path: a host-side primitive error leaked a host capability via: ${errBody}`);
|
|
215
|
+
assert.equal(reachedHostCapability(out.result), false, `error-path: a host-side primitive error leaked a host capability via: ${errBody}`);
|
|
170
216
|
}
|
|
171
217
|
for (const polBody of POLLUTION_BODIES) {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const out = await runner.run({ scriptSource, primitives: benignPrimitives(), scriptArgs: { probe: true } });
|
|
176
|
-
result = out.result;
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
218
|
+
const out = await runProbeBody(runner, META + polBody, benignPrimitives(), "prototype pollution");
|
|
219
|
+
if (!out.executed)
|
|
179
220
|
continue;
|
|
180
|
-
}
|
|
181
|
-
assert.equal(graphHasInjectedProtoKey(result), false, `prototype pollution: the marshalled result carries an injected __proto__/constructor/prototype own key via: ${polBody}`);
|
|
221
|
+
assert.equal(graphHasInjectedProtoKey(out.result), false, `prototype pollution: the marshalled result carries an injected __proto__/constructor/prototype own key via: ${polBody}`);
|
|
182
222
|
}
|
|
183
223
|
const hostSignal = new AbortController().signal;
|
|
184
224
|
for (const sigBody of SIGNAL_BODIES) {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
const out = await runner.run({ scriptSource, primitives: benignPrimitives(), scriptArgs: { probe: true }, signal: hostSignal });
|
|
189
|
-
result = out.result;
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
225
|
+
const out = await runProbeBody(runner, META + WITNESS_PRELUDE + sigBody, benignPrimitives(), "signal-path", hostSignal);
|
|
226
|
+
if (!out.executed)
|
|
192
227
|
continue;
|
|
193
|
-
}
|
|
194
|
-
assert.equal(looksLikeHostCapability(result), false, `signal-path: the host AbortSignal leaked a host capability via: ${sigBody}`);
|
|
228
|
+
assert.equal(reachedHostCapability(out.result), false, `signal-path: the host AbortSignal leaked a host capability via: ${sigBody}`);
|
|
195
229
|
}
|
|
196
230
|
}
|
|
197
231
|
const DETERMINISM_FORBIDDEN_BODIES = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
-
import { join, resolve, sep } from "node:path";
|
|
1
|
+
import { mkdirSync, readFileSync, realpathSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
3
3
|
import { sanitizeScope } from "../stores/file/fs-atomic.js";
|
|
4
4
|
function isPlainObject(v) {
|
|
5
5
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -30,6 +30,26 @@ export function normalizeStringArg(callArgs, defaultArgs, key = "topic") {
|
|
|
30
30
|
function safeFileStem(id) {
|
|
31
31
|
return /^[A-Za-z0-9._-]{1,120}$/.test(id) && !id.startsWith(".") ? id : undefined;
|
|
32
32
|
}
|
|
33
|
+
function containedBy(root, p) {
|
|
34
|
+
return p === root || p.startsWith(root + sep);
|
|
35
|
+
}
|
|
36
|
+
function canonicalizeDeepestExisting(p) {
|
|
37
|
+
let cur = p;
|
|
38
|
+
const missingTail = [];
|
|
39
|
+
for (;;) {
|
|
40
|
+
try {
|
|
41
|
+
const real = realpathSync(cur);
|
|
42
|
+
return missingTail.length === 0 ? real : join(real, ...[...missingTail].reverse());
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
const parent = dirname(cur);
|
|
46
|
+
if (parent === cur)
|
|
47
|
+
return undefined;
|
|
48
|
+
missingTail.push(basename(cur));
|
|
49
|
+
cur = parent;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
33
53
|
export function createFileWorkflowScriptStore(dir) {
|
|
34
54
|
const root = resolve(dir);
|
|
35
55
|
const scopeDir = (scope) => join(root, sanitizeScope(scope));
|
|
@@ -48,10 +68,20 @@ export function createFileWorkflowScriptStore(dir) {
|
|
|
48
68
|
load(scriptPath, scope) {
|
|
49
69
|
const base = scopeDir(scope);
|
|
50
70
|
const full = resolve(scriptPath);
|
|
51
|
-
if (
|
|
71
|
+
if (!containedBy(base, full)) {
|
|
52
72
|
throw new Error(`workflow script store: scriptPath is outside this scope's script partition`);
|
|
53
73
|
}
|
|
54
|
-
|
|
74
|
+
const physRoot = canonicalizeDeepestExisting(root);
|
|
75
|
+
const physBase = canonicalizeDeepestExisting(base);
|
|
76
|
+
const physFull = canonicalizeDeepestExisting(full);
|
|
77
|
+
const refusePhysical = () => {
|
|
78
|
+
throw new Error(`workflow script store: scriptPath is outside this scope's script partition after symlink resolution`);
|
|
79
|
+
};
|
|
80
|
+
if (physRoot !== undefined && physBase !== undefined && !containedBy(physRoot, physBase))
|
|
81
|
+
refusePhysical();
|
|
82
|
+
if (physBase !== undefined && physFull !== undefined && !containedBy(physBase, physFull))
|
|
83
|
+
refusePhysical();
|
|
84
|
+
return readFileSync(physFull ?? full, "utf-8");
|
|
55
85
|
},
|
|
56
86
|
resolveName(name) {
|
|
57
87
|
if (/^wf_/i.test(name))
|
|
@@ -480,13 +480,15 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
480
480
|
if (row !== undefined)
|
|
481
481
|
row.sessionId = sessionId;
|
|
482
482
|
};
|
|
483
|
-
const bceTick = (callKey, e) => {
|
|
483
|
+
const bceTick = (callKey, e, fromSessionId) => {
|
|
484
484
|
if (!bceSink)
|
|
485
485
|
return;
|
|
486
486
|
const id = waIdOf(callKey);
|
|
487
487
|
const row = bceLive.get(id);
|
|
488
488
|
if (row === undefined)
|
|
489
489
|
return;
|
|
490
|
+
if (fromSessionId !== undefined && row.sessionId !== undefined && fromSessionId !== row.sessionId)
|
|
491
|
+
return;
|
|
490
492
|
bceEmit({
|
|
491
493
|
kind: "tick",
|
|
492
494
|
taskId: id,
|
|
@@ -507,8 +509,10 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
507
509
|
if (!bceSink)
|
|
508
510
|
return;
|
|
509
511
|
const id = waIdOf(callKey);
|
|
512
|
+
const row = bceLive.get(id);
|
|
510
513
|
if (!bceLive.delete(id))
|
|
511
514
|
return;
|
|
515
|
+
const coord = sessionId ?? row?.sessionId;
|
|
512
516
|
bceEmit({
|
|
513
517
|
kind: "terminal",
|
|
514
518
|
taskId: id,
|
|
@@ -516,7 +520,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
516
520
|
owner: runId,
|
|
517
521
|
workflowRunId: runId,
|
|
518
522
|
...(scope !== undefined ? { scope } : {}),
|
|
519
|
-
...(
|
|
523
|
+
...(coord !== undefined ? { sessionId: coord, transcriptId: coord } : {}),
|
|
520
524
|
status,
|
|
521
525
|
summary: boundedRedactedSummary(summary, 300),
|
|
522
526
|
...(stats !== undefined ? { usage: { tokens: stats.tokens, turns: stats.turns, ...(stats.costMicroUsd !== undefined ? { costMicroUsd: stats.costMicroUsd } : {}) } } : {}),
|
|
@@ -873,7 +877,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
873
877
|
? {
|
|
874
878
|
onForwardEvent: (e) => {
|
|
875
879
|
if (e.type === "task_progress")
|
|
876
|
-
bceTick(callKey, e);
|
|
880
|
+
bceTick(callKey, e, attemptSessionId);
|
|
877
881
|
try {
|
|
878
882
|
baseInternals.onForwardEvent?.(e);
|
|
879
883
|
}
|
|
@@ -927,7 +931,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
927
931
|
const onTurnEndUsage = (usage) => {
|
|
928
932
|
if (finalized)
|
|
929
933
|
return;
|
|
930
|
-
const turnTokens = usage ? usage.
|
|
934
|
+
const turnTokens = usage ? usage.totalInputTokens + usage.outputTokens : 0;
|
|
931
935
|
const turnCost = usage?.costMicroUsd ?? 0;
|
|
932
936
|
beatTokens += turnTokens;
|
|
933
937
|
beatTurns += 1;
|
|
@@ -1181,7 +1185,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1181
1185
|
? {
|
|
1182
1186
|
onForwardEvent: (e) => {
|
|
1183
1187
|
if (e.type === "task_progress")
|
|
1184
|
-
bceTick(callKey, e);
|
|
1188
|
+
bceTick(callKey, e, childSessionId);
|
|
1185
1189
|
try {
|
|
1186
1190
|
enrichedForwardS?.(e);
|
|
1187
1191
|
}
|
|
@@ -1229,7 +1233,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1229
1233
|
const onTurnEndUsage = (usage) => {
|
|
1230
1234
|
if (finalized)
|
|
1231
1235
|
return;
|
|
1232
|
-
const turnTokens = usage ? usage.
|
|
1236
|
+
const turnTokens = usage ? usage.totalInputTokens + usage.outputTokens : 0;
|
|
1233
1237
|
const turnCost = usage?.costMicroUsd ?? 0;
|
|
1234
1238
|
beatTokens += turnTokens;
|
|
1235
1239
|
beatTurns += 1;
|
|
@@ -58,6 +58,8 @@ export declare class CenterPromptSource implements PromptArtifactSource {
|
|
|
58
58
|
private readonly engineVersion;
|
|
59
59
|
private candidate;
|
|
60
60
|
private state;
|
|
61
|
+
private tail;
|
|
62
|
+
private exclusive;
|
|
61
63
|
constructor(opts: {
|
|
62
64
|
store: PromptArtifactStore;
|
|
63
65
|
stateStore?: PromptSourceStateStore;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { mkdirSync, promises as fs } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { verifyPromptArtifact, } from "./artifact.js";
|
|
4
|
+
function normalizedEnvelope(parsed, artifact) {
|
|
5
|
+
return { ...parsed, artifact };
|
|
6
|
+
}
|
|
4
7
|
export class MemoryPromptArtifactStore {
|
|
5
8
|
byDigest = new Map();
|
|
6
9
|
async get(artifactDigest, opts) {
|
|
@@ -17,7 +20,7 @@ export class MemoryPromptArtifactStore {
|
|
|
17
20
|
const v = verifyPromptArtifact(parsed, opts);
|
|
18
21
|
if (!v.ok || v.artifact.artifactDigest !== artifactDigest)
|
|
19
22
|
return undefined;
|
|
20
|
-
return parsed;
|
|
23
|
+
return normalizedEnvelope(parsed, v.artifact);
|
|
21
24
|
}
|
|
22
25
|
async put(envelope) {
|
|
23
26
|
this.byDigest.set(envelope.artifact.artifactDigest, JSON.stringify(envelope));
|
|
@@ -54,7 +57,7 @@ export class FilePromptArtifactStore {
|
|
|
54
57
|
const v = verifyPromptArtifact(parsed, opts);
|
|
55
58
|
if (!v.ok || v.artifact.artifactDigest !== artifactDigest)
|
|
56
59
|
return undefined;
|
|
57
|
-
return parsed;
|
|
60
|
+
return normalizedEnvelope(parsed, v.artifact);
|
|
58
61
|
}
|
|
59
62
|
async put(envelope) {
|
|
60
63
|
const file = this.fileFor(envelope.artifact.artifactDigest);
|
|
@@ -113,6 +116,12 @@ export class CenterPromptSource {
|
|
|
113
116
|
engineVersion;
|
|
114
117
|
candidate;
|
|
115
118
|
state = "unavailable";
|
|
119
|
+
tail = Promise.resolve();
|
|
120
|
+
exclusive(fn) {
|
|
121
|
+
const run = this.tail.then(fn, fn);
|
|
122
|
+
this.tail = run.then(() => undefined, () => undefined);
|
|
123
|
+
return run;
|
|
124
|
+
}
|
|
116
125
|
constructor(opts) {
|
|
117
126
|
this.store = opts.store;
|
|
118
127
|
this.stateStore = opts.stateStore ?? new MemoryPromptSourceStateStore();
|
|
@@ -128,20 +137,22 @@ export class CenterPromptSource {
|
|
|
128
137
|
return env?.artifact;
|
|
129
138
|
}
|
|
130
139
|
async restore() {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
return this.exclusive(async () => {
|
|
141
|
+
const state = await this.stateStore.load();
|
|
142
|
+
if (state?.state === "disabled") {
|
|
143
|
+
this.candidate = undefined;
|
|
144
|
+
this.state = "disabled";
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (state?.state !== "active") {
|
|
148
|
+
this.candidate = undefined;
|
|
149
|
+
this.state = "unavailable";
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const env = await this.store.get(state.artifactDigest, { engineVersion: this.engineVersion });
|
|
153
|
+
this.candidate = env !== undefined ? { artifact: env.artifact, sourceRevision: state.sourceRevision } : undefined;
|
|
154
|
+
this.state = env !== undefined ? "active" : "unavailable";
|
|
155
|
+
});
|
|
145
156
|
}
|
|
146
157
|
sourceState() {
|
|
147
158
|
return this.state;
|
|
@@ -151,15 +162,19 @@ export class CenterPromptSource {
|
|
|
151
162
|
if (!v.ok)
|
|
152
163
|
return v;
|
|
153
164
|
const envelope = raw;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
return this.exclusive(async () => {
|
|
166
|
+
await this.store.put(envelope);
|
|
167
|
+
await this.stateStore.save({ state: "active", artifactDigest: v.artifact.artifactDigest, sourceRevision: envelope.sourceRevision });
|
|
168
|
+
this.candidate = { artifact: v.artifact, sourceRevision: envelope.sourceRevision };
|
|
169
|
+
this.state = "active";
|
|
170
|
+
return { ok: true, artifactDigest: v.artifact.artifactDigest };
|
|
171
|
+
});
|
|
159
172
|
}
|
|
160
173
|
async applyDisabled(sourceRevision) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
174
|
+
return this.exclusive(async () => {
|
|
175
|
+
await this.stateStore.save({ state: "disabled", sourceRevision });
|
|
176
|
+
this.candidate = undefined;
|
|
177
|
+
this.state = "disabled";
|
|
178
|
+
});
|
|
164
179
|
}
|
|
165
180
|
}
|
|
@@ -40,6 +40,7 @@ function mountDeclarations(basePack, decls, onWarn) {
|
|
|
40
40
|
const counters = {};
|
|
41
41
|
const extra = [];
|
|
42
42
|
let replacesRoleBase = false;
|
|
43
|
+
let replacedRoleBaseBy;
|
|
43
44
|
for (const d of decls) {
|
|
44
45
|
if (!d.id || d.id.startsWith("core/") || d.id.length > 128 || !/^[a-z0-9][a-z0-9/._-]*$/.test(d.id)) {
|
|
45
46
|
throw new Error(`prompt-provider stableBlocks: invalid section id ${JSON.stringify(d.id).slice(0, 140)} (must match ^[a-z0-9][a-z0-9/._-]*$, ≤128 chars, non-core/)`);
|
|
@@ -47,8 +48,10 @@ function mountDeclarations(basePack, decls, onWarn) {
|
|
|
47
48
|
const base = SLOT_BASE[d.slot];
|
|
48
49
|
if (base === undefined)
|
|
49
50
|
throw new Error(`prompt-provider stableBlocks: unknown slot "${String(d.slot)}" ("${d.id}")`);
|
|
50
|
-
if (d.slot === "identity")
|
|
51
|
+
if (d.slot === "identity") {
|
|
51
52
|
replacesRoleBase = true;
|
|
53
|
+
replacedRoleBaseBy ??= d.id;
|
|
54
|
+
}
|
|
52
55
|
const n = (counters[d.slot] = (counters[d.slot] ?? 0) + 1);
|
|
53
56
|
const declaredContentHash = verifyDeclaredContentHash(d, onWarn);
|
|
54
57
|
extra.push({
|
|
@@ -68,10 +71,21 @@ function mountDeclarations(basePack, decls, onWarn) {
|
|
|
68
71
|
});
|
|
69
72
|
}
|
|
70
73
|
return {
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
pack: {
|
|
75
|
+
...basePack,
|
|
76
|
+
sections: [...basePack.sections.filter((s) => !(replacesRoleBase && s.id === "core/role.base")), ...extra],
|
|
77
|
+
},
|
|
78
|
+
...(replacedRoleBaseBy !== undefined && basePack.sections.some((s) => s.id === "core/role.base")
|
|
79
|
+
? { replacedRoleBaseBy }
|
|
80
|
+
: {}),
|
|
73
81
|
};
|
|
74
82
|
}
|
|
83
|
+
function warnRoleBaseEviction(replacedRoleBaseBy, origin, onWarn) {
|
|
84
|
+
if (replacedRoleBaseBy === undefined || origin === "pack-default")
|
|
85
|
+
return;
|
|
86
|
+
const what = origin === "task-system-prompt" ? "the caller's per-task systemPrompt" : "the PromptProvider's own role text";
|
|
87
|
+
onWarn?.(`prompt-constitution: identity declaration "${replacedRoleBaseBy}" supplants the role base, so ${what} does NOT compose into this prompt (identity sections replace the role layer by design). Fold that text into the declaration, or use a scenario/behavior slot to keep the role base.`, "prompt-constitution");
|
|
88
|
+
}
|
|
75
89
|
export function assemblePrompt(inputs) {
|
|
76
90
|
const { provider, facts } = inputs;
|
|
77
91
|
const onWarn = inputs.onWarn
|
|
@@ -93,6 +107,7 @@ export function assemblePrompt(inputs) {
|
|
|
93
107
|
};
|
|
94
108
|
let pack = SEMA_DEFAULT_PACK;
|
|
95
109
|
let roleBase = inputs.userSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
110
|
+
let roleBaseOrigin = inputs.userSystemPrompt !== undefined ? "task-system-prompt" : "pack-default";
|
|
96
111
|
let constitution = "core";
|
|
97
112
|
let mountedCenter = [];
|
|
98
113
|
if (!inputs.isDefaultProvider && provider.stableBlocks) {
|
|
@@ -108,6 +123,10 @@ export function assemblePrompt(inputs) {
|
|
|
108
123
|
if (inputs.centerDeclarations && inputs.centerDeclarations.length > 0) {
|
|
109
124
|
onWarn?.("prompt-constitution: a center prompt artifact is adopted but the provider's assembled-identity pass-through owns the prompt — the center sections cannot mount on this migration path.", "prompt-constitution");
|
|
110
125
|
}
|
|
126
|
+
const droppedDecls = decls.filter((d) => d !== assembledIdentity);
|
|
127
|
+
if (droppedDecls.length > 0) {
|
|
128
|
+
onWarn?.(`prompt-provider stableBlocks: the assembled-identity pass-through owns the prompt — ${droppedDecls.length} other declaration(s) do NOT compose this run: ${droppedDecls.map((d) => `"${d.id}" (${d.slot})`).join(", ")}. Declare ONLY the role layer in the identity block to let them mount.`, "prompt-constitution");
|
|
129
|
+
}
|
|
111
130
|
const renderInputsPT = (date) => ({
|
|
112
131
|
roleBase: assembledIdentity.text,
|
|
113
132
|
facts,
|
|
@@ -153,29 +172,41 @@ export function assemblePrompt(inputs) {
|
|
|
153
172
|
return true;
|
|
154
173
|
});
|
|
155
174
|
mountedCenter = centerDecls;
|
|
156
|
-
|
|
175
|
+
const mountedSB = mountDeclarations(SEMA_DEFAULT_PACK, [...centerDecls, ...decls], onWarn);
|
|
176
|
+
pack = mountedSB.pack;
|
|
177
|
+
warnRoleBaseEviction(mountedSB.replacedRoleBaseBy, roleBaseOrigin, onWarn);
|
|
157
178
|
}
|
|
158
179
|
else if (!inputs.isDefaultProvider && provider.stableSystem) {
|
|
159
180
|
const roleOut = provider.stableSystem(stableCtx);
|
|
160
181
|
if (provider.replaceAll) {
|
|
161
182
|
constitution = "replaced";
|
|
162
183
|
roleBase = roleOut;
|
|
184
|
+
roleBaseOrigin = "provider";
|
|
163
185
|
pack = subsetPack(SEMA_DEFAULT_PACK, { exclude: REPLACE_ALL_EXCLUDED, roleBaseLegacyId: "provider.replace_all" });
|
|
164
186
|
}
|
|
165
187
|
else if (roleOut.includes(CYBER_RISK) && roleOut.includes(URL_SAFETY) && roleOut.includes(HARNESS_SECTION_ANCHOR)) {
|
|
166
188
|
constitution = "provider-assembled";
|
|
167
189
|
roleBase = roleOut;
|
|
190
|
+
roleBaseOrigin = "provider";
|
|
168
191
|
pack = subsetPack(SEMA_DEFAULT_PACK, { keep: OPAQUE_KEPT, roleBaseLegacyId: "provider.assembled" });
|
|
169
192
|
onWarn?.("prompt-constitution: this PromptProvider returns an already-assembled prompt (constitution anchor found). Core now appends the constitution structurally — return ONLY the role base from stableSystem (or set replaceAll:true to own the whole base). Passed through un-doubled.", "prompt-constitution");
|
|
170
193
|
}
|
|
171
194
|
else {
|
|
172
195
|
roleBase = roleOut;
|
|
196
|
+
roleBaseOrigin = "provider";
|
|
173
197
|
}
|
|
174
198
|
}
|
|
175
199
|
if (inputs.centerDeclarations && inputs.centerDeclarations.length > 0 && !(!inputs.isDefaultProvider && provider.stableBlocks)) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
200
|
+
if (constitution === "provider-assembled") {
|
|
201
|
+
onWarn?.("prompt-constitution: a center prompt artifact is adopted but this PromptProvider returned an already-assembled prompt — the pass-through owns the prompt and the center sections cannot mount on this migration path (an identity section would evict the provider text that carries the constitution). Return ONLY the role base from stableSystem to let center sections compose.", "prompt-constitution");
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const composedCenter = inputs.centerDeclarations;
|
|
205
|
+
const mountedC = mountDeclarations(pack, composedCenter, onWarn);
|
|
206
|
+
pack = mountedC.pack;
|
|
207
|
+
warnRoleBaseEviction(mountedC.replacedRoleBaseBy, roleBaseOrigin, onWarn);
|
|
208
|
+
mountedCenter = composedCenter;
|
|
209
|
+
}
|
|
179
210
|
}
|
|
180
211
|
const renderInputs = (date) => ({
|
|
181
212
|
roleBase,
|