@sema-agent/core 5.64.0 → 6.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/CHANGELOG.md +70 -0
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +11 -0
- package/dist/agents/verify.d.ts +1 -1
- package/dist/brain/anthropic.js +1 -1
- package/dist/brain/errors.d.ts +29 -0
- package/dist/brain/errors.js +20 -0
- package/dist/brain/open-responses.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +1 -0
- package/dist/brain/status-sink.js +12 -1
- package/dist/brain/stream-engine.js +17 -6
- package/dist/core/auto-compaction.d.ts +26 -0
- package/dist/core/auto-compaction.js +7 -2
- package/dist/core/auto-mode-arming.d.ts +138 -0
- package/dist/core/auto-mode-arming.js +181 -0
- package/dist/core/auto-mode-defaults.d.ts +13 -0
- package/dist/core/auto-mode-defaults.js +5 -0
- package/dist/core/auto-mode-prompt.d.ts +14 -3
- package/dist/core/auto-mode-prompt.js +10 -7
- package/dist/core/auto-mode-rebuild.d.ts +75 -0
- package/dist/core/auto-mode-rebuild.js +41 -0
- package/dist/core/auto-mode.d.ts +15 -0
- package/dist/core/auto-mode.js +4 -2
- package/dist/core/checkpoint-store.d.ts +113 -4
- package/dist/core/context-edit.d.ts +47 -5
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +106 -0
- package/dist/core/file-history-retention.js +36 -0
- package/dist/core/file-history-store.d.ts +768 -0
- package/dist/core/file-history-store.js +880 -0
- package/dist/core/governance-codes.d.ts +2 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +48 -8
- package/dist/core/hooks.js +39 -22
- package/dist/core/lsp.d.ts +2 -2
- package/dist/core/mcp.d.ts +29 -7
- package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
- package/dist/core/memory-engine/consolidation-driver.js +71 -4
- package/dist/core/memory-engine/consolidation.d.ts +25 -2
- package/dist/core/memory-engine/consolidation.js +4 -1
- package/dist/core/memory-engine/distiller.d.ts +84 -1
- package/dist/core/memory-engine/distiller.js +68 -0
- package/dist/core/memory-engine/dual-root.js +6 -0
- package/dist/core/memory-engine/engine.d.ts +329 -15
- package/dist/core/memory-engine/engine.js +364 -34
- package/dist/core/memory-engine/file-backend.d.ts +30 -0
- package/dist/core/memory-engine/file-backend.js +14 -13
- package/dist/core/memory-engine/frontmatter.d.ts +22 -1
- package/dist/core/memory-engine/frontmatter.js +3 -0
- package/dist/core/memory-engine/header-hints.d.ts +5 -0
- package/dist/core/memory-engine/index.d.ts +5 -4
- package/dist/core/memory-engine/index.js +5 -4
- package/dist/core/memory-engine/layout.d.ts +88 -2
- package/dist/core/memory-engine/layout.js +112 -3
- package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
- package/dist/core/memory-engine/provenance-wording.js +3 -0
- package/dist/core/memory-engine/tools.d.ts +89 -8
- package/dist/core/memory-engine/tools.js +263 -22
- package/dist/core/memory-engine/types.d.ts +80 -1
- package/dist/core/memory-recall.d.ts +6 -0
- package/dist/core/memory.d.ts +27 -1
- package/dist/core/memory.js +16 -2
- package/dist/core/permission-rule-consent.d.ts +20 -0
- package/dist/core/permission-rule-consent.js +12 -3
- package/dist/core/permission-rule-model.d.ts +67 -7
- package/dist/core/permission-rule-model.js +53 -7
- package/dist/core/permission-rule-store.js +15 -10
- package/dist/core/permission-rule-sync.js +15 -11
- package/dist/core/remote-env.d.ts +3 -3
- package/dist/core/retention-policy.d.ts +9 -0
- package/dist/core/retention-policy.js +5 -2
- package/dist/core/retention.d.ts +13 -2
- package/dist/core/runner/assemble-result.d.ts +19 -1
- package/dist/core/runner/assemble-result.js +17 -2
- package/dist/core/runner/compaction-call-options.d.ts +93 -0
- package/dist/core/runner/compaction-call-options.js +3 -0
- package/dist/core/runner/memory-capture-optout.d.ts +80 -0
- package/dist/core/runner/memory-capture-optout.js +53 -0
- package/dist/core/runner/prepare-config-doors.d.ts +5 -0
- package/dist/core/runner/prepare-config-doors.js +16 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +119 -5
- package/dist/core/runner/prepare-hands-readface.js +103 -8
- package/dist/core/runner/prepare-memory.d.ts +88 -0
- package/dist/core/runner/prepare-memory.js +306 -25
- package/dist/core/runner/prepare-task.d.ts +156 -5
- package/dist/core/runner/prepare-task.js +488 -98
- package/dist/core/runner/runtask.d.ts +27 -20
- package/dist/core/runner/runtask.js +283 -99
- package/dist/core/runner/session-file-state-replay.d.ts +18 -10
- package/dist/core/runner/session-file-state-replay.js +52 -1
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/turn-attachments.d.ts +22 -12
- package/dist/core/session-store.d.ts +1 -1
- package/dist/core/session-store.js +6 -1
- package/dist/core/session.d.ts +34 -1
- package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-history-store-contract.js +720 -0
- package/dist/core/stub-env.d.ts +4 -0
- package/dist/core/stub-env.js +1 -0
- package/dist/core/task-registry-shared.js +30 -2
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +172 -1
- package/dist/core/tool-policy.js +32 -1
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/trace.d.ts +24 -0
- package/dist/core/types.d.ts +875 -97
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/workflow-run-store-contract.js +17 -0
- package/dist/core/workflow-run-store.d.ts +20 -0
- package/dist/core/workflow-run-store.js +1 -0
- package/dist/engine/compaction/compaction.d.ts +88 -10
- package/dist/engine/compaction/compaction.js +109 -30
- package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
- package/dist/engine/execution-env/node-execution-env.js +28 -0
- package/dist/engine/harness/agent-harness.d.ts +52 -1
- package/dist/engine/harness/agent-harness.js +36 -1
- package/dist/engine/harness/types.d.ts +44 -1
- package/dist/engine/llm/types.d.ts +50 -4
- package/dist/engine/loop/agent-loop.d.ts +5 -1
- package/dist/engine/loop/agent-loop.js +25 -0
- package/dist/engine/loop/types.d.ts +19 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/session/session.js +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +14 -6
- package/dist/orchestration/run-workflow-tool.d.ts +20 -2
- package/dist/orchestration/run-workflow-tool.js +22 -3
- package/dist/orchestration/workflow-governance.d.ts +59 -1
- package/dist/orchestration/workflow-governance.js +61 -8
- package/dist/orchestration/workflow-meta.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +56 -13
- package/dist/orchestration/workflow-types.d.ts +112 -1
- package/dist/orchestration/workflow-types.js +2 -2
- package/dist/orchestration/workflow.d.ts +20 -0
- package/dist/orchestration/workflow.js +182 -14
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompts/default.d.ts +15 -7
- package/dist/prompts/default.js +3 -0
- package/dist/stores/file/file-history-store.d.ts +368 -0
- package/dist/stores/file/file-history-store.js +1248 -0
- package/dist/stores/file/index.d.ts +22 -13
- package/dist/stores/file/index.js +4 -4
- package/dist/stores/file/permission-rule-store.js +1 -0
- package/dist/stores/file/strategy-store.d.ts +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
- package/dist/tools/fs/bash-readonly-classifier.js +106 -4
- package/dist/tools/fs/fs-bash.js +9 -5
- package/dist/tools/fs/fs-shared.d.ts +52 -1
- package/dist/tools/fs/fs-shared.js +14 -0
- package/dist/tools/fs/fs-write.d.ts +5 -5
- package/dist/tools/fs/fs-write.js +71 -14
- package/dist/tools/fs/index.d.ts +6 -1
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/web.js +2 -1
- package/package.json +5 -1
- package/test/export-surface.snapshot.json +159 -23
- package/dist/core/file-snapshot-store.d.ts +0 -165
- package/dist/core/file-snapshot-store.js +0 -259
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
- package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
- package/dist/stores/file/file-snapshot-store.d.ts +0 -58
- package/dist/stores/file/file-snapshot-store.js +0 -353
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { AUTO_MODE_DEFAULT_FAILURE_THRESHOLD, AUTO_MODE_DEFAULT_TIMEOUT_MS, AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS, AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES, AUTO_MODE_DEFAULTS_SENTINEL, } from "./auto-mode-defaults.js";
|
|
2
|
+
export const AUTO_MODE_ARMING_RECIPE_VERSION = 1;
|
|
3
|
+
const isPlainRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4
|
+
function canonicalStrings(v) {
|
|
5
|
+
if (v === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (!Array.isArray(v))
|
|
8
|
+
return null;
|
|
9
|
+
for (let i = 0; i < v.length; i++) {
|
|
10
|
+
if (!(i in v) || typeof v[i] !== "string")
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return v.length === 0 ? undefined : v.slice();
|
|
14
|
+
}
|
|
15
|
+
function canonicalRuleSection(v) {
|
|
16
|
+
const list = canonicalStrings(v);
|
|
17
|
+
if (list === null || list === undefined)
|
|
18
|
+
return list;
|
|
19
|
+
let seen = false;
|
|
20
|
+
const deduped = list.filter((r) => (r === AUTO_MODE_DEFAULTS_SENTINEL ? (seen ? false : ((seen = true), true)) : true));
|
|
21
|
+
if (deduped.length === 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
return deduped.length === 1 && deduped[0] === AUTO_MODE_DEFAULTS_SENTINEL ? undefined : deduped;
|
|
24
|
+
}
|
|
25
|
+
function canonicalNumber(v, min, floor) {
|
|
26
|
+
if (v === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
29
|
+
return null;
|
|
30
|
+
const n = floor ? Math.floor(v) : v;
|
|
31
|
+
return n < min ? null : n;
|
|
32
|
+
}
|
|
33
|
+
export function sanitizeAutoModeArmingRecipe(value) {
|
|
34
|
+
if (!isPlainRecord(value))
|
|
35
|
+
return undefined;
|
|
36
|
+
if (value.v !== AUTO_MODE_ARMING_RECIPE_VERSION)
|
|
37
|
+
return undefined;
|
|
38
|
+
const settingsDenyRules = canonicalStrings(value.settingsDenyRules);
|
|
39
|
+
const sessionContext = canonicalStrings(value.sessionContext);
|
|
40
|
+
const timeoutMs = canonicalNumber(value.timeoutMs, 1, false);
|
|
41
|
+
const failureThreshold = canonicalNumber(value.failureThreshold, 1, true);
|
|
42
|
+
if (settingsDenyRules === null || sessionContext === null || timeoutMs === null || failureThreshold === null)
|
|
43
|
+
return undefined;
|
|
44
|
+
let rules;
|
|
45
|
+
if (value.rules !== undefined) {
|
|
46
|
+
if (!isPlainRecord(value.rules))
|
|
47
|
+
return undefined;
|
|
48
|
+
const allow = canonicalRuleSection(value.rules.allow);
|
|
49
|
+
const softDeny = canonicalRuleSection(value.rules.softDeny);
|
|
50
|
+
const hardDeny = canonicalRuleSection(value.rules.hardDeny);
|
|
51
|
+
const environment = canonicalRuleSection(value.rules.environment);
|
|
52
|
+
if (allow === null || softDeny === null || hardDeny === null || environment === null)
|
|
53
|
+
return undefined;
|
|
54
|
+
const merged = {
|
|
55
|
+
...(allow !== undefined ? { allow } : {}),
|
|
56
|
+
...(softDeny !== undefined ? { softDeny } : {}),
|
|
57
|
+
...(hardDeny !== undefined ? { hardDeny } : {}),
|
|
58
|
+
...(environment !== undefined ? { environment } : {}),
|
|
59
|
+
};
|
|
60
|
+
if (Object.keys(merged).length > 0)
|
|
61
|
+
rules = merged;
|
|
62
|
+
}
|
|
63
|
+
let window;
|
|
64
|
+
if (value.window !== undefined) {
|
|
65
|
+
if (!isPlainRecord(value.window))
|
|
66
|
+
return undefined;
|
|
67
|
+
const maxEntries = canonicalNumber(value.window.maxEntries, 0, true);
|
|
68
|
+
const maxCharsPerEntry = canonicalNumber(value.window.maxCharsPerEntry, 0, true);
|
|
69
|
+
if (maxEntries === null || maxCharsPerEntry === null)
|
|
70
|
+
return undefined;
|
|
71
|
+
const merged = {
|
|
72
|
+
...(maxEntries !== undefined && maxEntries !== AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES ? { maxEntries } : {}),
|
|
73
|
+
...(maxCharsPerEntry !== undefined && maxCharsPerEntry !== AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS ? { maxCharsPerEntry } : {}),
|
|
74
|
+
};
|
|
75
|
+
if (Object.keys(merged).length > 0)
|
|
76
|
+
window = merged;
|
|
77
|
+
}
|
|
78
|
+
const settingsEpoch = value.settingsEpoch;
|
|
79
|
+
if (settingsEpoch !== undefined && typeof settingsEpoch !== "string")
|
|
80
|
+
return undefined;
|
|
81
|
+
const promptDigest = value.promptDigest;
|
|
82
|
+
if (promptDigest !== undefined && (typeof promptDigest !== "string" || promptDigest === ""))
|
|
83
|
+
return undefined;
|
|
84
|
+
return {
|
|
85
|
+
v: AUTO_MODE_ARMING_RECIPE_VERSION,
|
|
86
|
+
...(promptDigest !== undefined ? { promptDigest } : {}),
|
|
87
|
+
...(rules !== undefined ? { rules } : {}),
|
|
88
|
+
...(settingsDenyRules !== undefined ? { settingsDenyRules } : {}),
|
|
89
|
+
...(sessionContext !== undefined ? { sessionContext } : {}),
|
|
90
|
+
...(window !== undefined ? { window } : {}),
|
|
91
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
92
|
+
...(failureThreshold !== undefined ? { failureThreshold } : {}),
|
|
93
|
+
...(settingsEpoch !== undefined && settingsEpoch !== "" ? { settingsEpoch } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function autoModeArmingRecipeOf(face, bind) {
|
|
97
|
+
return sanitizeAutoModeArmingRecipe({
|
|
98
|
+
v: AUTO_MODE_ARMING_RECIPE_VERSION,
|
|
99
|
+
...(bind?.promptDigest !== undefined ? { promptDigest: bind.promptDigest } : {}),
|
|
100
|
+
...(face.rules !== undefined ? { rules: face.rules } : {}),
|
|
101
|
+
...(face.settingsDenyRules !== undefined ? { settingsDenyRules: face.settingsDenyRules } : {}),
|
|
102
|
+
...(face.sessionContext !== undefined ? { sessionContext: face.sessionContext } : {}),
|
|
103
|
+
...(face.window !== undefined ? { window: face.window } : {}),
|
|
104
|
+
...(face.timeoutMs !== undefined ? { timeoutMs: face.timeoutMs } : {}),
|
|
105
|
+
...(face.failureThreshold !== undefined ? { failureThreshold: face.failureThreshold } : {}),
|
|
106
|
+
...(face.settingsEpoch !== undefined ? { settingsEpoch: face.settingsEpoch } : {}),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function sameArmingBody(a, b) {
|
|
110
|
+
const body = (r) => JSON.stringify([
|
|
111
|
+
r.rules?.allow ?? null,
|
|
112
|
+
r.rules?.softDeny ?? null,
|
|
113
|
+
r.rules?.hardDeny ?? null,
|
|
114
|
+
r.rules?.environment ?? null,
|
|
115
|
+
r.settingsDenyRules ?? null,
|
|
116
|
+
r.sessionContext ?? null,
|
|
117
|
+
r.window?.maxEntries ?? null,
|
|
118
|
+
r.window?.maxCharsPerEntry ?? null,
|
|
119
|
+
]);
|
|
120
|
+
return body(a) === body(b);
|
|
121
|
+
}
|
|
122
|
+
export function foldAutoModeArming(recorded, current) {
|
|
123
|
+
const rec = sanitizeAutoModeArmingRecipe(recorded);
|
|
124
|
+
if (rec === undefined) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
reason: "malformed_recipe",
|
|
128
|
+
message: "the recorded auto-mode arming recipe is not readable by this build (unknown recipe version, or not plain recipe data) — " +
|
|
129
|
+
"no classifier is rebuilt; the redemption keeps its honest refusal and the inherited ask flows the original chain",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (current === undefined) {
|
|
133
|
+
return {
|
|
134
|
+
ok: false,
|
|
135
|
+
reason: "current_settings_missing",
|
|
136
|
+
message: "no current auto-mode settings were declared for this rebuild, so nothing bounds it — the recorded arming is refused " +
|
|
137
|
+
"rather than rebuilt unbounded (pass the redeeming deployment's own face through autoModeArmingRecipeOf; a deployment " +
|
|
138
|
+
"with no overrides of its own still declares that, and the declaration is what the bound is made of)",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const cur = sanitizeAutoModeArmingRecipe(current);
|
|
142
|
+
if (cur === undefined) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
reason: "malformed_current_settings",
|
|
146
|
+
message: "the redeeming deployment's own auto-mode settings did not canonicalize into a recipe, so the recorded arming cannot be " +
|
|
147
|
+
"bounded by the settings in force — no classifier is rebuilt (fail-closed: an unbounded rebuild is the one outcome this fold exists to prevent)",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (!sameArmingBody(rec, cur)) {
|
|
151
|
+
const sameEpoch = rec.settingsEpoch !== undefined && rec.settingsEpoch === cur.settingsEpoch;
|
|
152
|
+
return sameEpoch
|
|
153
|
+
? {
|
|
154
|
+
ok: false,
|
|
155
|
+
reason: "epoch_inconsistent",
|
|
156
|
+
message: `the recorded arming and the current settings both declare settings epoch ${JSON.stringify(rec.settingsEpoch)} while their ` +
|
|
157
|
+
"prompt bodies differ — one of the two is mislabelled; refusing to rebuild rather than choosing which set of rules is the policy in force",
|
|
158
|
+
}
|
|
159
|
+
: {
|
|
160
|
+
ok: false,
|
|
161
|
+
reason: "settings_moved",
|
|
162
|
+
message: "the auto-mode settings moved between the recorded arming and this deployment's current ones (rule sections / settings-deny rules / " +
|
|
163
|
+
"session context / window bounds differ), and free-text rule sets have no sound stricter-than ordering — refusing to rebuild " +
|
|
164
|
+
"(the strictest decidable answer: the inherited ask flows the original chain to a human)",
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const minKnob = (a, b, fallback) => Math.min(a ?? fallback, b ?? fallback);
|
|
168
|
+
const timeoutMs = minKnob(rec.timeoutMs, cur.timeoutMs, AUTO_MODE_DEFAULT_TIMEOUT_MS);
|
|
169
|
+
const failureThreshold = minKnob(rec.failureThreshold, cur.failureThreshold, AUTO_MODE_DEFAULT_FAILURE_THRESHOLD);
|
|
170
|
+
return {
|
|
171
|
+
ok: true,
|
|
172
|
+
recorded: rec,
|
|
173
|
+
effective: {
|
|
174
|
+
...rec,
|
|
175
|
+
timeoutMs,
|
|
176
|
+
failureThreshold,
|
|
177
|
+
...(cur.settingsEpoch !== undefined ? { settingsEpoch: cur.settingsEpoch } : {}),
|
|
178
|
+
},
|
|
179
|
+
tightened: timeoutMs < (rec.timeoutMs ?? AUTO_MODE_DEFAULT_TIMEOUT_MS) || failureThreshold < (rec.failureThreshold ?? AUTO_MODE_DEFAULT_FAILURE_THRESHOLD),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Hard cap on one classification round-trip when `AutoModeDeciderOptions.timeoutMs` is omitted. */
|
|
2
|
+
export declare const AUTO_MODE_DEFAULT_TIMEOUT_MS = 15000;
|
|
3
|
+
/** Consecutive-failure streak that opens the session breaker when `failureThreshold` is omitted. */
|
|
4
|
+
export declare const AUTO_MODE_DEFAULT_FAILURE_THRESHOLD = 3;
|
|
5
|
+
/** Newest transcript entries in the classify window when `AutoModeWindowOptions.maxEntries` is omitted. */
|
|
6
|
+
export declare const AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES = 40;
|
|
7
|
+
/** Per-entry excerpt cap when `AutoModeWindowOptions.maxCharsPerEntry` is omitted. */
|
|
8
|
+
export declare const AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS = 2000;
|
|
9
|
+
/** The sentinel a deployment puts INSIDE a paired rule list to splice the CC default rules back in at
|
|
10
|
+
* that position (CC `XYt = "$defaults"`). It lives here, beside the other defaults, because the #503
|
|
11
|
+
* recipe canonicalizer needs its VALUE and must not load the assembly face (and its SHA-locked assets)
|
|
12
|
+
* to get it; `auto-mode-prompt.js` re-exports it, so the public name and its module face are unchanged. */
|
|
13
|
+
export declare const AUTO_MODE_DEFAULTS_SENTINEL = "$defaults";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const AUTO_MODE_DEFAULT_TIMEOUT_MS = 15_000;
|
|
2
|
+
export const AUTO_MODE_DEFAULT_FAILURE_THRESHOLD = 3;
|
|
3
|
+
export const AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES = 40;
|
|
4
|
+
export const AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS = 2_000;
|
|
5
|
+
export const AUTO_MODE_DEFAULTS_SENTINEL = "$defaults";
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { AutoModeClassifyInput } from "./auto-mode.js";
|
|
2
2
|
import type { Message } from "../internal/llm.js";
|
|
3
3
|
/** The sentinel a deployment puts INSIDE a rule list to splice the CC default rules back in at that
|
|
4
|
-
* position (CC `XYt = "$defaults"`). Without it, a non-empty user list REPLACES the default section.
|
|
5
|
-
|
|
4
|
+
* position (CC `XYt = "$defaults"`). Without it, a non-empty user list REPLACES the default section.
|
|
5
|
+
* DECLARED in `./auto-mode-defaults.js` (the leaf the #503 recipe canonicalizer can reach without
|
|
6
|
+
* loading these assets) and re-exported here, where every caller already looks for it. */
|
|
7
|
+
export { AUTO_MODE_DEFAULTS_SENTINEL } from "./auto-mode-defaults.js";
|
|
6
8
|
/** Deployment-authored rule overrides for the four paired sections of the permissions document.
|
|
7
9
|
* Each entry is ONE rule (rendered as a `- ` bullet); omit a field (or pass an empty array) to keep
|
|
8
10
|
* the CC default rules for that section verbatim. */
|
|
@@ -29,7 +31,8 @@ export interface BuildAutoModePromptOptions {
|
|
|
29
31
|
*/
|
|
30
32
|
export declare function buildAutoModePrompt(options?: BuildAutoModePromptOptions): string;
|
|
31
33
|
export interface AutoModeWindowOptions {
|
|
32
|
-
/** Newest transcript entries
|
|
34
|
+
/** Newest transcript entries INCLUDED (default 40). Zero includes none — the classifier judges the
|
|
35
|
+
* action with no run history, and the omission line still names every entry left out. */
|
|
33
36
|
maxEntries?: number;
|
|
34
37
|
/** Per-entry excerpt cap in characters (default 2_000 — tool results and long turns are truncated
|
|
35
38
|
* head-first with a `[… N chars truncated]` marker; the classifier weighs shape over bulk). */
|
|
@@ -39,6 +42,14 @@ export interface AutoModeWindowOptions {
|
|
|
39
42
|
* Render the transcript window half of the classifier's user prompt: the newest `maxEntries` messages,
|
|
40
43
|
* oldest→newest, each labeled by lane. Plain data — see the module header for the declared v1 gap vs
|
|
41
44
|
* CC's entry-formatter stack.
|
|
45
|
+
*
|
|
46
|
+
* `maxEntries` counts the entries INCLUDED, so a bound of zero includes none and says so. That case is
|
|
47
|
+
* handled explicitly rather than left to `slice`: `slice(-0)` is `slice(0)`, i.e. the WHOLE transcript,
|
|
48
|
+
* which inverts a declared bound into "unbounded" — and silently, because the omitted count would then
|
|
49
|
+
* be zero and the omission header would not render either. The recipe canonicalizer admits zero for both
|
|
50
|
+
* window knobs deliberately (its sibling knobs use a minimum of one), so a deployment can genuinely
|
|
51
|
+
* declare "classify the action with no transcript context"; a negative bound is not a coherent window
|
|
52
|
+
* either and takes the same arm rather than `slice`'s "drop the oldest N".
|
|
42
53
|
*/
|
|
43
54
|
export declare function renderAutoModeWindow(messages: readonly Message[], options?: AutoModeWindowOptions): string;
|
|
44
55
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS, AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES, AUTO_MODE_DEFAULTS_SENTINEL } from "./auto-mode-defaults.js";
|
|
1
2
|
import { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./auto-mode-prompt-assets.js";
|
|
2
3
|
import { cutEnginePrefix, cutEngineSegments, flattenableUserText } from "./untrusted-text.js";
|
|
3
|
-
export
|
|
4
|
+
export { AUTO_MODE_DEFAULTS_SENTINEL } from "./auto-mode-defaults.js";
|
|
4
5
|
function mergeRuleSection(user, defaultsInner) {
|
|
5
6
|
if (!user?.length)
|
|
6
7
|
return defaultsInner;
|
|
@@ -98,14 +99,16 @@ function renderEntry(m, cap) {
|
|
|
98
99
|
return `[assistant]\n${parts.join("\n")}`;
|
|
99
100
|
}
|
|
100
101
|
export function renderAutoModeWindow(messages, options) {
|
|
101
|
-
const maxEntries = options?.maxEntries ??
|
|
102
|
-
const cap = options?.maxCharsPerEntry ??
|
|
103
|
-
const window = messages.slice(-maxEntries);
|
|
104
|
-
if (
|
|
102
|
+
const maxEntries = options?.maxEntries ?? AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES;
|
|
103
|
+
const cap = options?.maxCharsPerEntry ?? AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS;
|
|
104
|
+
const window = maxEntries > 0 ? messages.slice(-maxEntries) : [];
|
|
105
|
+
if (messages.length === 0)
|
|
105
106
|
return "(transcript empty — this is the run's first gated action)\n";
|
|
106
107
|
const dropped = messages.length - window.length;
|
|
107
|
-
const head = dropped > 0 ? `(… ${dropped} earlier transcript entries omitted)\n
|
|
108
|
-
|
|
108
|
+
const head = dropped > 0 ? `(… ${dropped} earlier transcript entries omitted)\n` : "";
|
|
109
|
+
if (window.length === 0)
|
|
110
|
+
return head;
|
|
111
|
+
return `${head}${head === "" ? "" : "\n"}${window.map((m) => renderEntry(m, cap)).join("\n\n")}\n`;
|
|
109
112
|
}
|
|
110
113
|
export function renderAutoModeAction(input) {
|
|
111
114
|
const ask = input.askMessage ? `\npermission gate: ${input.askMessage}` : "";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type AutoModeDecider, type AutoModeDeciderOptions } from "./auto-mode.js";
|
|
2
|
+
import { type AutoModeArmingRecipe, type AutoModeRebuildRefusal } from "./auto-mode-arming.js";
|
|
3
|
+
import type { Message } from "../internal/llm.js";
|
|
4
|
+
/** The redeeming deployment's FRESH model leg: one tool-less completion over a prompt CORE assembles.
|
|
5
|
+
* The split is the trust gate's: rules reach the prompt only through the recipe (a deployment-authored
|
|
6
|
+
* object), never through the leg — the leg dispatches, it does not compose. */
|
|
7
|
+
export type AutoModeClassifierCompletion = (call: {
|
|
8
|
+
systemPrompt: string;
|
|
9
|
+
userPrompt: string;
|
|
10
|
+
}, signal?: AbortSignal) => Promise<string>;
|
|
11
|
+
export interface AutoModeRebuildOptions {
|
|
12
|
+
/** The recipe read off the parked chain entry (`ConstraintChainEntryMeta.autoModeArming`). */
|
|
13
|
+
recorded: unknown;
|
|
14
|
+
/** The redeeming deployment's CURRENT auto-mode settings, minted with `autoModeArmingRecipeOf` from
|
|
15
|
+
* its own face. REQUIRED: bound ② is "the rebuilt arming is bounded by the settings in force", and a
|
|
16
|
+
* caller that declares none has put nothing in force — see {@link foldAutoModeArming}. A deployment
|
|
17
|
+
* with no overrides of its own still declares that (an empty face mints a bare recipe). */
|
|
18
|
+
current: unknown;
|
|
19
|
+
/** The fresh model leg. */
|
|
20
|
+
complete: AutoModeClassifierCompletion;
|
|
21
|
+
/**
|
|
22
|
+
* The transcript the classifier judges the action IN CONTEXT of — the REDEEMING leg's own messages,
|
|
23
|
+
* read at classify time (the window is rebuilt per classification, exactly as the live arming does).
|
|
24
|
+
*
|
|
25
|
+
* REQUIRED, and the reason is a permissiveness one rather than a quality one: the 207 process judges an
|
|
26
|
+
* action against the run that produced it (session linking, earlier writes), so a classifier handed no
|
|
27
|
+
* history can allow what the same classifier would block having seen it — and the empty window renders
|
|
28
|
+
* an affirmative claim, "this is the run's first gated action", that would simply be false. A provider
|
|
29
|
+
* that returns `[]` is DECLARING the transcript empty, which is a different statement from an engine
|
|
30
|
+
* that silently had none to show. A provider that throws fails the classification closed
|
|
31
|
+
* (`unavailable` ⇒ the original chain), never into a blind verdict.
|
|
32
|
+
*/
|
|
33
|
+
transcript: () => readonly Message[] | Promise<readonly Message[]>;
|
|
34
|
+
/** The redeeming process's breaker alarm (the recorded arming cannot carry the ancestor's closure). */
|
|
35
|
+
onBreakerOpen?: AutoModeDeciderOptions["onBreakerOpen"];
|
|
36
|
+
}
|
|
37
|
+
export type AutoModeRebuildResult = {
|
|
38
|
+
ok: true;
|
|
39
|
+
/** A decider carrying the ancestor's criteria, over the caller's model leg. */
|
|
40
|
+
decider: AutoModeDecider;
|
|
41
|
+
/**
|
|
42
|
+
* The RECORDED recipe, canonical — put THIS back on the re-supplied chain entry
|
|
43
|
+
* (`parentConstraints[].autoMode.arming`) so the constraint-chain digest matches the parked row.
|
|
44
|
+
* Deliberately not `effective`: the digest binds what the ANCESTOR recorded, and a local tighten
|
|
45
|
+
* must not read as a different ancestor chain.
|
|
46
|
+
*/
|
|
47
|
+
arming: AutoModeArmingRecipe;
|
|
48
|
+
/** What the rebuilt decider actually runs under (the tighten-only fold of recorded ∧ current). */
|
|
49
|
+
effective: AutoModeArmingRecipe;
|
|
50
|
+
/** True iff the current settings pulled a knob in. */
|
|
51
|
+
tightened: boolean;
|
|
52
|
+
} | {
|
|
53
|
+
ok: false;
|
|
54
|
+
reason: AutoModeRebuildRefusal;
|
|
55
|
+
message: string;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Rebuild a decider with a PARKED ancestor's criteria from its recorded recipe plus a fresh model leg.
|
|
59
|
+
*
|
|
60
|
+
* What is reproduced: the assembled system prompt (`buildAutoModePrompt` over the recorded rule
|
|
61
|
+
* overrides / settings-deny rules / session context), the transcript-window bounds, the round-trip
|
|
62
|
+
* timeout and the breaker threshold — i.e. every input the ancestor's own `createAutoModeDecider` call
|
|
63
|
+
* had except the model leg and the alarm closure.
|
|
64
|
+
*
|
|
65
|
+
* What is NOT reproduced, deliberately: the ancestor's BREAKER STATE. The rebuilt decider starts closed,
|
|
66
|
+
* and that is sound only because a recipe is never recorded from a decider whose breaker is open OR whose
|
|
67
|
+
* failure streak is non-zero (bound ①, at the persistence point) — either state means the ancestor's own
|
|
68
|
+
* budget was spent or spending, and the row then carries no recipe at all.
|
|
69
|
+
*
|
|
70
|
+
* Also NOT reproduced: this build's PROMPT ASSETS are its own. The recipe records the deployment's
|
|
71
|
+
* overrides, so the assembled prompt is checked against the digest the arming recorded — a fleet running
|
|
72
|
+
* two asset versions refuses to rebuild rather than enforce a different set of default rules under the
|
|
73
|
+
* ancestor's name.
|
|
74
|
+
*/
|
|
75
|
+
export declare function rebuildAutoModeDecider(opts: AutoModeRebuildOptions): AutoModeRebuildResult;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createAutoModeDecider } from "./auto-mode.js";
|
|
3
|
+
import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "./auto-mode-prompt.js";
|
|
4
|
+
import { foldAutoModeArming, } from "./auto-mode-arming.js";
|
|
5
|
+
export function rebuildAutoModeDecider(opts) {
|
|
6
|
+
const folded = foldAutoModeArming(opts.recorded, opts.current);
|
|
7
|
+
if (!folded.ok)
|
|
8
|
+
return folded;
|
|
9
|
+
const effective = folded.effective;
|
|
10
|
+
const promptOptions = {
|
|
11
|
+
...(effective.rules !== undefined ? { rules: effective.rules } : {}),
|
|
12
|
+
...(effective.settingsDenyRules !== undefined ? { settingsDenyRules: effective.settingsDenyRules } : {}),
|
|
13
|
+
...(effective.sessionContext !== undefined ? { sessionContext: effective.sessionContext } : {}),
|
|
14
|
+
};
|
|
15
|
+
const systemPrompt = buildAutoModePrompt(promptOptions);
|
|
16
|
+
const assembledDigest = `apv1:${createHash("sha256").update(systemPrompt).digest("hex")}`;
|
|
17
|
+
if (folded.recorded.promptDigest !== assembledDigest) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
reason: "prompt_assets_moved",
|
|
21
|
+
message: folded.recorded.promptDigest === undefined
|
|
22
|
+
? "the recorded arming carries no classifier-prompt digest, so this build cannot establish that the prompt it assembles is " +
|
|
23
|
+
"the one the ancestor enforced (the recipe records overrides; the base shell and permissions document come from the build's " +
|
|
24
|
+
"own assets) — refusing to rebuild rather than enforce criteria that may not be the ancestor's"
|
|
25
|
+
: "this build assembles a DIFFERENT classifier prompt than the recorded arming did (prompt-asset version skew across the fleet: " +
|
|
26
|
+
`recorded ${folded.recorded.promptDigest}, assembled here ${assembledDigest}) — the overrides match but the default rules they ` +
|
|
27
|
+
"merge into do not, so the rebuilt classifier would enforce criteria the ancestor never ran; refusing to rebuild",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const decider = createAutoModeDecider({
|
|
31
|
+
...(effective.timeoutMs !== undefined ? { timeoutMs: effective.timeoutMs } : {}),
|
|
32
|
+
...(effective.failureThreshold !== undefined ? { failureThreshold: effective.failureThreshold } : {}),
|
|
33
|
+
...(opts.onBreakerOpen !== undefined ? { onBreakerOpen: opts.onBreakerOpen } : {}),
|
|
34
|
+
classify: async (input, signal) => {
|
|
35
|
+
const messages = await opts.transcript();
|
|
36
|
+
const userPrompt = renderAutoModeWindow(messages, effective.window) + renderAutoModeAction(input);
|
|
37
|
+
return opts.complete({ systemPrompt, userPrompt }, signal);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
return { ok: true, decider, arming: folded.recorded, effective, tightened: folded.tightened };
|
|
41
|
+
}
|
package/dist/core/auto-mode.d.ts
CHANGED
|
@@ -77,6 +77,21 @@ export interface AutoModeDecider {
|
|
|
77
77
|
/** True once the session breaker has opened (it never half-opens: is a SESSION fallback
|
|
78
78
|
* to non-auto, not a retry window — a flapping classifier must not oscillate the permission mode). */
|
|
79
79
|
breakerOpen(): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* The CURRENT consecutive-failure streak — the part of the breaker's state that `breakerOpen()` alone
|
|
82
|
+
* cannot express. A latch that has not tripped yet still carries how much budget is left before it
|
|
83
|
+
* does, and #503 needs exactly that: an arming recorded for cross-process rebuild starts a FRESH
|
|
84
|
+
* decider, so recording one from a decider that is already 2 failures into a threshold of 3 would hand
|
|
85
|
+
* the redemption more tolerance for a failing classifier than the ancestor had left — the redeemed leg
|
|
86
|
+
* stays auto-classified through failures that would have dropped the ancestor back to non-auto.
|
|
87
|
+
* {@link import("./tool-policy.js").constraintChainEntryOfLayer} therefore records an arming only at a
|
|
88
|
+
* ZERO streak, and reads a decider that does not implement this member as unknown (⇒ records nothing).
|
|
89
|
+
*
|
|
90
|
+
* OPTIONAL so that hand-built deciders keep type-checking; the engine's own
|
|
91
|
+
* {@link createAutoModeDecider} always implements it. A deployment that hand-rolls a decider and wants
|
|
92
|
+
* the persisted-arming path must implement it too — the conservative reading of absence is deliberate.
|
|
93
|
+
*/
|
|
94
|
+
consecutiveFailures?(): number;
|
|
80
95
|
}
|
|
81
96
|
/**
|
|
82
97
|
* Session-scoped decider: timeout + fail-closed error mapping + a one-way circuit breaker.
|
package/dist/core/auto-mode.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AUTO_MODE_DEFAULT_FAILURE_THRESHOLD, AUTO_MODE_DEFAULT_TIMEOUT_MS } from "./auto-mode-defaults.js";
|
|
1
2
|
export function parseAutoModeResponse(text) {
|
|
2
3
|
const t = text
|
|
3
4
|
.replace(/<thinking>[\s\S]*?<\/thinking>/g, "")
|
|
@@ -16,8 +17,8 @@ export function parseAutoModeResponse(text) {
|
|
|
16
17
|
return { kind: "block", category, reason };
|
|
17
18
|
}
|
|
18
19
|
export function createAutoModeDecider(opts) {
|
|
19
|
-
const timeoutMs = opts.timeoutMs ??
|
|
20
|
-
const threshold = Math.max(1, Math.floor(opts.failureThreshold ??
|
|
20
|
+
const timeoutMs = opts.timeoutMs ?? AUTO_MODE_DEFAULT_TIMEOUT_MS;
|
|
21
|
+
const threshold = Math.max(1, Math.floor(opts.failureThreshold ?? AUTO_MODE_DEFAULT_FAILURE_THRESHOLD));
|
|
21
22
|
let consecutiveFailures = 0;
|
|
22
23
|
let open = false;
|
|
23
24
|
const recordFailure = (cause) => {
|
|
@@ -33,6 +34,7 @@ export function createAutoModeDecider(opts) {
|
|
|
33
34
|
};
|
|
34
35
|
return {
|
|
35
36
|
breakerOpen: () => open,
|
|
37
|
+
consecutiveFailures: () => consecutiveFailures,
|
|
36
38
|
async decide(input, signal) {
|
|
37
39
|
if (open)
|
|
38
40
|
return { kind: "unavailable", cause: "breaker_open" };
|
|
@@ -768,6 +768,24 @@ export type PendingAction = {
|
|
|
768
768
|
* original-index preservation. The park→resume redemption chain walks the same consent
|
|
769
769
|
* protocol as the synchronous card — there is no second form. */
|
|
770
770
|
ruleOffers?: readonly import("./permission-rule-model.js").RuleOffer[];
|
|
771
|
+
/** #490 修② (additive; no checkpoint-version bump — the `previewWithheld`/`hasBidiControls`
|
|
772
|
+
* precedents above: an optional field an older reader ignores and the resume path never reads)
|
|
773
|
+
* — the PARK twin of `AskRequest.ruleOffersAbsence`, minted by the SAME one factory as
|
|
774
|
+
* {@link ruleOffers} so the parked card and the live card cannot answer differently. Present ⇔
|
|
775
|
+
* the rule-offer lane is in play for this call and has nothing to give, naming which door
|
|
776
|
+
* closed: `"mandated"` (no persisted rule could ever clear this ask — a real-approval demand,
|
|
777
|
+
* the tool's own mandate marks, a hook-raised ask, an ancestor's authority, or — #502 — a
|
|
778
|
+
* demotion this CALL's reversibility probe declared structural, which for the built-in shell
|
|
779
|
+
* probe means a read outside the directories the session declared; an inbox must NOT point at
|
|
780
|
+
* rule-writing on this arm, nor at a directory grant, which does not exist in this version),
|
|
781
|
+
* `"shadowed"` (a rule the person already wrote is
|
|
782
|
+
* speaking and does not clear it), `"lane_cannot_speak"` (this card has no rule to offer — the
|
|
783
|
+
* grammar has no text for the command, or the card's array-order contract declines to present
|
|
784
|
+
* the one text that exists; it is NOT a claim that no rule could ever be written).
|
|
785
|
+
* Mutually exclusive with {@link ruleOffers}. ABSENCE is not a claim — it also covers
|
|
786
|
+
* "there are offers" and the structural doors (no lane wired, another tool, a task that cannot
|
|
787
|
+
* hold a rule) — so read presence. See the synchronous seat's doc for the full contract. */
|
|
788
|
+
ruleOffersAbsence?: "mandated" | "lane_cannot_speak" | "shadowed";
|
|
771
789
|
/**
|
|
772
790
|
* design/80 D-1 §2 (slice 1a.2): the server-minted **opaque** boundInputHash of {@link args} — a
|
|
773
791
|
* SHA-256 (hex) via {@link import("./canonical-json.js").boundInputHashOf}, computed ONCE here at
|
|
@@ -1160,6 +1178,43 @@ export interface CheckpointState {
|
|
|
1160
1178
|
* delegated child (never `false` — presence-coded like the other monotonic bits here).
|
|
1161
1179
|
*/
|
|
1162
1180
|
isDelegatedChild?: true;
|
|
1181
|
+
/**
|
|
1182
|
+
* design/380 O1③ — the run TREE's placement fixed point at suspend: the SAME three-segment fold the
|
|
1183
|
+
* env-factory ctx mint uses (explicit `RunInternals.placementRoot` > the delegation lanes'
|
|
1184
|
+
* `rootSessionId` > the leg's own resolved session), stamped from TRUSTED internals so a durable
|
|
1185
|
+
* resume can restore it instead of falling back to the parked leg's own session (a target-bound
|
|
1186
|
+
* factory keys its placement lookup on the fixed point — the self fallback read a resumed leg as a
|
|
1187
|
+
* NEW placement). Restore is the `isDelegatedChild` posture replayed on the placement axis: an
|
|
1188
|
+
* EXPLICIT live `internals.placementRoot` wins when re-supplied; otherwise THIS key restores into
|
|
1189
|
+
* `internals.placementRoot` at prepare so descendants and the NEXT re-suspend mint inherit it too
|
|
1190
|
+
* — outranking a supplied `rootSessionId`, which is DELEGATION IDENTITY (the peer/registry axis a
|
|
1191
|
+
* revive leg re-supplies from its row), never a placement claim (rescan C1: reading it as one
|
|
1192
|
+
* permanently stuck every parked agent spawned under an explicit placement root). The resume entry
|
|
1193
|
+
* ALSO reads this key as an identity record: a resume whose supplied `internals.placementRoot`
|
|
1194
|
+
* CONTRADICTS it is refused pre-CAS (`resume.placement_mismatch`); either side absent skips the
|
|
1195
|
+
* check (an older row / a claim-less resume — the honest degradation arm), and `""` reads as
|
|
1196
|
+
* absence (the identity fields' discipline). Schema ADDITION (additive, old readers ignore it — the Q5 posture;
|
|
1197
|
+
* deliberately NOT version-gated: same rolling-deployment account as {@link Checkpoint.principal},
|
|
1198
|
+
* whose doc records version-gating as a design candidate — any new stamp is a lattice-wide window
|
|
1199
|
+
* rewrite, owned by that ticket, not ad-hoc here). Absent ⇒ minted before the field, or the fold
|
|
1200
|
+
* resolved empty on a factory-less run (never persisted as garbage).
|
|
1201
|
+
*/
|
|
1202
|
+
placementRootSessionId?: string;
|
|
1203
|
+
/**
|
|
1204
|
+
* design/380 O9a — MONOTONIC presence bit: this run's hand-tool content fold was EXTERNAL-TARGET
|
|
1205
|
+
* armed (the env declared `ExecutionEnv.externalContentTarget`, or an earlier leg of this suspend
|
|
1206
|
+
* chain did). Stamped at the suspend mint from the run's RESOLVED fold state — never from first
|
|
1207
|
+
* tool use, so a park landing BEFORE the first hand call is covered too. Presence-coded like
|
|
1208
|
+
* {@link isDelegatedChild} (absent = never armed; never `false`). The resume side folds
|
|
1209
|
+
* **persisted OR live declaration** (a monotonic join, never a downgrade): a run minted under a
|
|
1210
|
+
* declaring adapter keeps its memory fold and its prompt disclosure when resumed on an
|
|
1211
|
+
* undeclared/older adapter — the downgrade is closed in the derivation itself, deliberately
|
|
1212
|
+
* WITHOUT a new refusal arm (one fewer failure path, same safety). Feeds the memory face and the
|
|
1213
|
+
* prompt disclosure only — no gate/policy/roster behavior reads it (the design/378 red line).
|
|
1214
|
+
* Schema ADDITION (additive, Q5 posture; the version-gate residual is shared with
|
|
1215
|
+
* {@link placementRootSessionId} above and owned by the same version-lattice ticket).
|
|
1216
|
+
*/
|
|
1217
|
+
externalContentTarget?: true;
|
|
1163
1218
|
}
|
|
1164
1219
|
/**
|
|
1165
1220
|
* {@link CheckpointState} with **every** field made required-PRESENT, while each value keeps its original
|
|
@@ -1869,6 +1924,30 @@ export declare class CheckpointError extends Error {
|
|
|
1869
1924
|
* refusal exists to close. Rejected PRE-CAS (the checkpoint stays `pending`); re-resume with the
|
|
1870
1925
|
* original principal, or omit the field to inherit the recorded one. */
|
|
1871
1926
|
| "resume.principal_mismatch"
|
|
1927
|
+
/** design/380 O1③ (placement identity continuity — the `resume.principal_mismatch` rung's twin
|
|
1928
|
+
* on the placement axis): the resume supplied trusted internals whose EXPLICIT
|
|
1929
|
+
* `placementRoot` differs from the {@link CheckpointState.placementRootSessionId} recorded at
|
|
1930
|
+
* the suspend mint. A target-bound env factory keys its placement lookup on that fixed point,
|
|
1931
|
+
* so silently resuming under a different one would re-place the leg — and every descendant
|
|
1932
|
+
* the child-internals chain hands it to — on another target than the parked workspace lives
|
|
1933
|
+
* on. A supplied `rootSessionId` deliberately does NOT take this refusal (rescan C1): it is
|
|
1934
|
+
* delegation identity, re-supplied by every revive leg, and legitimately differs from the
|
|
1935
|
+
* recorded root whenever the spawner carried an explicit placement — the recorded root simply
|
|
1936
|
+
* outranks it in prepare's restore fold. Rejected PRE-CAS (the checkpoint stays `pending`);
|
|
1937
|
+
* re-resume with the recorded root, or omit `internals.placementRoot` to inherit it. Either
|
|
1938
|
+
* side absent (an older row / a claim-less resume) skips the check, and `""` reads as absence
|
|
1939
|
+
* — the honest degradation arm. */
|
|
1940
|
+
| "resume.placement_mismatch"
|
|
1941
|
+
/** design/380 O2: the deployment's `RunnerDeps.resumePreflight` refused this resume — an
|
|
1942
|
+
* explicit refusal verdict, a throw, a timeout past its bounded deadline, or an unreadable
|
|
1943
|
+
* answer (all fail-closed). The LAST rung before the CAS, so nothing was consumed. Two arms,
|
|
1944
|
+
* discriminated by the message: the default `retry_later` arm leaves the checkpoint `pending`
|
|
1945
|
+
* and the SAME token redeemable once the obstacle clears (`detail.retryAfterMs` carries the
|
|
1946
|
+
* deployment's wait hint when it gave one — the #449 G1 carrier); the explicit `terminal` arm
|
|
1947
|
+
* has ALREADY settled the row out of `pending` via the single-shot `expire` CAS (reaper
|
|
1948
|
+
* parity) before this throw — the token is NOT redeemable, by the deployment's own permanent
|
|
1949
|
+
* verdict (a dead binding must not be redialed forever). */
|
|
1950
|
+
| "resume.preflight_rejected"
|
|
1872
1951
|
/** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
|
|
1873
1952
|
* constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
|
|
1874
1953
|
* edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
|
|
@@ -1896,8 +1975,11 @@ export declare class CheckpointError extends Error {
|
|
|
1896
1975
|
* discriminant. */
|
|
1897
1976
|
readonly detail?: {
|
|
1898
1977
|
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
|
|
1899
|
-
/** #449 G1 — on `resume.usage_window_exhausted
|
|
1900
|
-
* (the same wait a suspended row carries as `gate.resumeAfterMs`).
|
|
1978
|
+
/** #449 G1 — on `resume.usage_window_exhausted`: how long until the binding window frees
|
|
1979
|
+
* (the same wait a suspended row carries as `gate.resumeAfterMs`). design/380 O2 — on a
|
|
1980
|
+
* retry-later `resume.preflight_rejected`: the deployment preflight's own wait hint, when it
|
|
1981
|
+
* gave one (same carrier, same semantics: retry the SAME token after the wait). Absent on
|
|
1982
|
+
* every other code. */
|
|
1901
1983
|
retryAfterMs?: number;
|
|
1902
1984
|
/** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
|
|
1903
1985
|
* deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
|
|
@@ -2024,6 +2106,30 @@ export declare class CheckpointError extends Error {
|
|
|
2024
2106
|
* refusal exists to close. Rejected PRE-CAS (the checkpoint stays `pending`); re-resume with the
|
|
2025
2107
|
* original principal, or omit the field to inherit the recorded one. */
|
|
2026
2108
|
| "resume.principal_mismatch"
|
|
2109
|
+
/** design/380 O1③ (placement identity continuity — the `resume.principal_mismatch` rung's twin
|
|
2110
|
+
* on the placement axis): the resume supplied trusted internals whose EXPLICIT
|
|
2111
|
+
* `placementRoot` differs from the {@link CheckpointState.placementRootSessionId} recorded at
|
|
2112
|
+
* the suspend mint. A target-bound env factory keys its placement lookup on that fixed point,
|
|
2113
|
+
* so silently resuming under a different one would re-place the leg — and every descendant
|
|
2114
|
+
* the child-internals chain hands it to — on another target than the parked workspace lives
|
|
2115
|
+
* on. A supplied `rootSessionId` deliberately does NOT take this refusal (rescan C1): it is
|
|
2116
|
+
* delegation identity, re-supplied by every revive leg, and legitimately differs from the
|
|
2117
|
+
* recorded root whenever the spawner carried an explicit placement — the recorded root simply
|
|
2118
|
+
* outranks it in prepare's restore fold. Rejected PRE-CAS (the checkpoint stays `pending`);
|
|
2119
|
+
* re-resume with the recorded root, or omit `internals.placementRoot` to inherit it. Either
|
|
2120
|
+
* side absent (an older row / a claim-less resume) skips the check, and `""` reads as absence
|
|
2121
|
+
* — the honest degradation arm. */
|
|
2122
|
+
| "resume.placement_mismatch"
|
|
2123
|
+
/** design/380 O2: the deployment's `RunnerDeps.resumePreflight` refused this resume — an
|
|
2124
|
+
* explicit refusal verdict, a throw, a timeout past its bounded deadline, or an unreadable
|
|
2125
|
+
* answer (all fail-closed). The LAST rung before the CAS, so nothing was consumed. Two arms,
|
|
2126
|
+
* discriminated by the message: the default `retry_later` arm leaves the checkpoint `pending`
|
|
2127
|
+
* and the SAME token redeemable once the obstacle clears (`detail.retryAfterMs` carries the
|
|
2128
|
+
* deployment's wait hint when it gave one — the #449 G1 carrier); the explicit `terminal` arm
|
|
2129
|
+
* has ALREADY settled the row out of `pending` via the single-shot `expire` CAS (reaper
|
|
2130
|
+
* parity) before this throw — the token is NOT redeemable, by the deployment's own permanent
|
|
2131
|
+
* verdict (a dead binding must not be redialed forever). */
|
|
2132
|
+
| "resume.preflight_rejected"
|
|
2027
2133
|
/** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
|
|
2028
2134
|
* constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
|
|
2029
2135
|
* edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
|
|
@@ -2051,8 +2157,11 @@ export declare class CheckpointError extends Error {
|
|
|
2051
2157
|
* discriminant. */
|
|
2052
2158
|
detail?: {
|
|
2053
2159
|
field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
|
|
2054
|
-
/** #449 G1 — on `resume.usage_window_exhausted
|
|
2055
|
-
* (the same wait a suspended row carries as `gate.resumeAfterMs`).
|
|
2160
|
+
/** #449 G1 — on `resume.usage_window_exhausted`: how long until the binding window frees
|
|
2161
|
+
* (the same wait a suspended row carries as `gate.resumeAfterMs`). design/380 O2 — on a
|
|
2162
|
+
* retry-later `resume.preflight_rejected`: the deployment preflight's own wait hint, when it
|
|
2163
|
+
* gave one (same carrier, same semantics: retry the SAME token after the wait). Absent on
|
|
2164
|
+
* every other code. */
|
|
2056
2165
|
retryAfterMs?: number;
|
|
2057
2166
|
/** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
|
|
2058
2167
|
* deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
|