@sema-agent/core 7.35.2 → 7.36.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 +61 -0
- package/dist/brain/anthropic.js +6 -6
- package/dist/brain/errors.d.ts +1 -14
- package/dist/brain/errors.js +2 -6
- package/dist/brain/open-responses.js +7 -7
- package/dist/brain/openai.js +7 -7
- package/dist/brain/provider-echo-shape.d.ts +26 -0
- package/dist/brain/provider-echo-shape.js +12 -0
- package/dist/brain/tool-call-id.d.ts +27 -15
- package/dist/brain/tool-call-id.js +43 -0
- package/dist/core/ask-question.d.ts +0 -30
- package/dist/core/ask-question.js +3 -2
- package/dist/core/checkpoint-store.d.ts +5 -3
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/context-edit.d.ts +4 -0
- package/dist/core/context-edit.js +5 -2
- package/dist/core/engine-notice.d.ts +7 -0
- package/dist/core/gate-lanes.js +12 -10
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/hooks.js +4 -2
- package/dist/core/mode-policy.js +2 -1
- package/dist/core/permission-rule-model.d.ts +2 -2
- package/dist/core/permission-rule-model.js +2 -2
- package/dist/core/permission-rules.js +2 -5
- package/dist/core/runner/active-skill-scope.js +5 -5
- package/dist/core/runner/decide-continuation.js +2 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +3 -0
- package/dist/core/runner/permission-rule-lanes.js +7 -6
- package/dist/core/runner/prepare-context-lane.d.ts +4 -0
- package/dist/core/runner/prepare-context-lane.js +7 -5
- package/dist/core/runner/prepare-gate-stations.js +17 -12
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +7 -6
- package/dist/core/runner/prepare-policy-chain.d.ts +2 -0
- package/dist/core/runner/prepare-policy-chain.js +10 -9
- package/dist/core/runner/prepare-safety-scan.js +9 -2
- package/dist/core/runner/prepare-task.js +3 -3
- package/dist/core/runner/resume-apply.js +2 -1
- package/dist/core/runner/run-attachment-seats.js +3 -1
- package/dist/core/runner/run-harness-handlers.js +6 -1
- package/dist/core/runner/run-leg.js +11 -9
- package/dist/core/runner/run-recovery-lanes.js +2 -0
- package/dist/core/runner/session-rule-policy.js +5 -4
- package/dist/core/runner/tool-call-id-fold-seat.d.ts +13 -0
- package/dist/core/runner/tool-call-id-fold-seat.js +31 -0
- package/dist/core/sensitive-path-policy.js +2 -2
- package/dist/core/side-query.d.ts +5 -1
- package/dist/core/side-query.js +5 -1
- package/dist/core/tool-name-readings.d.ts +366 -0
- package/dist/core/tool-name-readings.js +136 -0
- package/dist/core/tool-policy.d.ts +5 -5
- package/dist/core/tool-policy.js +15 -13
- package/dist/core/tool-registry.d.ts +1 -31
- package/dist/core/tool-registry.js +2 -22
- package/dist/core/tool-result-budget.d.ts +3 -0
- package/dist/core/tool-result-budget.js +5 -3
- package/dist/core/tool-roster.d.ts +30 -9
- package/dist/core/tool-roster.js +45 -5
- package/dist/engine/llm/types.d.ts +4 -0
- package/dist/tools/fs/file-read-check.d.ts +1 -1
- package/dist/tools/fs/file-read-check.js +4 -3
- package/dist/tools/fs/shell-read-check.d.ts +15 -1
- package/dist/tools/fs/shell-read-check.js +5 -2
- package/package.json +2 -1
- package/wire-changes/core@7.36.0.json +288 -0
package/dist/core/gate-lanes.js
CHANGED
|
@@ -11,13 +11,15 @@ import { CD_GIT_COMPOUND_SENTENCE, changesDirectoryBeforeGit, readOnlyShellVerdi
|
|
|
11
11
|
import { readShellCommand } from "./shell-lexer.js";
|
|
12
12
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
13
13
|
import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
14
|
-
import {
|
|
14
|
+
import { callFaceSeat } from "./tool-roster.js";
|
|
15
|
+
import { heldBy, wireOf } from "./tool-name-readings.js";
|
|
15
16
|
import { modeAcceptsFileCommand } from "./mode-policy.js";
|
|
16
17
|
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
17
18
|
import { exitGate } from "./runner/gate-exit.js";
|
|
18
19
|
import { traceHookCrash } from "./gate-crash-trace.js";
|
|
19
20
|
import { createGateDeadline, awaitWriteProtectionJudgment, withGateTimeout } from "./gate-bounded-await.js";
|
|
20
21
|
import { thrownText } from "./thrown-value.js";
|
|
22
|
+
const QUESTION_TOOL_NAMES = new Set([ASK_USER_QUESTION_TOOL_NAME]);
|
|
21
23
|
function isPlainOwnRecord(x) {
|
|
22
24
|
try {
|
|
23
25
|
if (x === null || typeof x !== "object" || Array.isArray(x))
|
|
@@ -170,7 +172,7 @@ export async function runGateLanes(pass) {
|
|
|
170
172
|
writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
|
|
171
173
|
});
|
|
172
174
|
const askOriginFacts = (org, ruleCallUnreadable) => ({
|
|
173
|
-
contentQuestion:
|
|
175
|
+
contentQuestion: heldBy("lanes.contentQuestionClass", QUESTION_TOOL_NAMES, pass.req),
|
|
174
176
|
markedUnresolvable: markedUnresolvable(),
|
|
175
177
|
org,
|
|
176
178
|
...(ruleCallUnreadable ? { ruleCallUnreadable } : {}),
|
|
@@ -195,7 +197,7 @@ export async function runGateLanes(pass) {
|
|
|
195
197
|
const applyOrgLayer = async (current, args) => {
|
|
196
198
|
if (input.orgRules === undefined)
|
|
197
199
|
return { decision: current, org: undefined };
|
|
198
|
-
if (
|
|
200
|
+
if (wireOf("lanes.orgContentAskExemption", pass.req) === ASK_USER_QUESTION_TOOL_NAME && input.orgRules.contentAskToolMounted === true)
|
|
199
201
|
return { decision: current, org: undefined };
|
|
200
202
|
let decided = current;
|
|
201
203
|
let pending;
|
|
@@ -271,7 +273,7 @@ export async function runGateLanes(pass) {
|
|
|
271
273
|
return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
|
|
272
274
|
};
|
|
273
275
|
const laneReq = { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
|
|
274
|
-
const laneAnswer = input.persistedRules !== undefined && pass.req
|
|
276
|
+
const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
|
|
275
277
|
{
|
|
276
278
|
const tightened = applyPersistedTightening(pass.decision, laneAnswer);
|
|
277
279
|
pass.decision = tightened.decision;
|
|
@@ -299,7 +301,7 @@ export async function runGateLanes(pass) {
|
|
|
299
301
|
pass.decision.action === "ask" &&
|
|
300
302
|
pass.decision.requiresRealApproval !== true &&
|
|
301
303
|
pass.decision.decisionReason !== "hook" &&
|
|
302
|
-
pass.req
|
|
304
|
+
wireOf("lanes.allowLayerQuestionSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
303
305
|
!markedUnresolvable();
|
|
304
306
|
if (input.persistedRules && allowLayerMayClear()) {
|
|
305
307
|
const answer = laneAnswer ?? {};
|
|
@@ -356,7 +358,7 @@ export async function runGateLanes(pass) {
|
|
|
356
358
|
classifyStanding(stamped, originFacts);
|
|
357
359
|
pass.decision = stampAskOrigin(stamped, originFacts);
|
|
358
360
|
}
|
|
359
|
-
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && pass.req
|
|
361
|
+
if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && wireOf("lanes.readOnlyShellAllow", pass.req) === COMMAND_RULE_TOOL) {
|
|
360
362
|
const commandParam = catalogRuleFaceOf(COMMAND_RULE_TOOL)?.primaryParams[0] ?? "command";
|
|
361
363
|
const args = pass.req.args;
|
|
362
364
|
const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
|
|
@@ -435,7 +437,7 @@ export async function runGateLanes(pass) {
|
|
|
435
437
|
}
|
|
436
438
|
if (input.sandboxAdmission !== undefined &&
|
|
437
439
|
pass.decision.action === "ask" &&
|
|
438
|
-
pass.req
|
|
440
|
+
wireOf("lanes.sandboxQuestionSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
439
441
|
input.isMarkedUnresolvable?.(input.event.toolCallId) !== true &&
|
|
440
442
|
pass.decision.requiresRealApproval !== true &&
|
|
441
443
|
pass.decision.decisionReason !== "hook" &&
|
|
@@ -463,7 +465,7 @@ export async function runGateLanes(pass) {
|
|
|
463
465
|
if (pass.decision.action === "ask" && input.permissionMode !== undefined && pass.askClassification !== undefined) {
|
|
464
466
|
const standing = pass.decision;
|
|
465
467
|
const mode = input.permissionMode();
|
|
466
|
-
const fileCommandByWord = modeAcceptsFileCommand(mode,
|
|
468
|
+
const fileCommandByWord = modeAcceptsFileCommand(mode, wireOf("lanes.modeFileCommand", pass.req), pass.policyRewrite ?? pass.currentInput);
|
|
467
469
|
const verdict = arbitrateByMode(mode, pass.askClassification, { ...(standing.origin !== undefined ? { origin: standing.origin } : {}), ...(standing.mandate !== undefined ? { mandate: standing.mandate } : {}) }, { fileCommand: pass.shellWritesPlaced === true && fileCommandByWord });
|
|
468
470
|
if (verdict.kind === "ask" && fileCommandByWord && pass.shellWritesPlaced !== true && pass.shellUnplacedOptions !== undefined && pass.askClassification.classes.size === 1 && pass.askClassification.classes.has("ordinary")) {
|
|
469
471
|
pass.decision = { ...standing, message: fileCommandOptionsSentence(pass.shellUnplacedOptions.program) };
|
|
@@ -528,7 +530,7 @@ export async function runGateLanes(pass) {
|
|
|
528
530
|
return { suspend: suspended, preToolContext: pass.preToolContext };
|
|
529
531
|
}
|
|
530
532
|
}
|
|
531
|
-
if (pass.decision.action === "ask" && pass.req
|
|
533
|
+
if (pass.decision.action === "ask" && wireOf("lanes.contentAskRoute", pass.req) === ASK_USER_QUESTION_TOOL_NAME) {
|
|
532
534
|
const outcome = input.resolveContentAsk !== undefined ? await (callSignal !== undefined ? input.resolveContentAsk(pass.req, callSignal) : input.resolveContentAsk(pass.req)) : { kind: "unavailable", parkDeclined: false };
|
|
533
535
|
if (outcome.kind === "answered") {
|
|
534
536
|
pass.decision = { action: "allow", updatedInput: outcome.presentedInput };
|
|
@@ -692,7 +694,7 @@ export async function runGateLanes(pass) {
|
|
|
692
694
|
break;
|
|
693
695
|
}
|
|
694
696
|
let editRuleCallUnreadable = false;
|
|
695
|
-
if (input.persistedRules !== undefined &&
|
|
697
|
+
if (input.persistedRules !== undefined && wireOf("lanes.persistedEditSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME) {
|
|
696
698
|
const editLane = await readPersistedRules({ toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
|
|
697
699
|
editRuleCallUnreadable = ruleCallUnreadableOf(editLane);
|
|
698
700
|
const editTightened = applyPersistedTightening(recheck, editLane);
|
|
@@ -135,7 +135,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
135
135
|
* the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
|
|
136
136
|
* so the catalog is not written a second time here and the two cannot drift.
|
|
137
137
|
*/
|
|
138
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "resume.legacy_mode_seed_migrated", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
|
|
138
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "resume.legacy_mode_seed_migrated", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "route.tool_call_id_folded", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
|
|
139
139
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
140
140
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
141
141
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -178,6 +178,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
178
178
|
"route.classifier_unroutable",
|
|
179
179
|
"route.base_url_changed_key_unchanged",
|
|
180
180
|
"route.response_model_mismatch",
|
|
181
|
+
"route.tool_call_id_folded",
|
|
181
182
|
"task.user_steer_undrained",
|
|
182
183
|
"task.user_followup_undrained",
|
|
183
184
|
"steering.parked_input_blocked",
|
|
@@ -269,6 +270,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
269
270
|
"route.classifier_unroutable": "operator",
|
|
270
271
|
"route.base_url_changed_key_unchanged": "operator",
|
|
271
272
|
"route.response_model_mismatch": "operator",
|
|
273
|
+
"route.tool_call_id_folded": "operator",
|
|
272
274
|
"skills.listing_truncated": "operator",
|
|
273
275
|
"agents.listing_truncated": "operator",
|
|
274
276
|
"tool_result.offload_put_failed": "operator",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -474,6 +474,9 @@ export interface PostToolUseFailureResult {
|
|
|
474
474
|
/** One resolved call in a postToolBatch payload (CC PostToolBatchToolCall shape, thin projection —
|
|
475
475
|
* `response` is a bounded text digest of the result, never the full details object). */
|
|
476
476
|
export interface PostToolBatchCall {
|
|
477
|
+
/** The name the model spelled on the tool_use block (an alias stays an alias) — the reference's PostToolBatch payload
|
|
478
|
+
* names each block the same way, while the per-call post hooks receive the resolved tool's wire name. Correlate
|
|
479
|
+
* the two faces by `toolCallId`. */
|
|
477
480
|
toolName: string;
|
|
478
481
|
input: unknown;
|
|
479
482
|
toolCallId: string;
|
package/dist/core/hooks.js
CHANGED
|
@@ -11,6 +11,7 @@ import { isAskOrigin } from "./ask-origin.js";
|
|
|
11
11
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
12
12
|
export { formatHookFeedback } from "./reminder-mint.js";
|
|
13
13
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
14
|
+
import { wireOf } from "./tool-name-readings.js";
|
|
14
15
|
export const DEFAULT_HOOK_TIMEOUT_MS = 600_000;
|
|
15
16
|
export const MAX_HOOK_TIMEOUT_MS = 2_147_483_647;
|
|
16
17
|
const POST_ABORT_GRACE_MS = 1_000;
|
|
@@ -247,9 +248,10 @@ export async function runToolGate(input) {
|
|
|
247
248
|
const { event, preToolUse, adjudicate, resolveAsk, suspendAsk } = input;
|
|
248
249
|
const { toolCallId, toolName } = event;
|
|
249
250
|
const callSignal = input.callSignal;
|
|
251
|
+
const screenedName = wireOf("gate.preToolUse", event);
|
|
250
252
|
const hookCtx = (seatSignal) => ({
|
|
251
253
|
toolCallId,
|
|
252
|
-
toolName,
|
|
254
|
+
toolName: screenedName,
|
|
253
255
|
...(seatSignal !== undefined ? { signal: seatSignal } : {}),
|
|
254
256
|
...(() => {
|
|
255
257
|
const c = input.trackedCwd?.();
|
|
@@ -284,7 +286,7 @@ export async function runToolGate(input) {
|
|
|
284
286
|
};
|
|
285
287
|
const screening = preToolUse !== undefined
|
|
286
288
|
? {
|
|
287
|
-
screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(
|
|
289
|
+
screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(screenedName, args, hookCtx(sig))),
|
|
288
290
|
screened: screenPreToolUseResult,
|
|
289
291
|
crashReason: preToolUseCrashReason,
|
|
290
292
|
unansweredReason: preToolUseUnansweredBlock,
|
package/dist/core/mode-policy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createFsWriteGatePolicy } from "./fs-write-gate-policy.js";
|
|
2
2
|
import { isProtectedWrite } from "./tool-registry.js";
|
|
3
3
|
import { rosterEntryNamed } from "./tool-roster.js";
|
|
4
|
+
import { wireOf } from "./tool-name-readings.js";
|
|
4
5
|
import { clampPermissionMode } from "./ask-origin.js";
|
|
5
6
|
import { argvOf, lexShellText } from "../shell/lex.js";
|
|
6
7
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
@@ -42,7 +43,7 @@ const ENGINE_ROSTER_SOURCES = new Set(["builtin", "synthetic"]);
|
|
|
42
43
|
export function planRefusesWrite(req, roster, readOnlyDeclared) {
|
|
43
44
|
if (isProtectedWrite(req))
|
|
44
45
|
return true;
|
|
45
|
-
const row = roster === undefined ? undefined : rosterEntryNamed(roster, req
|
|
46
|
+
const row = roster === undefined ? undefined : rosterEntryNamed(roster, wireOf("mode.planRosterRow", req));
|
|
46
47
|
return row !== undefined && !ENGINE_ROSTER_SOURCES.has(row.source) && row.effect !== "read" && !(row.effectProvenance === "default" && readOnlyDeclared?.(row.name) === true);
|
|
47
48
|
}
|
|
48
49
|
const planAsk = (message) => ({ action: "ask", message, decisionReason: "mode" });
|
|
@@ -141,8 +141,8 @@ export declare const READ_RULE_TOOL = "Read";
|
|
|
141
141
|
* catalog's declarations, exactly as the protective fences' covered set does.
|
|
142
142
|
*/
|
|
143
143
|
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | undefined;
|
|
144
|
-
/** The names the persisted grammar judges a call under: every
|
|
145
|
-
* name,
|
|
144
|
+
/** The names the persisted grammar judges a call under: every name the call answers to (the model's spelling, the wire
|
|
145
|
+
* name, the other aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
|
|
146
146
|
* tool that answers to that name under another wire name, as a policy's rule does. Empty ⇒ the grammar speaks for none. */
|
|
147
147
|
export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
|
|
148
148
|
/**
|
|
@@ -4,7 +4,7 @@ import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
|
4
4
|
import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
5
5
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { pathTargetOf } from "./tool-registry.js";
|
|
7
|
-
import {
|
|
7
|
+
import { heldValues } from "./tool-name-readings.js";
|
|
8
8
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
9
9
|
import { readShellCommand } from "./shell-lexer.js";
|
|
10
10
|
import { readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
@@ -32,7 +32,7 @@ export function ruleToolGrammarOf(toolName) {
|
|
|
32
32
|
return pathTargetOf(toolName) !== undefined ? "path" : undefined;
|
|
33
33
|
}
|
|
34
34
|
export function laneToolNamesOf(req) {
|
|
35
|
-
return
|
|
35
|
+
return heldValues("policy.persistedGrammar", { get: (n) => (ruleToolGrammarOf(n) !== undefined ? n : undefined) }, req);
|
|
36
36
|
}
|
|
37
37
|
export const MAX_RULE_TEXT_CHARS = 512;
|
|
38
38
|
export const BARE_INTERPRETER_NAMES = new Set([
|
|
@@ -6,7 +6,7 @@ import { pathFamilyOf, pathIdentityOf, spellingOfIdentity } from "../tools/fs/sa
|
|
|
6
6
|
import { effectivePathTargetOf } from "./effective-path-target.js";
|
|
7
7
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
8
8
|
import { ruleDenialSentence } from "./rule-denial-sentence.js";
|
|
9
|
-
import {
|
|
9
|
+
import { heldValues } from "./tool-name-readings.js";
|
|
10
10
|
export { parsePermissionRule };
|
|
11
11
|
export const BASH_GENERIC_PARAMS = new Set(catalogRuleFaceOf("Bash")?.params ?? []);
|
|
12
12
|
const DEFAULT_CAPS = {
|
|
@@ -381,10 +381,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
381
381
|
requiresRealApproval: true,
|
|
382
382
|
});
|
|
383
383
|
const entryFor = (req) => {
|
|
384
|
-
const entries =
|
|
385
|
-
const e = byTool.get(n);
|
|
386
|
-
return e !== undefined ? [e] : [];
|
|
387
|
-
});
|
|
384
|
+
const entries = heldValues("policy.permissionRules", byTool, req);
|
|
388
385
|
if (entries.length <= 1)
|
|
389
386
|
return entries[0];
|
|
390
387
|
const bare = {};
|
|
@@ -3,7 +3,7 @@ import { pathTargetValue } from "../tool-registry.js";
|
|
|
3
3
|
import { pathTargetBaseOf } from "../effective-path-target.js";
|
|
4
4
|
import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
|
|
5
5
|
import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
|
|
6
|
-
import {
|
|
6
|
+
import { heldValues, wireOf } from "../tool-name-readings.js";
|
|
7
7
|
export class ActiveSkillScope {
|
|
8
8
|
frames = [];
|
|
9
9
|
push(frame) {
|
|
@@ -23,7 +23,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
23
23
|
const { scope, env, rootPath, toolEffects } = opts;
|
|
24
24
|
return {
|
|
25
25
|
async check(req, signal) {
|
|
26
|
-
const
|
|
26
|
+
const entriesFor = (byName) => heldValues("policy.skillScope", byName, req).flat();
|
|
27
27
|
const frames = scope.active();
|
|
28
28
|
if (frames.length === 0)
|
|
29
29
|
return { action: "allow" };
|
|
@@ -48,7 +48,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
48
48
|
}
|
|
49
49
|
return { manifest: m, byName };
|
|
50
50
|
});
|
|
51
|
-
if (perFrame.length === 0 || perFrame.some((f) =>
|
|
51
|
+
if (perFrame.length === 0 || perFrame.some((f) => entriesFor(f.byName).length === 0)) {
|
|
52
52
|
const ids = manifests.map((m) => m.lineageId).join(", ");
|
|
53
53
|
return {
|
|
54
54
|
action: "deny",
|
|
@@ -57,7 +57,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
for (const frame of perFrame) {
|
|
60
|
-
const alternatives =
|
|
60
|
+
const alternatives = entriesFor(frame.byName);
|
|
61
61
|
const rejections = [];
|
|
62
62
|
let admitted = false;
|
|
63
63
|
for (const entry of alternatives) {
|
|
@@ -80,7 +80,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
80
80
|
const declaredTarget = req.face?.pathTarget;
|
|
81
81
|
const skillScopeEligible = declaredTarget !== undefined && declaredTarget.skillScopeEligible === true;
|
|
82
82
|
if (pathConstrainingActive && !skillScopeEligible) {
|
|
83
|
-
const eff = toolEffects?.get(req
|
|
83
|
+
const eff = toolEffects?.get(wireOf("policy.skillScope.effect", req)) ?? "write";
|
|
84
84
|
if (eff !== "read") {
|
|
85
85
|
const ids = manifests.filter((m) => m.allowPaths?.length).map((m) => m.lineageId).join(", ");
|
|
86
86
|
return {
|
|
@@ -3,6 +3,7 @@ import { boundInputHashOf } from "../canonical-json.js";
|
|
|
3
3
|
import { CheckpointError } from "../checkpoint-store.js";
|
|
4
4
|
import { formatHookFeedback } from "../hooks.js";
|
|
5
5
|
import { delimitUntrusted, REVIEWER_NOTE_MAX_BODY } from "../untrusted-text.js";
|
|
6
|
+
import { wireOf } from "../tool-name-readings.js";
|
|
6
7
|
export function resumeDecisionWasNegative(resume) {
|
|
7
8
|
const o = resume.outcome;
|
|
8
9
|
return o.decision === "reject" || o.decision === "deny";
|
|
@@ -49,7 +50,7 @@ export function sameWinner(incoming, persisted) {
|
|
|
49
50
|
(persisted.reason === undefined || incoming.reason === persisted.reason));
|
|
50
51
|
}
|
|
51
52
|
export function pendingContentAskCallId(cp) {
|
|
52
|
-
return cp.pendingAction.kind === "tool_approval" && cp.pendingAction
|
|
53
|
+
return cp.pendingAction.kind === "tool_approval" && wireOf("resume.contentAskRow", cp.pendingAction) === ASK_USER_QUESTION_TOOL_NAME
|
|
53
54
|
? cp.pendingAction.toolCallId
|
|
54
55
|
: undefined;
|
|
55
56
|
}
|
|
@@ -241,6 +241,9 @@ export declare function createRuleOffersOf(cfg: {
|
|
|
241
241
|
shellGatedTools: {
|
|
242
242
|
has(name: string): boolean;
|
|
243
243
|
};
|
|
244
|
+
toolMarkSources: {
|
|
245
|
+
get(name: string): ReadonlySet<string> | undefined;
|
|
246
|
+
};
|
|
244
247
|
taskRoot: string | undefined;
|
|
245
248
|
/** The live tracked-cwd ref (read per call — the relative-cd resolution base). */
|
|
246
249
|
cwdRef: {
|
|
@@ -3,7 +3,8 @@ import {} from "../hooks.js";
|
|
|
3
3
|
import { adjudicatePersistedPathRules, adjudicatePersistedRules, laneToolNamesOf, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
4
4
|
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
5
5
|
import { effectivePathTargetOf } from "../effective-path-target.js";
|
|
6
|
-
import {
|
|
6
|
+
import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
|
|
7
|
+
import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
|
|
7
8
|
import { orgRuleVerdictFor } from "../permission-rule-org.js";
|
|
8
9
|
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
9
10
|
import { thrownText } from "../thrown-value.js";
|
|
@@ -92,7 +93,7 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
92
93
|
const org = provider.partitions.org
|
|
93
94
|
? {
|
|
94
95
|
adjudicate: async (req) => {
|
|
95
|
-
if (
|
|
96
|
+
if (wireOf("offer.orgQuestionExemption", req) === cfg.questionToolName && cfg.questionToolMounted)
|
|
96
97
|
return { status: "available" };
|
|
97
98
|
let resolution;
|
|
98
99
|
try {
|
|
@@ -158,16 +159,16 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
158
159
|
}
|
|
159
160
|
export function createRuleOffersOf(cfg) {
|
|
160
161
|
return (call, args, ask) => {
|
|
161
|
-
const toolName = call
|
|
162
|
+
const toolName = wireOf("offer.laneTool", call);
|
|
162
163
|
if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
|
|
163
164
|
return {};
|
|
164
165
|
if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
|
|
165
166
|
return {};
|
|
166
|
-
const tierSeat =
|
|
167
|
+
const tierSeat = seatOf("offer.marks", cfg.irreversibilityTier, call);
|
|
167
168
|
const closedDoor = ruleClearanceOf(ask, {
|
|
168
|
-
egress:
|
|
169
|
+
egress: heldBy("offer.marks.egress", cfg.egressTools, call),
|
|
169
170
|
irreversibility: tierSeat?.value,
|
|
170
|
-
shellGated: tierSeat
|
|
171
|
+
shellGated: tierSeat?.shellGated(cfg.shellGatedTools, cfg.toolMarkSources) === true,
|
|
171
172
|
}).word;
|
|
172
173
|
if (closedDoor !== undefined)
|
|
173
174
|
return { ruleOffersAbsence: closedDoor };
|
|
@@ -24,6 +24,7 @@ import { type RunnerDeps } from "../types.js";
|
|
|
24
24
|
import type { Prepared, PreparedMicroCompact } from "./contracts.js";
|
|
25
25
|
import { type GitStatusLaneRef } from "./git-status-frame.js";
|
|
26
26
|
import type { ReadFileState } from "../../tools/fs/safety.js";
|
|
27
|
+
import { type ToolRosterDeltaSeat } from "../tool-roster.js";
|
|
27
28
|
export interface PrepareContextLaneInput {
|
|
28
29
|
/** borrowed-readonly — the task model: the budgets, the trigger-window geometry and `charsPerToken` are read off it. */
|
|
29
30
|
model: Model;
|
|
@@ -39,6 +40,8 @@ export interface PrepareContextLaneInput {
|
|
|
39
40
|
hostTaskId: string;
|
|
40
41
|
/** borrowed-readonly — the run's offload store, or undefined (the caps' and the machine's persist seat). */
|
|
41
42
|
offloadStore: ToolResultStore | undefined;
|
|
43
|
+
/** borrowed-readonly — the leg's live roster: a recorded tool name is judged as the wire name it resolves to. */
|
|
44
|
+
toolRosterDeltas: ToolRosterDeltaSeat;
|
|
42
45
|
/** borrowed-readonly — the doors' RESOLVED microCompact knob (immutable values, never the deps bag). */
|
|
43
46
|
microCompactKnob: {
|
|
44
47
|
machine: MicroCompactMachine;
|
|
@@ -135,6 +138,7 @@ export interface GuardChainArgs {
|
|
|
135
138
|
offloadStore: import("../tool-result-store.js").ToolResultStore | undefined;
|
|
136
139
|
sessionId: string;
|
|
137
140
|
taskId: string;
|
|
141
|
+
toolNameOf?: (recorded: string) => string;
|
|
138
142
|
deps: RunnerDeps;
|
|
139
143
|
trimPressureRef: {
|
|
140
144
|
droppedMessages: boolean;
|
|
@@ -8,8 +8,10 @@ import { emitTrace } from "../trace.js";
|
|
|
8
8
|
import { deliverEngineNotice } from "../types.js";
|
|
9
9
|
import { applyGitFrameGuard } from "./git-status-frame.js";
|
|
10
10
|
import { applyContextShrinkToReadFileState } from "../../tools/fs/index.js";
|
|
11
|
+
import { rosterToolName } from "../tool-roster.js";
|
|
11
12
|
export function prepareContextLane(input) {
|
|
12
|
-
const { model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, readFileState } = input;
|
|
13
|
+
const { model, deps, sessionId, runId, hostTaskId, offloadStore, toolRosterDeltas, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, readFileState } = input;
|
|
14
|
+
const toolNameOf = (recorded) => rosterToolName(toolRosterDeltas.current, recorded);
|
|
13
15
|
const contextOverlay = input.contextOverlay ?? (() => undefined);
|
|
14
16
|
const requestGeometry = () => {
|
|
15
17
|
const current = harness.getModel();
|
|
@@ -27,7 +29,7 @@ export function prepareContextLane(input) {
|
|
|
27
29
|
const replay = replayClearedProjection(healed, microCompact.ledger);
|
|
28
30
|
const replayed = replay.messages;
|
|
29
31
|
const capped = await capAggregateToolResults(replayed, {
|
|
30
|
-
store: offloadStore,
|
|
32
|
+
store: offloadStore, toolNameOf,
|
|
31
33
|
sessionId,
|
|
32
34
|
onCapped: (info) => pendingProjectionObservations.push(() => emitTrace(deps.tracer, () => ({
|
|
33
35
|
kind: "tool_result.capped",
|
|
@@ -49,7 +51,7 @@ export function prepareContextLane(input) {
|
|
|
49
51
|
});
|
|
50
52
|
let ccPass;
|
|
51
53
|
const edited = microCompact.machine === "off" ? mediaCapped : clearStaleToolResults(mediaCapped, {
|
|
52
|
-
budgetTokens: editAt,
|
|
54
|
+
budgetTokens: editAt, toolNameOf,
|
|
53
55
|
clearSource: replayed,
|
|
54
56
|
onCleared: (pass) => { ccPass = pass; },
|
|
55
57
|
anchoredTotalTokens: estimateContextTokens(mediaCapped, cpt).tokens,
|
|
@@ -67,7 +69,7 @@ export function prepareContextLane(input) {
|
|
|
67
69
|
}
|
|
68
70
|
const chain = await runGuardChain({
|
|
69
71
|
edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken: cpt, offloadStore,
|
|
70
|
-
sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
|
|
72
|
+
sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal, toolNameOf,
|
|
71
73
|
pendingProjectionObservations,
|
|
72
74
|
...(readFileState !== undefined ? { readFileState } : {}),
|
|
73
75
|
});
|
|
@@ -221,7 +223,7 @@ export async function runGuardChain(args) {
|
|
|
221
223
|
if (microCompact.machine === "off" && anchoredWorking > guardAt) {
|
|
222
224
|
let blockingPass;
|
|
223
225
|
const blockingEdited = clearStaleToolResults(working, {
|
|
224
|
-
budgetTokens: guardAt,
|
|
226
|
+
budgetTokens: guardAt, toolNameOf: args.toolNameOf,
|
|
225
227
|
clearSource: args.replayed,
|
|
226
228
|
onCleared: (pass) => { blockingPass = pass; },
|
|
227
229
|
anchoredTotalTokens: anchoredWorking,
|
|
@@ -9,8 +9,10 @@ import { askUnresolvableNotice } from "../ask-unresolvable-notice.js";
|
|
|
9
9
|
import { askReachesPerson } from "../wiring-manifest.js";
|
|
10
10
|
import { classifierUnavailableSeat } from "./classifier-ladder.js";
|
|
11
11
|
import { PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
|
|
12
|
-
import {
|
|
12
|
+
import { callFaceSeat, rosterToolName, toolCallFaceOf } from "../tool-roster.js";
|
|
13
|
+
import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js";
|
|
13
14
|
import { noteHostRewrite } from "../frame-settlement.js";
|
|
15
|
+
const PEER_MESSAGE_NAMES = new Set([SEND_MESSAGE_TOOL_NAME]);
|
|
14
16
|
function screenGateResult(result) {
|
|
15
17
|
if (result.suspend !== undefined)
|
|
16
18
|
return { defects: [] };
|
|
@@ -88,12 +90,12 @@ export function prepareGateStations(input) {
|
|
|
88
90
|
harness.on("tool_call", async (called) => {
|
|
89
91
|
const e = { ...called, toolName: rosterToolName(toolRosterDeltas.current, called.toolName) };
|
|
90
92
|
const call = { toolName: e.toolName, ...callFaceSeat(toolCallFaceOf(toolRosterDeltas.current, called.toolName)) };
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
+
const tierSeat = seatOf("gate.shellSeat", irreversibilityTier, call);
|
|
94
|
+
const markSources = new Set(heldValues("gate.markSources", toolMarkSources, call).flatMap((s) => [...s]));
|
|
93
95
|
blockedToolCalls.delete(e.toolCallId);
|
|
94
96
|
inheritedAskGrants.delete(e.toolCallId);
|
|
95
97
|
{
|
|
96
|
-
const complianceDeny =
|
|
98
|
+
const complianceDeny = heldValues("gate.compliance", { get: (n) => complianceCallDenial(complianceDenies, n) }, call)[0];
|
|
97
99
|
if (complianceDeny !== undefined) {
|
|
98
100
|
if (blockedTracked)
|
|
99
101
|
blockedToolCalls.add(e.toolCallId);
|
|
@@ -122,19 +124,19 @@ export function prepareGateStations(input) {
|
|
|
122
124
|
resolveAsk: resolveAskBound,
|
|
123
125
|
suspendAsk,
|
|
124
126
|
resolveContentAsk,
|
|
125
|
-
egress:
|
|
126
|
-
peerMessage:
|
|
127
|
+
egress: heldBy("gate.egress", egressTools, call),
|
|
128
|
+
peerMessage: heldBy("gate.peerMessage", PEER_MESSAGE_NAMES, call),
|
|
127
129
|
...(writeProtectionCheck !== undefined ? { writeProtectionCheck } : {}),
|
|
128
130
|
...(shellReadCheck !== undefined ? { shellReadCheck } : {}),
|
|
129
131
|
irreversibility: tierSeat?.value,
|
|
130
|
-
reversibilityProbe: tierSeat
|
|
132
|
+
reversibilityProbe: tierSeat?.read(reversibilityProbes),
|
|
131
133
|
abortSignal: abortController.signal,
|
|
132
134
|
...(notifyPermissionDenied ? { permissionDenied: notifyPermissionDenied } : {}),
|
|
133
135
|
onHookError: notifyHookError,
|
|
134
|
-
shellGated: tierSeat
|
|
136
|
+
shellGated: tierSeat?.shellGated(shellGatedTools, toolMarkSources) === true,
|
|
135
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 }) } } : {}),
|
|
136
138
|
permissionMode: () => permissionMode.current,
|
|
137
|
-
...(
|
|
139
|
+
...(markSources.size > 0 ? { toolMarkSources: markSources } : {}),
|
|
138
140
|
...(a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
139
141
|
onModeReleased,
|
|
140
142
|
ancestorArbitrationOf: (toolCallId) => ancestorArbitrations.get(toolCallId),
|
|
@@ -243,6 +245,7 @@ export function prepareGateStations(input) {
|
|
|
243
245
|
}
|
|
244
246
|
let content = e.content;
|
|
245
247
|
let changed = false;
|
|
248
|
+
const post = { toolName: rosterToolName(toolRosterDeltas.current, e.toolName) };
|
|
246
249
|
if (e.isError) {
|
|
247
250
|
if (hooks?.postToolUseFailure) {
|
|
248
251
|
let clonedDetails = e.details;
|
|
@@ -251,7 +254,8 @@ export function prepareGateStations(input) {
|
|
|
251
254
|
}
|
|
252
255
|
catch {
|
|
253
256
|
}
|
|
254
|
-
const
|
|
257
|
+
const failedName = wireOf("hook.postToolUseFailure", post);
|
|
258
|
+
const failureSeat = await runHookSeat("postToolUseFailure", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUseFailure(failedName, e.input, {
|
|
255
259
|
error: e.content
|
|
256
260
|
.filter((c) => c.type === "text")
|
|
257
261
|
.map((c) => c.text)
|
|
@@ -259,7 +263,7 @@ export function prepareGateStations(input) {
|
|
|
259
263
|
isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
|
|
260
264
|
content: e.content.map((c) => ({ ...c })),
|
|
261
265
|
details: clonedDetails,
|
|
262
|
-
}, { toolCallId: e.toolCallId, toolName:
|
|
266
|
+
}, { toolCallId: e.toolCallId, toolName: failedName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity }));
|
|
263
267
|
if (failureSeat.expired) {
|
|
264
268
|
notifyOwnHookCrash(hookSeatExpiredError("postToolUseFailure", hookTimeoutMs, failureSeat.cause, "the observation was abandoned and its additionalContext dropped; the failure result itself is unchanged"));
|
|
265
269
|
}
|
|
@@ -271,7 +275,8 @@ export function prepareGateStations(input) {
|
|
|
271
275
|
}
|
|
272
276
|
}
|
|
273
277
|
else if (hooks?.postToolUse) {
|
|
274
|
-
const
|
|
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 }));
|
|
275
280
|
if (successSeat.expired) {
|
|
276
281
|
notifyOwnHookCrash(hookSeatExpiredError("postToolUse", hookTimeoutMs, successSeat.cause, "the seat was abandoned; the tool result is delivered to the model UNMODIFIED (no updatedOutput, no additionalContext)"));
|
|
277
282
|
}
|
|
@@ -116,6 +116,8 @@ export interface PrepareParkAskInput {
|
|
|
116
116
|
shellGatedTools: ReadonlySet<string>;
|
|
117
117
|
/** borrowed-readonly — the per-tool tier, read only for the name the call's tier seat is read under (as at the gate). */
|
|
118
118
|
irreversibilityTier: IrreversibilityTier;
|
|
119
|
+
/** borrowed-readonly — who marked each name's seat (the descriptor's `shellGated` reads it as the gate does). */
|
|
120
|
+
toolMarkSources: ReadonlyMap<string, ReadonlySet<import("../ask-origin.js").ToolMarkSource>>;
|
|
119
121
|
/** borrowed-readonly — the operator's shell floor (`"always"`), persisted on a shell-gated descriptor; undefined = no floor. */
|
|
120
122
|
effectiveShellGate: ShellGateDoctrine | undefined;
|
|
121
123
|
/** borrowed-readonly — the EFFECTIVE durable-approval opt-in (the caller's, or the synthesized mandate scope): picks the
|
|
@@ -15,7 +15,8 @@ import { checkpointScopeOf } from "./checkpoint-scope.js";
|
|
|
15
15
|
import { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
|
|
16
16
|
import { DEFAULT_UNATTENDED_APPROVAL_TTL_MS, publishCommittedSuspend, sanitizedTtlMs } from "./park-commit.js";
|
|
17
17
|
import { remoteEnvFailureNote } from "./remote-env-retry.js";
|
|
18
|
-
import { callFaceSeat,
|
|
18
|
+
import { callFaceSeat, parkedToolIdentity, parkedToolFace } from "../tool-roster.js";
|
|
19
|
+
import { seatOf, wireOf } from "../tool-name-readings.js";
|
|
19
20
|
class ParkRefusal extends Error {
|
|
20
21
|
constructor(message, options) {
|
|
21
22
|
super(message, options);
|
|
@@ -26,7 +27,7 @@ const wordlessDemand = (toolName) => {
|
|
|
26
27
|
throw new Error(`a real-approval demand for "${toolName}" reached the park with no origin word — the origin stamp precedes every park`);
|
|
27
28
|
};
|
|
28
29
|
export function prepareParkAsk(input) {
|
|
29
|
-
const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
|
|
30
|
+
const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
|
|
30
31
|
if (askLane === undefined || saga === undefined)
|
|
31
32
|
return { parkAsk: undefined };
|
|
32
33
|
const { composedCallSignal, approvalPreviewOf } = askLane;
|
|
@@ -114,7 +115,7 @@ export function prepareParkAsk(input) {
|
|
|
114
115
|
};
|
|
115
116
|
const suspendAsk = parkLaneArmed && checkpointStore !== undefined
|
|
116
117
|
? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated, carry) => {
|
|
117
|
-
const syncFirstEligible =
|
|
118
|
+
const syncFirstEligible = wireOf("park.syncFirstRoute", req) === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
|
|
118
119
|
if (syncFirstEligible &&
|
|
119
120
|
runtimeCaps?.forceDurableGate !== true &&
|
|
120
121
|
liveFaceUnavailable !== true &&
|
|
@@ -234,7 +235,7 @@ export function prepareParkAsk(input) {
|
|
|
234
235
|
remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
|
|
235
236
|
}
|
|
236
237
|
token = mintCheckpointToken();
|
|
237
|
-
const tierSeat =
|
|
238
|
+
const tierSeat = seatOf("park.riskDescriptor", irreversibilityTier, req);
|
|
238
239
|
const riskDescriptor = buildRiskDescriptor({
|
|
239
240
|
toolName: req.toolName,
|
|
240
241
|
args: parkedArgs,
|
|
@@ -242,7 +243,7 @@ export function prepareParkAsk(input) {
|
|
|
242
243
|
...(shadowedRule !== undefined ? { shadowedRule } : {}),
|
|
243
244
|
...(probeReason !== undefined ? { probeReason } : {}),
|
|
244
245
|
...(probeCause !== undefined ? { probeCause } : {}),
|
|
245
|
-
shellGated: tierSeat
|
|
246
|
+
shellGated: tierSeat?.shellGated(shellGatedTools, toolMarkSources) === true,
|
|
246
247
|
...(effectiveShellGate !== undefined ? { shellGateDoctrine: effectiveShellGate } : {}),
|
|
247
248
|
});
|
|
248
249
|
gate =
|
|
@@ -303,7 +304,7 @@ export function prepareParkAsk(input) {
|
|
|
303
304
|
kind: "tool_approval",
|
|
304
305
|
toolCallId: req.toolCallId,
|
|
305
306
|
toolName: req.toolName,
|
|
306
|
-
...parkedToolIdentity(toolRosterDeltas.current, req.toolName), ...parkedToolFace(toolRosterDeltas.current, req
|
|
307
|
+
...parkedToolIdentity(toolRosterDeltas.current, req.toolName), ...parkedToolFace(toolRosterDeltas.current, req),
|
|
307
308
|
args: parkedArgs,
|
|
308
309
|
...(() => {
|
|
309
310
|
const p = approvalPreviewOf(req.toolName, parkedArgs);
|
|
@@ -85,6 +85,8 @@ export interface PreparePolicyChainInput {
|
|
|
85
85
|
envHandToolNames: ReadonlySet<string>;
|
|
86
86
|
/** borrowed-readonly — the shell-seat provenance set (the offer factory's `shellGated` mandate term, read per call). */
|
|
87
87
|
shellGatedTools: ReadonlySet<string>;
|
|
88
|
+
/** borrowed-readonly — who marked each name's seat (the offer factory's `shellGated` term reads it as the gate does). */
|
|
89
|
+
toolMarkSources: ReadonlyMap<string, ReadonlySet<import("../ask-origin.js").ToolMarkSource>>;
|
|
88
90
|
/** borrowed-readonly — the tracked-cwd seat, read per call by the lanes and the offer factory (never written here). */
|
|
89
91
|
handsCwdRef: CwdRef | undefined;
|
|
90
92
|
/** borrowed-readonly — the compiled read-deny judge, or undefined (the offer factory's directory negative control). */
|