@sema-agent/core 7.37.1 → 7.38.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 +135 -0
- package/dist/agents/observer.js +1 -1
- package/dist/agents/peer-notices.js +4 -4
- package/dist/agents/subagent.js +1 -1
- package/dist/agents/team-role.js +2 -2
- package/dist/brain/anthropic.js +10 -9
- package/dist/brain/errors.d.ts +26 -0
- package/dist/brain/errors.js +34 -0
- package/dist/brain/input-too-long.d.ts +0 -31
- package/dist/brain/input-too-long.js +1 -18
- package/dist/brain/open-responses.js +10 -9
- package/dist/brain/openai.js +10 -9
- package/dist/brain/status-sink.d.ts +1 -1
- package/dist/brain/stream-engine.js +10 -2
- package/dist/brain/stream-shared.d.ts +11 -3
- package/dist/brain/stream-shared.js +4 -2
- package/dist/brain/tool-call-id.d.ts +29 -8
- package/dist/brain/tool-call-id.js +36 -6
- package/dist/core/a2a-task-state.js +2 -2
- package/dist/core/artifact-host.js +6 -6
- package/dist/core/ask-origin.js +6 -6
- package/dist/core/ask-unresolvable-notice.js +1 -1
- package/dist/core/auto-mode.js +4 -4
- package/dist/core/background-agent-store.js +2 -2
- package/dist/core/checkpoint-execution-record.js +1 -1
- package/dist/core/checkpoint-store.d.ts +2 -2
- package/dist/core/checkpoint-store.js +2 -2
- package/dist/core/child-clamp-seats.js +3 -3
- package/dist/core/compliance.js +1 -1
- package/dist/core/context-edit.js +1 -1
- package/dist/core/context-guard.d.ts +0 -19
- package/dist/core/context-guard.js +0 -38
- package/dist/core/decision-receipt.d.ts +27 -1
- package/dist/core/decision-receipt.js +176 -1
- package/dist/core/engine-notice.d.ts +59 -8
- package/dist/core/engine-notice.js +2 -1
- package/dist/core/gate-fold.js +3 -2
- package/dist/core/gate-outcome.js +4 -4
- package/dist/core/governance-codes.d.ts +221 -13
- package/dist/core/governance-codes.js +104 -4
- package/dist/core/hook-seats.js +2 -2
- package/dist/core/hooks.d.ts +21 -2
- package/dist/core/hooks.js +4 -1
- package/dist/core/mailbox-store.js +3 -3
- package/dist/core/mcp-failure.js +4 -4
- package/dist/core/mcp-injection-drop.js +1 -1
- package/dist/core/memory-engine/consolidation.js +1 -1
- package/dist/core/memory-engine/delegation-settlement.d.ts +8 -0
- package/dist/core/memory-engine/delegation-settlement.js +7 -0
- package/dist/core/memory-engine/engine.d.ts +12 -2
- package/dist/core/memory-engine/engine.js +10 -6
- package/dist/core/memory-engine/export-bundle.js +1 -1
- package/dist/core/memory-engine/layout.d.ts +6 -6
- package/dist/core/memory-engine/layout.js +6 -8
- package/dist/core/memory-engine/session-write-home.d.ts +30 -0
- package/dist/core/memory-engine/session-write-home.js +34 -0
- package/dist/core/memory-engine/tools.js +1 -1
- package/dist/core/memory-engine/types.js +1 -1
- package/dist/core/memory.js +1 -1
- package/dist/core/mode-policy.d.ts +10 -1
- package/dist/core/mode-policy.js +15 -7
- package/dist/core/model-seat.js +1 -1
- package/dist/core/permission-rule-model.js +3 -3
- package/dist/core/permission-rule-store.js +2 -2
- package/dist/core/read-only-shell-table.js +8 -8
- package/dist/core/retention.js +2 -2
- package/dist/core/runner/assemble-result.js +15 -5
- package/dist/core/runner/classifier-ladder.js +1 -1
- package/dist/core/runner/denial-limit-arms.d.ts +2 -0
- package/dist/core/runner/denial-limit-arms.js +6 -2
- package/dist/core/runner/inherited-ask-grants.d.ts +6 -5
- package/dist/core/runner/inherited-ask-grants.js +3 -2
- package/dist/core/runner/prepare-context-lane.d.ts +19 -15
- package/dist/core/runner/prepare-context-lane.js +11 -15
- package/dist/core/runner/prepare-gate-stations.js +5 -5
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +10 -10
- package/dist/core/runner/run-harness-handlers.js +3 -0
- package/dist/core/runner/run-identity-wiring.d.ts +1 -1
- package/dist/core/runner/run-identity-wiring.js +0 -2
- package/dist/core/runner/run-leg.js +2 -2
- package/dist/core/runner/run-stop-and-final-verify.js +1 -0
- package/dist/core/runner/run-turn-boundary.js +1 -1
- package/dist/core/runner/session-mode-seats.d.ts +11 -3
- package/dist/core/runner/session-mode-seats.js +6 -0
- package/dist/core/runner/session-rule-policy.js +2 -3
- package/dist/core/runner/stream-steer-verb.js +1 -1
- package/dist/core/runner/tool-call-id-fold-seat.js +16 -9
- package/dist/core/runner/tool-turn-repair-seat.d.ts +11 -1
- package/dist/core/runner/tool-turn-repair-seat.js +35 -0
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runtime-caps.js +1 -1
- package/dist/core/safety-axis-vocab.d.ts +5 -0
- package/dist/core/safety-axis-vocab.js +5 -0
- package/dist/core/session-reconcile.d.ts +9 -7
- package/dist/core/session-reconcile.js +21 -1
- package/dist/core/shared-memory/types.js +1 -1
- package/dist/core/side-query.d.ts +6 -1
- package/dist/core/store-contracts/read-back-shape-contract.js +1 -1
- package/dist/core/swappable-deps.js +1 -1
- package/dist/core/task-notification.js +4 -4
- package/dist/core/task-registry-shared.js +1 -1
- package/dist/core/terminal-cause.d.ts +19 -0
- package/dist/core/tool-face.js +6 -6
- package/dist/core/tool-policy.d.ts +17 -11
- package/dist/core/tool-policy.js +38 -72
- package/dist/core/tool-roster.js +1 -1
- package/dist/core/tool-search-setting.js +1 -1
- package/dist/core/trace.d.ts +5 -4
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.js +2 -2
- package/dist/core/write-protect.js +1 -1
- package/dist/engine/harness/agent-harness.js +10 -6
- package/dist/engine/llm/tool-turns.d.ts +93 -4
- package/dist/engine/llm/tool-turns.js +63 -9
- package/dist/engine/llm/types.d.ts +11 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/workflow-governance.js +2 -2
- package/dist/orchestration/workflow-types.js +2 -2
- package/dist/shell/lex.js +7 -7
- package/dist/tools/fs/bash-program-position.js +2 -2
- package/dist/tools/fs/read-deny.js +2 -2
- package/dist/tools/fs/safety.js +1 -1
- package/dist/tools/fs/shell-read-judge.js +2 -2
- package/dist/tools/fs/shell-read-plan.js +2 -2
- package/dist/tools/fs/shell-read-write-targets.js +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +17 -1
- package/wire-changes/core@7.38.0.json +664 -0
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
* PrepareTask's CONTEXT-LANE phase, verbatim from the driver: the edit/guard budgets, the
|
|
3
3
|
* microCompact machine state with the trigger-window clamp's one loud outlet, the model's chars-per-token coordinate,
|
|
4
4
|
* and the `context` handler registered on the harness — heal → ledger replay → aggregate tool-result and media caps →
|
|
5
|
-
* frontier clear → the guard chain (arms A/B/C) → the git-frame guard → the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* frontier clear → the guard chain (arms A/B/C) → the git-frame guard → the lossy-projection flag → the projection
|
|
6
|
+
* seat — and the harness's sink for the provider boundary's tool-turn repairs (`engine/llm/tool-turns.ts`, the one
|
|
7
|
+
* law every request builder reads): the run's `context.tool_turn_repaired` seat, and the lossy flag's second half —
|
|
8
|
+
* a request the law left recorded content out of is lossy too. The four module-level helpers only this lane called
|
|
9
|
+
* moved with it (the machine-state builder, the frontier-clear recorder, the observation-buffer flush, the guard chain
|
|
10
|
+
* with its two types). The interface is the dependency list the segment had implicitly.
|
|
9
11
|
*
|
|
10
12
|
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await of its own (the handler it registers is async and
|
|
11
13
|
* runs per provider request), so the phase adds no yield between the gate stations before it and the turn wiring after
|
|
12
14
|
* it. Read-stability of the host handles: {@link RunInternals} (`@contract prepare.deps-read-stable`).
|
|
13
15
|
*
|
|
14
|
-
* REGISTRATION SIDE EFFECT, OWNED: this phase registers the ONE `context` handler on the harness
|
|
15
|
-
*
|
|
16
|
-
* that hook.
|
|
16
|
+
* REGISTRATION SIDE EFFECT, OWNED: this phase registers the ONE `context` handler on the harness and sets its
|
|
17
|
+
* `onToolTurnRepaired` sink; the harness owns both for the run's life (never unregistered — the harness is per
|
|
18
|
+
* prepare). No other station registers on that hook or sets that sink.
|
|
17
19
|
*/
|
|
18
20
|
import { type AgentHarness, type AgentMessage } from "../../internal/harness.js";
|
|
19
|
-
import type
|
|
21
|
+
import { type Model } from "../../internal/llm.js";
|
|
20
22
|
import { type ClearedProjectionLedger, type MicroCompactMachine, type OccurrenceFingerprint, type OccurrenceIndex } from "../context-edit.js";
|
|
21
23
|
import type { ContextWindowOverlay } from "../context-settings.js";
|
|
22
24
|
import { type ToolResultStore } from "../tool-result-store.js";
|
|
@@ -28,9 +30,9 @@ import { type ToolRosterDeltaSeat } from "../tool-roster.js";
|
|
|
28
30
|
export interface PrepareContextLaneInput {
|
|
29
31
|
/** borrowed-readonly — the task model: the budgets, the trigger-window geometry and `charsPerToken` are read off it. */
|
|
30
32
|
model: Model;
|
|
31
|
-
/** borrowed-readonly — the deployment seats, whole: `onNotice` (the clamp outlet, the offload-loss announcement
|
|
32
|
-
* (the cap / clear / trim frames), `mediaByteCapBytes` and `onMediaStripped` (the media
|
|
33
|
-
* git-frame degrade) — the guard chain and the machine-state builder read through the whole record. */
|
|
33
|
+
/** borrowed-readonly — the deployment seats, whole: `onNotice` (the clamp outlet, the offload-loss announcement, the
|
|
34
|
+
* tool-turn repair seat), `tracer` (the cap / clear / trim frames), `mediaByteCapBytes` and `onMediaStripped` (the media
|
|
35
|
+
* cap), `onError` (the git-frame degrade) — the guard chain and the machine-state builder read through the whole record. */
|
|
34
36
|
deps: RunnerDeps;
|
|
35
37
|
/** borrowed-readonly — the acquired session id (the offload persist key; every disclosure's attribution). */
|
|
36
38
|
sessionId: string;
|
|
@@ -47,8 +49,9 @@ export interface PrepareContextLaneInput {
|
|
|
47
49
|
machine: MicroCompactMachine;
|
|
48
50
|
clearOnRejection: boolean;
|
|
49
51
|
};
|
|
50
|
-
/** borrowed-mutable — the built harness.
|
|
51
|
-
* handler (the harness owns the listener; nothing unregisters it)
|
|
52
|
+
/** borrowed-mutable — the built harness. Writers here: `on("context", …)` — the ONE registration of the request-build
|
|
53
|
+
* handler (the harness owns the listener; nothing unregisters it) — and `onToolTurnRepaired`, the provider boundary's
|
|
54
|
+
* repair sink. */
|
|
52
55
|
harness: AgentHarness;
|
|
53
56
|
/** borrowed-mutable — the turn's halt fact (`Prepared.batchHaltRef`). Writer here: the handler CLEARS it at every new
|
|
54
57
|
* provider request (a new request means the halted boundary was legitimately passed). The judgment site sets it. */
|
|
@@ -60,8 +63,9 @@ export interface PrepareContextLaneInput {
|
|
|
60
63
|
postureFrameRef: {
|
|
61
64
|
protectedText?: string;
|
|
62
65
|
};
|
|
63
|
-
/** borrowed-mutable — the per-request lossy-projection flag.
|
|
64
|
-
* build
|
|
66
|
+
/** borrowed-mutable — the per-request lossy-projection flag. Writers here: the handler re-derives it at every request
|
|
67
|
+
* build, and the repair sink raises it when the provider boundary's law left recorded content out of the same request
|
|
68
|
+
* (the boundary runs after the handler, before the recording shim); the shim reads it immediately after. */
|
|
65
69
|
requestLossyRef: {
|
|
66
70
|
current: boolean;
|
|
67
71
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_CHARS_PER_TOKEN, estimateContextTokens } from "../../internal/harness.js";
|
|
2
|
+
import { TOOL_TURN_REPAIR_DROPS } from "../../internal/llm.js";
|
|
2
3
|
import { clearStaleToolResults, createClearedProjectionLedger, dropEmptyFailureAssistants, editBudget, replayClearedProjection, resolveTriggerWindow } from "../context-edit.js";
|
|
3
|
-
import {
|
|
4
|
+
import { guardBudget, insertTrimNotice, protectGitFrame, trimToBudget } from "../context-guard.js";
|
|
4
5
|
import { AGGREGATE_MEDIA_BUDGET_BYTES, capAggregateMediaBytes } from "../media-byte-cap.js";
|
|
5
6
|
import { capAggregateToolResults } from "../tool-result-budget.js";
|
|
6
7
|
import { createOffloadPersist } from "../tool-result-store.js";
|
|
@@ -9,6 +10,7 @@ import { deliverEngineNotice } from "../types.js";
|
|
|
9
10
|
import { applyGitFrameGuard } from "./git-status-frame.js";
|
|
10
11
|
import { applyContextShrinkToReadFileState } from "../../tools/fs/index.js";
|
|
11
12
|
import { rosterToolName } from "../tool-roster.js";
|
|
13
|
+
import { createToolTurnRepairSeat } from "./tool-turn-repair-seat.js";
|
|
12
14
|
export function prepareContextLane(input) {
|
|
13
15
|
const { model, deps, sessionId, runId, hostTaskId, offloadStore, toolRosterDeltas, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, readFileState } = input;
|
|
14
16
|
const toolNameOf = (recorded) => rosterToolName(toolRosterDeltas.current, recorded);
|
|
@@ -20,6 +22,12 @@ export function prepareContextLane(input) {
|
|
|
20
22
|
};
|
|
21
23
|
const microCompact = buildMicroCompactState(deps, model, sessionId, runId, offloadStore, microCompactKnob, contextOverlay());
|
|
22
24
|
const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
|
|
25
|
+
const announceRepairs = createToolTurnRepairSeat((notice) => deps.onNotice?.(notice), { sessionId, runId });
|
|
26
|
+
harness.onToolTurnRepaired = (repairs) => {
|
|
27
|
+
if (repairs.some((repair) => TOOL_TURN_REPAIR_DROPS[repair.form]))
|
|
28
|
+
requestLossyRef.current = true;
|
|
29
|
+
announceRepairs(repairs);
|
|
30
|
+
};
|
|
23
31
|
harness.on("context", async ({ messages, recheck, signal }) => {
|
|
24
32
|
batchHaltRef.current = undefined;
|
|
25
33
|
const pendingProjectionObservations = [];
|
|
@@ -97,17 +105,6 @@ export function prepareContextLane(input) {
|
|
|
97
105
|
if (kept.action === "kept" || kept.action === "reinserted")
|
|
98
106
|
trimmed = kept.messages;
|
|
99
107
|
}
|
|
100
|
-
const swept = dropOrphanToolResults(trimmed);
|
|
101
|
-
if (swept.dropped.length > 0) {
|
|
102
|
-
try {
|
|
103
|
-
deps.onError?.(new Error(`context belt: dropped ${swept.dropped.length} orphan toolResult(s) whose tool-call assistant was not in the request view — ` +
|
|
104
|
-
swept.dropped
|
|
105
|
-
.map((d) => `toolCallId=${d.toolCallId} at index ${d.index} (prev=${d.prevRole}, next=${d.nextRole})`)
|
|
106
|
-
.join("; ")), { phase: "hook", sessionId });
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
108
|
requestLossyRef.current =
|
|
112
109
|
replayed !== healed ||
|
|
113
110
|
capped !== replayed ||
|
|
@@ -115,10 +112,9 @@ export function prepareContextLane(input) {
|
|
|
115
112
|
edited !== mediaCapped ||
|
|
116
113
|
working !== edited ||
|
|
117
114
|
trimDroppedMessages ||
|
|
118
|
-
swept.dropped.length > 0 ||
|
|
119
115
|
gitStatusRef.overBudgetShrunk === true;
|
|
120
|
-
microCompact.projectionRef.current = { messages:
|
|
121
|
-
return { messages:
|
|
116
|
+
microCompact.projectionRef.current = { messages: trimmed, keyOf: replay.index.keyOf };
|
|
117
|
+
return { messages: trimmed };
|
|
122
118
|
}
|
|
123
119
|
finally {
|
|
124
120
|
flushProjectionObservations(pendingProjectionObservations);
|
|
@@ -102,7 +102,7 @@ export function prepareGateStations(input) {
|
|
|
102
102
|
const gate = directDeny("compliance");
|
|
103
103
|
gateOutcomes.set(e.toolCallId, gate);
|
|
104
104
|
if (notifyPermissionDenied) {
|
|
105
|
-
await notifyPermissionDenied({ toolName: e.toolName, input: cloneObserverInput(e.input), toolCallId: e.toolCallId, reason: complianceDeny, gate, identity: hookIdentity });
|
|
105
|
+
await notifyPermissionDenied({ toolName: e.toolName, input: cloneObserverInput(e.input), toolCallId: e.toolCallId, reason: complianceDeny, gate, identity: hookIdentity, permissionMode: permissionMode.observed });
|
|
106
106
|
}
|
|
107
107
|
return { block: true, reason: formatHookFeedback(complianceDeny, reminderMark), preToolContext: [] };
|
|
108
108
|
}
|
|
@@ -115,7 +115,7 @@ export function prepareGateStations(input) {
|
|
|
115
115
|
...(e.signal !== undefined ? { callSignal: e.signal } : {}),
|
|
116
116
|
identity: hookIdentity,
|
|
117
117
|
reminderMark,
|
|
118
|
-
preToolUse: ownGatePreToolUse,
|
|
118
|
+
...(ownGatePreToolUse !== undefined ? { preToolUse: (name, args, ctx) => ownGatePreToolUse(name, args, { ...ctx, permissionMode: permissionMode.observed }) } : {}),
|
|
119
119
|
hookTimeoutMs,
|
|
120
120
|
...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}),
|
|
121
121
|
...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
|
|
@@ -131,7 +131,7 @@ export function prepareGateStations(input) {
|
|
|
131
131
|
irreversibility: tierSeat?.value,
|
|
132
132
|
reversibilityProbe: tierSeat?.read(reversibilityProbes),
|
|
133
133
|
abortSignal: abortController.signal,
|
|
134
|
-
...(notifyPermissionDenied ? { permissionDenied: notifyPermissionDenied } : {}),
|
|
134
|
+
...(notifyPermissionDenied ? { permissionDenied: (p) => notifyPermissionDenied({ ...p, permissionMode: permissionMode.observed }) } : {}),
|
|
135
135
|
onHookError: notifyHookError,
|
|
136
136
|
shellGated: tierSeat?.shellGated(shellGatedTools, toolMarkSources) === true,
|
|
137
137
|
...(autoModeDecider.current !== undefined ? { autoMode: { decider: autoModeDecider.current, ...(autoModeDenialTracking.current !== undefined ? { denialTracking: autoModeDenialTracking.current } : {}), unavailable: classifierUnavailableSeat({ station: "leg", ...(autoModeDecider.current.timeoutMs !== undefined ? { budgetMs: autoModeDecider.current.timeoutMs } : {}), approvalSeat: askReachesPerson(askRoute), onNotice: deps.onNotice, sessionId, runId }) } } : {}),
|
|
@@ -263,7 +263,7 @@ export function prepareGateStations(input) {
|
|
|
263
263
|
isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
|
|
264
264
|
content: e.content.map((c) => ({ ...c })),
|
|
265
265
|
details: clonedDetails,
|
|
266
|
-
}, { toolCallId: e.toolCallId, toolName: failedName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity }));
|
|
266
|
+
}, { toolCallId: e.toolCallId, toolName: failedName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity, permissionMode: permissionMode.observed }));
|
|
267
267
|
if (failureSeat.expired) {
|
|
268
268
|
notifyOwnHookCrash(hookSeatExpiredError("postToolUseFailure", hookTimeoutMs, failureSeat.cause, "the observation was abandoned and its additionalContext dropped; the failure result itself is unchanged"));
|
|
269
269
|
}
|
|
@@ -276,7 +276,7 @@ export function prepareGateStations(input) {
|
|
|
276
276
|
}
|
|
277
277
|
else if (hooks?.postToolUse) {
|
|
278
278
|
const ranName = wireOf("hook.postToolUse", post);
|
|
279
|
-
const successSeat = await runHookSeat("postToolUse", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUse(ranName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: ranName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity }));
|
|
279
|
+
const successSeat = await runHookSeat("postToolUse", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUse(ranName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: ranName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity, permissionMode: permissionMode.observed }));
|
|
280
280
|
if (successSeat.expired) {
|
|
281
281
|
notifyOwnHookCrash(hookSeatExpiredError("postToolUse", hookTimeoutMs, successSeat.cause, "the seat was abandoned; the tool result is delivered to the model UNMODIFIED (no updatedOutput, no additionalContext)"));
|
|
282
282
|
}
|
|
@@ -36,7 +36,7 @@ export function prepareInheritedGate(input) {
|
|
|
36
36
|
if (modeSeed.migrated !== undefined) {
|
|
37
37
|
deliverEngineNotice(deps.onNotice, {
|
|
38
38
|
code: "resume.legacy_mode_seed_migrated",
|
|
39
|
-
message: `the resumed checkpoint row carried pre-mode keys (${modeSeed.migrated.droppedKeys.join(", ")}); read once into ${modeSeed.migrated.permissionMode !== undefined ? `permissionMode: "${modeSeed.migrated.permissionMode}"` : "no mode of its own (the chain's ceiling stands)"}${modeSeed.shellGate !== undefined ? ' with the shell floor "always" kept' : ""} — the next suspend writes the one key; this migration is kept through 7.
|
|
39
|
+
message: `the resumed checkpoint row carried pre-mode keys (${modeSeed.migrated.droppedKeys.join(", ")}); read once into ${modeSeed.migrated.permissionMode !== undefined ? `permissionMode: "${modeSeed.migrated.permissionMode}"` : "no mode of its own (the chain's ceiling stands)"}${modeSeed.shellGate !== undefined ? ' with the shell floor "always" kept' : ""} — the next suspend writes the one key; this migration is kept through 7.38.x and removed in 7.39.0, which refuses a row still carrying these keys (resume or abandon such rows before upgrading)`,
|
|
40
40
|
detail: { migrated: { ...(modeSeed.migrated.permissionMode !== undefined ? { permissionMode: modeSeed.migrated.permissionMode } : {}), droppedKeys: [...modeSeed.migrated.droppedKeys] }, sessionId, runId },
|
|
41
41
|
});
|
|
42
42
|
}
|
|
@@ -14,7 +14,7 @@ import { createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
|
14
14
|
import { arbitrateByMode, askClassMembersOf, askMandateMember } from "../ask-origin.js";
|
|
15
15
|
import { createModeGatePolicy, mountedToolDeclaresReadOnly } from "../mode-policy.js";
|
|
16
16
|
const ancestorFacts = (ask) => ({ ...(ask ?? {}), ancestorResolved: true });
|
|
17
|
-
import { headlessDenyAtFold, headlessDenyAtRecheck, INHERITED_STATION_FACTS, inheritedAskCarry, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js";
|
|
17
|
+
import { headlessDenyAtFold, headlessDenyAtRecheck, INHERITED_STATION_FACTS, inheritedAskCarry, inheritedAskFallbackMessage, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js";
|
|
18
18
|
import { settleReadRootGrant } from "./read-root-grant.js";
|
|
19
19
|
import { askGrantShapeOf } from "./inherited-ask-grants.js";
|
|
20
20
|
import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js";
|
|
@@ -24,8 +24,8 @@ import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js";
|
|
|
24
24
|
export async function preparePolicyChain(input) {
|
|
25
25
|
const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
|
|
26
26
|
const ancestorArbitrations = new Map();
|
|
27
|
-
const arbitrateInherited = (ask, toolCallId,
|
|
28
|
-
const verdict = arbitrateByMode(permissionMode.current, classification, {
|
|
27
|
+
const arbitrateInherited = (ask, toolCallId, judged) => {
|
|
28
|
+
const verdict = arbitrateByMode(permissionMode.current, judged.classification, { origin: judged.origin, ...askMandateMember(ancestorFacts(judged.ask.action === "ask" ? judged.ask : undefined)) });
|
|
29
29
|
if (verdict.kind === "ask" || verdict.kind === "accepted")
|
|
30
30
|
return undefined;
|
|
31
31
|
ancestorArbitrations.set(toolCallId, verdict);
|
|
@@ -300,7 +300,7 @@ export async function preparePolicyChain(input) {
|
|
|
300
300
|
const seat = seatMarksOf(call);
|
|
301
301
|
return {
|
|
302
302
|
...INHERITED_STATION_FACTS,
|
|
303
|
-
mandateMarks: { egress: seat.egress, shellGated:
|
|
303
|
+
mandateMarks: { egress: seat.egress, shellGated: false, ...(seat.tier === "always" && !seat.shellGated ? { irreversibility: "always" } : {}) },
|
|
304
304
|
...(seat.sources.size > 0 ? { toolMarkSources: seat.sources } : {}),
|
|
305
305
|
...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
306
306
|
};
|
|
@@ -382,7 +382,7 @@ export async function preparePolicyChain(input) {
|
|
|
382
382
|
return vetoed(judged.result);
|
|
383
383
|
const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere } = judged;
|
|
384
384
|
re = editAsk;
|
|
385
|
-
const editArbitrated = arbitrateInherited(re, creq.toolCallId, judged
|
|
385
|
+
const editArbitrated = arbitrateInherited(re, creq.toolCallId, judged);
|
|
386
386
|
if (editArbitrated !== undefined) {
|
|
387
387
|
const out = editArbitrated.action === "allow" ? { action: "allow", updatedInput: editArgs } : editArbitrated;
|
|
388
388
|
carryPrecedingSettlement(settledBy, out);
|
|
@@ -393,7 +393,7 @@ export async function preparePolicyChain(input) {
|
|
|
393
393
|
toolCallId: creq.toolCallId,
|
|
394
394
|
args: editArgs,
|
|
395
395
|
...ruleOffersOf(creq, editArgs, ancestorFacts(re)),
|
|
396
|
-
message: re.message ??
|
|
396
|
+
message: re.message ?? inheritedAskFallbackMessage(creq.toolName),
|
|
397
397
|
...askSourceIdentity(),
|
|
398
398
|
...riskAxesOf(creq),
|
|
399
399
|
...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
@@ -463,7 +463,7 @@ export async function preparePolicyChain(input) {
|
|
|
463
463
|
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
464
464
|
return { action: "allow" };
|
|
465
465
|
}
|
|
466
|
-
const arbitrated = arbitrateInherited(inheritedAsk, creq.toolCallId, judged
|
|
466
|
+
const arbitrated = arbitrateInherited(inheritedAsk, creq.toolCallId, judged);
|
|
467
467
|
if (arbitrated !== undefined)
|
|
468
468
|
return arbitrated;
|
|
469
469
|
const presentedArgs = creq.args;
|
|
@@ -473,7 +473,7 @@ export async function preparePolicyChain(input) {
|
|
|
473
473
|
toolCallId: creq.toolCallId,
|
|
474
474
|
args: presentedArgs,
|
|
475
475
|
...ruleOffersOf(creq, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
|
|
476
|
-
message: inheritedAsk.message ??
|
|
476
|
+
message: inheritedAsk.message ?? inheritedAskFallbackMessage(creq.toolName),
|
|
477
477
|
...askSourceIdentity(),
|
|
478
478
|
...riskAxesOf(creq),
|
|
479
479
|
...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
@@ -537,7 +537,7 @@ export async function preparePolicyChain(input) {
|
|
|
537
537
|
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
538
538
|
return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
|
|
539
539
|
}
|
|
540
|
-
const arbitrated = arbitrateInherited(inheritedAsk.action === "ask" ? { ...inheritedAsk, ...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}) } : inheritedAsk, creq.toolCallId, judged
|
|
540
|
+
const arbitrated = arbitrateInherited(inheritedAsk.action === "ask" ? { ...inheritedAsk, ...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}) } : inheritedAsk, creq.toolCallId, judged);
|
|
541
541
|
if (arbitrated !== undefined)
|
|
542
542
|
return arbitrated;
|
|
543
543
|
const askT0 = now();
|
|
@@ -546,7 +546,7 @@ export async function preparePolicyChain(input) {
|
|
|
546
546
|
toolCallId: creq.toolCallId,
|
|
547
547
|
args: presentedArgs,
|
|
548
548
|
...ruleOffersOf(creq, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
|
|
549
|
-
message: inheritedAsk.message ??
|
|
549
|
+
message: inheritedAsk.message ?? inheritedAskFallbackMessage(creq.toolName),
|
|
550
550
|
...askSourceIdentity(),
|
|
551
551
|
...riskAxesOf(creq),
|
|
552
552
|
...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
@@ -12,12 +12,14 @@ import { hasVerifiableStructureSignal } from "./grounding-signal.js";
|
|
|
12
12
|
import { batchResponseDigest, toolEndBodyFrom } from "./tool-end-body.js";
|
|
13
13
|
import { createResponseModelSeat } from "./response-model-seat.js";
|
|
14
14
|
import { createToolCallIdFoldSeat } from "./tool-call-id-fold-seat.js";
|
|
15
|
+
import { createToolTurnIncompleteSeat } from "./tool-turn-repair-seat.js";
|
|
15
16
|
import { wireOf } from "../tool-name-readings.js";
|
|
16
17
|
import { rosterToolName } from "../tool-roster.js";
|
|
17
18
|
export function createHarnessHandlers(input) {
|
|
18
19
|
const { prepared, stats, rs, deps } = input;
|
|
19
20
|
const responseModelSeat = createResponseModelSeat(deps.runnerHooks.onNotice, { sessionId: prepared.sessionId, runId: prepared.runId });
|
|
20
21
|
const toolCallIdFoldSeat = createToolCallIdFoldSeat(deps.runnerHooks.onNotice, { sessionId: prepared.sessionId, runId: prepared.runId });
|
|
22
|
+
const toolTurnIncompleteSeat = createToolTurnIncompleteSeat(deps.runnerHooks.onNotice, { sessionId: prepared.sessionId, runId: prepared.runId });
|
|
21
23
|
const { spec, queue, internals, ident, parentToolCallId, subagentName, pushContent, emitCommitted, startedToolCallIds, toolStartAt, toolProgress, writeFamilyOf, toolLabels, postToolBatchHook, batchArgs, internalsNotifier } = deps;
|
|
22
24
|
let lastWorkspaceCwd = prepared.cwdRef?.current;
|
|
23
25
|
const announceWorkspaceMove = () => {
|
|
@@ -152,6 +154,7 @@ export function createHarnessHandlers(input) {
|
|
|
152
154
|
if (m.role === "assistant") {
|
|
153
155
|
rs.turn.turnResponseModel = responseModelSeat.observe(m);
|
|
154
156
|
toolCallIdFoldSeat.observe(m);
|
|
157
|
+
toolTurnIncompleteSeat.observe(m);
|
|
155
158
|
}
|
|
156
159
|
if (m.role === "assistant") {
|
|
157
160
|
const rep = m.repetition;
|
|
@@ -12,7 +12,7 @@ export interface RunIdentityWiringInput {
|
|
|
12
12
|
* task_notification frames. */
|
|
13
13
|
queue: PushQueue<TaskEvent>;
|
|
14
14
|
/** borrowed-mutable — the leg's prepared seat: the harness sinks (`onUndrainedEngineNotes`, `engineInjectionsHeld`,
|
|
15
|
-
* `onUndrainedUserInputs`, `onEngineNoteConsumed
|
|
15
|
+
* `onUndrainedUserInputs`, `onEngineNoteConsumed`) are installed, `nextTurn` is appended to; the ids, the identity
|
|
16
16
|
* envelope, the hold refs, the memory session and the listing / roster faces are read. */
|
|
17
17
|
prepared: Prepared;
|
|
18
18
|
/** borrowed-readonly — the trusted run-scoped channel: `parentToolCallId`, `onNotifyInjectorReady`. */
|
|
@@ -3,7 +3,6 @@ import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js"
|
|
|
3
3
|
import { recordFrameReceipt, toolResultReceiptKey } from "../frame-settlement.js";
|
|
4
4
|
import { discloseDroppedPending, renderTaskNotificationXml, taskNotificationDedupKey } from "../task-notification.js";
|
|
5
5
|
import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
|
|
6
|
-
import { createToolTurnRepairSeat } from "./tool-turn-repair-seat.js";
|
|
7
6
|
export function runIdentityWiring(input) {
|
|
8
7
|
const { spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, mcpRedialRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, notificationLane, pendingSessionNotifications, runner } = input;
|
|
9
8
|
notificationLane.harness = prepared.harness;
|
|
@@ -85,7 +84,6 @@ export function runIdentityWiring(input) {
|
|
|
85
84
|
if (key !== undefined)
|
|
86
85
|
recordFrameReceipt(seam, key, carrier.kind === "tool_result" ? carrier.content : undefined, entryId);
|
|
87
86
|
};
|
|
88
|
-
prepared.harness.onToolTurnRepaired = createToolTurnRepairSeat((notice) => runner.deps.onNotice?.(notice), { sessionId: prepared.sessionId, runId: prepared.runId });
|
|
89
87
|
if (notifyRef)
|
|
90
88
|
notifyRef.inject = injectTaskNotification;
|
|
91
89
|
if (captureOptOutRef && prepared.memoryEngineSession?.captureOptOut !== undefined) {
|
|
@@ -157,7 +157,7 @@ export async function runLeg(input) {
|
|
|
157
157
|
let outcome;
|
|
158
158
|
try {
|
|
159
159
|
const seat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => parkedScreen(steer.text, {
|
|
160
|
-
identity: prepared.hookIdentity,
|
|
160
|
+
identity: prepared.hookIdentity, permissionMode: prepared.permissionMode.observed,
|
|
161
161
|
signal: sig,
|
|
162
162
|
source: "parked_redelivery",
|
|
163
163
|
...(steer.inputId !== undefined ? { inputId: steer.inputId } : {}),
|
|
@@ -283,7 +283,7 @@ export async function runLeg(input) {
|
|
|
283
283
|
const userPromptSubmit = (spec.hooks ?? runner.deps.hooks)?.userPromptSubmit;
|
|
284
284
|
if (userPromptSubmit) {
|
|
285
285
|
try {
|
|
286
|
-
const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, signal: sig, source: "objective", ...(objectiveActor !== undefined ? { actor: snapshotActorAssertion(objectiveActor) } : {}) }));
|
|
286
|
+
const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, permissionMode: prepared.permissionMode.observed, signal: sig, source: "objective", ...(objectiveActor !== undefined ? { actor: snapshotActorAssertion(objectiveActor) } : {}) }));
|
|
287
287
|
if (promptSeat.expired) {
|
|
288
288
|
if (promptSeat.cause === "timeout") {
|
|
289
289
|
try {
|
|
@@ -110,6 +110,7 @@ export function runStopAndFinalVerify(input) {
|
|
|
110
110
|
getBranch: () => prepared.session.getBranch(),
|
|
111
111
|
backgroundTasks: () => prepared.listBackgroundTasks(),
|
|
112
112
|
identity: prepared.hookIdentity,
|
|
113
|
+
permissionMode: prepared.permissionMode.observed,
|
|
113
114
|
signal: sig,
|
|
114
115
|
}));
|
|
115
116
|
if (stopSeat.expired) {
|
|
@@ -469,7 +469,7 @@ export function createTurnBoundary(input) {
|
|
|
469
469
|
const injectedThisTurn = finalVerifyInjectedThisTurn ? "final_verification" : undefined;
|
|
470
470
|
if (!boundarySteered && !prepared.abortController.signal.aborted) {
|
|
471
471
|
try {
|
|
472
|
-
const batchSeat = await runHookSeat("postToolBatch", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }, (sig) => postToolBatchHook(batch, injectedThisTurn !== undefined ? { injectedThisTurn } : undefined, { identity: prepared.hookIdentity, signal: sig }));
|
|
472
|
+
const batchSeat = await runHookSeat("postToolBatch", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }, (sig) => postToolBatchHook(batch, injectedThisTurn !== undefined ? { injectedThisTurn } : undefined, { identity: prepared.hookIdentity, permissionMode: prepared.permissionMode.observed, signal: sig }));
|
|
473
473
|
if (batchSeat.expired) {
|
|
474
474
|
runnerHooks.onError?.(hookSeatExpiredError("postToolBatch", prepared.hookTimeoutMs, batchSeat.cause, "the boundary observation was abandoned and its additionalContext dropped; the turn boundary itself is unchanged"), { phase: "hook", sessionId: prepared.sessionId });
|
|
475
475
|
}
|
|
@@ -20,10 +20,19 @@ export interface LegModeCeiling {
|
|
|
20
20
|
/** The spawning leg's live mode while it runs; the session's current leg's after it (`default` before any leg of
|
|
21
21
|
* the session committed — no child can read it then). */
|
|
22
22
|
readonly current: PermissionMode;
|
|
23
|
+
/** The same word read without starting anything (`PermissionModeController.observed` on the leg it reads). Optional on
|
|
24
|
+
* the TYPE only: the engine's ceilings always answer it, and a reader falls to `current` when a hand-built ceiling (a
|
|
25
|
+
* host's literal, a fixture) carries none — so the member's arrival is additive for every constructor of this shape. */
|
|
26
|
+
readonly observed?: PermissionMode;
|
|
23
27
|
readonly sessionId: string;
|
|
24
28
|
/** A child leg holds the ceiling until `signal` aborts (its own leg's end): each move of the ceiling re-judges it. */
|
|
25
29
|
hold(holder: ModeCeilingHolder, signal: AbortSignal): void;
|
|
26
30
|
}
|
|
31
|
+
/** A leg's mode seat as a ceiling reads it: the decision read and the observation read. */
|
|
32
|
+
type LegModeSeat = {
|
|
33
|
+
readonly current: PermissionMode;
|
|
34
|
+
readonly observed: PermissionMode;
|
|
35
|
+
};
|
|
27
36
|
/**
|
|
28
37
|
* The seats of one Runner, keyed by session id and held WEAKLY: a seat lives while a leg ceiling holds it, and the
|
|
29
38
|
* next leg of the session finds the same object as long as any child still reads it. In-process only — a session
|
|
@@ -38,11 +47,10 @@ export declare class SessionModeSeats {
|
|
|
38
47
|
* session's current leg) and its end (once the leg has STOPPED — its loop settled, not merely cancelled: a cancelled
|
|
39
48
|
* leg whose tool is still pending keeps answering its own children).
|
|
40
49
|
*/
|
|
41
|
-
legCeiling(sessionId: string, leg: {
|
|
42
|
-
readonly current: PermissionMode;
|
|
43
|
-
}): {
|
|
50
|
+
legCeiling(sessionId: string, leg: LegModeSeat): {
|
|
44
51
|
ceiling: LegModeCeiling;
|
|
45
52
|
commit: () => void;
|
|
46
53
|
end: () => void;
|
|
47
54
|
};
|
|
48
55
|
}
|
|
56
|
+
export {};
|
|
@@ -8,6 +8,9 @@ class Seat {
|
|
|
8
8
|
get current() {
|
|
9
9
|
return this.leg?.current ?? "default";
|
|
10
10
|
}
|
|
11
|
+
get observed() {
|
|
12
|
+
return this.leg?.observed ?? "default";
|
|
13
|
+
}
|
|
11
14
|
addHolder(holder, via, signal) {
|
|
12
15
|
if (signal.aborted)
|
|
13
16
|
return;
|
|
@@ -36,6 +39,9 @@ class LegCeiling {
|
|
|
36
39
|
get current() {
|
|
37
40
|
return this.live ? this.leg.current : this.seat.current;
|
|
38
41
|
}
|
|
42
|
+
get observed() {
|
|
43
|
+
return this.live ? this.leg.observed : (this.seat.observed ?? this.seat.current);
|
|
44
|
+
}
|
|
39
45
|
hold(holder, signal) {
|
|
40
46
|
this.seat.addHolder(holder, this, signal);
|
|
41
47
|
}
|
|
@@ -136,11 +136,10 @@ export function createSessionRulePolicy(rules, opts) {
|
|
|
136
136
|
nameSets: [{ ...(seed.toolDeny?.length ? { deny: [...seed.toolDeny] } : {}), ...(seed.toolAllow?.length ? { allow: [...seed.toolAllow] } : {}) }],
|
|
137
137
|
async check(req, signal) {
|
|
138
138
|
const { toolDeny, toolAllow, toolDenyCovering, toolAllowCovering, cmdPolicy, allowDirs } = compile(await readRules(req.toolCallId, signal));
|
|
139
|
-
const toolName = req.toolName;
|
|
140
139
|
const listed = (set) => heldBy("policy.sessionRules", set, req);
|
|
141
|
-
if (listed(toolDeny) || namespacedCoveringHit(toolDenyCovering,
|
|
140
|
+
if (listed(toolDeny) || namespacedCoveringHit(toolDenyCovering, req))
|
|
142
141
|
return deny(req.toolName);
|
|
143
|
-
if (toolAllow && !listed(toolAllow) && !namespacedCoveringHit(toolAllowCovering,
|
|
142
|
+
if (toolAllow && !listed(toolAllow) && !namespacedCoveringHit(toolAllowCovering, req)) {
|
|
144
143
|
return deny(req.toolName, "it is not in the session-rule allowlist");
|
|
145
144
|
}
|
|
146
145
|
if (cmdPolicy) {
|
|
@@ -94,7 +94,7 @@ export function streamSteerVerb(input) {
|
|
|
94
94
|
if (screen !== undefined) {
|
|
95
95
|
let decision;
|
|
96
96
|
try {
|
|
97
|
-
const seat = await runHookSeat("userPromptSubmit", { timeoutMs: h.hookTimeoutMs, signal: h.abortController.signal, abortEnds: true }, (sig) => screen(text, { identity: h.hookIdentity, signal: sig, source: "steer", inputId: effectiveInputId, ...(actor !== undefined ? { actor: snapshotActorAssertion(actor) } : {}) }));
|
|
97
|
+
const seat = await runHookSeat("userPromptSubmit", { timeoutMs: h.hookTimeoutMs, signal: h.abortController.signal, abortEnds: true }, (sig) => screen(text, { identity: h.hookIdentity, permissionMode: h.permissionMode.observed, signal: sig, source: "steer", inputId: effectiveInputId, ...(actor !== undefined ? { actor: snapshotActorAssertion(actor) } : {}) }));
|
|
98
98
|
if (seat.expired) {
|
|
99
99
|
if (seat.cause === "timeout") {
|
|
100
100
|
try {
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { deliverEngineNotice } from "../engine-notice.js";
|
|
2
|
-
import { providerToolCallId,
|
|
2
|
+
import { providerToolCallId, toolCallFoldReason } from "../../brain/tool-call-id.js";
|
|
3
3
|
function isCall(block) {
|
|
4
4
|
return typeof block === "object" && block !== null && block.type === "toolCall" && typeof block.id === "string";
|
|
5
5
|
}
|
|
6
|
+
const REASON_CLAUSE = {
|
|
7
|
+
charset: () => "named a tool call with an id outside the shape the engine keys calls under (a control byte, a space, a non-ASCII character or a `~`)",
|
|
8
|
+
length: (wireId) => `named a tool call with an id outside the shape the engine keys calls under (${wireId.length} characters, over 128)`,
|
|
9
|
+
duplicate: () => "gave two tool calls in one response the same id, and every provider requires a request's call ids to be unique",
|
|
10
|
+
};
|
|
6
11
|
export function createToolCallIdFoldSeat(onNotice, ids) {
|
|
7
12
|
let announced = false;
|
|
8
13
|
return {
|
|
@@ -10,19 +15,21 @@ export function createToolCallIdFoldSeat(onNotice, ids) {
|
|
|
10
15
|
if (announced || !Array.isArray(message.content))
|
|
11
16
|
return;
|
|
12
17
|
for (const call of message.content.filter(isCall)) {
|
|
13
|
-
const
|
|
14
|
-
const reason = wireId === call.id ? undefined : toolCallIdFoldReason(wireId);
|
|
18
|
+
const reason = toolCallFoldReason(call);
|
|
15
19
|
if (reason === undefined)
|
|
16
20
|
continue;
|
|
17
21
|
announced = true;
|
|
22
|
+
const wireId = providerToolCallId(call);
|
|
18
23
|
deliverEngineNotice(onNotice, {
|
|
19
24
|
code: "route.tool_call_id_folded",
|
|
20
|
-
message: `The provider
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
message: `The provider ${REASON_CLAUSE[reason](wireId)}. ` +
|
|
26
|
+
(reason === "duplicate"
|
|
27
|
+
? `The engine keys the later call as "${call.id}" on every face — frames, cards, transcript, park rows — and hands ` +
|
|
28
|
+
`the provider a distinct id for it and its result, so both calls run and both results reach the model. `
|
|
29
|
+
: `The engine keys it as "${call.id}" on every face — frames, cards, transcript, park rows — and hands the ` +
|
|
30
|
+
`provider its own id back for the call and its result, so the run is unchanged. `) +
|
|
31
|
+
`Once per run; later calls with such ids are folded the same way without another notice.`,
|
|
32
|
+
detail: { toolCallId: call.id, ...(reason === "duplicate" ? {} : { rawLength: wireId.length }), reason, sessionId: ids.sessionId, runId: ids.runId },
|
|
26
33
|
});
|
|
27
34
|
return;
|
|
28
35
|
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import type { EngineNotice } from "../engine-notice.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type ToolTurnIncompleteCause, type ToolTurnRepair } from "../../internal/llm.js";
|
|
3
3
|
/** The notice's attribution ids — the same pair, for the same reason, as the fold seat's. */
|
|
4
4
|
export interface ToolTurnRepairSeatIds {
|
|
5
5
|
sessionId: string;
|
|
6
6
|
runId: string;
|
|
7
7
|
}
|
|
8
|
+
/** The person's line for one cause — once per run per cause, whichever observer saw it first. */
|
|
9
|
+
export interface ToolTurnIncompleteSeat {
|
|
10
|
+
/** Announce `count` items of `cause` (and, for a missing result, the calls) unless this run already said it. */
|
|
11
|
+
announce(cause: ToolTurnIncompleteCause, count: number, toolCallIds?: readonly string[]): void;
|
|
12
|
+
/** Observe one assistant message: announces each stream drop it reports (`streamDropsOf`). */
|
|
13
|
+
observe(message: {
|
|
14
|
+
readonly diagnostics?: unknown;
|
|
15
|
+
}): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createToolTurnIncompleteSeat(onNotice: ((notice: EngineNotice) => void) | undefined, ids: ToolTurnRepairSeatIds): ToolTurnIncompleteSeat;
|
|
8
18
|
export declare function createToolTurnRepairSeat(onNotice: ((notice: EngineNotice) => void) | undefined, ids: ToolTurnRepairSeatIds): (repairs: readonly ToolTurnRepair[]) => void;
|
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
import { deliverEngineNotice } from "../engine-notice.js";
|
|
2
|
+
import { isProviderEchoShape } from "../../brain/errors.js";
|
|
3
|
+
import { streamDropsOf } from "../../internal/llm.js";
|
|
2
4
|
const FORM_SENTENCE = {
|
|
3
5
|
trailing_text: "a block after an assistant's tool calls was moved ahead of them",
|
|
4
6
|
late_result: "a result recorded later was moved up behind its call",
|
|
5
7
|
missing_result: "a call with no result was closed as interrupted (outcome unknown)",
|
|
6
8
|
duplicate_result: "a second result recorded for one call was folded into the first (an interrupted close beside a recorded result is left out)",
|
|
9
|
+
duplicate_call: "a second call with the same id in one assistant message was left out (its results answer the first)",
|
|
10
|
+
orphan_result: "a result that answers no call before it was left out",
|
|
7
11
|
};
|
|
12
|
+
const INCOMPLETE_SENTENCE = {
|
|
13
|
+
truncated: (n) => `${n} tool call(s) the model made were cut off before their arguments were complete, so they did not run; the model was asked to re-issue them`,
|
|
14
|
+
unnamed: (n) => `${n} tool call(s) the model made arrived with no tool name, so they did not run; the model was asked to re-issue them`,
|
|
15
|
+
bad_frame: (n) => `${n} frame(s) of the model's streamed response could not be read and were dropped, so the response may be missing content; the model was told`,
|
|
16
|
+
missing_result_synthesized: (n) => `${n} earlier tool call(s) have no recorded result, so the model was told their outcome is unknown`,
|
|
17
|
+
};
|
|
18
|
+
export function createToolTurnIncompleteSeat(onNotice, ids) {
|
|
19
|
+
const announced = new Set();
|
|
20
|
+
const announce = (cause, count, toolCallIds) => {
|
|
21
|
+
if (announced.has(cause))
|
|
22
|
+
return;
|
|
23
|
+
announced.add(cause);
|
|
24
|
+
const shown = (toolCallIds ?? []).filter((id) => isProviderEchoShape(id));
|
|
25
|
+
deliverEngineNotice(onNotice, {
|
|
26
|
+
code: "task.tool_turn_incomplete",
|
|
27
|
+
message: `${INCOMPLETE_SENTENCE[cause](count)}. Announced once per run for this cause.`,
|
|
28
|
+
detail: { cause, count, ...(shown.length > 0 ? { toolCallIds: shown } : {}), sessionId: ids.sessionId, runId: ids.runId },
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
announce,
|
|
33
|
+
observe(message) {
|
|
34
|
+
for (const drop of streamDropsOf(message))
|
|
35
|
+
announce(drop.cause, drop.count);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
8
39
|
export function createToolTurnRepairSeat(onNotice, ids) {
|
|
9
40
|
const announced = new Set();
|
|
41
|
+
const incomplete = createToolTurnIncompleteSeat(onNotice, ids);
|
|
10
42
|
return (repairs) => {
|
|
43
|
+
const missing = repairs.filter((repair) => repair.form === "missing_result").flatMap((repair) => repair.toolCallIds);
|
|
44
|
+
if (missing.length > 0)
|
|
45
|
+
incomplete.announce("missing_result_synthesized", missing.length, missing);
|
|
11
46
|
const fresh = repairs.filter((repair) => {
|
|
12
47
|
const key = `${repair.form}\u0000${[...repair.toolCallIds].sort().join("\u0000")}`;
|
|
13
48
|
if (announced.has(key))
|
|
@@ -18,7 +18,7 @@ export const TOOL_SEARCH_REMINDER_CONFIG = {
|
|
|
18
18
|
EVERY_N_TURNS: 15,
|
|
19
19
|
MAX_NAMES: 10,
|
|
20
20
|
};
|
|
21
|
-
export const TOTAL_TOKENS_REMINDER_MODES = ["off", "infinite", "fixed", "countdown", "padded-countdown"];
|
|
21
|
+
export const TOTAL_TOKENS_REMINDER_MODES = Object.freeze(["off", "infinite", "fixed", "countdown", "padded-countdown"]);
|
|
22
22
|
export const TOTAL_TOKENS_REMINDER_DEFAULT_MODE = "padded-countdown";
|
|
23
23
|
export const TOTAL_TOKENS_FIXED_ARM_VALUE = 5_000_000;
|
|
24
24
|
export const CHANGED_FILES_MAX = 20;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const AUTO_MODE_DENY_SOURCES = ["org", "local", "settings"];
|
|
1
|
+
export const AUTO_MODE_DENY_SOURCES = Object.freeze(["org", "local", "settings"]);
|
|
2
2
|
const AUTO_MODE_DENY_SOURCE_SET = new Set(AUTO_MODE_DENY_SOURCES);
|
|
3
3
|
export function isAutoModeDenySource(v) {
|
|
4
4
|
return AUTO_MODE_DENY_SOURCE_SET.has(v);
|
|
@@ -30,3 +30,8 @@ export declare const SAFETY_AXIS_VOCABULARY: {
|
|
|
30
30
|
/** `ToolPolicy` decision — per-tool adjudication; `deny` short-circuits the `deny > ask > allow` fold (tool-policy.ts). */
|
|
31
31
|
readonly permissionDecision: readonly ["allow", "ask", "deny"];
|
|
32
32
|
};
|
|
33
|
+
/** The mark tighten's sentence — the ONE spelling of each safety-mark frame (an egress-marked tool = an external write; an
|
|
34
|
+
* always-irreversible tool), read by the gate's two tighten arms (`gate-fold.ts`) and by the inherited lane's card for a
|
|
35
|
+
* marked tool (`judgeInheritedClassifier`): a person who answers a card for a marked call is told what the mark means,
|
|
36
|
+
* whichever lane minted the card. */
|
|
37
|
+
export declare function markTightenMessage(toolName: string, mark: "egress" | "irreversible"): string;
|
|
@@ -9,3 +9,8 @@ export const SAFETY_AXIS_VOCABULARY = {
|
|
|
9
9
|
};
|
|
10
10
|
const _toolEffectExhaustive = true;
|
|
11
11
|
void _toolEffectExhaustive;
|
|
12
|
+
export function markTightenMessage(toolName, mark) {
|
|
13
|
+
return mark === "egress"
|
|
14
|
+
? `tool "${toolName}" performs an external write (egress) — explicit approval required`
|
|
15
|
+
: `tool "${toolName}" performs an irreversible action — explicit approval required`;
|
|
16
|
+
}
|