@sema-agent/core 7.53.0 → 7.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +291 -1
- package/LICENSE +3 -1
- package/dist/agents/peer-session-drain.d.ts +0 -1
- package/dist/agents/peer-session-drain.js +0 -2
- package/dist/agents/send-message-tool.js +3 -4
- package/dist/agents/subagent.d.ts +3 -5
- package/dist/agents/subagent.js +18 -11
- package/dist/agents/tool-filter.d.ts +7 -4
- package/dist/agents/tool-filter.js +7 -3
- package/dist/core/a2a.js +4 -2
- package/dist/core/allow-entry.d.ts +1 -0
- package/dist/core/allow-entry.js +1 -0
- package/dist/core/approval-object.d.ts +87 -0
- package/dist/core/approval-object.js +85 -0
- package/dist/core/arg-summary.d.ts +3 -2
- package/dist/core/arg-summary.js +17 -11
- package/dist/core/ask-origin.d.ts +43 -27
- package/dist/core/ask-origin.js +20 -11
- package/dist/core/ask-question.d.ts +0 -1
- package/dist/core/ask-question.js +4 -67
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/auto-compaction.d.ts +49 -50
- package/dist/core/auto-compaction.js +41 -51
- package/dist/core/auto-mode-prompt.d.ts +4 -2
- package/dist/core/auto-mode-prompt.js +4 -3
- package/dist/core/canonical-json.d.ts +69 -0
- package/dist/core/canonical-json.js +130 -8
- package/dist/core/checkpoint-requirements.d.ts +13 -7
- package/dist/core/checkpoint-requirements.js +4 -1
- package/dist/core/checkpoint-store.d.ts +33 -18
- package/dist/core/checkpoint-store.js +5 -4
- package/dist/core/child-clamp-seats.d.ts +45 -6
- package/dist/core/child-clamp-seats.js +56 -4
- package/dist/core/consolidate-scope.d.ts +1 -1
- package/dist/core/cross-boundary-taint.d.ts +213 -0
- package/dist/core/cross-boundary-taint.js +196 -0
- package/dist/core/decision-receipt.d.ts +44 -1
- package/dist/core/decision-receipt.js +16 -0
- package/dist/core/effective-path-target.d.ts +3 -0
- package/dist/core/effective-path-target.js +4 -0
- package/dist/core/engine-notice.d.ts +34 -2
- package/dist/core/engine-notice.js +17 -1
- package/dist/core/engine-storage-backend.d.ts +73 -0
- package/dist/core/engine-storage-backend.js +1 -0
- package/dist/core/file-history-store.d.ts +6 -2
- package/dist/core/file-history-store.js +1 -0
- package/dist/core/gate-fold.js +10 -2
- package/dist/core/gate-lanes.js +30 -21
- package/dist/core/gate-outcome.d.ts +7 -3
- package/dist/core/governance-codes.d.ts +7 -2
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/hooks.d.ts +14 -14
- package/dist/core/hooks.js +1 -2
- package/dist/core/locked-config.d.ts +28 -1
- package/dist/core/locked-config.js +30 -3
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mcp-server-spec.d.ts +66 -9
- package/dist/core/mcp-server-spec.js +52 -19
- package/dist/core/mcp.js +5 -4
- package/dist/core/memory-engine/engine.js +2 -1
- package/dist/core/memory-engine/export-bundle.d.ts +1 -1
- package/dist/core/memory-engine/export-bundle.js +1 -1
- package/dist/core/memory-engine/file-backend.d.ts +2 -6
- package/dist/core/memory-engine/file-backend.js +1 -12
- package/dist/core/memory.d.ts +3 -3
- package/dist/core/mode-policy.d.ts +13 -8
- package/dist/core/mode-policy.js +3 -2
- package/dist/core/mods/kernel/receive.d.ts +2 -1
- package/dist/core/mods/kernel/receive.js +7 -2
- package/dist/core/parent-spec-seat.d.ts +10 -13
- package/dist/core/parent-spec-seat.js +14 -6
- package/dist/core/pause-registry.d.ts +35 -1
- package/dist/core/pause-registry.js +10 -0
- package/dist/core/permission-rule-consent.d.ts +18 -5
- package/dist/core/permission-rule-consent.js +13 -8
- package/dist/core/permission-rule-model.d.ts +29 -6
- package/dist/core/permission-rule-model.js +6 -1
- package/dist/core/permission-rule-org.d.ts +37 -0
- package/dist/core/permission-rule-org.js +34 -42
- package/dist/core/permission-rule-session.js +2 -2
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +1 -0
- package/dist/core/permission-rule-sync.js +3 -2
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/read-only-shell.js +30 -9
- package/dist/core/remote-env.d.ts +41 -1
- package/dist/core/remote-env.js +18 -0
- package/dist/core/resume-startup-cause.d.ts +18 -0
- package/dist/core/resume-startup-cause.js +12 -0
- package/dist/core/runner/approval-feedback.d.ts +46 -0
- package/dist/core/runner/approval-feedback.js +41 -0
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.js +2 -2
- package/dist/core/runner/contracts.d.ts +33 -41
- package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
- package/dist/core/runner/cross-boundary-lane.js +190 -0
- package/dist/core/runner/decide-continuation.d.ts +4 -4
- package/dist/core/runner/decide-continuation.js +3 -1
- package/dist/core/runner/gate-exit.d.ts +32 -12
- package/dist/core/runner/gate-exit.js +75 -33
- package/dist/core/runner/inherited-ask-grants.js +2 -2
- package/dist/core/runner/person-path-rows.d.ts +2 -2
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +7 -9
- package/dist/core/runner/prepare-acquire-reconcile.js +8 -6
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
- package/dist/core/runner/prepare-config-doors.d.ts +12 -15
- package/dist/core/runner/prepare-config-doors.js +8 -3
- package/dist/core/runner/prepare-context-lane.js +6 -3
- package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
- package/dist/core/runner/prepare-delegation-surface.js +2 -3
- package/dist/core/runner/prepare-execution-env.d.ts +1 -2
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
- package/dist/core/runner/prepare-gate-stations.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
- package/dist/core/runner/prepare-hands-readface.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-park-ask.d.ts +1 -1
- package/dist/core/runner/prepare-park-ask.js +9 -3
- package/dist/core/runner/prepare-policy-chain.js +2 -7
- package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
- package/dist/core/runner/prepare-safety-scan.js +34 -27
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
- package/dist/core/runner/prepare-suspend-saga.js +6 -2
- package/dist/core/runner/prepare-task.js +13 -10
- package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
- package/dist/core/runner/prepare-turn-wiring.js +14 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/prepare-workspace-restore.js +2 -2
- package/dist/core/runner/resume-admission.js +9 -9
- package/dist/core/runner/resume-apply.js +4 -4
- package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
- package/dist/core/runner/resume-policy-outcome.js +18 -15
- package/dist/core/runner/resume-preflight.js +2 -2
- package/dist/core/runner/resume-review-outcome.js +3 -3
- package/dist/core/runner/run-leg.js +57 -33
- package/dist/core/runner/run-settle-and-teardown.js +2 -0
- package/dist/core/runner/run-terminal-adoption.js +15 -4
- package/dist/core/runner/session-file-state-replay.d.ts +5 -2
- package/dist/core/runner/session-file-state-replay.js +3 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/tool-face-overlay.d.ts +24 -6
- package/dist/core/runner/tool-face-overlay.js +24 -15
- package/dist/core/runner-deps.d.ts +9 -9
- package/dist/core/session-store.d.ts +35 -16
- package/dist/core/session-store.js +20 -12
- package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
- package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
- package/dist/core/shell-wrapper-table.d.ts +5 -3
- package/dist/core/shell-wrapper-table.js +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
- package/dist/core/store-contracts/session-store-contract.js +683 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
- package/dist/core/task-event.d.ts +6 -4
- package/dist/core/task-registry-agent.js +1 -0
- package/dist/core/task-registry-shared.d.ts +5 -0
- package/dist/core/task-registry.d.ts +7 -0
- package/dist/core/task-registry.js +17 -0
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +6 -6
- package/dist/core/tighten-task-spec.d.ts +6 -4
- package/dist/core/tighten-task-spec.js +2 -2
- package/dist/core/tool-catalog-entries.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +52 -52
- package/dist/core/tool-face-reader.d.ts +49 -0
- package/dist/core/tool-face-reader.js +94 -0
- package/dist/core/tool-face.d.ts +17 -8
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +64 -23
- package/dist/core/tool-policy.js +62 -56
- package/dist/core/tool-roster.d.ts +46 -28
- package/dist/core/tool-roster.js +91 -14
- package/dist/core/tool-spec-members.js +2 -2
- package/dist/core/tool-spec.d.ts +20 -4
- package/dist/core/tools.js +2 -0
- package/dist/core/trace.d.ts +24 -2
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +7 -0
- package/dist/core/untrusted-text.js +3 -8
- package/dist/core/user-attachment.d.ts +43 -0
- package/dist/core/user-attachment.js +92 -15
- package/dist/engine/compaction/compaction.d.ts +2 -2
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/harness/types.d.ts +7 -4
- package/dist/engine/llm/types.d.ts +7 -2
- package/dist/engine/llm/types.js +2 -1
- package/dist/engine/session/parent-chain-guard.d.ts +10 -0
- package/dist/engine/session/parent-chain-guard.js +10 -0
- package/dist/engine/session/repo-utils.d.ts +5 -0
- package/dist/engine/session/repo-utils.js +9 -0
- package/dist/engine/session/storage-base.js +5 -2
- package/dist/index.d.ts +23 -9
- package/dist/index.js +19 -8
- package/dist/internal/harness.d.ts +1 -0
- package/dist/internal/harness.js +1 -0
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +1 -0
- package/dist/orchestration/workflow-governance.d.ts +1 -1
- package/dist/orchestration/workflow.d.ts +3 -0
- package/dist/orchestration/workflow.js +2 -1
- package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
- package/dist/prompt-assembly/catalog/schema.js +3 -0
- package/dist/prompt-assembly/overlay.js +2 -2
- package/dist/prompt-assembly/select.js +2 -2
- package/dist/prompts/default.d.ts +1 -1
- package/dist/shell/lex.d.ts +87 -8
- package/dist/shell/lex.js +283 -49
- package/dist/stores/file/index.d.ts +8 -6
- package/dist/tools/artifact/artifact-tool.js +10 -0
- package/dist/tools/fs/bash-program-position.d.ts +3 -15
- package/dist/tools/fs/bash-program-position.js +3 -10
- package/dist/tools/fs/bash-readonly-classifier.js +2 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-bash.js +72 -101
- package/dist/tools/fs/fs-read.d.ts +12 -0
- package/dist/tools/fs/fs-read.js +10 -9
- package/dist/tools/fs/fs-shared.d.ts +27 -31
- package/dist/tools/fs/fs-shared.js +12 -15
- package/dist/tools/fs/fs-write.js +1 -1
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/read-deny-home.d.ts +1 -1
- package/dist/tools/fs/read-deny-home.js +5 -4
- package/dist/tools/fs/read-deny.d.ts +16 -3
- package/dist/tools/fs/read-deny.js +12 -9
- package/dist/tools/fs/safety.d.ts +7 -6
- package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
- package/dist/tools/fs/shell-read-bindings.js +15 -16
- package/dist/tools/fs/shell-read-device.js +1 -1
- package/dist/tools/fs/shell-read-plan.js +36 -47
- package/dist/tools/fs/shell-read-word.d.ts +1 -6
- package/dist/tools/fs/shell-read-word.js +2 -20
- package/dist/tools/fs/shell-reentry-text.js +1 -1
- package/dist/tools/web.d.ts +2 -2
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +5654 -5520
- package/wire-changes/core@7.53.0.json +30 -8
- package/wire-changes/core@7.54.0.json +2870 -0
- package/wire-changes/core@7.54.1.json +37 -0
- package/dist/core/shell-scan.d.ts +0 -60
- package/dist/core/shell-scan.js +0 -183
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The resume ladder's APPROVAL-LANE capture and binding rung (E4), verbatim from
|
|
3
3
|
* `Runner.resumeStream`: on a human / irreversible_ask row every deployment-supplied field of the
|
|
4
|
-
* `policy_ask` decide is read exactly once into a plain twin (answer
|
|
5
|
-
* JSON
|
|
4
|
+
* `policy_ask` decide is read exactly once into a plain twin (answer and updatedInput by the one capture, in their
|
|
5
|
+
* JSON form, the decision word, the refused keys, the host's decision facts and attribution, the deny note),
|
|
6
6
|
* then the decision-action binding (boundCallId, boundInputHash by version), the env_failed replay guard and
|
|
7
7
|
* the content-ask answer binding. Synchronous; every refusal throws pre-CAS.
|
|
8
8
|
*/
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { classifyQuestionOutcome, isLiveQuestionFace } from "../ask-question.js";
|
|
2
|
+
import { capturedJsonOf } from "../canonical-json.js";
|
|
3
|
+
import { bindingRefusalOf } from "../tool-policy.js";
|
|
2
4
|
import { CHECKPOINT_FEATURES, CheckpointError, checkpointVersionOf, winnerFromOutcome } from "../checkpoint-store.js";
|
|
3
|
-
import {
|
|
5
|
+
import { isGateKind, resumeGateMatches } from "../pause-registry.js";
|
|
6
|
+
import { sanitizeUntrustedText, sentinelTagRefusal } from "../untrusted-text.js";
|
|
4
7
|
import { assertOutcomeText, captureHostDecision, describeSuppliedValue, pendingContentAskCallId, refuseSuppliedKeys, sameWinner, SUPPLIED_RECORD_KEY } from "./decide-continuation.js";
|
|
8
|
+
import { refuseUnfitFeedback } from "./approval-feedback.js";
|
|
5
9
|
const REFUSED_DECIDE_KEYS = [
|
|
6
10
|
SUPPLIED_RECORD_KEY,
|
|
7
11
|
["settledBy", 'a retired key — the host supplies decision FACTS as hostDecision{decidedBy: "person" | "sla_timeout" | "policy", approver?} and core mints the settlement word from them'],
|
|
@@ -12,7 +16,8 @@ export function resumePolicyOutcome(input) {
|
|
|
12
16
|
let suppliedAnswer;
|
|
13
17
|
let redeemedAnswer;
|
|
14
18
|
let plainPolicyOutcome;
|
|
15
|
-
|
|
19
|
+
const storedGateKind = cp.gate.kind;
|
|
20
|
+
if (isGateKind(storedGateKind) && resumeGateMatches(storedGateKind, "policy_ask")) {
|
|
16
21
|
const decide = outcome;
|
|
17
22
|
suppliedAnswer = decide.answer;
|
|
18
23
|
if (suppliedAnswer !== undefined) {
|
|
@@ -25,17 +30,11 @@ export function resumePolicyOutcome(input) {
|
|
|
25
30
|
}
|
|
26
31
|
const updatedInputRaw = decide.updatedInput;
|
|
27
32
|
let updatedInput;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
if (Object.is(updatedInput, -0))
|
|
35
|
-
updatedInput = 0;
|
|
36
|
-
if (updatedInput !== undefined && !canonicalizeCapturedPlainData(updatedInput)) {
|
|
37
|
-
throw new CheckpointError("checkpoint.invalid_outcome", "resume carried an `updatedInput` outside the plain-JSON value domain (a Map/Set/Date/buffer, a sparse or expando-carrying array, a non-finite number, or a cycle) — " +
|
|
38
|
-
"such a value reads differently on the two sides of the durable store; refusing pre-CAS, the checkpoint stays pending");
|
|
33
|
+
if (updatedInputRaw !== undefined) {
|
|
34
|
+
const captured = capturedJsonOf(updatedInputRaw);
|
|
35
|
+
if (!captured.ok)
|
|
36
|
+
throw new CheckpointError("checkpoint.invalid_outcome", bindingRefusalOf(cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.toolName : "the parked call", updatedInputRaw, captured.fault));
|
|
37
|
+
updatedInput = captured.value;
|
|
39
38
|
}
|
|
40
39
|
const decision = decide.decision;
|
|
41
40
|
if (decision !== "allow" && decision !== "deny") {
|
|
@@ -45,6 +44,8 @@ export function resumePolicyOutcome(input) {
|
|
|
45
44
|
const hostDecision = captureHostDecision(cp, decide.hostDecision, "policy_ask", { word: decision, allows: decision === "allow" });
|
|
46
45
|
const reason = decide.reason;
|
|
47
46
|
assertOutcomeText(reason, "reason");
|
|
47
|
+
const feedback = decide.feedback;
|
|
48
|
+
assertOutcomeText(feedback, "feedback");
|
|
48
49
|
plainPolicyOutcome = {
|
|
49
50
|
gate: "policy_ask",
|
|
50
51
|
boundCallId: decide.boundCallId,
|
|
@@ -52,11 +53,13 @@ export function resumePolicyOutcome(input) {
|
|
|
52
53
|
decision,
|
|
53
54
|
...(updatedInput !== undefined ? { updatedInput } : {}),
|
|
54
55
|
...(reason !== undefined ? { reason } : {}),
|
|
56
|
+
...(feedback !== undefined ? { feedback } : {}),
|
|
55
57
|
...(redeemedAnswer !== undefined ? { answer: redeemedAnswer } : {}),
|
|
56
58
|
hostDecision,
|
|
57
59
|
};
|
|
60
|
+
refuseUnfitFeedback(plainPolicyOutcome);
|
|
58
61
|
if (plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason && sanitizeUntrustedText(plainPolicyOutcome.reason) !== plainPolicyOutcome.reason) {
|
|
59
|
-
throw new CheckpointError("checkpoint.invalid_outcome", "resume deny/reject reason
|
|
62
|
+
throw new CheckpointError("checkpoint.invalid_outcome", sentinelTagRefusal("resume deny/reject reason"));
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
if (plainPolicyOutcome !== undefined) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CheckpointError } from "../checkpoint-store.js";
|
|
2
2
|
import { MAX_TIMER_MS } from "../mods/kernel/index.js";
|
|
3
|
-
import { hasDestroy } from "../remote-env.js";
|
|
3
|
+
import { frozenHandleCopy, hasDestroy } from "../remote-env.js";
|
|
4
4
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
5
5
|
import { describeSuppliedValue } from "./decide-continuation.js";
|
|
6
6
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
@@ -63,7 +63,7 @@ export async function resumePreflight(input) {
|
|
|
63
63
|
sessionId: cp.sessionId,
|
|
64
64
|
...(cp.state.placementRootSessionId !== undefined ? { placementRootSessionId: cp.state.placementRootSessionId } : {}),
|
|
65
65
|
...(cp.principal ? { principal: cp.principal } : {}),
|
|
66
|
-
...(cp.state.workspaceHandle !== undefined ? { workspaceHandle:
|
|
66
|
+
...(cp.state.workspaceHandle !== undefined ? { workspaceHandle: frozenHandleCopy(cp.state.workspaceHandle) } : {}),
|
|
67
67
|
gateKind: cp.gate.kind,
|
|
68
68
|
};
|
|
69
69
|
const refusalTail = "; the checkpoint stays pending and the same token is redeemable once the obstacle clears";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CheckpointError } from "../checkpoint-store.js";
|
|
2
2
|
import { isGateKind, PAUSE_REGISTRY, resumeGateMatches } from "../pause-registry.js";
|
|
3
|
-
import { sanitizeUntrustedText } from "../untrusted-text.js";
|
|
3
|
+
import { sanitizeUntrustedText, sentinelTagRefusal } from "../untrusted-text.js";
|
|
4
4
|
import { assertOutcomeText, captureHostDecision, describeSuppliedValue, refuseSuppliedKeys, SUPPLIED_RECORD_KEY } from "./decide-continuation.js";
|
|
5
5
|
export function resumeReviewOutcome(input) {
|
|
6
6
|
const { cp, outcome, outcomeGate, wakeMessage } = input;
|
|
@@ -49,13 +49,13 @@ export function resumeReviewOutcome(input) {
|
|
|
49
49
|
if (plainReviewOutcome !== undefined) {
|
|
50
50
|
const reason = plainReviewOutcome.reason;
|
|
51
51
|
if (plainReviewOutcome.decision === "reject" && reason && sanitizeUntrustedText(reason) !== reason) {
|
|
52
|
-
throw new CheckpointError("checkpoint.invalid_outcome", "resume deny/reject reason
|
|
52
|
+
throw new CheckpointError("checkpoint.invalid_outcome", sentinelTagRefusal("resume deny/reject reason"));
|
|
53
53
|
}
|
|
54
54
|
const capturedPlan = plainReviewOutcome.gate === "plan_review" ? plainReviewOutcome.editedPlan : undefined;
|
|
55
55
|
if (plainReviewOutcome.decision === "edit" &&
|
|
56
56
|
capturedPlan &&
|
|
57
57
|
sanitizeUntrustedText(capturedPlan) !== capturedPlan) {
|
|
58
|
-
throw new CheckpointError("checkpoint.invalid_outcome", "resume editedPlan
|
|
58
|
+
throw new CheckpointError("checkpoint.invalid_outcome", sentinelTagRefusal("resume editedPlan"));
|
|
59
59
|
}
|
|
60
60
|
if (cp.reopenReason === "env_failed") {
|
|
61
61
|
const winner = cp.resolvedOutcome;
|
|
@@ -12,15 +12,16 @@ import { readToolCallArguments, uuidv7 } from "../../internal/harness.js";
|
|
|
12
12
|
import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "../permission-rule-org.js";
|
|
13
13
|
import { applyPersistedTightening, disclosedRuleSet } from "../persisted-rule-arms.js";
|
|
14
14
|
import { argsReadBy, stampArgsReading } from "../effective-path-target.js";
|
|
15
|
-
import {
|
|
15
|
+
import { skillRowRefusal } from "../skill-grant.js";
|
|
16
|
+
import { judgeApprovalObject } from "../approval-object.js";
|
|
16
17
|
import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
|
|
17
18
|
import { computeCostMicroUsd } from "../pricing.js";
|
|
18
19
|
import { mintSystemReminder } from "../reminder-mint.js";
|
|
19
20
|
import { hasDestroy } from "../remote-env.js";
|
|
20
|
-
import { advertisedWritableRootsSeat, attestationOf, exemptRootsSeat, humanRefusalSentence, ownedReading, policyCannotAnswer, policyRefusalMessage, refuseOutOfContractDecision } from "../tool-policy.js";
|
|
21
|
+
import { advertisedWritableRootsSeat, attestationOf, exemptRootsSeat, humanRefusalSentence, licenseCovers, ownedReading, policyCannotAnswer, policyRefusalMessage, refuseOutOfContractDecision } from "../tool-policy.js";
|
|
21
22
|
import { failedJudgmentDeny } from "./policy-throw.js";
|
|
22
23
|
import { pathDenialCardOf } from "../../tools/fs/safety.js";
|
|
23
|
-
import { callFaceSeat,
|
|
24
|
+
import { callFaceSeat, parkedRowLiveFace, rosterEntryNamed } from "../tool-roster.js";
|
|
24
25
|
import { deliverEngineNotice } from "../types.js";
|
|
25
26
|
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
26
27
|
import { errorCodeOf } from "./terminal-account.js";
|
|
@@ -28,6 +29,7 @@ import { DEFAULT_PRECALL_OUTPUT_TOKENS, platformLimitTerminal } from "./clock-an
|
|
|
28
29
|
import { legContextOverlayOption } from "./compaction-call-options.js";
|
|
29
30
|
import { mintOutputStyleReminder, owedPostureText } from "./compaction-restate.js";
|
|
30
31
|
import { deepJsonEqual, resumeContinuation } from "./decide-continuation.js";
|
|
32
|
+
import { approvalWordsOf, owedApprovalWords, withApprovalWords } from "./approval-feedback.js";
|
|
31
33
|
import { chainCannotJudge } from "./call-hook-stations.js";
|
|
32
34
|
import { settleExecutionRecord } from "./execution-record.js";
|
|
33
35
|
import { holdPrompt } from "./prompt-block.js";
|
|
@@ -64,6 +66,10 @@ export async function runLeg(input) {
|
|
|
64
66
|
}
|
|
65
67
|
if (resume) {
|
|
66
68
|
const walltimeExhaustedResume = effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0;
|
|
69
|
+
const approvalWords = approvalWordsOf(resume.outcome);
|
|
70
|
+
const approvalFrames = owedApprovalWords(approvalWords, uuidv7());
|
|
71
|
+
let approvalCommitted = false;
|
|
72
|
+
prepared.undeliveredContinuationRef.current.parkedSteers.push(...approvalFrames);
|
|
67
73
|
const resumeWindowRetryAfterMs = !walltimeExhaustedResume && prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
|
|
68
74
|
if (resumeWindowRetryAfterMs !== undefined) {
|
|
69
75
|
const owesNothing = resume.outcome.gate === "resource_limit" &&
|
|
@@ -236,13 +242,15 @@ export async function runLeg(input) {
|
|
|
236
242
|
...ident(),
|
|
237
243
|
});
|
|
238
244
|
}
|
|
245
|
+
if (approvalWords !== undefined)
|
|
246
|
+
continuation = withApprovalWords(continuation, engineSegments, approvalWords);
|
|
239
247
|
const pending = resume.cp.pendingAction;
|
|
240
248
|
const rejectionHalt = pending.kind === "tool_approval" && resume.outcome.gate === "policy_ask"
|
|
241
249
|
? maybeHumanRejectionHalt({ gate: resume.outcome.gateOutcome, isDelegatedChild: prepared.hookIdentity.isDelegatedChild, toolName: pending.toolName, toolCallId: pending.toolCallId })
|
|
242
250
|
: undefined;
|
|
243
251
|
if (rejectionHalt !== undefined && pending.kind === "tool_approval")
|
|
244
252
|
prepared.batchHaltRef.current = { rejectedToolCallId: pending.toolCallId };
|
|
245
|
-
prepared.undeliveredContinuationRef.current = { parkedSteers: resumeFrames.map(({ entry }) => entry).filter((e) => !withheldSteerIds.has(e.inputId)), orphanTasks };
|
|
253
|
+
prepared.undeliveredContinuationRef.current = { parkedSteers: [...resumeFrames.map(({ entry }) => entry).filter((e) => !withheldSteerIds.has(e.inputId)), ...approvalFrames], orphanTasks };
|
|
246
254
|
if (!prepared.abortController.signal.aborted) {
|
|
247
255
|
if (resume !== undefined)
|
|
248
256
|
resume.decisionDelivered = true;
|
|
@@ -264,11 +272,22 @@ export async function runLeg(input) {
|
|
|
264
272
|
const preview = redactThenCut(prepared.outputStyleFrame, 220);
|
|
265
273
|
prepared.frameReceipts.pending.push({ source: "output_style", text: continuation, commit: () => void (queue.push({ type: "steering_injected", source: "output_style", preview, ...ident() }), (rs.attach.attachmentsInjected += 1)) });
|
|
266
274
|
}
|
|
275
|
+
if (approvalFrames.length > 0) {
|
|
276
|
+
prepared.frameReceipts.pending.push({
|
|
277
|
+
source: "approval_feedback",
|
|
278
|
+
text: continuation,
|
|
279
|
+
commit: () => {
|
|
280
|
+
approvalCommitted = true;
|
|
281
|
+
const seat = prepared.undeliveredContinuationRef.current;
|
|
282
|
+
seat.parkedSteers = seat.parkedSteers.filter((f) => !approvalFrames.includes(f));
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
}
|
|
267
286
|
final = await withBrainSinks(() => prepared.harness.prompt(continuation, {
|
|
268
287
|
engineSegments,
|
|
269
288
|
settleUnansweredToolCalls: rejectionHalt !== undefined ? { halt: rejectionHalt } : {},
|
|
270
289
|
onDelivered: () => {
|
|
271
|
-
prepared.undeliveredContinuationRef.current = { parkedSteers: [], orphanTasks: [] };
|
|
290
|
+
prepared.undeliveredContinuationRef.current = { parkedSteers: approvalCommitted ? [] : [...approvalFrames], orphanTasks: [] };
|
|
272
291
|
for (const e of appliedEvents)
|
|
273
292
|
queue.push(e);
|
|
274
293
|
},
|
|
@@ -531,19 +550,6 @@ async function applyResumeDecision(runner, prepared, resume, emit, emitCommitted
|
|
|
531
550
|
return;
|
|
532
551
|
if (pendingAction.kind !== "tool_approval")
|
|
533
552
|
return;
|
|
534
|
-
if (resume.outcome.gate === "policy_ask" &&
|
|
535
|
-
resume.outcome.decision === "allow" &&
|
|
536
|
-
!prepared.tools.some((t) => t.name === pendingAction.toolName)) {
|
|
537
|
-
const e = new Error(`the approved tool "${pendingAction.toolName}" is no longer available on resume — refusing to continue as if it ran; the checkpoint is reopened for a retry with the tool present`);
|
|
538
|
-
e.code = "resume.tool_unavailable";
|
|
539
|
-
throw e;
|
|
540
|
-
}
|
|
541
|
-
const liveRoster = prepared.toolRosterDeltas.current;
|
|
542
|
-
if (pendingAction.kind === "tool_approval" && liveRoster !== undefined && judgeParkedToolIdentity(pendingAction, rosterEntryNamed(liveRoster, pendingAction.toolName)) === "mismatch") {
|
|
543
|
-
const e = new Error(`the approved tool "${pendingAction.toolName}" on this leg is not the tool the approval was minted against (its contract / shape / revision differ) — refusing to execute a replacement wearing the name; the checkpoint is reopened for a retry with the original tool present`);
|
|
544
|
-
e.code = "resume.tool_contract_mismatch";
|
|
545
|
-
throw e;
|
|
546
|
-
}
|
|
547
553
|
await resolvePendingCall(runner, prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart);
|
|
548
554
|
}
|
|
549
555
|
async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
|
|
@@ -551,12 +557,17 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
551
557
|
const { outcome } = resume;
|
|
552
558
|
if (pendingAction.kind !== "tool_approval" || outcome.gate !== "policy_ask")
|
|
553
559
|
return;
|
|
560
|
+
const mounted = () => { const tool = prepared.tools.find((x) => x.name === pendingAction.toolName); const roster = prepared.toolRosterDeltas.current; return tool === undefined || roster === undefined ? {} : { tool, entry: rosterEntryNamed(roster, pendingAction.toolName) }; };
|
|
561
|
+
const reopen = (miss) => { throw Object.assign(new Error(miss.message), { code: miss.code }); };
|
|
562
|
+
const liveTool = mounted().tool;
|
|
563
|
+
const atSession = outcome.decision === "allow" ? judgeApprovalObject(pendingAction, { tool: mounted().entry }, "session") : "match";
|
|
564
|
+
if (atSession !== "match" && atSession.disposition === "reopen")
|
|
565
|
+
reopen(atSession);
|
|
554
566
|
const decidedGate = outcome.gateOutcome;
|
|
555
567
|
if (decidedGate === undefined) {
|
|
556
568
|
throw new CheckpointError("checkpoint.invalid_outcome", "pending action reached the resolver with no settlement record on its decision (gateOutcome) — refusing to execute or settle a decision that carries no record");
|
|
557
569
|
}
|
|
558
570
|
const humanRewrite = wireOf("resume.questionArgsShown", pendingAction) === ASK_USER_QUESTION_TOOL_NAME ? undefined : outcome.updatedInput;
|
|
559
|
-
const liveTool = prepared.tools.find((x) => x.name === pendingAction.toolName);
|
|
560
571
|
const reading = liveTool === undefined ? undefined : ownedReading((a) => readToolCallArguments(liveTool, { type: "toolCall", id: pendingAction.toolCallId, name: pendingAction.toolName, arguments: {} }, a));
|
|
561
572
|
const beltFace = () => { const f = parkedRowLiveFace(prepared.toolRosterDeltas.current, pendingAction); if (f !== undefined && reading !== undefined)
|
|
562
573
|
stampArgsReading(f, reading); return f; };
|
|
@@ -568,7 +579,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
568
579
|
} };
|
|
569
580
|
const decided = outcome.decision === "allow" ? tryRead(humanRewrite !== undefined ? humanRewrite : pendingAction.args) : { args: humanRewrite !== undefined ? humanRewrite : pendingAction.args };
|
|
570
581
|
const resolvedArgs = decided.args;
|
|
571
|
-
const pendingLabel = (() => { const l =
|
|
582
|
+
const pendingLabel = (() => { const l = liveTool?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
|
|
572
583
|
const spelledName = pendingAction.names?.[0] ?? pendingAction.toolName;
|
|
573
584
|
const occurrences = prepared.toolOccurrences;
|
|
574
585
|
const callStart = { toolCallId: pendingAction.toolCallId, spelledName, wireName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs };
|
|
@@ -591,6 +602,11 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
591
602
|
await settleEnd(true, { content: text, ...(marks !== undefined ? { details: marks } : {}) }, undefined, true);
|
|
592
603
|
emitCommitted(await prepared.session.appendMessage(resultRow(text, true)), "toolResult", pendingAction.toolCallId);
|
|
593
604
|
};
|
|
605
|
+
const vetoApproval = async (miss) => {
|
|
606
|
+
const text = formatHookFeedback(miss.message, prepared.reminderMark);
|
|
607
|
+
await settleEnd(true, { content: text, ...(miss.code !== undefined ? { details: { code: miss.code } } : {}) }, "policy");
|
|
608
|
+
emitCommitted(await prepared.session.appendMessage(resultRow(text, true)), "toolResult", pendingAction.toolCallId);
|
|
609
|
+
};
|
|
594
610
|
if (outcome.decision === "deny") {
|
|
595
611
|
const settlementKind = outcome.gateOutcome?.settlement?.kind;
|
|
596
612
|
const reason = settlementKind === "policy_refused"
|
|
@@ -612,6 +628,8 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
612
628
|
if (outcome.decision !== "allow") {
|
|
613
629
|
throw new CheckpointError("checkpoint.invalid_outcome", `pending action reached the resolver with decision "${String(outcome.decision)}" — only "allow" executes and only "deny" injects a denial; refusing to execute`);
|
|
614
630
|
}
|
|
631
|
+
if (atSession !== "match")
|
|
632
|
+
return vetoApproval(atSession);
|
|
615
633
|
if (decided.unread !== undefined)
|
|
616
634
|
return settleUnreadable(decided.unread.error, humanRewrite !== undefined ? "the arguments the decision edited it to" : "its arguments");
|
|
617
635
|
const screened = await prepared.approvedCallScreen?.({ toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, args: structuredClone(resolvedArgs), decidedBy: outcome.hostDecision.decidedBy });
|
|
@@ -625,7 +643,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
625
643
|
if (rescreened?.unread !== undefined)
|
|
626
644
|
return settleUnreadable(rescreened.unread.error, "the arguments a PreToolUse hook rewrote it to");
|
|
627
645
|
const executedArgs = rescreened !== undefined ? rescreened.args : resolvedArgs;
|
|
628
|
-
const unseen = !
|
|
646
|
+
const unseen = !licenseCovers(pendingAction.boundInputHash, executedArgs);
|
|
629
647
|
let beltReading;
|
|
630
648
|
const beltReq = () => ({ toolName: pendingAction.toolName, args: executedArgs, toolCallId: pendingAction.toolCallId, ...callFaceSeat(beltFace()), budget: beltBudget, ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}), ...(prepared.handsRoot !== undefined ? { root: prepared.handsRoot } : {}), ...advertisedWritableRootsSeat(prepared.advertisedWritableDirs), ...exemptRootsSeat(prepared.exemptRoots), ...(beltReading !== undefined ? { targetReading: beltReading } : {}) });
|
|
631
649
|
const unjudgeable = unseen ? chainCannotJudge(resume.cp.state.inheritedGate) : undefined;
|
|
@@ -783,18 +801,24 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
783
801
|
reading.verdict.admit?.();
|
|
784
802
|
}
|
|
785
803
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
804
|
+
let tool;
|
|
805
|
+
for (;;) {
|
|
806
|
+
const { targetReading: _belts, ...unread } = beltReq();
|
|
807
|
+
const targetReading = (await prepared.stampCallTarget?.(unread, prepared.abortController.signal))?.targetReading;
|
|
808
|
+
const atExecution = judgeApprovalObject(pendingAction, { tool: mounted().entry, args: executedArgs, skills: prepared.skillSpecs, skillToolName: SKILL_TOOL_NAME, ...(humanRewrite !== undefined ? { decided: resolvedArgs } : {}), targetReading }, "execution");
|
|
809
|
+
if (atExecution !== "match")
|
|
810
|
+
return atExecution.disposition === "reopen" ? reopen(atExecution) : vetoApproval(atExecution);
|
|
811
|
+
tool = mounted().tool;
|
|
812
|
+
const waited = prepared.crossBoundary?.storeWaits() ?? 0;
|
|
813
|
+
const crossBoundaryRefusal = await prepared.crossBoundary?.check?.exit({ toolName: pendingAction.toolName, args: executedArgs, toolCallId: pendingAction.toolCallId }, outcome.hostDecision.decidedBy);
|
|
814
|
+
if (crossBoundaryRefusal !== undefined) {
|
|
815
|
+
const text = formatHookFeedback(crossBoundaryRefusal.message, prepared.reminderMark);
|
|
816
|
+
await settleEnd(true, { content: text }, "policy");
|
|
817
|
+
emitCommitted(await prepared.session.appendMessage(resultRow(text, true)), "toolResult", pendingAction.toolCallId);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if ((prepared.crossBoundary?.storeWaits() ?? 0) === waited)
|
|
821
|
+
break;
|
|
798
822
|
}
|
|
799
823
|
const args = executedArgs;
|
|
800
824
|
screened?.admit();
|
|
@@ -282,6 +282,8 @@ export async function runSettleAndTeardown(input) {
|
|
|
282
282
|
const writeDateAnchor = owedDateAnchor(prepared.session, prepared.dateChange, rs.attach.dateState);
|
|
283
283
|
if (writeDateAnchor !== undefined)
|
|
284
284
|
await settleTeardownLeg(writeDateAnchor, "date anchor (settle leg)", (e) => runner.deps.onError?.(e, { phase: "degraded", sessionId: prepared.sessionId }));
|
|
285
|
+
if (prepared.crossBoundary !== undefined)
|
|
286
|
+
await prepared.crossBoundary.settle();
|
|
285
287
|
const ownedEnvDying = prepared.ownedEnv !== undefined &&
|
|
286
288
|
hasDestroy(prepared.ownedEnv) &&
|
|
287
289
|
isIsolated(prepared.ownedEnv) &&
|
|
@@ -10,6 +10,7 @@ import { compactionHookOptions, recordCompactionReuse, seamCCompactionOptions }
|
|
|
10
10
|
import { compactionRestateOptions } from "./compaction-restate.js";
|
|
11
11
|
import { MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js";
|
|
12
12
|
import { emitSettled, reconciledEnd } from "./tool-end-body.js";
|
|
13
|
+
import { isApprovalWordsFrame } from "./approval-feedback.js";
|
|
13
14
|
import { asThrownError, thrownText } from "../thrown-value.js";
|
|
14
15
|
export async function runTerminalAdoption(input) {
|
|
15
16
|
const { spec, queue, prepared, rs, stats, ident, emitCommitted, startedToolCallIds, toolLabels, timeout, compactionBrain, compactionBreaker, windowSafetyOptions, undrainedUserAtEnd, userInputLostAtEnd, final, abortedLive, userInterruptedLive, callerStop, runner, sessions, next } = input;
|
|
@@ -78,21 +79,28 @@ export async function runTerminalAdoption(input) {
|
|
|
78
79
|
}
|
|
79
80
|
let migratedParked = { steer: 0, followUp: 0 };
|
|
80
81
|
let carriedContinuation = 0;
|
|
82
|
+
let carriedApproval = 0;
|
|
83
|
+
let approvalRefusal;
|
|
81
84
|
if (committedPause !== undefined && runner.deps.checkpointStore !== undefined) {
|
|
82
85
|
const parkToken = committedPause.cause.token;
|
|
83
86
|
const parkScope = committedPause.scope;
|
|
84
87
|
const store = runner.deps.checkpointStore;
|
|
85
88
|
const carried = [];
|
|
86
89
|
const owed = [
|
|
87
|
-
...prepared.undeliveredContinuationRef.current.parkedSteers.map((
|
|
90
|
+
...prepared.undeliveredContinuationRef.current.parkedSteers.map((frame) => {
|
|
91
|
+
const { seq: _seq, ...input } = frame;
|
|
92
|
+
return { input, ...(isApprovalWordsFrame(frame) ? { approval: true } : {}) };
|
|
93
|
+
}),
|
|
88
94
|
...prepared.harness.readParkableUserInputs().map((record) => ({ input: record, live: record })),
|
|
89
95
|
];
|
|
90
|
-
for (const { input, live } of owed) {
|
|
96
|
+
for (const { input, live, approval } of owed) {
|
|
91
97
|
try {
|
|
92
98
|
if (!(await store.setPendingSteer(parkToken, parkScope, input)))
|
|
93
99
|
break;
|
|
94
100
|
if (live !== undefined)
|
|
95
101
|
carried.push(live);
|
|
102
|
+
else if (approval)
|
|
103
|
+
carriedApproval += 1;
|
|
96
104
|
else
|
|
97
105
|
carriedContinuation += 1;
|
|
98
106
|
}
|
|
@@ -102,14 +110,17 @@ export async function runTerminalAdoption(input) {
|
|
|
102
110
|
sessionId: prepared.sessionId,
|
|
103
111
|
});
|
|
104
112
|
const parkCode = errorCodeOf(parkErr);
|
|
105
|
-
if (parkCode === "steering.invalid_content" || parkCode === "steering.queue_full")
|
|
113
|
+
if (parkCode === "steering.invalid_content" || parkCode === "steering.queue_full") {
|
|
114
|
+
if (approval)
|
|
115
|
+
approvalRefusal = parkCode;
|
|
106
116
|
continue;
|
|
117
|
+
}
|
|
107
118
|
break;
|
|
108
119
|
}
|
|
109
120
|
}
|
|
110
121
|
migratedParked = prepared.harness.dropParkedUserInputs(carried);
|
|
111
122
|
}
|
|
112
|
-
const owedUserInput = { held: undrainedUserAtEnd.current, migrated: migratedParked, continuation: { frames: prepared.undeliveredContinuationRef.current.parkedSteers, carried: carriedContinuation } };
|
|
123
|
+
const owedUserInput = { held: undrainedUserAtEnd.current, migrated: migratedParked, continuation: { frames: prepared.undeliveredContinuationRef.current.parkedSteers.filter((f) => !isApprovalWordsFrame(f)), carried: carriedContinuation, approval: { owed: prepared.undeliveredContinuationRef.current.parkedSteers.filter(isApprovalWordsFrame).length, carried: carriedApproval, ...(approvalRefusal !== undefined ? { refusal: approvalRefusal } : {}) } } };
|
|
113
124
|
if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
|
|
114
125
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
|
|
115
126
|
}
|
|
@@ -41,10 +41,13 @@ export type TranscriptFileRecord = {
|
|
|
41
41
|
content: string;
|
|
42
42
|
/** The recording message's own timestamp — the most honest "last read at" available. */
|
|
43
43
|
at: number;
|
|
44
|
+
/** A Read result's call id — the transmission that carried the text (a Write's or an Edit's post-condition: none). */ transmittedByToolCallId?: string;
|
|
44
45
|
} | {
|
|
45
46
|
path: string;
|
|
46
|
-
/** The entry the delivery committed. */
|
|
47
|
-
observation: UserAttachmentObservation["entry"]
|
|
47
|
+
/** The entry the delivery committed, stamped with the frame that carried it (a trim that drops the frame retires it). */
|
|
48
|
+
observation: UserAttachmentObservation["entry"] & {
|
|
49
|
+
transmittedByToolCallId: string;
|
|
50
|
+
};
|
|
48
51
|
at: number;
|
|
49
52
|
};
|
|
50
53
|
/**
|
|
@@ -3,6 +3,7 @@ import { isAbsoluteForFamily, pathFamilyOf } from "../../tools/fs/safety.js";
|
|
|
3
3
|
import { applyRecordedEdit } from "../../tools/fs/fs-shared.js";
|
|
4
4
|
import { normalizeFileText } from "../../tools/fs/encoding.js";
|
|
5
5
|
import { isOffloadedDetailReplacement } from "../tool-result-store.js";
|
|
6
|
+
import { attachmentTransmissionId } from "../user-attachment.js";
|
|
6
7
|
const READ_TOOL = "Read";
|
|
7
8
|
const WRITE_TOOL = "Write";
|
|
8
9
|
const EDIT_TOOL = "Edit";
|
|
@@ -69,7 +70,7 @@ export function wholeFileRecordsFromTranscript(messages) {
|
|
|
69
70
|
if (attachment?.observation !== undefined) {
|
|
70
71
|
const { key, entry } = attachment.observation;
|
|
71
72
|
if (isAbsoluteForFamily(pathFamilyOf({ root: key }), key))
|
|
72
|
-
byPath.set(key, { path: key, observation: entry, at: m.timestamp });
|
|
73
|
+
byPath.set(key, { path: key, observation: { ...entry, transmittedByToolCallId: attachmentTransmissionId(attachment) }, at: m.timestamp });
|
|
73
74
|
continue;
|
|
74
75
|
}
|
|
75
76
|
if (m.role !== "toolResult" || m.isError)
|
|
@@ -112,7 +113,7 @@ export function wholeFileRecordsFromTranscript(messages) {
|
|
|
112
113
|
continue;
|
|
113
114
|
if (isOffloadedDetailReplacement(content))
|
|
114
115
|
continue;
|
|
115
|
-
byPath.set(filePath, { path: filePath, content, at: m.timestamp });
|
|
116
|
+
byPath.set(filePath, { path: filePath, content, at: m.timestamp, ...(isRead ? { transmittedByToolCallId: m.toolCallId } : {}) });
|
|
116
117
|
}
|
|
117
118
|
return [...byPath.values()];
|
|
118
119
|
}
|
|
@@ -6,7 +6,7 @@ import { classicEventBase, failedHandlerClause, screenUserPrompt } from "../mods
|
|
|
6
6
|
import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
|
|
7
7
|
import { injectionLaneOf, isSystemInjectionPriority, steerPriorityOf, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
|
|
8
8
|
import { deliverEngineNotice } from "../types.js";
|
|
9
|
-
import { inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
9
|
+
import { inlineUntrusted, sanitizeUntrustedText, sentinelTagRefusal } from "../untrusted-text.js";
|
|
10
10
|
import { announceAcceptance, armAcceptance, nextHumanInputSeq } from "./steer-admission.js";
|
|
11
11
|
import { steerReplayRefusal } from "../steer-replay.js";
|
|
12
12
|
export function streamSteerVerb(input) {
|
|
@@ -18,7 +18,7 @@ export function streamSteerVerb(input) {
|
|
|
18
18
|
}
|
|
19
19
|
const trusted = options?.trusted ? true : false;
|
|
20
20
|
if (trusted && sanitizeUntrustedText(text) !== text) {
|
|
21
|
-
throw steeringError("trusted steering text
|
|
21
|
+
throw steeringError(sentinelTagRefusal("trusted steering text"), "steering.invalid_content");
|
|
22
22
|
}
|
|
23
23
|
const inputId = options?.inputId;
|
|
24
24
|
if (inputId !== undefined) {
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
* tool name like `allowTools` / `toolAxes`). Validated against the MATERIALIZED schema at every listing
|
|
5
5
|
* (the first mount and every RefreshMcpTools): `pathTarget.param` must be a top-level property; an
|
|
6
6
|
* `approvalCard` must find the keys the card needs; a `ruleFace` may only name scalar keys; `family`
|
|
7
|
-
* must be a member of the closed set
|
|
7
|
+
* must be a member of the closed set; every `resultCards` entry must live under the tool's own card namespace.
|
|
8
|
+
* A failing overlay does NOT mount — the tool mounts BARE (no
|
|
8
9
|
* face) and the leg announces `config.tool_face_invalid` once per tool; a `toolFaces` key naming a
|
|
9
10
|
* tool the listing does not carry announces `config.tool_face_undeclared` once per name. Never a
|
|
10
11
|
* silent partial application: an overlay is one declaration, applied whole or not at all.
|
|
11
12
|
*
|
|
12
13
|
* Prepare-path machinery (layer 1): pure over the tool objects and the server specs; the notice sink
|
|
13
|
-
* is a parameter.
|
|
14
|
+
* is a parameter. The declaration itself is read once, by `readToolFaces` (`core/tool-face-reader.ts`), at the preparation door and at the
|
|
15
|
+
* materialization entrance; the specs this file consumes carry that reader's copy, never the caller's object.
|
|
14
16
|
*/
|
|
15
17
|
import type { AgentTool } from "../../internal/harness-types.js";
|
|
16
18
|
import type { McpServerSpec } from "../mcp-server-spec.js";
|
|
@@ -20,10 +22,24 @@ import { type EngineNotice } from "../engine-notice.js";
|
|
|
20
22
|
* card's full schema; this is the pre-filter the overlay is validated against). */
|
|
21
23
|
export declare const APPROVAL_CARD_REQUIRED_KEYS: Readonly<Record<ToolApprovalCard, readonly string[]>>;
|
|
22
24
|
export type McpToolFace = NonNullable<McpServerSpec["toolFaces"]>[string];
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
+
/** The tool a face rides on, as far as the judge needs to know it: its name (a reserved approval card belongs to the engine's own tool of that
|
|
26
|
+
* name) and, when the caller holds the tool's card namespace, the `cardId` its `resultCards` must live under (`<cardId>/<card>`). */
|
|
27
|
+
export interface FaceTool {
|
|
25
28
|
readonly toolName: string;
|
|
26
|
-
|
|
29
|
+
readonly cardId?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Why an overlay is refused — a closed reason list, each a distinct declaration mistake. */
|
|
32
|
+
export declare function toolFaceProblem(face: McpToolFace, schema: AgentTool["parameters"], tool?: FaceTool): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The one judge of a face. With the tool's materialized `schema` it is {@link toolFaceProblem}: the declaration against the
|
|
35
|
+
* schema it rides on. With no schema it is the same judge over the DECLARATION ALONE (`readToolFaces`, `core/tool-face-reader.ts`): every
|
|
36
|
+
* check that needs the schema's keys (a path target's `param` / `patternParam`, an approval card's required keys, the names a summary or a
|
|
37
|
+
* rule face lists) waits for the listing; the closed words, the bounds and the cross-member rules do not need it and are the same
|
|
38
|
+
* checks, in the same order, with the same sentences. A face this judges is a well-typed one (the reader types it first). A tool that
|
|
39
|
+
* holds its card namespace (`tool.cardId`) has its `resultCards` held to it here too, so a caller's face and a server's face are
|
|
40
|
+
* judged by this one function, not by a rule beside it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function faceProblem(face: McpToolFace, schema: AgentTool["parameters"] | undefined, tool?: FaceTool): string | undefined;
|
|
27
43
|
export interface ApplyToolFacesInput {
|
|
28
44
|
/** The materialized tools of ONE server (namespaced names). */
|
|
29
45
|
tools: readonly AgentTool[];
|
|
@@ -38,5 +54,7 @@ export interface ApplyToolFacesInput {
|
|
|
38
54
|
* untyped carry `defineTool` uses, so the roster reads it off the instance). A face that does NOT fit the materialized
|
|
39
55
|
* schema is a bad declaration: the tool is NOT mounted on this leg (announced once, `config.tool_face_invalid`) —
|
|
40
56
|
* mounting it bare would silently drop the write protection the operator declared for it (an MCP name has no catalog
|
|
41
|
-
* floor), and a schema refresh that invalidates only a display hint must fail loud, never open.
|
|
57
|
+
* floor), and a schema refresh that invalidates only a display hint must fail loud, never open. The faces it reads are the
|
|
58
|
+
* server entry's own: the reader's copy of the declaration (`readToolFaces`), put there by the preparation door or the
|
|
59
|
+
* materialization entrance, never a caller's object. */
|
|
42
60
|
export declare function applyMcpToolFaces(input: ApplyToolFacesInput): AgentTool[];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { MCP_NAMESPACE } from "../protocol-table.js";
|
|
2
2
|
import { mintNamespacePrefix } from "../protocol-naming.js";
|
|
3
3
|
import { RESERVED_APPROVAL_CARDS, TOOL_APPROVAL_CARDS, TOOL_FAMILIES, TOOL_PATH_ABSENCES, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_CHARS, RENDER_HINT_MAX_LIST, TOOL_KEY_MAX_CHARS, } from "../tool-face.js";
|
|
4
|
-
import { engineCardOf, inputKeysOf } from "../tool-roster.js";
|
|
4
|
+
import { engineCardOf, inputKeysOf, offendingResultCard, protocolToolOf, rosterCardId } from "../tool-roster.js";
|
|
5
|
+
import { engineCardTypes } from "../tool-registry.js";
|
|
5
6
|
import { deliverEngineNotice } from "../engine-notice.js";
|
|
6
7
|
export const APPROVAL_CARD_REQUIRED_KEYS = Object.freeze({
|
|
7
8
|
"file-edit": ["file_path"],
|
|
@@ -14,17 +15,20 @@ export const APPROVAL_CARD_REQUIRED_KEYS = Object.freeze({
|
|
|
14
15
|
generic: [],
|
|
15
16
|
});
|
|
16
17
|
export function toolFaceProblem(face, schema, tool) {
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
return faceProblem(face, schema, tool);
|
|
19
|
+
}
|
|
20
|
+
export function faceProblem(face, schema, tool) {
|
|
21
|
+
const bound = schema === undefined ? undefined : inputKeysOf(schema);
|
|
22
|
+
const top = bound === undefined ? undefined : new Set(bound.keys.map((k) => k.name));
|
|
19
23
|
if (face.family !== undefined && !TOOL_FAMILIES.includes(face.family))
|
|
20
24
|
return `family ${JSON.stringify(face.family)} is outside the closed set`;
|
|
21
25
|
const overlong = (label, values) => {
|
|
22
26
|
const k = values.find((v) => v.length > TOOL_KEY_MAX_CHARS);
|
|
23
27
|
return k === undefined ? undefined : `${label} ${JSON.stringify(k.slice(0, 40) + "…")} is ${k.length} characters; a roster key is at most ${TOOL_KEY_MAX_CHARS}`;
|
|
24
28
|
};
|
|
25
|
-
const vocabulary = new Set(scalar);
|
|
29
|
+
const vocabulary = new Set(bound?.scalar);
|
|
26
30
|
if (face.pathTarget !== undefined) {
|
|
27
|
-
if (typeof face.pathTarget.param !== "string" || !top.has(face.pathTarget.param))
|
|
31
|
+
if (top !== undefined && (typeof face.pathTarget.param !== "string" || !top.has(face.pathTarget.param)))
|
|
28
32
|
return `pathTarget.param ${JSON.stringify(face.pathTarget.param)} is not a top-level property of the tool's schema`;
|
|
29
33
|
if (!TOOL_PATH_ACCESSES.includes(face.pathTarget.access))
|
|
30
34
|
return `pathTarget.access ${JSON.stringify(face.pathTarget.access)} is outside the closed set`;
|
|
@@ -40,7 +44,7 @@ export function toolFaceProblem(face, schema, tool) {
|
|
|
40
44
|
return `pathTarget.absent "base" needs pathTarget.access "read" — it says the call is about the whole base directory when the slot is empty, which is a search SCOPE; a write with no path names no file, and the containment fences judge a write on its own target`;
|
|
41
45
|
const patternParam = face.pathTarget.patternParam;
|
|
42
46
|
if (patternParam !== undefined) {
|
|
43
|
-
if (typeof patternParam !== "string" || !top.has(patternParam))
|
|
47
|
+
if (top !== undefined && (typeof patternParam !== "string" || !top.has(patternParam)))
|
|
44
48
|
return `pathTarget.patternParam ${JSON.stringify(patternParam)} is not a top-level property of the tool's schema`;
|
|
45
49
|
if (absent !== "base")
|
|
46
50
|
return `pathTarget.patternParam ${JSON.stringify(patternParam)} is declared but pathTarget.absent is ${JSON.stringify(absent ?? "none")} — the pattern base is only ever read for an absent slot, so this declaration would be inert`;
|
|
@@ -62,7 +66,7 @@ export function toolFaceProblem(face, schema, tool) {
|
|
|
62
66
|
return `renderHints.approvalCard ${JSON.stringify(card)} is outside the closed set`;
|
|
63
67
|
if (RESERVED_APPROVAL_CARDS.includes(card) && (tool === undefined || engineCardOf(tool) !== card))
|
|
64
68
|
return `renderHints.approvalCard ${JSON.stringify(card)} is reserved for the engine's own tool that owns it; a declared tool takes another card or none`;
|
|
65
|
-
const missing = APPROVAL_CARD_REQUIRED_KEYS[card].filter((k) => !top.has(k));
|
|
69
|
+
const missing = top === undefined ? [] : APPROVAL_CARD_REQUIRED_KEYS[card].filter((k) => !top.has(k));
|
|
66
70
|
if (missing.length > 0)
|
|
67
71
|
return `renderHints.approvalCard ${JSON.stringify(card)} needs ${missing.join(", ")} on the tool's schema`;
|
|
68
72
|
}
|
|
@@ -77,16 +81,21 @@ export function toolFaceProblem(face, schema, tool) {
|
|
|
77
81
|
const long = overlong("renderHints.summaryParams", hints.summaryParams ?? []) ?? overlong("renderHints.resultCards", hints.resultCards ?? []);
|
|
78
82
|
if (long !== undefined)
|
|
79
83
|
return long;
|
|
80
|
-
const
|
|
84
|
+
const foreignCard = tool?.cardId === undefined ? undefined : offendingResultCard(hints, tool.cardId, engineCardTypes());
|
|
85
|
+
if (foreignCard !== undefined)
|
|
86
|
+
return `renderHints.resultCards names ${JSON.stringify(foreignCard.slice(0, 80))}, outside the tool's own card namespace "${tool.cardId}/<card>" (engine cards and other contracts' namespaces are reserved)`;
|
|
87
|
+
const stray = top === undefined ? undefined : (hints.summaryParams ?? []).find((p) => !top.has(p));
|
|
81
88
|
if (stray !== undefined)
|
|
82
89
|
return `renderHints.summaryParams names ${JSON.stringify(stray)}, which is not a top-level property`;
|
|
83
90
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
if (top !== undefined) {
|
|
92
|
+
for (const p of face.ruleFace?.params ?? [])
|
|
93
|
+
if (!vocabulary.has(p))
|
|
94
|
+
return `ruleFace.params names ${JSON.stringify(p)}, which is neither a scalar top-level property nor the declared path target`;
|
|
95
|
+
for (const p of face.ruleFace?.primaryParams ?? [])
|
|
96
|
+
if (!vocabulary.has(p))
|
|
97
|
+
return `ruleFace.primaryParams names ${JSON.stringify(p)}, which is neither a top-level property nor the declared path target`;
|
|
98
|
+
}
|
|
90
99
|
const long = overlong("ruleFace.params", face.ruleFace?.params ?? []) ?? overlong("ruleFace.primaryParams", face.ruleFace?.primaryParams ?? []);
|
|
91
100
|
if (long !== undefined)
|
|
92
101
|
return long;
|
|
@@ -121,7 +130,7 @@ export function applyMcpToolFaces(input) {
|
|
|
121
130
|
if (raw === undefined || face === undefined)
|
|
122
131
|
return t;
|
|
123
132
|
const hit = { raw, face };
|
|
124
|
-
const problem = toolFaceProblem(hit.face, t.parameters, { toolName: t.name });
|
|
133
|
+
const problem = toolFaceProblem(hit.face, t.parameters, { toolName: t.name, cardId: rosterCardId("mcp", t.name, input.server.name, protocolToolOf(t.name), "") });
|
|
125
134
|
if (problem !== undefined) {
|
|
126
135
|
const key = `invalid:${t.name}`;
|
|
127
136
|
if (!input.announced.has(key)) {
|