@sema-agent/core 5.65.0 → 7.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 +58 -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 +18 -0
- 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 +13 -3
- package/dist/core/governance-codes.js +15 -0
- package/dist/core/hooks.d.ts +39 -7
- package/dist/core/hooks.js +38 -21
- 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 +3 -0
- package/dist/core/memory-engine/engine.d.ts +328 -15
- package/dist/core/memory-engine/engine.js +355 -29
- 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 +64 -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 +62 -2
- package/dist/core/permission-rule-consent.js +105 -14
- package/dist/core/permission-rule-model.d.ts +118 -16
- package/dist/core/permission-rule-model.js +57 -9
- package/dist/core/permission-rule-session.d.ts +124 -0
- package/dist/core/permission-rule-session.js +121 -0
- package/dist/core/permission-rule-store.d.ts +65 -2
- package/dist/core/permission-rule-store.js +75 -16
- package/dist/core/permission-rule-sync.d.ts +9 -0
- package/dist/core/permission-rule-sync.js +52 -19
- 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 +110 -5
- package/dist/core/runner/prepare-hands-readface.js +99 -7
- package/dist/core/runner/prepare-memory.d.ts +88 -0
- package/dist/core/runner/prepare-memory.js +305 -24
- package/dist/core/runner/prepare-task.d.ts +141 -1
- package/dist/core/runner/prepare-task.js +478 -81
- package/dist/core/runner/runtask.d.ts +9 -20
- package/dist/core/runner/runtask.js +133 -96
- 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/store-contracts/permission-rule-sync-contract.js +15 -1
- package/dist/core/task-registry-shared.js +11 -1
- 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 +31 -2
- package/dist/core/types.d.ts +795 -89
- 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 +6 -2
- package/dist/core/workflow-run-store.d.ts +4 -1
- 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 +26 -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 +20 -9
- package/dist/index.js +16 -7
- 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 +78 -2
- package/dist/orchestration/workflow.d.ts +20 -0
- package/dist/orchestration/workflow.js +163 -14
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompts/default.d.ts +7 -7
- 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.d.ts +11 -0
- package/dist/stores/file/permission-rule-store.js +23 -9
- 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 +169 -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
|
|
@@ -67,6 +67,32 @@ export declare function contextEditFrontier(window: number): number;
|
|
|
67
67
|
* re-run when the frontier pass is off → in-turn forced compaction behind the adopt seam → trim
|
|
68
68
|
* demoted to the disaster-only last resort), so the default configuration never hands an
|
|
69
69
|
* under-20k refusal straight to a message-dropping trim.
|
|
70
|
+
*
|
|
71
|
+
* WHOLE-MACHINE CROSS-CHECK against the 2.1.250 corpus (design/374 slice 4 — the "the knob number
|
|
72
|
+
* was copied, the machine was not" disease is checked by naming the machine's parts, not the one
|
|
73
|
+
* number). CC's four load-bearing parts, each against its 250 coordinate, all verified present here:
|
|
74
|
+
* ① the ≥20000-token minimum-savings gate — {@link MIN_CLEAR_SAVINGS_TOKENS} vs `Mnn`
|
|
75
|
+
* (cli250.js:41714, `BBn`: `if (tokensSaved < Mnn) return null`). CC gates TWICE (execution +
|
|
76
|
+
* before asking the server to reject); sema has one point because the "ask for the rejection"
|
|
77
|
+
* half needs a cooperating server (see ④).
|
|
78
|
+
* ② the nine-member clearable whitelist — {@link COMPACTABLE_TOOLS} vs `eQt` (cli250.js:41714),
|
|
79
|
+
* re-resolved member-by-member off the 250 corpus, byte-identical (see that constant).
|
|
80
|
+
* ③ cleared-but-recoverable — CC replaces the content with a POINTER, not a void
|
|
81
|
+
* (`<persisted-output>Tool result saved to: {path}\n\nUse Read to view</persisted-output>`,
|
|
82
|
+
* cli250.js:40044), which is what makes a keep window of 5 survivable at all. Ours is the same
|
|
83
|
+
* construction in this engine's carrier: the full text is offloaded first and the marker
|
|
84
|
+
* carries the store ref ({@link ContextEditOptions.offload} → {@link clearOneResult}), read
|
|
85
|
+
* back through ReadToolResult instead of a filesystem path (registered divergence D-9). The
|
|
86
|
+
* runner wires it on BOTH arms by default — the frontier pass and the MC-R rejection arm share
|
|
87
|
+
* one persist closure — so the shipped default clears to a pointer, never to a dead end.
|
|
88
|
+
* ④ the TRIGGER — CC's client machine fires ONLY on the server's `context_hint` 422/424 refusal
|
|
89
|
+
* (cli250.js:58375; `tengu_time_based_microcompact` is a historical event name, its `trigger`
|
|
90
|
+
* is the constant `"context_hint"`). Under BYOM there is no such server, so the ask half is
|
|
91
|
+
* unportable and the refusal half is aligned on wire truth (the MC-R arm classifies the
|
|
92
|
+
* provider's own context-overflow refusal) — registered divergence D-3. sema additionally
|
|
93
|
+
* keeps a PROACTIVE frontier trigger CC's client does not have, anchored to Anthropic's
|
|
94
|
+
* platform context-management policy rather than to CC-CLI — registered divergence D-1, and
|
|
95
|
+
* re-stated at {@link clearStaleToolResults}'s ANCHOR paragraph.
|
|
70
96
|
*/
|
|
71
97
|
export type ContextEditMachine = "legacy" | "cc";
|
|
72
98
|
/**
|
|
@@ -82,8 +108,10 @@ export type ContextEditMachine = "legacy" | "cc";
|
|
|
82
108
|
* exactly the terminal-batch shape that needs it. This is why the slice-3 "keep default 3→5"
|
|
83
109
|
* flip rides the MACHINE default, not this constant: painting 5 onto the opt-out machine would
|
|
84
110
|
* recreate the probed failure inside the escape hatch.
|
|
85
|
-
* - CC machine (the default since slice 3): 5 (2.1.223 `uAp`;
|
|
86
|
-
*
|
|
111
|
+
* - CC machine (the default since slice 3): 5 (2.1.223 `uAp`; 2.1.250 `var k = 5` at the call site
|
|
112
|
+
* cli250.js:58375, and 2.1.245 carries the same value — zero drift across 245→250; same value
|
|
113
|
+
* again on CC 88's `timeBasedMCConfig`, i.e. stable across all three corpus generations, and it
|
|
114
|
+
* is a hard-coded constant upstream, not a setting). The #384 adjudication adopted 5
|
|
87
115
|
* CONDITIONAL on the whole machine coming with it (20k gate + deep clear + re-ordered
|
|
88
116
|
* backstops); the cc machine carries that condition, and slice 3's backstop re-ordering
|
|
89
117
|
* completed it — 5 is now the effective default keep, and the 200k probe above is answered by
|
|
@@ -92,7 +120,8 @@ export type ContextEditMachine = "legacy" | "cc";
|
|
|
92
120
|
*/
|
|
93
121
|
export declare const DEFAULT_KEEP_RECENT_TOOL_RESULTS = 3;
|
|
94
122
|
export declare const CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS = 5;
|
|
95
|
-
/** design/374 §3.1 — CC's minimum-savings gate (`vUs` :397773
|
|
123
|
+
/** design/374 §3.1 — CC's minimum-savings gate (`vUs` :397773; 2.1.250 `Mnn` cli250.js:41714, same
|
|
124
|
+
* value): the cc machine refuses to clear at
|
|
96
125
|
* all unless the structural estimate of what WOULD be cleared reaches this many tokens. Rationale
|
|
97
126
|
* (independent of "CC does it"): every clear breaks the provider prefix cache once, so a clear that
|
|
98
127
|
* saves less than the re-prefill it causes is a net loss; the gate prices that trade. The LEGACY
|
|
@@ -105,7 +134,18 @@ export declare const MIN_KEEP_RECENT_TOOL_RESULTS = 1;
|
|
|
105
134
|
* checklist #54 (CC microCompact.ts:41-50 `COMPACTABLE_TOOLS` parity): the INCLUSION whitelist of
|
|
106
135
|
* tools whose results may be content-cleared by the stale-result pass. CC's set is exactly
|
|
107
136
|
* { Read, Bash, PowerShell (SHELL_TOOL_NAMES), Grep, Glob, WebSearch, WebFetch, Edit, Write } —
|
|
108
|
-
* cheap-to-regenerate I/O.
|
|
137
|
+
* cheap-to-regenerate I/O.
|
|
138
|
+
*
|
|
139
|
+
* RE-VERIFIED against the 2.1.250 corpus (design/374 slice 4, member-by-member — a set is exactly
|
|
140
|
+
* the shape where "the count still matches" is not evidence): `eQt = new Set([wt, ...VD, Yo, Xo,
|
|
141
|
+
* yO, Yr, tn, er])` at cli250.js:41714, whose aliases resolve to `wt`="Read", `VD`=[`Xe`="Bash",
|
|
142
|
+
* `Nt`="PowerShell"], `Yo`="Grep", `Xo`="Glob", `yO`="WebSearch", `Yr`="WebFetch", `tn`="Edit",
|
|
143
|
+
* `er`="Write" — nine members, byte-identical to the set below, zero drift from the 223 reading.
|
|
144
|
+
* The exclusion side is the load-bearing half and is unchanged too: everything NOT here is
|
|
145
|
+
* PRESERVED (Agent/Task/Skill/ToolSearch/TodoWrite/Monitor/SendMessage/NotebookEdit and ALL MCP
|
|
146
|
+
* tools are never content-cleared by this pass, upstream or here).
|
|
147
|
+
*
|
|
148
|
+
* Everything else is PRESERVED: expensive/decision-bearing results
|
|
109
149
|
* (Agent subagent reports, Task* registry state, TodoWrite acks, Monitor, MCP tools, custom
|
|
110
150
|
* ToolSpecs) survive to the harder defenses (compaction summarizes them instead of blanking them).
|
|
111
151
|
* PowerShell is kept for CC fidelity even though sema does not mount it. Overridable per call via
|
|
@@ -184,7 +224,9 @@ export interface ContextEditOptions {
|
|
|
184
224
|
* Absent ⇒ legacy structural gate + accounting (byte-identical behavior).
|
|
185
225
|
*/
|
|
186
226
|
anchoredTotalTokens?: number;
|
|
187
|
-
/** design/123 D2 — structural coefficient (`Model.charsPerToken`); default 4
|
|
227
|
+
/** design/123 D2 — structural coefficient (`Model.charsPerToken`); default 3 (design/374 slice 4;
|
|
228
|
+
* it was 4 before). Note this does NOT reach the cc machine's ≥20k gate, which is CC-exactly
|
|
229
|
+
* hard-coded to 4 — see {@link structuralClearSavings}. */
|
|
188
230
|
charsPerToken?: number;
|
|
189
231
|
/**
|
|
190
232
|
* checklist #54 — which tools' results are clearable. Defaults to {@link COMPACTABLE_TOOLS}
|
|
@@ -16,7 +16,7 @@ import type { Model } from "../internal/llm.js";
|
|
|
16
16
|
* message AT/BEFORE the anchor reduces it by ZERO (the anchor usage was billed with that content —
|
|
17
17
|
* a request-view drop only pays off on the NEXT request), so the trim keeps cutting deeper until
|
|
18
18
|
* the anchored total meets the budget or only the minimal tail is left. Absent ⇒ legacy structural
|
|
19
|
-
* behavior, byte-identical. `charsPerToken` (design/123 D2): structural coefficient, default 4.
|
|
19
|
+
* behavior, byte-identical. `charsPerToken` (design/123 D2): structural coefficient, default 3 (design/374 slice 4).
|
|
20
20
|
*/
|
|
21
21
|
export declare function trimToBudget(messages: AgentMessage[], budgetTokens: number, anchoredTotalTokens?: number, charsPerToken?: number): AgentMessage[];
|
|
22
22
|
/**
|