@sema-agent/core 7.19.0 → 7.20.1
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 +199 -2
- package/dist/agents/subagent.js +12 -20
- package/dist/agents/teacher.js +2 -5
- package/dist/agents/verify.js +21 -10
- package/dist/brain/anthropic.js +3 -1
- package/dist/brain/open-responses.js +4 -2
- package/dist/brain/openai.js +3 -1
- package/dist/brain/stream-engine.d.ts +4 -0
- package/dist/brain/stream-engine.js +4 -0
- package/dist/core/auto-mode.d.ts +37 -18
- package/dist/core/auto-mode.js +1 -1
- package/dist/core/child-clamp-seats.d.ts +79 -0
- package/dist/core/child-clamp-seats.js +29 -0
- package/dist/core/engine-notice.d.ts +76 -49
- package/dist/core/fs-write-gate-policy.d.ts +17 -9
- package/dist/core/fs-write-gate-policy.js +51 -4
- package/dist/core/gate-bounded-await.d.ts +38 -0
- package/dist/core/gate-bounded-await.js +53 -0
- package/dist/core/gate-fold.js +16 -48
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +9 -6
- package/dist/core/governance-codes.d.ts +16 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +8 -8
- package/dist/core/memory-engine/engine.d.ts +14 -9
- package/dist/core/memory-engine/engine.js +21 -8
- package/dist/core/memory-engine/layout.d.ts +31 -0
- package/dist/core/memory-engine/layout.js +68 -3
- package/dist/core/memory-engine/tools.js +6 -0
- package/dist/core/memory-engine/types.d.ts +24 -0
- package/dist/core/memory.d.ts +19 -0
- package/dist/core/memory.js +12 -0
- package/dist/core/remote-env.d.ts +32 -0
- package/dist/core/remote-env.js +4 -1
- package/dist/core/runner/advertised-writable-dirs.d.ts +59 -1
- package/dist/core/runner/advertised-writable-dirs.js +51 -3
- package/dist/core/runner/announce-once-ledger.d.ts +15 -0
- package/dist/core/runner/announce-once-ledger.js +8 -0
- package/dist/core/runner/classifier-ladder.d.ts +5 -0
- package/dist/core/runner/classifier-ladder.js +13 -2
- package/dist/core/runner/contracts.d.ts +44 -1
- package/dist/core/runner/denial-limit-arms.d.ts +14 -1
- package/dist/core/runner/denial-limit-arms.js +5 -4
- package/dist/core/runner/env-capability-census.d.ts +30 -0
- package/dist/core/runner/env-capability-census.js +38 -0
- package/dist/core/runner/forward-sink.d.ts +32 -0
- package/dist/core/runner/forward-sink.js +41 -0
- package/dist/core/runner/gate-exit.d.ts +4 -0
- package/dist/core/runner/gate-exit.js +4 -1
- package/dist/core/runner/git-status-frame.d.ts +0 -9
- package/dist/core/runner/listing-fold.d.ts +57 -0
- package/dist/core/runner/listing-fold.js +75 -0
- package/dist/core/runner/prepare-execution-env.d.ts +1 -1
- package/dist/core/runner/prepare-execution-env.js +2 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
- package/dist/core/runner/prepare-hands-readface.js +3 -5
- package/dist/core/runner/prepare-listings.d.ts +3 -0
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory.js +7 -1
- package/dist/core/runner/prepare-policy-chain.js +5 -5
- package/dist/core/runner/prepare-prompt-inputs.d.ts +2 -2
- package/dist/core/runner/prepare-run-refs.d.ts +4 -2
- package/dist/core/runner/prepare-run-refs.js +5 -4
- package/dist/core/runner/prepare-task.js +8 -6
- package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -1
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/run-clock-and-content.js +3 -7
- package/dist/core/runner/run-harness-handlers.js +10 -10
- package/dist/core/runner/run-leg.js +28 -19
- package/dist/core/runner/run-settle-and-teardown.js +2 -5
- package/dist/core/runner/run-turn-boundary.js +44 -33
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/synthetic-tools.d.ts +13 -39
- package/dist/core/runner/synthetic-tools.js +15 -59
- package/dist/core/runner/task-root-canonical.d.ts +45 -0
- package/dist/core/runner/task-root-canonical.js +18 -0
- package/dist/core/runner/turn-attachments.d.ts +125 -49
- package/dist/core/runner/turn-attachments.js +124 -66
- package/dist/core/runner/write-protection-station.d.ts +59 -0
- package/dist/core/runner/write-protection-station.js +32 -0
- package/dist/core/safe-notify.d.ts +5 -0
- package/dist/core/safe-notify.js +24 -2
- package/dist/core/sensitive-path-policy.d.ts +53 -27
- package/dist/core/sensitive-path-policy.js +16 -26
- package/dist/core/skill-listing-budget.d.ts +34 -40
- package/dist/core/skill-listing-budget.js +25 -26
- package/dist/core/skills-directory.js +17 -10
- package/dist/core/task-registry.js +2 -1
- package/dist/core/task-spec.d.ts +8 -7
- package/dist/core/tool-policy.d.ts +12 -1
- package/dist/core/tool-policy.js +39 -25
- package/dist/core/wiring-manifest.d.ts +31 -0
- package/dist/core/wiring-manifest.js +1 -0
- package/dist/core/write-protect.d.ts +124 -18
- package/dist/core/write-protect.js +91 -34
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -5
- package/dist/orchestration/workflow-primitives.js +10 -20
- package/dist/stores/file/memory-store.d.ts +5 -2
- package/dist/stores/file/memory-store.js +8 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +13 -2
- package/dist/tools/fs/bash-readonly-classifier.js +318 -36
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +12 -25
- package/dist/tools/fs/fs-shared.d.ts +22 -0
- package/dist/tools/fs/fs-shared.js +30 -0
- package/dist/tools/fs/index.d.ts +1 -1
- package/dist/tools/fs/index.js +1 -1
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +25 -3
- package/wire-changes/core@7.20.0.json +568 -0
- package/wire-changes/core@7.20.1.json +183 -0
package/dist/agents/verify.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { CODE_AGENT_PROMPT } from "../prompts/default.js";
|
|
3
3
|
import { releaseSession } from "./session-util.js";
|
|
4
|
+
import { childClampSeats } from "../core/child-clamp-seats.js";
|
|
4
5
|
import { mapNestedSuspend, isDurablePause, pauseFamily } from "./suspend-guard.js";
|
|
5
6
|
import { delimitUntrusted, sanitizeUntrustedText } from "../core/untrusted-text.js";
|
|
6
7
|
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
@@ -136,17 +137,21 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
136
137
|
const runVerifier = async (impl, round) => {
|
|
137
138
|
refuseUnhonorableInternals(internals, "before_leg");
|
|
138
139
|
const roundEvidence = round === 1 ? config.evidence : undefined;
|
|
139
|
-
const
|
|
140
|
+
const verifierSpec = {
|
|
140
141
|
objective: verifierObjective(objective, impl, roundEvidence),
|
|
141
142
|
systemPrompt: verifierPrompt,
|
|
142
143
|
...(config.verifierModel ? { model: config.verifierModel } : { modelRole: "verifier" }),
|
|
143
144
|
roles: specBase.roles,
|
|
144
145
|
tools: verifierTools,
|
|
145
146
|
handsReadOnly: config.verifierHandsReadOnly ?? true,
|
|
146
|
-
...(foldedRootsFace ? { readFace: "roots" } : {}),
|
|
147
|
-
...(foldedRootsWriteFace ? { writeFace: "roots" } : {}),
|
|
148
|
-
...(foldedReadDeny.length > 0 ? { readDenyPatterns: [...foldedReadDeny] } : {}),
|
|
149
147
|
interactiveTools: false,
|
|
148
|
+
...childClampSeats({
|
|
149
|
+
handsReadOnly: specBase.handsReadOnly,
|
|
150
|
+
interactiveTools: specBase.interactiveTools,
|
|
151
|
+
readFace: foldedRootsFace ? "roots" : undefined,
|
|
152
|
+
writeFace: foldedRootsWriteFace ? "roots" : undefined,
|
|
153
|
+
readDenyPatterns: foldedReadDeny,
|
|
154
|
+
}),
|
|
150
155
|
outputSchema: VerdictSchema,
|
|
151
156
|
enableBlockedReport: false,
|
|
152
157
|
limits: { ...(specBase.limits?.maxWalltimeMs !== undefined ? { maxWalltimeMs: specBase.limits.maxWalltimeMs } : {}) },
|
|
@@ -167,7 +172,8 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
167
172
|
...(specBase.clientContext !== undefined ? { clientContext: { ...specBase.clientContext } } : {}),
|
|
168
173
|
...(specBase.promptProfile !== undefined ? { promptProfile: specBase.promptProfile } : {}),
|
|
169
174
|
signal: specBase.signal,
|
|
170
|
-
}
|
|
175
|
+
};
|
|
176
|
+
const v = await runner.runTask(verifierSpec, legInternals());
|
|
171
177
|
try {
|
|
172
178
|
const cost = (v.stats.costMicroUsd ?? 0) + (v.stats.nested?.costMicroUsd ?? 0);
|
|
173
179
|
return { verdict: v.structuredOutput, cost, ...(isDurablePause(v.terminal) ? { paused: { ...v, terminal: v.terminal } } : {}) };
|
|
@@ -241,14 +247,19 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
241
247
|
if (config.costCeilingMicroUsd != null && spend >= config.costCeilingMicroUsd)
|
|
242
248
|
break;
|
|
243
249
|
refuseUnhonorableInternals(internals, "before_leg");
|
|
244
|
-
|
|
250
|
+
const fixLegSpec = {
|
|
245
251
|
...specBase,
|
|
246
252
|
sessionId: current.sessionId,
|
|
247
253
|
objective: fixObjective(outcome.findings),
|
|
248
|
-
...(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
254
|
+
...childClampSeats({
|
|
255
|
+
handsReadOnly: specBase.handsReadOnly,
|
|
256
|
+
interactiveTools: specBase.interactiveTools,
|
|
257
|
+
readFace: foldedRootsFace ? "roots" : specBase.readFace,
|
|
258
|
+
writeFace: foldedRootsWriteFace ? "roots" : specBase.writeFace,
|
|
259
|
+
readDenyPatterns: foldedReadDeny,
|
|
260
|
+
}),
|
|
261
|
+
};
|
|
262
|
+
current = await runner.runTask(fixLegSpec, legInternals());
|
|
252
263
|
spend += (current.stats.costMicroUsd ?? 0) + (current.stats.nested?.costMicroUsd ?? 0);
|
|
253
264
|
implAccount.add(current.stats);
|
|
254
265
|
if (isDurablePause(current.terminal)) {
|
package/dist/brain/anthropic.js
CHANGED
|
@@ -365,6 +365,7 @@ export function createAnthropicBrain(config = {}) {
|
|
|
365
365
|
const blocks = new Map();
|
|
366
366
|
const usageRaw = {};
|
|
367
367
|
let stopReason = null;
|
|
368
|
+
const terminalFrameSeen = () => stopReason != null;
|
|
368
369
|
let stopDetails;
|
|
369
370
|
let nextContentIndex = 0;
|
|
370
371
|
let malformedFrames = 0;
|
|
@@ -532,6 +533,7 @@ export function createAnthropicBrain(config = {}) {
|
|
|
532
533
|
hasSubstantiveText,
|
|
533
534
|
hasCompletedToolCall,
|
|
534
535
|
hasOnlyThinking: hasThinking && !hasSubstantiveText && !hasCompletedToolCall,
|
|
536
|
+
terminalFrameSeen: terminalFrameSeen(),
|
|
535
537
|
};
|
|
536
538
|
},
|
|
537
539
|
sealForRetry() {
|
|
@@ -666,7 +668,7 @@ export function createAnthropicBrain(config = {}) {
|
|
|
666
668
|
emptyNoFinish ||
|
|
667
669
|
(toolError !== undefined && noUsableContent) ||
|
|
668
670
|
(truncatedEmpty && !dynamicCut && !staticReasoningCut);
|
|
669
|
-
const tornWithContent = !errored && opts?.truncated !== true &&
|
|
671
|
+
const tornWithContent = !errored && opts?.truncated !== true && !terminalFrameSeen() && !noUsableContent && !degenerate;
|
|
670
672
|
const errorMessageOut = tornWithContent ? (errorMessage ? `${errorMessage}\n${STREAM_TORN_WARNING}` : STREAM_TORN_WARNING) : errorMessage;
|
|
671
673
|
const errorKind = degenerate
|
|
672
674
|
? "degenerate"
|
|
@@ -303,6 +303,7 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
303
303
|
let usageRaw;
|
|
304
304
|
let responseId;
|
|
305
305
|
let terminalStatus;
|
|
306
|
+
const terminalFrameSeen = () => terminalStatus !== undefined;
|
|
306
307
|
let incompleteReason;
|
|
307
308
|
let accumRefusal = "";
|
|
308
309
|
const capabilityNotes = [];
|
|
@@ -573,6 +574,7 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
573
574
|
hasSubstantiveText,
|
|
574
575
|
hasCompletedToolCall,
|
|
575
576
|
hasOnlyThinking: hasReasoning && !hasSubstantiveText && !hasCompletedToolCall,
|
|
577
|
+
terminalFrameSeen: terminalFrameSeen(),
|
|
576
578
|
};
|
|
577
579
|
},
|
|
578
580
|
sealForRetry() {
|
|
@@ -690,7 +692,7 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
690
692
|
const safetyCut = incompleteReason === INCOMPLETE_CONTENT_FILTER;
|
|
691
693
|
const refused = accumRefusal.trim().length > 0;
|
|
692
694
|
const incompleteUnmapped = terminalStatus === "incomplete" && incompleteReason !== INCOMPLETE_MAX_OUTPUT_TOKENS && incompleteReason !== INCOMPLETE_CONTENT_FILTER;
|
|
693
|
-
const emptyNoFinish = noUsableContent && (
|
|
695
|
+
const emptyNoFinish = noUsableContent && (!terminalFrameSeen() || incompleteUnmapped) && !degenerate;
|
|
694
696
|
const maxTokensNote = sentMaxTokens !== undefined
|
|
695
697
|
? `effective max_output_tokens=${sentMaxTokens} (from ${sentMaxTokensLane === "options"
|
|
696
698
|
? "options.maxTokens"
|
|
@@ -718,7 +720,7 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
718
720
|
emptyNoFinish ||
|
|
719
721
|
(toolError !== undefined && noUsableContent) ||
|
|
720
722
|
(truncatedEmpty && !dynamicCut && !staticReasoningCut);
|
|
721
|
-
const tornWithContent = !errored && opts?.truncated !== true && (
|
|
723
|
+
const tornWithContent = !errored && opts?.truncated !== true && (!terminalFrameSeen() || incompleteUnmapped) && !noUsableContent && !degenerate;
|
|
722
724
|
const partialWarning = terminalStatus === undefined ? STREAM_TORN_WARNING : RESPONSE_INCOMPLETE_WARNING;
|
|
723
725
|
const errorMessageOut = tornWithContent ? (errorMessage ? `${errorMessage}\n${partialWarning}` : partialWarning) : errorMessage;
|
|
724
726
|
const errorKind = degenerate
|
package/dist/brain/openai.js
CHANGED
|
@@ -373,6 +373,7 @@ export function createOpenAIBrain(config = {}) {
|
|
|
373
373
|
let textIndex = 0;
|
|
374
374
|
const toolAccum = new Map();
|
|
375
375
|
let finishReason = null;
|
|
376
|
+
const terminalFrameSeen = () => finishReason != null;
|
|
376
377
|
let usageRaw;
|
|
377
378
|
let malformedFrames = 0;
|
|
378
379
|
let malformedSample;
|
|
@@ -438,6 +439,7 @@ export function createOpenAIBrain(config = {}) {
|
|
|
438
439
|
hasSubstantiveText,
|
|
439
440
|
hasCompletedToolCall,
|
|
440
441
|
hasOnlyThinking: accumReasoning.length > 0 && !hasSubstantiveText && !hasCompletedToolCall,
|
|
442
|
+
terminalFrameSeen: terminalFrameSeen(),
|
|
441
443
|
};
|
|
442
444
|
},
|
|
443
445
|
sealForRetry() {
|
|
@@ -619,7 +621,7 @@ export function createOpenAIBrain(config = {}) {
|
|
|
619
621
|
emptyNoFinish ||
|
|
620
622
|
(toolError !== undefined && noUsableContent) ||
|
|
621
623
|
(truncatedEmpty && !dynamicCut);
|
|
622
|
-
const tornWithContent = !errored && opts?.truncated !== true &&
|
|
624
|
+
const tornWithContent = !errored && opts?.truncated !== true && !terminalFrameSeen() && !noUsableContent && !degenerate;
|
|
623
625
|
const errorMessageOut = tornWithContent ? (errorMessage ? `${errorMessage}\n${STREAM_TORN_WARNING}` : STREAM_TORN_WARNING) : errorMessage;
|
|
624
626
|
const errorKind = degenerate
|
|
625
627
|
? "degenerate"
|
|
@@ -194,11 +194,15 @@ export interface StreamParser {
|
|
|
194
194
|
* - `hasOnlyThinking` — thinking streamed but nothing substantive (no non-blank text, no completed
|
|
195
195
|
* tool call) → tier B (seal + whole-turn retry). A dangling tool-call accumulation alongside the
|
|
196
196
|
* thinking does not veto this (nothing was admitted; the retry's snapshot replacement drops it).
|
|
197
|
+
* - `terminalFrameSeen` — the provider TERMINATED this stream (openai `finish_reason`, anthropic
|
|
198
|
+
* `message_delta.stop_reason`, responses `response.completed`/`response.incomplete`). A FACT, not a tier:
|
|
199
|
+
* the parser reports it off the same state its own finalize reads, and the tiering owns what it means.
|
|
197
200
|
*/
|
|
198
201
|
snapshot(): {
|
|
199
202
|
hasSubstantiveText: boolean;
|
|
200
203
|
hasCompletedToolCall: boolean;
|
|
201
204
|
hasOnlyThinking: boolean;
|
|
205
|
+
terminalFrameSeen: boolean;
|
|
202
206
|
};
|
|
203
207
|
/**
|
|
204
208
|
* design/124 §0.5-2 tier B: seal the UI stream before a whole-turn retry — emit `thinking_end` for
|
|
@@ -647,6 +647,10 @@ export function runStreamingBrain(args) {
|
|
|
647
647
|
return;
|
|
648
648
|
}
|
|
649
649
|
const snap = parser.snapshot();
|
|
650
|
+
if (snap.terminalFrameSeen) {
|
|
651
|
+
parser.finalize();
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
650
654
|
if (snap.hasSubstantiveText || snap.hasCompletedToolCall) {
|
|
651
655
|
midstreamWarning =
|
|
652
656
|
failure.kind === "stalled"
|
package/dist/core/auto-mode.d.ts
CHANGED
|
@@ -13,10 +13,12 @@ export declare function isAutoModeUnavailableCause(v: unknown): v is AutoModeUna
|
|
|
13
13
|
/**
|
|
14
14
|
* HOW a reply failed to carry a verdict — the closed set behind `parse_error.form`. `missing` = the reader found
|
|
15
15
|
* NO answer (empty text, prose, an answer only inside a thinking block); `conflict` = it found TWO distinct
|
|
16
|
-
* `<block>` answers (raw or thinking-stripped). The split
|
|
17
|
-
* answer is asked for again (CC 2.1.259 `Vrr` re-asks an
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* `<block>` answers (raw or thinking-stripped). The split decides who gets a SECOND ATTEMPT at the same
|
|
17
|
+
* question, on both seats that offer one: a missing answer is asked for again (CC 2.1.259 `Vrr` re-asks an
|
|
18
|
+
* unparseable reply) and handed on to the next candidate ({@link rungDidNotRule}); a conflict gets neither —
|
|
19
|
+
* it carries a `yes`, and any second attempt that answered `no` would erase it. One rule, two seats: whatever
|
|
20
|
+
* a re-ask may not overwrite, a later rung may not overwrite either. Not on any wire face; the trace frame and
|
|
21
|
+
* the gate record keep the one word `parse_error`.
|
|
20
22
|
*/
|
|
21
23
|
export declare const AUTO_MODE_PARSE_FAILURES: readonly ["missing", "conflict"];
|
|
22
24
|
export type AutoModeParseFailure = (typeof AUTO_MODE_PARSE_FAILURES)[number];
|
|
@@ -26,8 +28,9 @@ export declare function isAutoModeParseFailure(v: unknown): v is AutoModeParseFa
|
|
|
26
28
|
* WHY a classification moved DOWN the candidate ladder — the closed set behind
|
|
27
29
|
* {@link AutoModeClassifierRound}'s `fallback.cause`, i.e. the ways a candidate produces NO RULING
|
|
28
30
|
* ({@link rungDidNotRule}). Two words are the unavailable causes verbatim (the leg threw / rejected; the
|
|
29
|
-
* round-trip cap elapsed); `parse_error` is the candidate that ANSWERED
|
|
30
|
-
*
|
|
31
|
+
* round-trip cap elapsed); `parse_error` is the candidate that ANSWERED with no answer in it at all (the
|
|
32
|
+
* `missing` form). A ruling is not a member, and neither is a CONTRADICTORY reply (`conflict`): the ladder
|
|
33
|
+
* stops at a rung that settled the classification, whichever way it settled it.
|
|
31
34
|
*/
|
|
32
35
|
export declare const AUTO_MODE_FALLBACK_CAUSES: readonly ["error", "timeout", "parse_error"];
|
|
33
36
|
export type AutoModeFallbackCause = (typeof AUTO_MODE_FALLBACK_CAUSES)[number];
|
|
@@ -65,8 +68,9 @@ export type AutoModeVerdict = ({
|
|
|
65
68
|
} & AutoModeRoundAttributed)
|
|
66
69
|
/** The model responded but not in the `<block>…` contract shape. Handled as a BLOCK with the CC
|
|
67
70
|
* parse-failure sentence ({@link classifierParseFailureDenyMessage}), counted by the denial limit. `form`
|
|
68
|
-
* says WHICH way the reply failed ({@link AutoModeParseFailure})
|
|
69
|
-
* reply once and
|
|
71
|
+
* says WHICH way the reply failed ({@link AutoModeParseFailure}) and decides who may try the question a
|
|
72
|
+
* second time: the classify leg re-asks a `missing` reply once and the ladder hands it to the next rung;
|
|
73
|
+
* a `conflict` gets neither ({@link rungDidNotRule}). */
|
|
70
74
|
| ({
|
|
71
75
|
kind: "parse_error";
|
|
72
76
|
raw: string;
|
|
@@ -100,20 +104,18 @@ export interface AutoModeClassifierRound {
|
|
|
100
104
|
* the QUESTION (the transcript window every rung shares), not in a rung's answer.
|
|
101
105
|
*
|
|
102
106
|
* WHY IT EXISTS. The ladder advances on "the rung produced no ruling", and a hook that rejects looks exactly
|
|
103
|
-
* like that. But a classification whose prompt could not be built fails IDENTICALLY on every rung (the build is
|
|
104
|
-
* shared promise, by contract — "the same prompt, re-asked" is literal), so
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* stops attributing it to models that were never asked.
|
|
107
|
+
* like that. But a classification whose prompt could not be built fails IDENTICALLY on every rung (the build is
|
|
108
|
+
* one shared promise, by contract — "the same prompt, re-asked" is literal), so without this word the walk
|
|
109
|
+
* records a fallback and a deny sentence naming two models for a classification in which NO MODEL WAS EVER
|
|
110
|
+
* DISPATCHED. The verdict is unchanged (fail-closed `unavailable`); what this changes is that the record stops
|
|
111
|
+
* attributing it to models that were never asked.
|
|
109
112
|
*
|
|
110
113
|
* Carried as a `code` on the thrown error rather than a class so any classify hook — including one this
|
|
111
114
|
* package never constructed — can raise it without importing a constructor.
|
|
112
115
|
*
|
|
113
116
|
* WHAT IT DOES NOT COVER, stated: a build that OUTRUNS a rung's cap is still recorded as that rung's
|
|
114
117
|
* `timeout`. The cap bounds the whole classify call, assembly included, so at that point nothing distinguishes
|
|
115
|
-
* a slow transcript store from a slow model.
|
|
116
|
-
* seam change, filed for a later version.
|
|
118
|
+
* a slow transcript store from a slow model.
|
|
117
119
|
*/
|
|
118
120
|
export declare const AUTO_MODE_ROUND_NOT_ASKED_CODE = "auto_mode.round_not_asked";
|
|
119
121
|
/** Mint the marker above around whatever failed, keeping the original as `cause` for the host's own logs.
|
|
@@ -124,8 +126,25 @@ export declare const AUTO_MODE_ROUND_NOT_ASKED_CODE = "auto_mode.round_not_asked
|
|
|
124
126
|
export declare function autoModeRoundNotAsked(reason: unknown): Error & {
|
|
125
127
|
code: string;
|
|
126
128
|
};
|
|
127
|
-
/**
|
|
128
|
-
*
|
|
129
|
+
/**
|
|
130
|
+
* Why a rung did not settle the classification, or `undefined` when it SETTLED it and the ladder stops — the
|
|
131
|
+
* ONE predicate behind the advance (there is no `if (fallback)` arm anywhere).
|
|
132
|
+
*
|
|
133
|
+
* A rung settles the classification two ways: it RULES (`allow` / `block`), or it answers with a
|
|
134
|
+
* CONTRADICTION (`parse_error` of form `conflict` — two distinct `<block>` answers in one reply). The second
|
|
135
|
+
* is not a ruling and stays the fail-closed deny it is; what it is NOT is an unanswered question. It holds a
|
|
136
|
+
* `yes`, so asking the SAME question again — of the same model as a re-ask, or of the next rung as a fallback
|
|
137
|
+
* — can only overwrite that `yes` with a `no` and allow a call the classifier already objected to. One rule
|
|
138
|
+
* for both second attempts: {@link AUTO_MODE_PARSE_FAILURES} names the form, the leg reads it for the re-ask
|
|
139
|
+
* and this predicate reads it for the walk. Only a reply with NO answer in it (`missing`) moves the ladder.
|
|
140
|
+
*
|
|
141
|
+
* @contract auto_mode.ladder.advances_on_no_evidence — the candidate ladder advances on exactly one
|
|
142
|
+
* condition, and it is the ABSENCE of evidence: the rung's leg threw or rejected, its round-trip cap elapsed,
|
|
143
|
+
* or its reply carried no answer at all. A rung that RULED and a rung whose reply CONTRADICTED itself both
|
|
144
|
+
* settle the classification where they stand — the contradiction as the fail-closed `parse_error` deny it
|
|
145
|
+
* already is, attributed to that rung and disclosing no fallback — so no later candidate is ever asked a
|
|
146
|
+
* question an earlier one has already answered `yes` to.
|
|
147
|
+
*/
|
|
129
148
|
export declare function rungDidNotRule(v: AutoModeVerdict): AutoModeFallbackCause | undefined;
|
|
130
149
|
/**
|
|
131
150
|
* The model-facing deny text for an `unavailable` verdict — CC 2.1.250 `x1t`, with the parenthetical `EIt`
|
package/dist/core/auto-mode.js
CHANGED
|
@@ -56,7 +56,7 @@ export function rungDidNotRule(v) {
|
|
|
56
56
|
if (v.kind === "unavailable")
|
|
57
57
|
return v.cause;
|
|
58
58
|
if (v.kind === "parse_error")
|
|
59
|
-
return "parse_error";
|
|
59
|
+
return v.form === "conflict" ? undefined : "parse_error";
|
|
60
60
|
return undefined;
|
|
61
61
|
}
|
|
62
62
|
export function classifierUnavailableDenyMessage(toolName, cause, attribution) {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TIGHTEN-ONLY CLAMP SEATS an engine-built child spec carries from the run that builds it — and the
|
|
3
|
+
* ONE carrier every such constructor passes them through.
|
|
4
|
+
*
|
|
5
|
+
* Why a carrier and not a convention. The engine builds child specs in several places (the delegation
|
|
6
|
+
* lane, the observer sidecar, the wake/revival fold, the teacher's internal legs, the verify gate's two
|
|
7
|
+
* legs, the workflow lane), and each re-deriving per seat WHICH value is the tightening one lands a new
|
|
8
|
+
* seat one constructor at a time — the ones it does not reach staying invisible, since a `grep readFace`
|
|
9
|
+
* census cannot see a station that carries the face through a persisted seed or a synthesized fold.
|
|
10
|
+
*
|
|
11
|
+
* Two mechanical halves replace the census:
|
|
12
|
+
* · the COMPILER — {@link ChildClampSource} requires every seat as a PRESENT key (an `undefined` value
|
|
13
|
+
* is fine; a missing key is not), so adding a member to {@link CHILD_CLAMP_SEATS} reds `tsc` at every
|
|
14
|
+
* construction site until each one says what it carries there;
|
|
15
|
+
* · the SCAN — `scripts/verify-child-clamp-seats.mjs` reads {@link ENGINE_CHILD_SPEC_CONSTRUCTORS} and
|
|
16
|
+
* requires each registered constructor to go through this carrier, requires the carrier's own source
|
|
17
|
+
* to cover every seat, and requires every tighten-only seat `tightenTaskSpec` governs to be either
|
|
18
|
+
* carried here or listed in {@link CHILD_CLAMP_SEATS_NOT_CARRIED} with a reason.
|
|
19
|
+
*
|
|
20
|
+
* What "tighten-only" means for this seat list: the value that travels is the RESTRICTING one and only
|
|
21
|
+
* that one, so a parent that clamped nothing changes nothing about its children, and nothing a child can
|
|
22
|
+
* author lifts a clamp its parent ran under. That is why the carrier omits a key rather than writing a
|
|
23
|
+
* loosening value — a constructor may state its own stricter default and spread this after it.
|
|
24
|
+
*/
|
|
25
|
+
import type { ReadDenyEntry } from "../tools/fs/read-deny.js";
|
|
26
|
+
import type { TaskSpec } from "./types.js";
|
|
27
|
+
/**
|
|
28
|
+
* The clamp seats, as string literals (the closed set the scan and the wire-manifest reader both parse —
|
|
29
|
+
* a spread or an identifier here reads as an empty set).
|
|
30
|
+
*/
|
|
31
|
+
export declare const CHILD_CLAMP_SEATS: readonly ["handsReadOnly", "interactiveTools", "readFace", "writeFace", "readDenyPatterns"];
|
|
32
|
+
/** One clamp seat's name. */
|
|
33
|
+
export type ChildClampSeat = (typeof CHILD_CLAMP_SEATS)[number];
|
|
34
|
+
/**
|
|
35
|
+
* The seats `tightenTaskSpec` governs that this carrier deliberately does NOT carry, each with the reason
|
|
36
|
+
* — the "forgot" / "decided" distinction, kept where a new reader will trip over it. The scan requires
|
|
37
|
+
* every seat named in `tightenTaskSpec` to be in this table or in {@link CHILD_CLAMP_SEATS}.
|
|
38
|
+
*/
|
|
39
|
+
export declare const CHILD_CLAMP_SEATS_NOT_CARRIED: Readonly<Record<string, string>>;
|
|
40
|
+
/**
|
|
41
|
+
* The engine-built child-spec constructors, as `<repo-relative file>#<name>` — the closed registry the
|
|
42
|
+
* scan enumerates. A constructor that builds a spec for a child RUN belongs here; the scan requires each
|
|
43
|
+
* row to resolve to exactly one declaration IN ITS OWN FILE that calls {@link childClampSeats}.
|
|
44
|
+
*
|
|
45
|
+
* Why the file is half the key, and why a shape the scan cannot read is a RED rather than an absence: a
|
|
46
|
+
* registry keyed on a bare NAME asks a syntactic scan to enumerate every shape that binds a callable, and
|
|
47
|
+
* that list has no end — every shape it misses leaves the real constructor invisible while a namesake
|
|
48
|
+
* answers for it.
|
|
49
|
+
*/
|
|
50
|
+
export declare const ENGINE_CHILD_SPEC_CONSTRUCTORS: readonly ["src/agents/subagent.ts#buildChildSpec", "src/agents/subagent.ts#observerSpec", "src/agents/subagent.ts#resumeSpec", "src/agents/teacher.ts#inheritedDeploymentConfig", "src/agents/verify.ts#verifierSpec", "src/agents/verify.ts#fixLegSpec", "src/orchestration/workflow-primitives.ts#workflowChildClamps"];
|
|
51
|
+
/**
|
|
52
|
+
* The clamp source a construction site hands the carrier: EVERY seat as a present key. A site whose lane
|
|
53
|
+
* has no value for a seat writes `undefined` explicitly — which is the point, since that is a sentence
|
|
54
|
+
* a reader (and a diff) can see, unlike an absent key.
|
|
55
|
+
*/
|
|
56
|
+
export interface ChildClampSource {
|
|
57
|
+
/** The write-hands clamp: only `true` travels. */
|
|
58
|
+
handsReadOnly: boolean | undefined;
|
|
59
|
+
/** The hard-headless clamp: only `false` travels. */
|
|
60
|
+
interactiveTools: boolean | undefined;
|
|
61
|
+
/** The read containment face: only `"roots"` travels. */
|
|
62
|
+
readFace: TaskSpec["readFace"] | undefined;
|
|
63
|
+
/** The write containment face: only `"roots"` travels. */
|
|
64
|
+
writeFace: TaskSpec["writeFace"] | undefined;
|
|
65
|
+
/** The deny-set additions: an add-only axis, so a non-empty list travels as a spawn-time copy. */
|
|
66
|
+
readDenyPatterns: readonly ReadDenyEntry[] | undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Project a parent's clamps onto a child spec — the ONE place that decides, per seat, which value is the
|
|
70
|
+
* tightening one and how it is copied. Spread the result onto the child spec; a seat with nothing to
|
|
71
|
+
* clamp contributes NO KEY, so a constructor may state its own stricter value above the spread and keep
|
|
72
|
+
* it (a `false` from the parent can never lift it).
|
|
73
|
+
*
|
|
74
|
+
* @contract child_spec.clamps_travel_whole — every child spec the ENGINE builds carries the whole of its
|
|
75
|
+
* parent's tighten-only clamp seats (`CHILD_CLAMP_SEATS`) through this one carrier: only the restricting
|
|
76
|
+
* value of each seat travels, arrays are copied at spawn, and a seat the parent did not clamp adds no key
|
|
77
|
+
* — so a clamp can never end one level deep, and a new seat cannot reach some constructors and not others.
|
|
78
|
+
*/
|
|
79
|
+
export declare function childClampSeats(source: ChildClampSource): Partial<TaskSpec>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const CHILD_CLAMP_SEATS = ["handsReadOnly", "interactiveTools", "readFace", "writeFace", "readDenyPatterns"];
|
|
2
|
+
export const CHILD_CLAMP_SEATS_NOT_CARRIED = Object.freeze({
|
|
3
|
+
toolPolicy: "not a clamp a child spec copies: a parent's denies reach its children through the ancestor-constraint chain the child's own prepare folds, not by putting the parent's policy object on the child seat",
|
|
4
|
+
shellGate: "travels on the inherited-gate lane (`InheritedGate.shellGate`, a trusted internals seat), not on the child spec — a spec-level copy would be a second source for the same clamp",
|
|
5
|
+
onAsk: "per-lane by construction: the delegation lane wraps the parent's approver with spawn provenance before it becomes the child's seat, and a lane with no approver must hand down none",
|
|
6
|
+
hooks: "a deployment seat, not an inherited clamp — `tightenTaskSpec` refuses to merge two hook sets rather than composing them, so there is no tightening value to carry",
|
|
7
|
+
excludeTools: "roster control rather than containment: the lanes that copy a parent's roster do so beside these seats, and the workflow lane authors its child roster from the governance baseline instead of the host's",
|
|
8
|
+
deferTools: "roster control, same reading as `excludeTools` above",
|
|
9
|
+
});
|
|
10
|
+
export const ENGINE_CHILD_SPEC_CONSTRUCTORS = [
|
|
11
|
+
"src/agents/subagent.ts#buildChildSpec",
|
|
12
|
+
"src/agents/subagent.ts#observerSpec",
|
|
13
|
+
"src/agents/subagent.ts#resumeSpec",
|
|
14
|
+
"src/agents/teacher.ts#inheritedDeploymentConfig",
|
|
15
|
+
"src/agents/verify.ts#verifierSpec",
|
|
16
|
+
"src/agents/verify.ts#fixLegSpec",
|
|
17
|
+
"src/orchestration/workflow-primitives.ts#workflowChildClamps",
|
|
18
|
+
];
|
|
19
|
+
export function childClampSeats(source) {
|
|
20
|
+
return {
|
|
21
|
+
...(source.handsReadOnly === true ? { handsReadOnly: true } : {}),
|
|
22
|
+
...(source.interactiveTools === false ? { interactiveTools: false } : {}),
|
|
23
|
+
...(source.readFace === "roots" ? { readFace: "roots" } : {}),
|
|
24
|
+
...(source.writeFace === "roots" ? { writeFace: "roots" } : {}),
|
|
25
|
+
...(source.readDenyPatterns !== undefined && source.readDenyPatterns.length > 0
|
|
26
|
+
? { readDenyPatterns: [...source.readDenyPatterns] }
|
|
27
|
+
: {}),
|
|
28
|
+
};
|
|
29
|
+
}
|