@sema-agent/core 7.18.0 → 7.20.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 +276 -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 +8 -3
- package/dist/brain/open-responses.js +12 -4
- package/dist/brain/openai.js +7 -2
- package/dist/brain/reasoning.d.ts +13 -13
- package/dist/brain/reasoning.js +2 -2
- package/dist/brain/stream-engine.d.ts +4 -0
- package/dist/brain/stream-engine.js +6 -1
- package/dist/brain/stream-shared.d.ts +3 -0
- package/dist/brain/stream-shared.js +1 -0
- package/dist/core/ask-origin.d.ts +16 -0
- package/dist/core/ask-origin.js +8 -0
- package/dist/core/auto-mode-arming.d.ts +6 -0
- package/dist/core/auto-mode-arming.js +5 -5
- package/dist/core/auto-mode-defaults.d.ts +43 -15
- package/dist/core/auto-mode-defaults.js +4 -1
- package/dist/core/auto-mode.d.ts +74 -26
- package/dist/core/auto-mode.js +80 -11
- package/dist/core/checkpoint-store.d.ts +7 -0
- package/dist/core/checkpoint-store.js +2 -2
- 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 +89 -41
- 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 +20 -48
- package/dist/core/gate-lanes.js +17 -9
- package/dist/core/gate-outcome.d.ts +34 -6
- package/dist/core/gate-outcome.js +7 -0
- package/dist/core/governance-codes.d.ts +16 -1
- package/dist/core/governance-codes.js +18 -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/types.d.ts +24 -0
- package/dist/core/memory.d.ts +19 -0
- package/dist/core/memory.js +12 -0
- package/dist/core/read-root-candidate.d.ts +12 -0
- package/dist/core/read-root-candidate.js +24 -0
- package/dist/core/remote-env.d.ts +32 -0
- package/dist/core/remote-env.js +4 -1
- package/dist/core/roles.d.ts +9 -1
- package/dist/core/roles.js +4 -0
- 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 +154 -11
- package/dist/core/runner/classifier-ladder.js +170 -10
- package/dist/core/runner/contracts.d.ts +46 -1
- package/dist/core/runner/denial-limit-arms.d.ts +14 -1
- package/dist/core/runner/denial-limit-arms.js +7 -6
- 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-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +20 -31
- package/dist/core/runner/prepare-config-doors.d.ts +6 -0
- package/dist/core/runner/prepare-config-doors.js +8 -10
- 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 +8 -0
- package/dist/core/runner/prepare-gate-stations.js +6 -2
- 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-project-context.d.ts +5 -1
- package/dist/core/runner/prepare-project-context.js +3 -2
- 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/resume-apply.js +3 -3
- 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 +31 -16
- package/dist/core/runner/run-settle-and-teardown.js +2 -5
- package/dist/core/runner/run-turn-boundary.js +44 -25
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/synthetic-tools.d.ts +41 -29
- package/dist/core/runner/synthetic-tools.js +25 -40
- 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 +160 -53
- package/dist/core/runner/turn-attachments.js +153 -51
- 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 +84 -17
- package/dist/core/sensitive-path-policy.js +37 -19
- package/dist/core/skill-listing-budget.d.ts +106 -0
- package/dist/core/skill-listing-budget.js +57 -0
- package/dist/core/skills-directory.js +17 -10
- package/dist/core/task-registry.js +2 -1
- package/dist/core/task-spec.d.ts +29 -2
- package/dist/core/tool-policy.d.ts +31 -5
- package/dist/core/tool-policy.js +45 -32
- package/dist/core/tool-spec.d.ts +37 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/write-protect.d.ts +105 -20
- package/dist/core/write-protect.js +89 -32
- package/dist/index.d.ts +8 -7
- 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 +40 -2
- package/dist/tools/fs/bash-readonly-classifier.js +376 -41
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +19 -28
- 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/dist/tools/fs/read-deny.d.ts +4 -3
- package/dist/tools/fs/safety.d.ts +15 -0
- package/dist/tools/fs/safety.js +7 -0
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +31 -5
- package/wire-changes/core@7.18.0.json +1273 -0
- package/wire-changes/core@7.19.0.json +457 -0
- package/wire-changes/core@7.20.0.json +568 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Hard cap on one classification RUNG (both attempts) when `AutoModeDeciderOptions.timeoutMs` is omitted — CC
|
|
3
|
+
* 2.1.259's per-attempt classifier base (`j7e = 60000`; 2.1.250 `oue`, the same number), which upstream
|
|
4
|
+
* stretches to 120 s with prompt size and sema leaves to the deployment knob. The 15 s that stood here was
|
|
5
|
+
* never upstream's: live 2026-09-15 a thinking-by-default gateway answers in 5–165 s (Qwen 3.8 p50 11–24 s,
|
|
6
|
+
* kimi and GLM p50 ~10 s), so 15 s turned most classifications into `unavailable (timed out)`. Worst-case
|
|
7
|
+
* walk = this × rungs (2) = 120 s.
|
|
8
|
+
*/
|
|
9
|
+
export declare const AUTO_MODE_DEFAULT_TIMEOUT_MS = 60000;
|
|
10
|
+
/**
|
|
11
|
+
* What an arming RECORDING that carries no `timeoutMs` meant when it was written: the default of its day. Every
|
|
12
|
+
* recipe minted since the deadline moved to 60 s MATERIALISES its bound, so an absent recorded knob dates the
|
|
13
|
+
* recording — the fold reads it as the 15 s it ran under, never as the current default
|
|
14
|
+
* (`auto_mode.arming.absent_timeout_reads_recorded_default`, on the fold).
|
|
15
|
+
*/
|
|
16
|
+
export declare const AUTO_MODE_RECORDED_ABSENT_TIMEOUT_MS = 15000;
|
|
3
17
|
/** Newest transcript entries in the classify window when `AutoModeWindowOptions.maxEntries` is omitted. */
|
|
4
18
|
export declare const AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES = 40;
|
|
5
19
|
/** Per-entry excerpt cap when `AutoModeWindowOptions.maxCharsPerEntry` is omitted. */
|
|
@@ -17,25 +31,39 @@ export declare const AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS = 2000;
|
|
|
17
31
|
* parses as a block (the parser tolerates a missing tail), a truncated allow does not exist (it is
|
|
18
32
|
* seven tokens), and the cap's job is to make a model that ignores the "no preamble" instruction —
|
|
19
33
|
* or that reasons in-text — cost a bounded, quickly-detected contract failure instead of a slow one.
|
|
20
|
-
* Paired with `reasoning:"off"` at the call: the cap hosts the ANSWER, so it
|
|
21
|
-
* wire
|
|
34
|
+
* Paired with `reasoning:"off"` at the call: the cap hosts the ANSWER, so it stands alone only where the
|
|
35
|
+
* wire SPELLS off (`thinkingOffSpelled`); every other seat adds a thinking allowance to it.
|
|
22
36
|
*/
|
|
23
37
|
export declare const AUTO_MODE_CLASSIFIER_MAX_TOKENS = 256;
|
|
24
38
|
/**
|
|
25
|
-
* The output-budget ALLOWANCE for a classifier request
|
|
26
|
-
* seat asks for `reasoning:"low"` instead of `"off"` — a model whose wire
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
39
|
+
* The output-budget ALLOWANCE for a classifier request whose thinking the wire could not turn OFF: added to
|
|
40
|
+
* the verdict cap when the seat asks for `reasoning:"low"` instead of `"off"` — a model whose wire has no off
|
|
41
|
+
* spelling (`reasoning:true` on an effort-only wire with no declared off, the anthropic adaptive family) and a
|
|
42
|
+
* model that declares NO reasoning (the wire then carries nothing and the request is the gateway's default,
|
|
43
|
+
* which on a default-on gateway is a think). The seat pays for the least the wire can grade plus the verdict,
|
|
44
|
+
* never for an open-ended budget.
|
|
30
45
|
*
|
|
31
|
-
* 2048 is anchored twice: CC 2.1.
|
|
32
|
-
* its `rejects_disabled_thinking` family (`BHe`); and it is 2 × `MIN_THINKING_TOKENS` (1024), so
|
|
33
|
-
* anthropic BUDGET path a HARD cap of 256 + 2048 clears the cap-wins skip (`budgetCapSkipsThinking`)
|
|
34
|
-
* hosts a legal `low` budget (`1024 ≤ budget_tokens < max_tokens`) without the brain having to raise a
|
|
35
|
-
* it refuses to raise.
|
|
36
|
-
* is the
|
|
46
|
+
* 2048 is anchored twice: CC 2.1.259 `MU` adds exactly this many tokens to the verdict cap for a model in
|
|
47
|
+
* its `rejects_disabled_thinking` family (2.1.250 `E4`/`BHe`); and it is 2 × `MIN_THINKING_TOKENS` (1024), so
|
|
48
|
+
* on the anthropic BUDGET path a HARD cap of 256 + 2048 clears the cap-wins skip (`budgetCapSkipsThinking`)
|
|
49
|
+
* and hosts a legal `low` budget (`1024 ≤ budget_tokens < max_tokens`) without the brain having to raise a
|
|
50
|
+
* cap it refuses to raise. Live 2026-09-15: deepseek's default think fits it ~90–100% of the time; the tail
|
|
51
|
+
* is the re-ask seat's ({@link AUTO_MODE_CLASSIFIER_THINK_BUDGET_TOKENS}). What a cap can and cannot promise is
|
|
52
|
+
* the seat's contract (`classifier.budget_is_room`, on `classifierRungSeat`).
|
|
37
53
|
*/
|
|
38
54
|
export declare const AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS = 2048;
|
|
55
|
+
/**
|
|
56
|
+
* The RE-ASK seat's allowance — the room a rung gets ONCE after its first attempt produced no verdict: CC
|
|
57
|
+
* 2.1.259's stage-2 classifier cap (`max_tokens: 8192 + bn`). Live 2026-09-15 the thinking tail that overruns
|
|
58
|
+
* 2048 (deepseek 1–2 of 20, some past 4096; Qwen up to 6k) sits under it. One re-ask, never a loop.
|
|
59
|
+
*/
|
|
60
|
+
export declare const AUTO_MODE_CLASSIFIER_THINK_BUDGET_TOKENS = 8192;
|
|
61
|
+
/**
|
|
62
|
+
* How many CONSECUTIVE no-verdict classifications on ONE model mint `classifier.contract_broken` (once per run
|
|
63
|
+
* per model): one is a fluke, two is a pattern, and at three the denial limit already hands the user a card —
|
|
64
|
+
* the operator line lands before that one.
|
|
65
|
+
*/
|
|
66
|
+
export declare const AUTO_MODE_CLASSIFIER_CONTRACT_BROKEN_AFTER = 2;
|
|
39
67
|
/** The sentinel a deployment puts INSIDE a paired rule list to splice the CC default rules back in at
|
|
40
68
|
* that position (CC `XYt = "$defaults"`). It lives here, beside the other defaults, because the #503
|
|
41
69
|
* recipe canonicalizer needs its VALUE and must not load the assembly face (and its SHA-locked assets)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
export const AUTO_MODE_DEFAULT_TIMEOUT_MS =
|
|
1
|
+
export const AUTO_MODE_DEFAULT_TIMEOUT_MS = 60_000;
|
|
2
|
+
export const AUTO_MODE_RECORDED_ABSENT_TIMEOUT_MS = 15_000;
|
|
2
3
|
export const AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES = 40;
|
|
3
4
|
export const AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS = 2_000;
|
|
4
5
|
export const AUTO_MODE_CLASSIFIER_MAX_TOKENS = 256;
|
|
5
6
|
export const AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS = 2048;
|
|
7
|
+
export const AUTO_MODE_CLASSIFIER_THINK_BUDGET_TOKENS = 8192;
|
|
8
|
+
export const AUTO_MODE_CLASSIFIER_CONTRACT_BROKEN_AFTER = 2;
|
|
6
9
|
export const AUTO_MODE_DEFAULTS_SENTINEL = "$defaults";
|
|
7
10
|
export const AUTO_MODE_DENIAL_LIMIT_DEFAULTS = Object.freeze({ maxConsecutive: 3, maxTotal: 20 });
|
|
8
11
|
export const AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS = 120_000;
|
package/dist/core/auto-mode.d.ts
CHANGED
|
@@ -10,12 +10,27 @@ export declare const AUTO_MODE_UNAVAILABLE_CAUSES: readonly ["error", "timeout"]
|
|
|
10
10
|
export type AutoModeUnavailableCause = (typeof AUTO_MODE_UNAVAILABLE_CAUSES)[number];
|
|
11
11
|
/** Whether a value is a member of the unavailable-cause set — the screen a row reader applies to a stored word. */
|
|
12
12
|
export declare function isAutoModeUnavailableCause(v: unknown): v is AutoModeUnavailableCause;
|
|
13
|
+
/**
|
|
14
|
+
* HOW a reply failed to carry a verdict — the closed set behind `parse_error.form`. `missing` = the reader found
|
|
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 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`.
|
|
22
|
+
*/
|
|
23
|
+
export declare const AUTO_MODE_PARSE_FAILURES: readonly ["missing", "conflict"];
|
|
24
|
+
export type AutoModeParseFailure = (typeof AUTO_MODE_PARSE_FAILURES)[number];
|
|
25
|
+
/** Whether a value is a member of the parse-failure set. */
|
|
26
|
+
export declare function isAutoModeParseFailure(v: unknown): v is AutoModeParseFailure;
|
|
13
27
|
/**
|
|
14
28
|
* WHY a classification moved DOWN the candidate ladder — the closed set behind
|
|
15
29
|
* {@link AutoModeClassifierRound}'s `fallback.cause`, i.e. the ways a candidate produces NO RULING
|
|
16
30
|
* ({@link rungDidNotRule}). Two words are the unavailable causes verbatim (the leg threw / rejected; the
|
|
17
|
-
* round-trip cap elapsed); `parse_error` is the candidate that ANSWERED
|
|
18
|
-
*
|
|
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.
|
|
19
34
|
*/
|
|
20
35
|
export declare const AUTO_MODE_FALLBACK_CAUSES: readonly ["error", "timeout", "parse_error"];
|
|
21
36
|
export type AutoModeFallbackCause = (typeof AUTO_MODE_FALLBACK_CAUSES)[number];
|
|
@@ -52,10 +67,14 @@ export type AutoModeVerdict = ({
|
|
|
52
67
|
cause: AutoModeUnavailableCause;
|
|
53
68
|
} & AutoModeRoundAttributed)
|
|
54
69
|
/** The model responded but not in the `<block>…` contract shape. Handled as a BLOCK with the CC
|
|
55
|
-
* parse-failure sentence ({@link
|
|
70
|
+
* parse-failure sentence ({@link classifierParseFailureDenyMessage}), counted by the denial limit. `form`
|
|
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}). */
|
|
56
74
|
| ({
|
|
57
75
|
kind: "parse_error";
|
|
58
76
|
raw: string;
|
|
77
|
+
form: AutoModeParseFailure;
|
|
59
78
|
} & AutoModeRoundAttributed);
|
|
60
79
|
/** The member every verdict arm shares: WHICH MODEL this classification asked and which one answered. */
|
|
61
80
|
export interface AutoModeRoundAttributed {
|
|
@@ -85,20 +104,18 @@ export interface AutoModeClassifierRound {
|
|
|
85
104
|
* the QUESTION (the transcript window every rung shares), not in a rung's answer.
|
|
86
105
|
*
|
|
87
106
|
* WHY IT EXISTS. The ladder advances on "the rung produced no ruling", and a hook that rejects looks exactly
|
|
88
|
-
* like that. But a classification whose prompt could not be built fails IDENTICALLY on every rung (the build is
|
|
89
|
-
* shared promise, by contract — "the same prompt, re-asked" is literal), so
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* 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.
|
|
94
112
|
*
|
|
95
113
|
* Carried as a `code` on the thrown error rather than a class so any classify hook — including one this
|
|
96
114
|
* package never constructed — can raise it without importing a constructor.
|
|
97
115
|
*
|
|
98
116
|
* WHAT IT DOES NOT COVER, stated: a build that OUTRUNS a rung's cap is still recorded as that rung's
|
|
99
117
|
* `timeout`. The cap bounds the whole classify call, assembly included, so at that point nothing distinguishes
|
|
100
|
-
* a slow transcript store from a slow model.
|
|
101
|
-
* seam change, filed for a later version.
|
|
118
|
+
* a slow transcript store from a slow model.
|
|
102
119
|
*/
|
|
103
120
|
export declare const AUTO_MODE_ROUND_NOT_ASKED_CODE = "auto_mode.round_not_asked";
|
|
104
121
|
/** Mint the marker above around whatever failed, keeping the original as `cause` for the host's own logs.
|
|
@@ -109,8 +126,25 @@ export declare const AUTO_MODE_ROUND_NOT_ASKED_CODE = "auto_mode.round_not_asked
|
|
|
109
126
|
export declare function autoModeRoundNotAsked(reason: unknown): Error & {
|
|
110
127
|
code: string;
|
|
111
128
|
};
|
|
112
|
-
/**
|
|
113
|
-
*
|
|
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
|
+
*/
|
|
114
148
|
export declare function rungDidNotRule(v: AutoModeVerdict): AutoModeFallbackCause | undefined;
|
|
115
149
|
/**
|
|
116
150
|
* The model-facing deny text for an `unavailable` verdict — CC 2.1.250 `x1t`, with the parenthetical `EIt`
|
|
@@ -121,23 +155,35 @@ export declare function rungDidNotRule(v: AutoModeVerdict): AutoModeFallbackCaus
|
|
|
121
155
|
* NOT move keeps the CC text byte for byte.
|
|
122
156
|
*/
|
|
123
157
|
export declare function classifierUnavailableDenyMessage(toolName: string, cause: AutoModeUnavailableCause, attribution?: AutoModeClassifierRound): string;
|
|
124
|
-
/** The model-facing deny text for a `parse_error` verdict — CC 2.1.250 `Ure` over the R3t stem: the classifier
|
|
125
|
-
* answered, but not with a verdict, and auto mode blocks what it could not evaluate. */
|
|
126
|
-
export declare const CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE = "Auto mode could not evaluate this action and is blocking it for safety \u2014 the classifier's reply carried no verdict.";
|
|
127
158
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
159
|
+
* The model-facing deny text for a `parse_error` verdict — CC 2.1.259 `i7t` stem ("Auto mode could not evaluate
|
|
160
|
+
* this action and is blocking it for safety"; 2.1.250 `Ure`): the classifier answered, but not with a verdict,
|
|
161
|
+
* and auto mode blocks what it could not evaluate. With a candidate ladder wired the tail NAMES the model whose
|
|
162
|
+
* reply carried no verdict (`attribution.modelUsed`) — a declared departure from upstream, whose card names no
|
|
163
|
+
* model: the person reading this card is the one who can change the catalog, and on a single-model deployment
|
|
164
|
+
* the name is the whole diagnosis. Without attribution the sentence is unchanged. The stem is a stable prefix
|
|
165
|
+
* either way; one writer for both deny sites.
|
|
166
|
+
*/
|
|
167
|
+
export declare function classifierParseFailureDenyMessage(attribution?: AutoModeClassifierRound): string;
|
|
168
|
+
/**
|
|
169
|
+
* Parse the classifier's output contract — the CC 2.1.259 verdict reader (`MTt`, its thinking strip `QTt`, its
|
|
170
|
+
* reason reader `TTt`; unchanged from 2.1.250 `wCe`/`xCe`/`yCe`), plus ONE arm of sema's own:
|
|
130
171
|
* blocked: `<block>yes</block><category>Rule Name</category><reason>[Rule Name] …</reason>`
|
|
131
172
|
* allowed: `<block>no</block>`
|
|
132
173
|
*
|
|
133
174
|
* The verdict is the set of `<block>yes|no` hits, NOT a prefix match:
|
|
134
175
|
* 1. CONFLICT, read on the RAW text (thinking included): `<block>(yes|no)\b` collected over the whole
|
|
135
|
-
* response — more than one distinct answer ⇒ no verdict (`parse_error`). A scratch-space
|
|
136
|
-
* contradicting a final "no" is a contradiction, not an allow.
|
|
137
|
-
* 2. HITS, read on the text with
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
176
|
+
* response — more than one distinct answer ⇒ no verdict (`parse_error`, form `conflict`). A scratch-space
|
|
177
|
+
* "yes" contradicting a final "no" is a contradiction, not an allow.
|
|
178
|
+
* 2. HITS, read on the text with thinking blocks removed ({@link stripThinking}): `<block>(yes|no)\b` with
|
|
179
|
+
* the closing tag OPTIONAL, at ANY position. Two distinct answers ⇒ `conflict`; the one distinct answer ⇒
|
|
180
|
+
* `no` = allow, `yes` = block.
|
|
181
|
+
* 3. BARE ANSWER (sema, ruled 2026-09-15 — small open models drop the tag; recorded as a divergence; a caller
|
|
182
|
+
* that knows the reply is not a complete answer passes `bareAnswer: false` and this arm is closed): zero
|
|
183
|
+
* hits AND the whole thinking-stripped text is an answer by {@link bareAnswerOf} ⇒ that word is the
|
|
184
|
+
* verdict (a bare `yes` blocks with empty category/reason) — unless a `<block>` answer inside the
|
|
185
|
+
* thinking disagrees with it (`conflict`). Zero hits and anything else — prose, a question with its
|
|
186
|
+
* answer, a reply that only answered inside its thinking — ⇒ `parse_error`, form `missing`.
|
|
141
187
|
* So a preamble before the tag, prose after `<block>no`, a missing `</block>` and a reply that repeats
|
|
142
188
|
* its answer all read as the answer they carry; the tag must be spelled `<block>yes` / `<block>no` with
|
|
143
189
|
* no whitespace inside (the word-boundary form — `<block>yesterday` is not a hit) and case does not matter.
|
|
@@ -149,11 +195,13 @@ export declare const CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE = "Auto mode could no
|
|
|
149
195
|
* line from the tool name in that case. Downgrading a stated block to `parse_error` would lose the
|
|
150
196
|
* classifier's explicit verdict on the exact calls it flagged.
|
|
151
197
|
*
|
|
152
|
-
* `parse_error` is fail-closed: the gate handles it as a BLOCK (CC 2.1.
|
|
198
|
+
* `parse_error` is fail-closed: the gate handles it as a BLOCK (CC 2.1.259's own outcome for a reply with no
|
|
153
199
|
* verdict) that the denial limit counts like any other — never a silent allow, and never a question handed
|
|
154
200
|
* to a person on the classifier's behalf.
|
|
155
201
|
*/
|
|
156
|
-
export declare function parseAutoModeResponse(text: string
|
|
202
|
+
export declare function parseAutoModeResponse(text: string, options?: {
|
|
203
|
+
bareAnswer?: boolean;
|
|
204
|
+
}): AutoModeVerdict;
|
|
157
205
|
/** What the decider hands the deployment's `classify` hook (the assembled prompt is the hook's job
|
|
158
206
|
* when it owns assembly; when using {@link buildAutoModePrompt} the decider passes its output). */
|
|
159
207
|
export interface AutoModeClassifyInput {
|
package/dist/core/auto-mode.js
CHANGED
|
@@ -5,6 +5,11 @@ const AUTO_MODE_UNAVAILABLE_CAUSE_SET = new Set(AUTO_MODE_UNAVAILABLE_CAUSES);
|
|
|
5
5
|
export function isAutoModeUnavailableCause(v) {
|
|
6
6
|
return AUTO_MODE_UNAVAILABLE_CAUSE_SET.has(v);
|
|
7
7
|
}
|
|
8
|
+
export const AUTO_MODE_PARSE_FAILURES = ["missing", "conflict"];
|
|
9
|
+
const AUTO_MODE_PARSE_FAILURE_SET = new Set(AUTO_MODE_PARSE_FAILURES);
|
|
10
|
+
export function isAutoModeParseFailure(v) {
|
|
11
|
+
return AUTO_MODE_PARSE_FAILURE_SET.has(v);
|
|
12
|
+
}
|
|
8
13
|
export const AUTO_MODE_FALLBACK_CAUSES = ["error", "timeout", "parse_error"];
|
|
9
14
|
const AUTO_MODE_FALLBACK_CAUSE_SET = new Set(AUTO_MODE_FALLBACK_CAUSES);
|
|
10
15
|
export function isAutoModeFallbackCause(v) {
|
|
@@ -51,7 +56,7 @@ export function rungDidNotRule(v) {
|
|
|
51
56
|
if (v.kind === "unavailable")
|
|
52
57
|
return v.cause;
|
|
53
58
|
if (v.kind === "parse_error")
|
|
54
|
-
return "parse_error";
|
|
59
|
+
return v.form === "conflict" ? undefined : "parse_error";
|
|
55
60
|
return undefined;
|
|
56
61
|
}
|
|
57
62
|
export function classifierUnavailableDenyMessage(toolName, cause, attribution) {
|
|
@@ -64,19 +69,83 @@ export function classifierUnavailableDenyMessage(toolName, cause, attribution) {
|
|
|
64
69
|
"If it keeps failing, continue with other tasks that don't require this action and come back to it later. " +
|
|
65
70
|
"Note: reading files, searching code, and other read-only operations do not require the classifier and can still be used.");
|
|
66
71
|
}
|
|
67
|
-
export
|
|
68
|
-
|
|
72
|
+
export function classifierParseFailureDenyMessage(attribution) {
|
|
73
|
+
const stem = "Auto mode could not evaluate this action and is blocking it for safety";
|
|
74
|
+
return attribution === undefined ? `${stem} — the classifier's reply carried no verdict.` : `${stem} — the classifier model "${attribution.modelUsed}" replied without a verdict.`;
|
|
75
|
+
}
|
|
76
|
+
function stripThinking(text) {
|
|
77
|
+
return text.replace(/<(thinking|think)>[\s\S]*?<\/\1>/gi, "").replace(/<(thinking|think)>[\s\S]*$/i, "");
|
|
78
|
+
}
|
|
79
|
+
const BARE_ANSWER_WRAPPERS = [
|
|
80
|
+
["**", "**"],
|
|
81
|
+
["__", "__"],
|
|
82
|
+
["*", "*"],
|
|
83
|
+
["_", "_"],
|
|
84
|
+
["`", "`"],
|
|
85
|
+
['"', '"'],
|
|
86
|
+
["'", "'"],
|
|
87
|
+
["«", "»"],
|
|
88
|
+
];
|
|
89
|
+
function unwrapBareAnswer(text) {
|
|
90
|
+
let s = text;
|
|
91
|
+
for (;;) {
|
|
92
|
+
const before = s;
|
|
93
|
+
s = s.trim().replace(/[.!]+$/, "").trim();
|
|
94
|
+
for (const [open, close] of BARE_ANSWER_WRAPPERS) {
|
|
95
|
+
if (s.length > open.length + close.length && s.startsWith(open) && s.endsWith(close)) {
|
|
96
|
+
s = s.slice(open.length, s.length - close.length);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (s === before)
|
|
101
|
+
return s;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function bareAnswerOf(text) {
|
|
105
|
+
const visible = text.replace(/[\u200b-\u200d\ufeff]/g, "").trim();
|
|
106
|
+
if (visible.includes("```") || visible.includes("\n"))
|
|
107
|
+
return undefined;
|
|
108
|
+
let s = unwrapBareAnswer(visible);
|
|
109
|
+
const labelled = labelledAnswer(s);
|
|
110
|
+
if (labelled !== undefined)
|
|
111
|
+
s = unwrapBareAnswer(labelled);
|
|
112
|
+
const word = /^(yes|no)$/i.exec(s)?.[1]?.toLowerCase();
|
|
113
|
+
return word === "yes" || word === "no" ? word : undefined;
|
|
114
|
+
}
|
|
115
|
+
function labelledAnswer(s) {
|
|
116
|
+
const label = "(?:final[ \\t]+)?(?:block|answer|verdict|decision)";
|
|
117
|
+
for (const [open, close] of [["", ""], ...BARE_ANSWER_WRAPPERS]) {
|
|
118
|
+
const re = new RegExp(`^${escapeRe(open)}${label}${escapeRe(close)}[ \\t]*[:=][ \\t]*(.*)$`, "i");
|
|
119
|
+
const m = re.exec(s);
|
|
120
|
+
if (m !== null)
|
|
121
|
+
return m[1];
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
function escapeRe(s) {
|
|
126
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
127
|
+
}
|
|
128
|
+
export function parseAutoModeResponse(text, options) {
|
|
69
129
|
const rawAnswers = new Set([...text.matchAll(/<block>(yes|no)\b/gi)].map((m) => m[1].toLowerCase()));
|
|
70
130
|
if (rawAnswers.size > 1)
|
|
71
|
-
return { kind: "parse_error", raw: text };
|
|
72
|
-
const stripped = text
|
|
131
|
+
return { kind: "parse_error", raw: text, form: "conflict" };
|
|
132
|
+
const stripped = stripThinking(text);
|
|
73
133
|
const hits = [...stripped.matchAll(/<block>(yes|no)\b(<\/block>)?/gi)];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
134
|
+
let answer;
|
|
135
|
+
if (hits.length > 0) {
|
|
136
|
+
const answers = new Set(hits.map((m) => m[1].toLowerCase()));
|
|
137
|
+
if (answers.size !== 1)
|
|
138
|
+
return { kind: "parse_error", raw: text, form: "conflict" };
|
|
139
|
+
answer = answers.has("yes") ? "yes" : "no";
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
answer = options?.bareAnswer === false ? undefined : bareAnswerOf(stripped);
|
|
143
|
+
if (answer === undefined)
|
|
144
|
+
return { kind: "parse_error", raw: text, form: "missing" };
|
|
145
|
+
if (rawAnswers.size > 0 && !rawAnswers.has(answer))
|
|
146
|
+
return { kind: "parse_error", raw: text, form: "conflict" };
|
|
147
|
+
}
|
|
148
|
+
if (answer === "no")
|
|
80
149
|
return { kind: "allow" };
|
|
81
150
|
const category = /<category>([\s\S]*?)<\/category>/i.exec(stripped)?.[1]?.trim() ?? "";
|
|
82
151
|
const reason = /<reason>([\s\S]*?)<\/reason>/.exec(stripped)?.[1]?.trim() ?? "";
|
|
@@ -369,6 +369,13 @@ export interface PendingSteerInput {
|
|
|
369
369
|
inputId?: string;
|
|
370
370
|
priority?: SystemInjectionPriority;
|
|
371
371
|
}
|
|
372
|
+
/** True ONLY for a PLAIN data record (not null / array / class-instance / proxy-with-odd-prototype) — the only
|
|
373
|
+
* shape we safely digest. Adversarial args (a proxy, a getter that throws) must never crash the mint, so this +
|
|
374
|
+
* {@link safeDataValue}/{@link safeKeys} make {@link buildRiskDescriptor} fully TOTAL. */
|
|
375
|
+
export declare function isPlainRecord(x: unknown): x is Record<string, unknown>;
|
|
376
|
+
/** Read `obj[key]` as a DATA property only — NEVER invokes a getter/accessor (so a throwing getter can't escape
|
|
377
|
+
* AND a non-deterministic getter can't vary the digest); a trapped descriptor read ⇒ undefined. */
|
|
378
|
+
export declare function safeDataValue(obj: Record<string, unknown>, key: string): unknown;
|
|
372
379
|
/**
|
|
373
380
|
* design/80 §D-E: build the DETERMINISTIC, REDACTED {@link RiskDescriptor} for an escalation checkpoint at
|
|
374
381
|
* MINT. Pure — a function ONLY of (`toolName`, `args`, the D-2 `safety` axis, the `shellGated` flag); it reads
|
|
@@ -73,7 +73,7 @@ export const MAX_PENDING_STEER_CHARS = 16_000;
|
|
|
73
73
|
export const PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES = 48_000;
|
|
74
74
|
export const MAX_PENDING_STEER_ENTRIES = Math.floor(PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES / MAX_PENDING_STEER_CHARS);
|
|
75
75
|
export const PENDING_STEER_FROZEN_FIELDS = ["text", "trusted", "actor", "seq", "inputId", "priority"];
|
|
76
|
-
function isPlainRecord(x) {
|
|
76
|
+
export function isPlainRecord(x) {
|
|
77
77
|
try {
|
|
78
78
|
if (x === null || typeof x !== "object" || Array.isArray(x))
|
|
79
79
|
return false;
|
|
@@ -84,7 +84,7 @@ function isPlainRecord(x) {
|
|
|
84
84
|
return false;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
function safeDataValue(obj, key) {
|
|
87
|
+
export function safeDataValue(obj, key) {
|
|
88
88
|
try {
|
|
89
89
|
const d = Object.getOwnPropertyDescriptor(obj, key);
|
|
90
90
|
return d && "value" in d ? d.value : undefined;
|
|
@@ -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
|
+
}
|