@sema-agent/core 7.17.7 → 7.19.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 +233 -13
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -0
- package/dist/agents/teacher.js +1 -0
- package/dist/agents/verify.js +3 -0
- package/dist/brain/anthropic.js +6 -3
- package/dist/brain/open-responses.js +10 -4
- package/dist/brain/openai.js +5 -2
- package/dist/brain/reasoning.d.ts +13 -13
- package/dist/brain/reasoning.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +18 -0
- package/dist/brain/route-adjudicator.js +16 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/brain/stream-shared.d.ts +3 -0
- package/dist/brain/stream-shared.js +1 -0
- package/dist/core/agent-definition.d.ts +10 -0
- package/dist/core/artifact-host.d.ts +41 -4
- package/dist/core/artifact-host.js +18 -2
- 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 +184 -59
- package/dist/core/auto-mode.js +188 -13
- package/dist/core/checkpoint-store.d.ts +23 -2
- package/dist/core/checkpoint-store.js +2 -2
- package/dist/core/engine-notice.d.ts +77 -1
- package/dist/core/engine-notice.js +7 -0
- package/dist/core/gate-fold.js +5 -1
- package/dist/core/gate-lanes.js +9 -8
- package/dist/core/gate-outcome.d.ts +42 -2
- package/dist/core/gate-outcome.js +23 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hook-seats.d.ts +66 -0
- package/dist/core/hook-seats.js +37 -0
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/read-root-candidate.d.ts +12 -0
- package/dist/core/read-root-candidate.js +24 -0
- package/dist/core/roles.d.ts +9 -1
- package/dist/core/roles.js +4 -0
- package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
- package/dist/core/runner/assemble-result.d.ts +2 -1
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/classifier-ladder.d.ts +227 -0
- package/dist/core/runner/classifier-ladder.js +214 -0
- package/dist/core/runner/contracts.d.ts +71 -13
- package/dist/core/runner/decide-continuation.d.ts +1 -3
- package/dist/core/runner/decide-continuation.js +1 -7
- package/dist/core/runner/deferral-census.d.ts +52 -0
- package/dist/core/runner/deferral-census.js +44 -0
- package/dist/core/runner/denial-limit-arms.js +2 -2
- package/dist/core/runner/derived-route-fallback.d.ts +8 -4
- package/dist/core/runner/derived-route-fallback.js +8 -3
- package/dist/core/runner/gate-exit.d.ts +12 -0
- package/dist/core/runner/gate-exit.js +6 -3
- package/dist/core/runner/prepare-artifact.d.ts +10 -2
- package/dist/core/runner/prepare-artifact.js +5 -4
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +43 -55
- package/dist/core/runner/prepare-config-doors.d.ts +7 -1
- package/dist/core/runner/prepare-config-doors.js +13 -13
- package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
- package/dist/core/runner/prepare-defer-classify.js +8 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
- package/dist/core/runner/prepare-hands-readface.js +60 -24
- package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
- package/dist/core/runner/prepare-inherited-gate.js +10 -2
- package/dist/core/runner/prepare-lsp.d.ts +2 -2
- package/dist/core/runner/prepare-lsp.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +2 -0
- package/dist/core/runner/prepare-project-context.d.ts +5 -1
- package/dist/core/runner/prepare-project-context.js +7 -4
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
- package/dist/core/runner/prepare-prompt-inputs.js +4 -1
- package/dist/core/runner/prepare-run-refs.d.ts +9 -1
- package/dist/core/runner/prepare-run-refs.js +3 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
- package/dist/core/runner/prepare-suspend-saga.js +5 -2
- package/dist/core/runner/prepare-task.js +19 -14
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
- package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
- package/dist/core/runner/prepare-turn-wiring.js +3 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/resume-apply.js +3 -3
- package/dist/core/runner/resume-checkpoint-screen.js +7 -0
- package/dist/core/runner/run-harness-handlers.js +4 -1
- package/dist/core/runner/run-identity-wiring.js +6 -2
- package/dist/core/runner/run-leg.d.ts +2 -0
- package/dist/core/runner/run-leg.js +45 -45
- package/dist/core/runner/run-notification-lane.js +55 -27
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +16 -13
- package/dist/core/runner/run-turn-boundary.js +15 -5
- package/dist/core/runner/runtask.d.ts +6 -5
- package/dist/core/runner/runtask.js +26 -14
- package/dist/core/runner/skill-base-dir.d.ts +54 -0
- package/dist/core/runner/skill-base-dir.js +42 -0
- package/dist/core/runner/synthetic-tools.d.ts +83 -48
- package/dist/core/runner/synthetic-tools.js +69 -32
- package/dist/core/runner/tool-defer-gate.d.ts +22 -9
- package/dist/core/runner/tool-defer-gate.js +15 -5
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +2 -0
- package/dist/core/runner/tool-progress-lane.d.ts +42 -0
- package/dist/core/runner/tool-progress-lane.js +61 -0
- package/dist/core/runner/turn-attachments.d.ts +43 -12
- package/dist/core/runner/turn-attachments.js +58 -14
- package/dist/core/runner-deps.d.ts +48 -7
- package/dist/core/sensitive-path-policy.d.ts +58 -17
- package/dist/core/sensitive-path-policy.js +33 -5
- package/dist/core/skill-listing-budget.d.ts +112 -0
- package/dist/core/skill-listing-budget.js +58 -0
- package/dist/core/swappable-deps.d.ts +9 -7
- package/dist/core/swappable-deps.js +7 -3
- package/dist/core/task-event.d.ts +96 -29
- package/dist/core/task-notification.d.ts +142 -16
- package/dist/core/task-notification.js +106 -0
- package/dist/core/task-result.d.ts +9 -1
- package/dist/core/task-spec.d.ts +46 -3
- package/dist/core/tighten-task-spec.js +7 -5
- package/dist/core/tool-model-gate.d.ts +1 -1
- package/dist/core/tool-policy.d.ts +19 -4
- package/dist/core/tool-policy.js +6 -7
- package/dist/core/tool-search-setting.d.ts +78 -0
- package/dist/core/tool-search-setting.js +90 -0
- package/dist/core/tool-spec.d.ts +45 -4
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +46 -0
- package/dist/core/wiring-manifest.js +9 -1
- package/dist/core/write-protect.d.ts +8 -3
- package/dist/core/write-protect.js +8 -5
- package/dist/engine/execution-env/node-execution-env.js +10 -4
- package/dist/engine/harness/agent-harness.d.ts +42 -0
- package/dist/engine/harness/agent-harness.js +61 -29
- package/dist/engine/loop/agent-loop.d.ts +68 -3
- package/dist/engine/loop/agent-loop.js +109 -16
- package/dist/index.d.ts +13 -8
- package/dist/index.js +11 -7
- package/dist/internal/harness.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +6 -1
- package/dist/orchestration/workflow-governance.d.ts +7 -1
- package/dist/orchestration/workflow-governance.js +9 -5
- package/dist/orchestration/workflow-primitives.d.ts +3 -0
- package/dist/orchestration/workflow-primitives.js +6 -1
- package/dist/orchestration/workflow-types.d.ts +48 -13
- package/dist/orchestration/workflow-types.js +1 -1
- package/dist/orchestration/workflow.js +45 -17
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/section-usage.d.ts +26 -0
- package/dist/prompt-assembly/section-usage.js +10 -0
- package/dist/prompt-assembly/types.d.ts +8 -6
- package/dist/prompts/default.d.ts +13 -0
- package/dist/prompts/default.js +16 -8
- package/dist/tools/artifact/artifact-text.d.ts +10 -1
- package/dist/tools/artifact/artifact-text.js +6 -3
- package/dist/tools/artifact/artifact-tool.d.ts +12 -2
- package/dist/tools/artifact/artifact-tool.js +203 -70
- package/dist/tools/artifact/conformance.d.ts +56 -0
- package/dist/tools/artifact/conformance.js +349 -0
- package/dist/tools/artifact/local-stub.js +1 -1
- package/dist/tools/artifact/reject-breaker.d.ts +28 -0
- package/dist/tools/artifact/reject-breaker.js +42 -0
- package/dist/tools/fs/bash-lexer.d.ts +132 -0
- package/dist/tools/fs/bash-lexer.js +563 -0
- package/dist/tools/fs/bash-program-position.d.ts +142 -0
- package/dist/tools/fs/bash-program-position.js +125 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +81 -16
- package/dist/tools/fs/bash-readonly-classifier.js +180 -558
- package/dist/tools/fs/containment-face.d.ts +89 -0
- package/dist/tools/fs/containment-face.js +81 -0
- package/dist/tools/fs/fs-bash.d.ts +46 -1
- package/dist/tools/fs/fs-bash.js +165 -25
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +24 -5
- package/dist/tools/fs/index.js +18 -6
- package/dist/tools/fs/read-deny.d.ts +4 -3
- package/dist/tools/fs/repo-map.d.ts +2 -2
- package/dist/tools/fs/safety.d.ts +18 -3
- package/dist/tools/fs/safety.js +11 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +99 -13
- package/wire-changes/core@7.17.7.json +49 -0
- package/wire-changes/core@7.18.0.json +1273 -0
- package/wire-changes/core@7.19.0.json +457 -0
- package/dist/tools/fs/read-face.d.ts +0 -68
- package/dist/tools/fs/read-face.js +0 -47
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAskOrigin } from "./ask-origin.js";
|
|
2
|
+
import { isAutoModeFallbackCause } from "./auto-mode.js";
|
|
2
3
|
export const SETTLEMENT_KINDS = [
|
|
3
4
|
"human_allowed",
|
|
4
5
|
"human_refused",
|
|
@@ -17,6 +18,13 @@ const SETTLEMENT_KIND_SET = new Set(SETTLEMENT_KINDS);
|
|
|
17
18
|
export function isSettlementKind(v) {
|
|
18
19
|
return SETTLEMENT_KIND_SET.has(v);
|
|
19
20
|
}
|
|
21
|
+
export function decidersNote(text) {
|
|
22
|
+
return text === "" ? undefined : text;
|
|
23
|
+
}
|
|
24
|
+
export function settledByPerson(kind, approver, note) {
|
|
25
|
+
const given = decidersNote(note);
|
|
26
|
+
return { kind, who: { party: "person", ...(approver !== undefined ? { approver } : {}) }, when: Date.now(), ...(given !== undefined ? { note: given } : {}) };
|
|
27
|
+
}
|
|
20
28
|
export const SETTLEMENT_IS_REFUSAL = {
|
|
21
29
|
human_allowed: false,
|
|
22
30
|
human_refused: true,
|
|
@@ -89,5 +97,20 @@ export function screenGateOutcome(g) {
|
|
|
89
97
|
else if (isDeniedBy(d.deniedBy) && !DENIED_BY_MAY_CARRY_CAUSE[d.deniedBy])
|
|
90
98
|
defects.push(`I5: a classifier cause cannot stand beside deniedBy "${d.deniedBy}"`);
|
|
91
99
|
}
|
|
100
|
+
const attribution = d.classifier;
|
|
101
|
+
if (attribution !== undefined) {
|
|
102
|
+
const named = (v) => typeof v === "string" && v !== "";
|
|
103
|
+
if (!named(attribution.modelRequested) || !named(attribution.modelUsed))
|
|
104
|
+
defects.push("I6: disposition.classifier must name both the model the round was for and the model that answered");
|
|
105
|
+
const fb = attribution.fallback;
|
|
106
|
+
if (fb !== undefined) {
|
|
107
|
+
if (!named(fb.from) || !named(fb.to))
|
|
108
|
+
defects.push("I6: disposition.classifier.fallback must name the rung it left and the rung it arrived at");
|
|
109
|
+
else if (fb.to !== attribution.modelUsed)
|
|
110
|
+
defects.push(`I6: the fallback landed on "${fb.to}" but the record names "${attribution.modelUsed}" as the model that answered`);
|
|
111
|
+
if (!isAutoModeFallbackCause(fb.cause))
|
|
112
|
+
defects.push(`I6: fallback cause "${String(fb.cause)}" is outside the closed set`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
92
115
|
return defects;
|
|
93
116
|
}
|
|
@@ -104,7 +104,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
104
104
|
* src/ for notice mint shapes and names any code that is minted but unregistered, or registered but
|
|
105
105
|
* no longer minted.
|
|
106
106
|
*/
|
|
107
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "route.fallback_to_primary", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "tool_result.offload_put_failed"];
|
|
107
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "tool_result.offload_put_failed"];
|
|
108
108
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
109
109
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
110
110
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -102,6 +102,8 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
102
102
|
"config.models_swapped",
|
|
103
103
|
"config.read_face_swapped",
|
|
104
104
|
"config.read_face_deployment_clamped",
|
|
105
|
+
"config.write_face_swapped",
|
|
106
|
+
"config.write_face_deployment_clamped",
|
|
105
107
|
"config.tool_model_gate_removed",
|
|
106
108
|
"config.tool_model_gate_unknown_class",
|
|
107
109
|
"config.tool_model_gate_env_invalid",
|
|
@@ -116,6 +118,9 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
116
118
|
"peer.held_settled",
|
|
117
119
|
"peer.idle_subscription",
|
|
118
120
|
"classifier.denial_limit",
|
|
121
|
+
"classifier.fallback",
|
|
122
|
+
"classifier.role_defaulted",
|
|
123
|
+
"classifier.contract_broken",
|
|
119
124
|
"checkpoint.execution_outcome_unrecorded",
|
|
120
125
|
"delegation.transcript_integrity",
|
|
121
126
|
"delegation.ask_unresolvable",
|
|
@@ -137,6 +142,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
137
142
|
"memory.consolidation_refused",
|
|
138
143
|
"memory.consolidation_withheld",
|
|
139
144
|
"route.fallback_to_primary",
|
|
145
|
+
"route.classifier_unroutable",
|
|
140
146
|
"route.base_url_changed_key_unchanged",
|
|
141
147
|
"task.user_steer_undrained",
|
|
142
148
|
"task.user_followup_undrained",
|
|
@@ -147,6 +153,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
147
153
|
"task.interrupt_unconsumed",
|
|
148
154
|
"memory.capture_opted_out",
|
|
149
155
|
"memory.capture_optout_unpersisted",
|
|
156
|
+
"skills.listing_truncated",
|
|
150
157
|
"tool_result.offload_put_failed",
|
|
151
158
|
];
|
|
152
159
|
const NOTICE_AUDIENCE_TABLE = {
|
|
@@ -159,6 +166,9 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
159
166
|
"task.user_steer_undrained": "user",
|
|
160
167
|
"task.user_followup_undrained": "user",
|
|
161
168
|
"classifier.denial_limit": "user",
|
|
169
|
+
"classifier.fallback": "operator",
|
|
170
|
+
"classifier.role_defaulted": "operator",
|
|
171
|
+
"classifier.contract_broken": "operator",
|
|
162
172
|
"task.turn_interrupted": "user",
|
|
163
173
|
"steering.parked_input_blocked": "user",
|
|
164
174
|
"task.halt_unconsumed": "user",
|
|
@@ -175,6 +185,8 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
175
185
|
"config.models_swapped": "operator",
|
|
176
186
|
"config.read_face_swapped": "operator",
|
|
177
187
|
"config.read_face_deployment_clamped": "operator",
|
|
188
|
+
"config.write_face_swapped": "operator",
|
|
189
|
+
"config.write_face_deployment_clamped": "operator",
|
|
178
190
|
"config.tool_model_gate_removed": "operator",
|
|
179
191
|
"config.tool_model_gate_unknown_class": "operator",
|
|
180
192
|
"config.tool_model_gate_env_invalid": "operator",
|
|
@@ -200,7 +212,9 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
200
212
|
"memory.consolidation_incomplete": "operator",
|
|
201
213
|
"memory.consolidation_refused": "operator",
|
|
202
214
|
"route.fallback_to_primary": "operator",
|
|
215
|
+
"route.classifier_unroutable": "operator",
|
|
203
216
|
"route.base_url_changed_key_unchanged": "operator",
|
|
217
|
+
"skills.listing_truncated": "operator",
|
|
204
218
|
"tool_result.offload_put_failed": "operator",
|
|
205
219
|
};
|
|
206
220
|
export const NOTICE_AUDIENCE = NOTICE_AUDIENCE_TABLE;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hook seam's SEAT discipline and its disclosure vocabulary: which callback seats exist, what a
|
|
3
|
+
* deployment is allowed to put in one, and how a wiring disclosure names the ones that are wired.
|
|
4
|
+
*
|
|
5
|
+
* Kept beside the interface rather than inside it: `hooks.ts` owns the seam's BEHAVIOUR, and this module owns
|
|
6
|
+
* the seat as a CONFIGURATION VALUE — the same split `wiring-manifest.ts` keeps for the ask/question seats.
|
|
7
|
+
*/
|
|
8
|
+
import type { Hooks } from "./hooks.js";
|
|
9
|
+
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
10
|
+
import type { SeamProvenance } from "./wiring-manifest.js";
|
|
11
|
+
/**
|
|
12
|
+
* The CALLBACK SEATS of {@link Hooks} as a word list — the vocabulary a wiring disclosure names a wired seam
|
|
13
|
+
* by (`WiringManifest.hooks[].family`), in declaration order. `preToolUseObservational` is deliberately NOT a
|
|
14
|
+
* member (it is a DECLARATION ABOUT `preToolUse`); `timeoutMs` is not a seat either.
|
|
15
|
+
*/
|
|
16
|
+
export declare const HOOK_FAMILIES: readonly ["preToolUse", "postToolUse", "userPromptSubmit", "stop", "postToolUseFailure", "postToolBatch", "preCompact", "postCompact", "stopFailure", "permissionDenied"];
|
|
17
|
+
/**
|
|
18
|
+
* One hook seat's name.
|
|
19
|
+
*
|
|
20
|
+
* @contract hooks.family_vocabulary — {@link HOOK_FAMILIES} is the only spelling of a hook seat's name on any
|
|
21
|
+
* disclosure face, and {@link HookFamiliesCoverEverySeat} makes a new callback member of {@link Hooks} red at
|
|
22
|
+
* `tsc` until it has a word there — a disclosure that omits a seat under-states what runs on the critical path.
|
|
23
|
+
*/
|
|
24
|
+
export type HookFamily = (typeof HOOK_FAMILIES)[number];
|
|
25
|
+
/** Every CALLBACK member of {@link Hooks} — derived from the interface, never a hand-kept second list. */
|
|
26
|
+
type HookCallbackSeat = {
|
|
27
|
+
[K in keyof Hooks]-?: NonNullable<Hooks[K]> extends (...args: never[]) => unknown ? K : never;
|
|
28
|
+
}[keyof Hooks];
|
|
29
|
+
/** The closed-set fence: `never` while every callback seat has a word, the MISSING SEAT'S NAME when one does
|
|
30
|
+
* not (the constraint form names it; a conditional type says only "something changed"). */
|
|
31
|
+
export type HookFamiliesCoverEverySeat = AssertAllKeysHandled<Exclude<HookCallbackSeat, HookFamily>>;
|
|
32
|
+
/** The typed refusal a hook seat holding a present NON-CALLBACK value raises at prepare. */
|
|
33
|
+
export declare const HOOKS_INVALID_CODE = "config.hooks_invalid";
|
|
34
|
+
/**
|
|
35
|
+
* Screen the resolved hook record's seats (`spec.hooks ?? deps.hooks`).
|
|
36
|
+
*
|
|
37
|
+
* @contract hooks.seat_value — a hook seat is a FUNCTION or it is absent. A PRESENT non-function
|
|
38
|
+
* (`preToolUse: null`, a string, a bare object) is a configuration error and refuses the whole prepare with
|
|
39
|
+
* {@link HOOKS_INVALID_CODE}, naming the family and the type received. It is not folded to "unwired": the tool
|
|
40
|
+
* gate arms on PRESENCE (`hooks?.preToolUse !== undefined`) and would invoke the non-function and fail closed
|
|
41
|
+
* on every call, while a callability-based disclosure reported no hooks at all. The sibling seams already
|
|
42
|
+
* refuse the same way (`resolveQuestionSeam` / `resolveElicitSeam`). `undefined` stays exactly what it was:
|
|
43
|
+
* an unwired seat, silent.
|
|
44
|
+
*/
|
|
45
|
+
export declare function assertHookSeatsCallable(hooks: Hooks | undefined, provenance: SeamProvenance): void;
|
|
46
|
+
/**
|
|
47
|
+
* One WIRED HOOK SEAT of this leg. `family` is the seat's name; `owner` is WHICH SEAT supplied the record, the
|
|
48
|
+
* `spec > deps` provenance the ask/question seams report. Deliberately no third owner word: core mounts NO
|
|
49
|
+
* engine-owned hooks, and a delegated child's inherited `preToolUse` rides the gate's constraint fold.
|
|
50
|
+
*/
|
|
51
|
+
export interface WiringManifestHookEntry {
|
|
52
|
+
family: HookFamily;
|
|
53
|
+
owner: SeamProvenance;
|
|
54
|
+
/** Only on the `preToolUse` entry, and only when the deployment DECLARED the face observational
|
|
55
|
+
* (`Hooks.preToolUseObservational`) — it still runs on every call but is not folded into a delegated
|
|
56
|
+
* child's inherited constraints. Absence means "not declared", never "declared false". */
|
|
57
|
+
observational?: true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The wiring manifest's hook read face ({@link WiringManifestHookEntry}): one entry per CALLBACK seat the
|
|
61
|
+
* resolved record wires, in the vocabulary's declaration order. The VOCABULARY is the judge — families are
|
|
62
|
+
* walked, never the record's own keys. `owner` is a property of the RECORD, so every entry agrees. Callability
|
|
63
|
+
* and presence cannot diverge here: {@link assertHookSeatsCallable} has already refused the leg where they would.
|
|
64
|
+
*/
|
|
65
|
+
export declare function wiredHookEntries(hooks: Hooks | undefined, owner: SeamProvenance, preToolUseObservational: boolean): WiringManifestHookEntry[];
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const HOOK_FAMILIES = [
|
|
2
|
+
"preToolUse",
|
|
3
|
+
"postToolUse",
|
|
4
|
+
"userPromptSubmit",
|
|
5
|
+
"stop",
|
|
6
|
+
"postToolUseFailure",
|
|
7
|
+
"postToolBatch",
|
|
8
|
+
"preCompact",
|
|
9
|
+
"postCompact",
|
|
10
|
+
"stopFailure",
|
|
11
|
+
"permissionDenied",
|
|
12
|
+
];
|
|
13
|
+
export const HOOKS_INVALID_CODE = "config.hooks_invalid";
|
|
14
|
+
export function assertHookSeatsCallable(hooks, provenance) {
|
|
15
|
+
if (hooks === undefined)
|
|
16
|
+
return;
|
|
17
|
+
for (const family of HOOK_FAMILIES) {
|
|
18
|
+
const seat = hooks[family];
|
|
19
|
+
if (seat === undefined || typeof seat === "function")
|
|
20
|
+
continue;
|
|
21
|
+
const e = new Error(`the resolved hooks record (${provenance}) holds ${seat === null ? "null" : typeof seat} at \`${family}\` — a hook seat is a callback or it is absent. ` +
|
|
22
|
+
`A present non-callback is not an unwired seat: the tool gate arms on presence and would refuse every call through it. Omit the key, or wire a function.`);
|
|
23
|
+
e.code = HOOKS_INVALID_CODE;
|
|
24
|
+
throw e;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function wiredHookEntries(hooks, owner, preToolUseObservational) {
|
|
28
|
+
if (hooks === undefined)
|
|
29
|
+
return [];
|
|
30
|
+
const entries = [];
|
|
31
|
+
for (const family of HOOK_FAMILIES) {
|
|
32
|
+
if (typeof hooks[family] !== "function")
|
|
33
|
+
continue;
|
|
34
|
+
entries.push({ family, owner, ...(family === "preToolUse" && preToolUseObservational ? { observational: true } : {}) });
|
|
35
|
+
}
|
|
36
|
+
return entries;
|
|
37
|
+
}
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1354,7 +1354,7 @@ export interface ToolGateInput {
|
|
|
1354
1354
|
* (the write-protection tighten below). Absent ⇒ the deployment replaced the table with `[]` (or
|
|
1355
1355
|
* the caller runs the gate without one) and the decision path is byte-identical.
|
|
1356
1356
|
*/
|
|
1357
|
-
writeProtectionCheck?: (toolName: string, args: unknown, face?: import("./tool-registry.js").ToolCallLike["face"]) => import("./write-protect.js").WriteProtectedHit | null;
|
|
1357
|
+
writeProtectionCheck?: (toolName: string, args: unknown, face?: import("./tool-registry.js").ToolCallLike["face"], cwd?: string) => import("./write-protect.js").WriteProtectedHit | null;
|
|
1358
1358
|
/**
|
|
1359
1359
|
* design/143 批2 (A, CC 2.1.207 auto mode): when present, a surviving `ask` is routed to the
|
|
1360
1360
|
* small-model policy CLASSIFIER before any human/durable resolution:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The display bound on a grant candidate's directory: generous next to a real `PATH_MAX`, tight enough
|
|
2
|
+
* that the seat cannot become an unbounded prose channel onto an approval card. */
|
|
3
|
+
export declare const READ_ROOT_CANDIDATE_DIR_MAX = 1024;
|
|
4
|
+
/**
|
|
5
|
+
* The validator for a probe's {@link import("./tool-spec.js").ReversibilityVerdict.readRootCandidate}: the
|
|
6
|
+
* errand `normalizeProbeCause` does for the cause, fail-closed on the disclosure alone — a malformed value
|
|
7
|
+
* yields `undefined`, the ask stands, the card carries no offer. It REFUSES where the cause NEUTRALIZES,
|
|
8
|
+
* per `shell.read_boundary.grant_candidate_is_the_grant`: the cause is prose, so folding a separator out of
|
|
9
|
+
* it keeps it true, while this string IS the grant, and a card showing one spelling while the deployment
|
|
10
|
+
* applies another is the failure the seat prevents.
|
|
11
|
+
*/
|
|
12
|
+
export declare function normalizeReadRootCandidate(value: unknown): import("./tool-spec.js").ReadRootGrantCandidate | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isPlainRecord, safeDataValue } from "./checkpoint-store.js";
|
|
2
|
+
import { hasWin32AliasedComponent, isAbsolutePathForm, normalizeAbsPathLexically, pathFamilyOf, win32NamespaceScreen } from "../tools/fs/safety.js";
|
|
3
|
+
export const READ_ROOT_CANDIDATE_DIR_MAX = 1024;
|
|
4
|
+
export function normalizeReadRootCandidate(value) {
|
|
5
|
+
if (!isPlainRecord(value))
|
|
6
|
+
return undefined;
|
|
7
|
+
if (safeDataValue(value, "clearsThisAsk") !== true)
|
|
8
|
+
return undefined;
|
|
9
|
+
const dir = safeDataValue(value, "dir");
|
|
10
|
+
if (typeof dir !== "string" || dir.length === 0 || dir.length > READ_ROOT_CANDIDATE_DIR_MAX)
|
|
11
|
+
return undefined;
|
|
12
|
+
if (/[\u0000-\u001f\u007f\u2028\u2029]/.test(dir))
|
|
13
|
+
return undefined;
|
|
14
|
+
if (!isAbsolutePathForm(dir))
|
|
15
|
+
return undefined;
|
|
16
|
+
const folded = normalizeAbsPathLexically(dir);
|
|
17
|
+
if (folded !== dir || normalizeAbsPathLexically(`${dir}/..`) === dir)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (hasWin32AliasedComponent(dir))
|
|
20
|
+
return undefined;
|
|
21
|
+
if (!win32NamespaceScreen(dir, pathFamilyOf({ root: dir })).ok)
|
|
22
|
+
return undefined;
|
|
23
|
+
return { dir, clearsThisAsk: true };
|
|
24
|
+
}
|
package/dist/core/roles.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "../internal/harness-types.js";
|
|
2
2
|
import type { Model } from "../internal/llm.js";
|
|
3
|
-
import type { ModelRef, ModelRole, ModelRoles } from "./types.js";
|
|
3
|
+
import type { ModelRef, ModelRole, ModelRoles, RoleSpec } from "./types.js";
|
|
4
4
|
export type { ModelRole, RoleSpec, ModelRoles } from "./types.js";
|
|
5
5
|
/** Resolve a string ModelRef against the catalog (a Model object passes through). */
|
|
6
6
|
export declare function resolveModel(ref: ModelRef, models?: Record<string, Model>): Model;
|
|
@@ -87,6 +87,14 @@ export declare function parseModelMention(text: string, allowedNames: Iterable<s
|
|
|
87
87
|
* (即使 catalog 恰有裸模型名 `pro`/`flash` 也不入档位路由——INERT 靠配置成立,不靠键名运气)。
|
|
88
88
|
*/
|
|
89
89
|
export declare const ROLE_TIER_DEFAULTS: Readonly<Partial<Record<ModelRole, string>>>;
|
|
90
|
+
/**
|
|
91
|
+
* Whether a RoleSpec NAMES a model — a string ref, a Model object, or a config object carrying `model` or
|
|
92
|
+
* `select` — as opposed to a preset-only object (`{ thinking }`, `{ systemPrompt }`) that shapes the role's
|
|
93
|
+
* request but leaves its model to the defaults. The classifier seat's own default (the tier below the run's
|
|
94
|
+
* main model, `classifier-ladder.ts`) applies exactly when this is false: a `{select}` keeps its selection, a
|
|
95
|
+
* preset-only role does not disable the relative pick. One reader, the same parts {@link specParts} reads.
|
|
96
|
+
*/
|
|
97
|
+
export declare function roleSpecNamesModel(spec: RoleSpec | undefined): boolean;
|
|
90
98
|
export interface ResolvedRole {
|
|
91
99
|
model: Model;
|
|
92
100
|
/** The effective thinking level: explicit `spec.thinking` wins, else the role's default. */
|
package/dist/core/roles.js
CHANGED
|
@@ -130,6 +130,10 @@ function specParts(s) {
|
|
|
130
130
|
const cfg = s;
|
|
131
131
|
return { model: cfg.model, select: cfg.select, thinking: cfg.thinking, systemPrompt: cfg.systemPrompt };
|
|
132
132
|
}
|
|
133
|
+
export function roleSpecNamesModel(spec) {
|
|
134
|
+
const parts = specParts(spec);
|
|
135
|
+
return parts.model !== undefined || parts.select !== undefined;
|
|
136
|
+
}
|
|
133
137
|
function toConfig(s) {
|
|
134
138
|
if (s == null) {
|
|
135
139
|
return undefined;
|
|
@@ -21,9 +21,14 @@
|
|
|
21
21
|
* no marker and an absent directory narrows.
|
|
22
22
|
*
|
|
23
23
|
* Reverse check (the seat's own invariant): nothing enters `roots` except through `admit`, and every
|
|
24
|
-
* caller of `admit` is an advertiser deciding its advertisement off the returned verdict — so
|
|
25
|
-
*
|
|
26
|
-
* that
|
|
24
|
+
* caller of `admit` is an advertiser deciding its advertisement off the returned verdict — so no
|
|
25
|
+
* directory reaches the ROOTS FENCE through this seat without the prompt advertising it, and none
|
|
26
|
+
* the prompt advertises is refused by that fence.
|
|
27
|
+
*
|
|
28
|
+
* The invariant is about this seat, not about the whole write surface: under an open write face
|
|
29
|
+
* ({@link import("../../tools/fs/containment-face.js").ContainmentFace}) the roots fence judges
|
|
30
|
+
* nothing, so what the prompt advertises is a floor rather than the whole list — the `# Environment`
|
|
31
|
+
* block says so in its own line, and the advertisement stays true (these directories ARE writable).
|
|
27
32
|
*/
|
|
28
33
|
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
29
34
|
export interface AdvertisedWritableDir {
|
|
@@ -153,11 +153,12 @@ export interface ResultFlags {
|
|
|
153
153
|
* distinct codes from arriving as one anonymous `limits.max_turns_exceeded`. Empty/absent ⇒ the field is omitted. */
|
|
154
154
|
remoteEnvFailures?: TaskResult["remoteEnvFailures"];
|
|
155
155
|
/** The run's effective read posture (resolved face + normalized deny
|
|
156
|
-
* additions), echoed on `TaskResult.effectiveReadFace` / `.effectiveReadDenyPatterns`. Pure
|
|
156
|
+
* additions), echoed on `TaskResult.effectiveReadFace` / `.effectiveWriteFace` / `.effectiveReadDenyPatterns`. Pure
|
|
157
157
|
* pass-through (assembly neither adds nor filters) and INDEPENDENT of the status/errorCode chain:
|
|
158
158
|
* the posture is a fact about the leg that ran, whatever terminal it reached — a post-completion
|
|
159
159
|
* spawner folds it stricter-wins into follow-on legs; every other consumer may ignore it. */
|
|
160
160
|
effectiveReadFace?: TaskResult["effectiveReadFace"];
|
|
161
|
+
effectiveWriteFace?: TaskResult["effectiveWriteFace"];
|
|
161
162
|
effectiveReadDenyPatterns?: TaskResult["effectiveReadDenyPatterns"];
|
|
162
163
|
/** The memory-visibility observation, echoed on
|
|
163
164
|
* `TaskResult.effectiveMemoryScopes`. Pure pass-through on every terminal, same law as the
|
|
@@ -166,5 +166,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
166
166
|
if (flags.unpricedSpend)
|
|
167
167
|
delete publicStats.costMicroUsd;
|
|
168
168
|
const stampHaltedByUser = haltConsumed(flags.haltConsumption);
|
|
169
|
-
return { taskId, ...(flags.runId !== undefined ? { runId: flags.runId } : {}), sessionId, terminal, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, ...(apiFailure !== undefined ? { apiFailure } : {}), ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.editedFiles !== undefined && flags.editedFiles.length > 0 ? { editedFiles: flags.editedFiles } : {}), ...(flags.haltedOnUserRejection === true ? { haltedOnUserRejection: true } : {}), ...(stampHaltedByUser ? { haltedByUser: true } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: [...flags.remoteEnvFailures] } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), ...(flags.effectiveReasoning !== undefined ? { effectiveReasoning: flags.effectiveReasoning } : {}), stats: publicStats };
|
|
169
|
+
return { taskId, ...(flags.runId !== undefined ? { runId: flags.runId } : {}), sessionId, terminal, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, ...(apiFailure !== undefined ? { apiFailure } : {}), ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.editedFiles !== undefined && flags.editedFiles.length > 0 ? { editedFiles: flags.editedFiles } : {}), ...(flags.haltedOnUserRejection === true ? { haltedOnUserRejection: true } : {}), ...(stampHaltedByUser ? { haltedByUser: true } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: [...flags.remoteEnvFailures] } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveWriteFace !== undefined ? { effectiveWriteFace: flags.effectiveWriteFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), ...(flags.effectiveReasoning !== undefined ? { effectiveReasoning: flags.effectiveReasoning } : {}), stats: publicStats };
|
|
170
170
|
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The auto-mode classifier's CANDIDATE LADDER, as one module: which models a classification may be asked on
|
|
3
|
+
* (and which model stands in when no `classifier` role names one), its per-rung reasoning seat and the
|
|
4
|
+
* per-attempt user-message tail, how a rung's reply is read for a re-ask or a failure, and the once-per-run
|
|
5
|
+
* disclosures its first move and its broken contract mint.
|
|
6
|
+
*
|
|
7
|
+
* Beside `derived-route-fallback.ts` and in the same family — both are seats the caps phase's classifier
|
|
8
|
+
* arming reaches DOWN for, and both answer "this leg's model did not work out" one layer apart. Which is why
|
|
9
|
+
* the rung comparison here borrows the route adjudicator's own identity law instead of inventing one.
|
|
10
|
+
*
|
|
11
|
+
* THE TWO LAYERS, since one sentence ("the classifier seat did not work out ⇒ use the run's main model") is
|
|
12
|
+
* spelled in two places. They are ordered, and each has its own trigger and its own record face:
|
|
13
|
+
*
|
|
14
|
+
* ① PRE-FLIGHT — `derivedRouteFallsBack`, BEFORE any call. Trigger: the classifier model's route differs
|
|
15
|
+
* from the primary's AND the brain's own route judge finds the credential/URL pairing broken. Effect:
|
|
16
|
+
* the arming SWAPS the seat, so the ladder below is built from the main model and rung 0 IS the main
|
|
17
|
+
* model. Record: the once-per-run operator notice `route.fallback_to_primary` (`detail.{seat, from, to,
|
|
18
|
+
* cause}`) — and only that. `GateDisposition.classifier.modelRequested` reads the MAIN model's id,
|
|
19
|
+
* because the ladder it describes never carried the configured seat; the gate record answers "which
|
|
20
|
+
* model was asked", and "what did the deployment configure, and was it swapped" is the notice's
|
|
21
|
+
* question. `classifierFallback` governs THIS layer too: under `"none"` the pre-flight does not swap —
|
|
22
|
+
* it keeps the declared seat and announces `route.classifier_unroutable`, so the deployment that said
|
|
23
|
+
* "deny rather than fall back" gets the denial (the seat's calls are refused at the request gate)
|
|
24
|
+
* instead of a silent fallback the knob had just declined.
|
|
25
|
+
* ② THE LADDER — here, AFTER a call. Trigger: the rung produced no RULING. Effect: the next rung is asked.
|
|
26
|
+
* Record: `GateDisposition.classifier.fallback` on every classification this run's own gate decided (an
|
|
27
|
+
* inherited-ancestor chain carries the form and no attribution) + the throttled `classifier.fallback`.
|
|
28
|
+
*
|
|
29
|
+
* They FOLD rather than stack: after a ① SWAP the seat and the main model are the same route, so
|
|
30
|
+
* {@link classifierLadderRungs} returns ONE rung and ② cannot fire in the same run (and under `"none"`, where
|
|
31
|
+
* ① does not swap, the ladder is one rung by the knob). The split's cost is the
|
|
32
|
+
* `modelRequested` reading above; the unification (the pre-flight as rung 0 under its own cause word, one
|
|
33
|
+
* record face for both) changes the notice set and is a ticket, not a repair.
|
|
34
|
+
*/
|
|
35
|
+
import type { AssistantMessage, Model } from "../../internal/llm.js";
|
|
36
|
+
import { type AutoModeClassified } from "../auto-mode.js";
|
|
37
|
+
import type { RunnerDeps } from "../types.js";
|
|
38
|
+
/**
|
|
39
|
+
* The rungs a classification walks, in order: the classifier seat, and — under `classifierFallback: "main"` (the
|
|
40
|
+
* default) — the run's main model behind it. The knob is screened by `autoModeClassifierFallbackOf`, here and
|
|
41
|
+
* (idempotently) at the caps phase that reads the same word for the pre-flight's posture, so a value outside
|
|
42
|
+
* the closed set refuses the prepare loudly (`config.auto_mode_classifier_fallback_invalid`) instead of
|
|
43
|
+
* reading as the default.
|
|
44
|
+
*
|
|
45
|
+
* ONE RUNG when the two resolve to the SAME ROUTE. Route identity, not `Model.id`: two catalog entries may
|
|
46
|
+
* legitimately share a wire id while pointing at different endpoints (the route adjudicator's own law, and
|
|
47
|
+
* the split its pre-flight exists for) — an id-only comparison would collapse a healthy main endpoint and a
|
|
48
|
+
* broken classifier endpoint into one rung, turning the ladder off for the deployment that needs it most.
|
|
49
|
+
* The collapse where it does apply is deliberate: re-asking the SAME model is a retry, which is what CC's
|
|
50
|
+
* `unavailableOuterRetries` does and what this ladder is not. A catalog value the identity law cannot
|
|
51
|
+
* normalize (a non-string `baseUrl` — it throws) reads as TWO rungs: being wrong that way costs one extra
|
|
52
|
+
* call on a broken catalog, the other way turns the feature off invisibly, and the bad value still earns its
|
|
53
|
+
* loud refusal at the brain's request gate.
|
|
54
|
+
*/
|
|
55
|
+
export declare function classifierLadderRungs(knob: unknown, classifier: Model, main: Model): readonly Model[];
|
|
56
|
+
/**
|
|
57
|
+
* WHY the classifier seat is the model it is when no `classifier` role names one — the closed set behind the
|
|
58
|
+
* `classifier.role_defaulted` notice's `reason`. `tier_below_main` = the nearest directly-bound tier below the
|
|
59
|
+
* run's main model; `tier_table` = the static `flash` row (a main bound to no tier); `role_fallback` = the
|
|
60
|
+
* `summarize → default` chain (no tiers at all); `main_only` = the main model is the only model there is (a
|
|
61
|
+
* single-model catalog, or nothing bound below the main's tier); `role_unresolvable` = a `classifier` role
|
|
62
|
+
* NAMED a model the catalog cannot resolve (a stale or misspelled reference), so the main model stands in —
|
|
63
|
+
* the pre-#805 leg fell back the same way, silently.
|
|
64
|
+
*/
|
|
65
|
+
export declare const CLASSIFIER_ROLE_DEFAULT_REASONS: readonly ["tier_below_main", "tier_table", "role_fallback", "main_only", "role_unresolvable"];
|
|
66
|
+
export type ClassifierRoleDefaultReason = (typeof CLASSIFIER_ROLE_DEFAULT_REASONS)[number];
|
|
67
|
+
/** What {@link classifierSeatModel} returns: the model, and — when no role NAMED it — why it is that one. */
|
|
68
|
+
export type ClassifierSeat = {
|
|
69
|
+
readonly model: Model;
|
|
70
|
+
readonly defaulted?: ClassifierRoleDefaultReason;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The model the classifier SEAT resolves to for this run — ONE resolver for "which model classifies":
|
|
74
|
+
* · a `classifier` role that NAMES a model (`roleSpecNamesModel`: a string ref, a Model object, `{model}`,
|
|
75
|
+
* `{select}`) ⇒ the role resolver's answer, exactly as before (a preset-only role — `{thinking}` alone —
|
|
76
|
+
* does not name one and takes the default below);
|
|
77
|
+
* · else, with tier bindings declared (`deps.tiers`) and a main model on a tier ⇒ the nearest tier strictly
|
|
78
|
+
* BELOW the main's whose route differs from the main's — `tier_below_main`; the main's tier is the LOWEST
|
|
79
|
+
* tier its route sits on (a route bound as both `max` and `flash` is a `flash` main: deterministic, and
|
|
80
|
+
* the cheaper reading); nothing below ⇒ the main itself, `main_only`. Read off the expanded catalog's
|
|
81
|
+
* tier keys, the Runner's own resolution of a tier word: a key synthesised for an unbound tier falls DOWN
|
|
82
|
+
* to the next bound tier, which is the model this walk reaches anyway, and a catalog key spelled like a
|
|
83
|
+
* tier shadows the binding here as it does everywhere. No thinking preference: whether a model's
|
|
84
|
+
* thinking can be turned off is the SEAT's business ({@link classifierRungSeat}), not routing's;
|
|
85
|
+
* · else the role resolver's own fallback (the static tier row `classifier → flash` when tiers are active,
|
|
86
|
+
* `tier_table`; the `summarize → default` chain otherwise, `role_fallback`); a catalog with no roles at
|
|
87
|
+
* all ⇒ the main model, `main_only`.
|
|
88
|
+
* The relative pick follows the run's MAIN model on purpose: a side channel that resolved without it is the
|
|
89
|
+
* misfit the ticket names (a `flash` classifier above a `lite` main; two tiers below a `max` main).
|
|
90
|
+
*/
|
|
91
|
+
export declare function classifierSeatModel(args: {
|
|
92
|
+
deps: Pick<RunnerDeps, "models" | "roles" | "tiers">;
|
|
93
|
+
main: Model;
|
|
94
|
+
}): ClassifierSeat;
|
|
95
|
+
/**
|
|
96
|
+
* The `classifier.role_defaulted` line — minted ONCE per run by the arming, AFTER the route pre-flight, so it
|
|
97
|
+
* names the seat actually asked (a pre-flight swap to the main model is its own line,
|
|
98
|
+
* `route.fallback_to_primary`, same run). Silent when a role named the model: nothing was defaulted.
|
|
99
|
+
*/
|
|
100
|
+
export declare function announceClassifierRoleDefaulted(args: {
|
|
101
|
+
onNotice: RunnerDeps["onNotice"];
|
|
102
|
+
seat: ClassifierSeat;
|
|
103
|
+
sessionId?: string;
|
|
104
|
+
runId?: string;
|
|
105
|
+
}): void;
|
|
106
|
+
/**
|
|
107
|
+
* The classifier's REASONING SEAT, read off the model a classification is actually asked on — ONE predicate:
|
|
108
|
+
* does the wire SPELL off ({@link thinkingOffSpelled})? Yes ⇒ `off` + the verdict-sized cap (the request pays
|
|
109
|
+
* for the answer alone); no ⇒ `low` + the verdict cap + the low allowance (a model whose thinking the wire
|
|
110
|
+
* cannot turn off is asked for the least it can grade, in the room that costs — CC 2.1.259 `MU`: spell off,
|
|
111
|
+
* or leave thinking to the provider and pay 2048). A model that declares no reasoning at all is on the `low`
|
|
112
|
+
* seat too: its wire carries nothing either way, so the request is the gateway's default, and a default-on
|
|
113
|
+
* gateway thinks — the arm that used to read it as "never thinks" is the one the field report was about.
|
|
114
|
+
* PER RUNG, not once per arming: the second rung is a different model with its own wire.
|
|
115
|
+
*
|
|
116
|
+
* The THINK seat is the RE-ASK seat ({@link classifierReplyAsksAgain}): a first attempt that produced no
|
|
117
|
+
* verdict is asked once more with thinking allowed the room of CC's stage-2 classifier (8192) — an unknown
|
|
118
|
+
* model that answered NOTHING at its cap has just told us it thinks past it, and is re-asked as one that does.
|
|
119
|
+
*/
|
|
120
|
+
export declare const CLASSIFIER_OFF_SEAT: {
|
|
121
|
+
reasoning: "off";
|
|
122
|
+
maxTokens: number;
|
|
123
|
+
};
|
|
124
|
+
export declare const CLASSIFIER_LOW_SEAT: {
|
|
125
|
+
reasoning: "low";
|
|
126
|
+
maxTokens: number;
|
|
127
|
+
};
|
|
128
|
+
export declare const CLASSIFIER_THINK_SEAT: {
|
|
129
|
+
reasoning: "low";
|
|
130
|
+
maxTokens: number;
|
|
131
|
+
};
|
|
132
|
+
export type ClassifierSeatRequest = typeof CLASSIFIER_OFF_SEAT | typeof CLASSIFIER_LOW_SEAT | typeof CLASSIFIER_THINK_SEAT;
|
|
133
|
+
/**
|
|
134
|
+
* The seat for one rung's FIRST attempt — see the seat table above.
|
|
135
|
+
*
|
|
136
|
+
* @contract classifier.budget_is_room — every classifier output cap is ROOM the request asks for, never a
|
|
137
|
+
* bound the engine can vouch for: a gateway may exclude reasoning tokens from `max_tokens` (a vLLM-fronted
|
|
138
|
+
* Qwen gateway answered a cap of 128 with 938 output tokens and `stop`), so an over-long think is cut by the
|
|
139
|
+
* round-trip timeout (`unavailable` / `timeout`), not by the cap (`parse_error` on an empty reply), and the
|
|
140
|
+
* two are attested as the two different facts they are.
|
|
141
|
+
*/
|
|
142
|
+
export declare function classifierRungSeat(model: Model): typeof CLASSIFIER_OFF_SEAT | typeof CLASSIFIER_LOW_SEAT;
|
|
143
|
+
/**
|
|
144
|
+
* The user-message TAIL of each attempt — CC 2.1.259's classifier suffixes, verbatim: the FIRST attempt
|
|
145
|
+
* carries the fast-mode suffix (`$ns`: the answer must begin with `<block>`, no preamble), the RE-ASK carries
|
|
146
|
+
* the thinking-stage suffix (`Uns`: think in `<thinking>` first, then answer). The base prompt (2.1.250 `KRg`)
|
|
147
|
+
* already says "no preamble" once; upstream repeats it at the END of the user message, and it is the repeat
|
|
148
|
+
* that lands — live 2026-09-15, a Qwen 3.8 with thinking off went from 70% tagged replies (1.2–3.9k chars of
|
|
149
|
+
* "Looking at this action…" prose) to 90% at 8 output tokens with the first suffix, and the re-ask suffix
|
|
150
|
+
* had it write its reasoning inside `<thinking>` (stripped by the reader) before the tag.
|
|
151
|
+
*/
|
|
152
|
+
export declare const CLASSIFIER_ATTEMPT_SUFFIX: {
|
|
153
|
+
readonly first: "\nErr on the side of blocking. Your ENTIRE response MUST begin with <block>. Do NOT output any analysis, reasoning, or commentary before <block>. No \"Looking at...\" or similar preamble.";
|
|
154
|
+
readonly reask: "\nReview the classification process and follow it carefully, making sure you deny actions that should be blocked. As a reminder, explicit (not suggestive or implicit) user confirmation is required to override blocks. Use <thinking> before responding with <block>. Think longer on ambiguous or borderline actions; keep reasoning brief for clear-cut ones.";
|
|
155
|
+
};
|
|
156
|
+
/** The classifier reply's answer text — every text block joined; thinking blocks are not the verdict. */
|
|
157
|
+
export declare function classifierReplyText(reply: AssistantMessage): string;
|
|
158
|
+
/**
|
|
159
|
+
* CC 2.1.259 `$Tt`: an EMPTY reply that stopped at the output cap is "unparseable" — a reply to ask for again,
|
|
160
|
+
* not a refusal and not a verdict. The engine's brains report that cut two ways: `stopReason:"length"` (an
|
|
161
|
+
* engine-shrunk cap) or `stopReason:"error"` with `errorKind:"length_empty"` (a static cap — the classifier's
|
|
162
|
+
* own). An empty reply with any OTHER stop is CC's `policy_refusal` arm: not re-asked.
|
|
163
|
+
*/
|
|
164
|
+
export declare function classifierReplyEmptyAtCap(reply: AssistantMessage): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* A reply that may answer in the TAG form only: its text is not a complete answer of its own. Output cut by the cap
|
|
167
|
+
* (`stopReason: "length"`), a dropped connection or a stalled stream (`partialFinalized`, with a synthesised
|
|
168
|
+
* `stop`), and a reply that CALLED A TOOL (`stopReason: "toolUse"`, or a tool-call block in the content — the
|
|
169
|
+
* classifier is given no tools, and the openai brain reports a length cut with a surviving call as `toolUse`):
|
|
170
|
+
* whatever text arrived is a prefix or a fragment — a `No` on its way to `No, this must be blocked` — so it may
|
|
171
|
+
* carry a verdict only through a `<block>` tag, which reads a `<block>no` prefix as CC does; the bare-answer arm
|
|
172
|
+
* never sees it.
|
|
173
|
+
*/
|
|
174
|
+
export declare function classifierReplyTagOnly(reply: AssistantMessage): boolean;
|
|
175
|
+
/**
|
|
176
|
+
* The text the DECIDER reads for its verdict: the reply's answer text, except that a tag-only reply
|
|
177
|
+
* ({@link classifierReplyTagOnly}) whose text carries NO tagged answer reads as EMPTY — a cut prefix must not
|
|
178
|
+
* reach the bare-answer arm as if it were the whole reply (adversarial passes 2–3: a connection cut after `No`,
|
|
179
|
+
* and a scratch `<block>` mention before a cut `No`, executed the gated tool). Judged by the reader itself with
|
|
180
|
+
* its bare arm closed, so "no tagged verdict" and "no verdict" cannot drift. Only the MISSING form is emptied:
|
|
181
|
+
* a tag-only reply that carries a CONFLICT keeps its text, so the decider records the conflict and the leg
|
|
182
|
+
* does not re-ask it (adversarial pass 4: emptying every parse failure had turned a cut conflict into a
|
|
183
|
+
* missing reply, and the re-ask into an allow). The decider then records `parse_error` / `missing` for the
|
|
184
|
+
* emptied text, the same word an empty reply gets.
|
|
185
|
+
*/
|
|
186
|
+
export declare function classifierReplyVerdictText(reply: AssistantMessage): string;
|
|
187
|
+
/**
|
|
188
|
+
* Whether a first attempt is asked AGAIN on the THINK seat — ONE rule: the reply is MISSING a verdict, either
|
|
189
|
+
* empty at the cap ({@link classifierReplyEmptyAtCap}), cut or fragmentary without a tagged verdict
|
|
190
|
+
* ({@link classifierReplyTagOnly}), or text in which the reader finds no answer at all (`parse_error` of form
|
|
191
|
+
* `missing`: prose, a thinking-only reply). Not a CONFLICT (two `<block>` answers — it carries a `yes`, and a second attempt that answered `no`
|
|
192
|
+
* would erase it: the fail-closed `parse_error` it is stands) and not an empty reply with a non-cap stop
|
|
193
|
+
* (CC's refusal arm). CC 2.1.259 `Vrr` re-asks an unparseable reply up to `maxRetries` times; sema re-asks
|
|
194
|
+
* ONCE, on the stage-2 room, so a rung costs at most two calls. A failed reply ({@link classifierReplyFailure})
|
|
195
|
+
* is judged before this and never reaches it.
|
|
196
|
+
*/
|
|
197
|
+
export declare function classifierReplyAsksAgain(reply: AssistantMessage): boolean;
|
|
198
|
+
/**
|
|
199
|
+
* A reply that is a LEG FAILURE rather than an answer — the brains RETURN a provider error as a message
|
|
200
|
+
* (`stopReason: "error"` with the provider's text on `errorMessage`: an HTTP 4xx/5xx, a quota refusal, an
|
|
201
|
+
* overloaded gateway) and a cancelled request as `"aborted"`; neither is a reply the reader may judge. Read as
|
|
202
|
+
* text they were `parse_error` — a card saying "the classifier's reply carried no verdict" about a model that
|
|
203
|
+
* never answered, a ladder record blaming `parse_error` instead of `error`. The leg THROWS what this returns
|
|
204
|
+
* and the decider maps the throw to `unavailable` (`error`), as it maps every leg failure; the sibling
|
|
205
|
+
* side-channel legs (compaction, consolidation) already read an error stop this way. The cap-cut form
|
|
206
|
+
* (`errorKind: "length_empty"`) is not a failure: it is the empty-at-cap reply the re-ask owns.
|
|
207
|
+
*/
|
|
208
|
+
export declare function classifierReplyFailure(reply: AssistantMessage): Error | undefined;
|
|
209
|
+
/**
|
|
210
|
+
* The per-decision READ of a classification, as one seat: the `auto_mode.classified` trace frame (wall time,
|
|
211
|
+
* verdict, cause), the ladder's throttled `classifier.fallback` disclosure and the per-model
|
|
212
|
+
* `classifier.contract_broken` streak. All are observers — a throwing sink never reaches the gate (the decider
|
|
213
|
+
* swallows it), and none changes a verdict.
|
|
214
|
+
*
|
|
215
|
+
* The frame's `model` is the model the classification RAN ON, which is what the field has always meant (its
|
|
216
|
+
* declaration reads "the resolved classifier role, or the main model it fell back to"): the route
|
|
217
|
+
* pre-flight's swap and the ladder's walk are two ways to reach that same sentence. `seatModelId` is the
|
|
218
|
+
* fallback for a classification that carries no attribution at all.
|
|
219
|
+
*/
|
|
220
|
+
export declare function classifierRoundObserver(args: {
|
|
221
|
+
tracer: RunnerDeps["tracer"];
|
|
222
|
+
onNotice: RunnerDeps["onNotice"];
|
|
223
|
+
taskId: string;
|
|
224
|
+
seatModelId: string;
|
|
225
|
+
sessionId?: string;
|
|
226
|
+
runId?: string;
|
|
227
|
+
}): (info: AutoModeClassified) => void;
|