@sema-agent/core 7.33.7 → 7.34.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 +357 -0
- package/dist/agents/cross-session-judge.d.ts +16 -7
- package/dist/agents/cross-session-judge.js +18 -15
- package/dist/agents/peer-session-drain.d.ts +4 -0
- package/dist/agents/peer-session-drain.js +5 -2
- package/dist/agents/repair-loop.d.ts +1 -1
- package/dist/agents/subagent.d.ts +3 -0
- package/dist/agents/subagent.js +12 -3
- package/dist/brain/anthropic.js +1 -0
- package/dist/brain/open-responses.js +1 -0
- package/dist/brain/openai.js +1 -0
- package/dist/brain/stream-engine.d.ts +3 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/core/a2a.d.ts +3 -1
- package/dist/core/a2a.js +1 -1
- package/dist/core/ask-origin.d.ts +351 -154
- package/dist/core/ask-origin.js +223 -45
- package/dist/core/ask-unresolvable-notice.d.ts +5 -2
- package/dist/core/ask-unresolvable-notice.js +4 -3
- package/dist/core/auto-mode-arming.d.ts +17 -4
- package/dist/core/auto-mode-arming.js +31 -6
- package/dist/core/auto-mode-defaults.d.ts +16 -7
- package/dist/core/auto-mode-defaults.js +2 -1
- package/dist/core/auto-mode-directive.d.ts +18 -30
- package/dist/core/auto-mode-directive.js +9 -18
- package/dist/core/auto-mode-prompt.d.ts +62 -1
- package/dist/core/auto-mode-prompt.js +42 -4
- package/dist/core/auto-mode-rebuild.d.ts +2 -1
- package/dist/core/auto-mode-rebuild.js +1 -0
- package/dist/core/auto-mode.d.ts +32 -3
- package/dist/core/auto-mode.js +80 -41
- package/dist/core/checkpoint-store.d.ts +50 -35
- package/dist/core/checkpoint-store.js +58 -3
- package/dist/core/child-clamp-seats.js +1 -0
- package/dist/core/classifier-unavailable-arm.d.ts +57 -0
- package/dist/core/classifier-unavailable-arm.js +17 -0
- package/dist/core/effective-path-target.d.ts +12 -0
- package/dist/core/effective-path-target.js +18 -7
- package/dist/core/engine-notice.d.ts +53 -2
- package/dist/core/fs-write-gate-policy.d.ts +5 -1
- package/dist/core/fs-write-gate-policy.js +4 -1
- package/dist/core/gate-fold.js +17 -1
- package/dist/core/gate-lanes.js +111 -46
- package/dist/core/gate-outcome.d.ts +40 -7
- package/dist/core/gate-outcome.js +26 -4
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +12 -0
- package/dist/core/hooks.d.ts +50 -29
- package/dist/core/hooks.js +1 -3
- package/dist/core/mcp.js +1 -1
- package/dist/core/mode-policy.d.ts +182 -0
- package/dist/core/mode-policy.js +179 -0
- package/dist/core/oracle-isolation.d.ts +3 -3
- package/dist/core/permission-rule-model.d.ts +16 -3
- package/dist/core/permission-rule-model.js +36 -3
- package/dist/core/permission-rule-provider.d.ts +22 -11
- package/dist/core/permission-rule-provider.js +17 -7
- package/dist/core/permission-rules.d.ts +23 -1
- package/dist/core/permission-rules.js +35 -8
- package/dist/core/persisted-rule-arms.d.ts +10 -9
- package/dist/core/persisted-rule-arms.js +9 -6
- package/dist/core/present-plan-tool.d.ts +1 -1
- package/dist/core/present-plan-tool.js +3 -3
- package/dist/core/read-only-shell.d.ts +19 -19
- package/dist/core/read-only-shell.js +38 -12
- package/dist/core/retired-keys.d.ts +9 -3
- package/dist/core/retired-keys.js +5 -1
- package/dist/core/runner/classifier-ladder.d.ts +43 -1
- package/dist/core/runner/classifier-ladder.js +29 -0
- package/dist/core/runner/contracts.d.ts +37 -35
- package/dist/core/runner/denial-limit-arms.d.ts +30 -7
- package/dist/core/runner/denial-limit-arms.js +15 -9
- package/dist/core/runner/env-capability-census.js +1 -1
- package/dist/core/runner/gate-exit.d.ts +30 -4
- package/dist/core/runner/gate-exit.js +28 -7
- package/dist/core/runner/permission-rule-lanes.d.ts +9 -5
- package/dist/core/runner/permission-rule-lanes.js +44 -40
- package/dist/core/runner/persisted-path-rows.d.ts +64 -0
- package/dist/core/runner/persisted-path-rows.js +89 -0
- package/dist/core/runner/prepare-artifact.d.ts +5 -1
- package/dist/core/runner/prepare-artifact.js +9 -3
- package/dist/core/runner/prepare-ask-lane.d.ts +5 -3
- package/dist/core/runner/prepare-ask-lane.js +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
- package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
- package/dist/core/runner/prepare-gate-stations.d.ts +33 -6
- package/dist/core/runner/prepare-gate-stations.js +32 -19
- package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
- package/dist/core/runner/prepare-hands-readface.js +64 -53
- package/dist/core/runner/prepare-inherited-gate.d.ts +22 -22
- package/dist/core/runner/prepare-inherited-gate.js +37 -23
- package/dist/core/runner/prepare-park-ask.d.ts +2 -2
- package/dist/core/runner/prepare-park-ask.js +2 -1
- package/dist/core/runner/prepare-policy-chain.d.ts +22 -2
- package/dist/core/runner/prepare-policy-chain.js +39 -12
- package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
- package/dist/core/runner/prepare-protocol-tools.js +13 -3
- package/dist/core/runner/prepare-run-refs.d.ts +6 -6
- package/dist/core/runner/prepare-run-refs.js +9 -4
- package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
- package/dist/core/runner/prepare-safety-scan.js +19 -3
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
- package/dist/core/runner/prepare-suspend-saga.js +6 -6
- package/dist/core/runner/prepare-task.js +51 -22
- package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
- package/dist/core/runner/prepare-wiring-manifest.js +5 -3
- package/dist/core/runner/read-root-grant.d.ts +9 -0
- package/dist/core/runner/read-root-grant.js +12 -0
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/runner/run-identity-wiring.js +1 -1
- package/dist/core/runner/run-leg.js +24 -15
- package/dist/core/runner/run-notification-lane.js +1 -0
- package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
- package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
- package/dist/core/runner/run-turn-boundary.js +1 -1
- package/dist/core/runner/runtask.js +3 -0
- package/dist/core/runner/stream-mode-verb.d.ts +27 -0
- package/dist/core/runner/stream-mode-verb.js +19 -0
- package/dist/core/runner/turn-attachments.d.ts +3 -3
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/write-protection-station.d.ts +7 -1
- package/dist/core/runner/write-protection-station.js +6 -3
- package/dist/core/runner-deps.d.ts +31 -34
- package/dist/core/runtime-caps.d.ts +12 -3
- package/dist/core/safety-axis-vocab.d.ts +2 -2
- package/dist/core/safety-axis-vocab.js +1 -1
- package/dist/core/secret-env.d.ts +16 -2
- package/dist/core/secret-env.js +25 -1
- package/dist/core/sensitive-path-policy.d.ts +4 -2
- package/dist/core/sensitive-path-policy.js +39 -30
- package/dist/core/spec-contract.d.ts +15 -0
- package/dist/core/spec-contract.js +19 -10
- package/dist/core/task-event.d.ts +1 -1
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +41 -89
- package/dist/core/task-stream.d.ts +16 -0
- package/dist/core/terminal-cause.js +1 -0
- package/dist/core/tighten-task-spec.d.ts +2 -2
- package/dist/core/tighten-task-spec.js +12 -14
- package/dist/core/tool-catalog-entries.js +4 -3
- package/dist/core/tool-policy.d.ts +54 -25
- package/dist/core/tool-policy.js +43 -2
- package/dist/core/tool-registry.d.ts +2 -0
- package/dist/core/tool-registry.js +3 -0
- package/dist/core/tool-spec.d.ts +8 -9
- package/dist/core/trace.d.ts +7 -6
- package/dist/core/types.d.ts +2 -1
- package/dist/core/wiring-manifest.d.ts +31 -5
- package/dist/core/wiring-manifest.js +28 -1
- package/dist/core/write-protect.d.ts +11 -1
- package/dist/core/write-protect.js +26 -15
- package/dist/engine/execution-env/kill-tree.js +4 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -0
- package/dist/engine/execution-env/node-execution-env.js +34 -35
- package/dist/engine/harness/types.d.ts +6 -7
- package/dist/engine/llm/types.d.ts +2 -4
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.js +12 -3
- package/dist/orchestration/run-spec.d.ts +4 -0
- package/dist/orchestration/run-spec.js +4 -3
- package/dist/orchestration/workflow-governance.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-size-guideline.js +2 -2
- package/dist/stores/file/fs-atomic.js +2 -1
- package/dist/tools/artifact/artifact-tool.d.ts +17 -0
- package/dist/tools/artifact/artifact-tool.js +16 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
- package/dist/tools/fs/bash-readonly-classifier.js +88 -0
- package/dist/tools/fs/containment-face.d.ts +9 -3
- package/dist/tools/fs/containment-face.js +2 -0
- package/dist/tools/fs/file-read-check.d.ts +49 -0
- package/dist/tools/fs/file-read-check.js +71 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-read.d.ts +1 -0
- package/dist/tools/fs/fs-search-tools.d.ts +2 -2
- package/dist/tools/fs/fs-search-tools.js +4 -4
- package/dist/tools/fs/fs-shared.js +20 -6
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +39 -3
- package/dist/tools/fs/index.js +38 -15
- package/dist/tools/fs/read-deny-home.d.ts +93 -0
- package/dist/tools/fs/read-deny-home.js +225 -0
- package/dist/tools/fs/read-deny.d.ts +176 -34
- package/dist/tools/fs/read-deny.js +268 -61
- package/dist/tools/fs/safety.d.ts +6 -1
- package/dist/tools/fs/safety.js +18 -5
- package/dist/tools/fs/search.d.ts +3 -0
- package/dist/tools/fs/search.js +47 -9
- package/dist/tools/fs/shell-read-check.d.ts +42 -11
- package/dist/tools/fs/shell-read-check.js +188 -13
- package/dist/tools/fs/shell-read-establish.d.ts +7 -3
- package/dist/tools/fs/shell-read-establish.js +134 -21
- package/dist/tools/fs/shell-read-identity.d.ts +7 -0
- package/dist/tools/fs/shell-read-identity.js +21 -0
- package/dist/tools/fs/shell-read-judge.d.ts +92 -32
- package/dist/tools/fs/shell-read-judge.js +43 -18
- package/dist/tools/fs/shell-read-plan.d.ts +4 -0
- package/dist/tools/fs/shell-read-plan.js +26 -4
- package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
- package/dist/tools/fs/shell-read-write-targets.js +173 -0
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +103 -11
- package/wire-changes/core@7.33.8.json +165 -0
- package/wire-changes/core@7.34.0.json +2621 -0
|
@@ -3,11 +3,12 @@ import { uuidv7 } from "../internal/harness.js";
|
|
|
3
3
|
import { PROBE_CAUSE_PATH_MAX, PROBE_REASON_MAX, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
4
4
|
import { carriesBidiControls } from "./tool-policy.js";
|
|
5
5
|
import { readDenialLimitFallback } from "./auto-mode.js";
|
|
6
|
-
import { isAskOrigin, isPersistedRuleMandate
|
|
6
|
+
import { isAskOrigin, isPersistedRuleMandate } from "./ask-origin.js";
|
|
7
7
|
import { checkpointExecutionRecorded, executionRecordDisposition, ExecutionOutcomeConflictError, } from "./checkpoint-execution-record.js";
|
|
8
8
|
import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
|
|
9
9
|
import { redactSecrets } from "./untrusted-egress.js";
|
|
10
10
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
11
|
+
import { isPermissionMode } from "./ask-origin.js";
|
|
11
12
|
export function mintCheckpointToken() {
|
|
12
13
|
return randomBytes(16).toString("hex");
|
|
13
14
|
}
|
|
@@ -298,15 +299,15 @@ export function summarizeCheckpoint(cp) {
|
|
|
298
299
|
...(tool?.previewWithheld === "oversize" || tool?.previewWithheld === "unavailable" ? { previewWithheld: tool.previewWithheld } : {}),
|
|
299
300
|
...(tool?.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
300
301
|
...(tool?.mandated === true ? { mandated: true } : {}),
|
|
302
|
+
...(tool?.authoredBy === "deployment" ? { authoredBy: "deployment" } : {}),
|
|
301
303
|
...(() => {
|
|
302
304
|
const fb = readDenialLimitFallback(tool?.denialLimitFallback);
|
|
303
305
|
return fb !== undefined ? { denialLimitFallback: fb } : {};
|
|
304
306
|
})(),
|
|
305
307
|
...(() => {
|
|
306
308
|
const origin = tool?.origin;
|
|
307
|
-
const ruleStoreUnreadable = tool?.ruleStoreUnreadable;
|
|
308
309
|
const mandate = tool?.mandate;
|
|
309
|
-
return { ...(isAskOrigin(origin) ? { origin } : {}), ...(
|
|
310
|
+
return { ...(isAskOrigin(origin) ? { origin } : {}), ...(isPersistedRuleMandate(mandate) ? { mandate } : {}) };
|
|
310
311
|
})(),
|
|
311
312
|
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
312
313
|
};
|
|
@@ -732,3 +733,57 @@ export class InMemoryCheckpointStore {
|
|
|
732
733
|
return this.cps.size;
|
|
733
734
|
}
|
|
734
735
|
}
|
|
736
|
+
export function migrateLegacyModeSeed(gate) {
|
|
737
|
+
if (gate === undefined)
|
|
738
|
+
return {};
|
|
739
|
+
const raw = gate;
|
|
740
|
+
const refusal = (code, message) => {
|
|
741
|
+
const e = new Error(message);
|
|
742
|
+
e.code = code;
|
|
743
|
+
return e;
|
|
744
|
+
};
|
|
745
|
+
const dropped = [];
|
|
746
|
+
let permissionMode;
|
|
747
|
+
const declared = raw["permissionMode"];
|
|
748
|
+
if (declared !== undefined) {
|
|
749
|
+
if (!isPermissionMode(declared))
|
|
750
|
+
throw refusal("config.permission_mode_invalid", `the checkpoint row's inheritedGate.permissionMode is ${JSON.stringify(declared)}, not a permission mode — the row cannot be resumed under a mode this engine does not know`);
|
|
751
|
+
permissionMode = declared;
|
|
752
|
+
if (raw["autoModeRequested"] !== undefined)
|
|
753
|
+
dropped.push("autoModeRequested");
|
|
754
|
+
if (raw["approverPosture"] !== undefined)
|
|
755
|
+
dropped.push("approverPosture");
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
const intent = raw["autoModeRequested"];
|
|
759
|
+
if (intent === true) {
|
|
760
|
+
permissionMode = "auto";
|
|
761
|
+
dropped.push("autoModeRequested");
|
|
762
|
+
}
|
|
763
|
+
else if (intent !== undefined && intent !== false) {
|
|
764
|
+
throw refusal("resume.legacy_mode_seed", `the checkpoint row's inheritedGate.autoModeRequested is ${JSON.stringify(intent)}, which the one-time mode migration cannot read (only true / false / absent are) — the row is refused rather than resumed under a guessed mode`);
|
|
765
|
+
}
|
|
766
|
+
else if (intent === false) {
|
|
767
|
+
dropped.push("autoModeRequested");
|
|
768
|
+
}
|
|
769
|
+
if (raw["approverPosture"] !== undefined)
|
|
770
|
+
dropped.push("approverPosture");
|
|
771
|
+
}
|
|
772
|
+
const prePlan = raw["prePlanMode"];
|
|
773
|
+
if (prePlan !== undefined && (!isPermissionMode(prePlan) || prePlan === "plan"))
|
|
774
|
+
throw refusal("config.permission_mode_invalid", `the checkpoint row's inheritedGate.prePlanMode is ${JSON.stringify(prePlan)}, not a permission mode a plan can return to`);
|
|
775
|
+
const floor = raw["shellGate"];
|
|
776
|
+
let shellGate;
|
|
777
|
+
if (floor === "always")
|
|
778
|
+
shellGate = "always";
|
|
779
|
+
else if (floor === "off" || floor === "classify")
|
|
780
|
+
dropped.push("shellGate");
|
|
781
|
+
else if (floor !== undefined)
|
|
782
|
+
throw refusal("resume.legacy_mode_seed", `the checkpoint row's inheritedGate.shellGate is ${JSON.stringify(floor)}, which is neither the floor word "always" nor a retired doctrine word — the row is refused rather than resumed under a guessed floor`);
|
|
783
|
+
return {
|
|
784
|
+
...(permissionMode !== undefined ? { permissionMode } : {}),
|
|
785
|
+
...(prePlan !== undefined ? { prePlanMode: prePlan } : {}),
|
|
786
|
+
...(shellGate !== undefined ? { shellGate } : {}),
|
|
787
|
+
...(dropped.length > 0 ? { migrated: { ...(declared === undefined && permissionMode !== undefined ? { permissionMode } : {}), droppedKeys: dropped } } : {}),
|
|
788
|
+
};
|
|
789
|
+
}
|
|
@@ -2,6 +2,7 @@ export const CHILD_CLAMP_SEATS = ["handsReadOnly", "interactiveTools", "readFace
|
|
|
2
2
|
export const CHILD_CLAMP_SEATS_NOT_CARRIED = Object.freeze({
|
|
3
3
|
toolPolicy: "not a clamp a child spec copies: a parent's denies reach its children through the ancestor-constraint chain the child's own prepare folds, not by putting the parent's policy object on the child seat",
|
|
4
4
|
shellGate: "travels on the inherited-gate lane (`InheritedGate.shellGate`, a trusted internals seat), not on the child spec — a spec-level copy would be a second source for the same clamp",
|
|
5
|
+
permissionMode: "travels on the inherited-gate lane (`InheritedGate.permissionMode` / `permissionModeCeiling` — the parent's declared word and its LIVE seat): a child with no word of its own inherits that ceiling on every read, and a word it does declare is clamped under it; a spec-level copy would freeze the parent's word at spawn and be a second source for the same clamp",
|
|
5
6
|
onAsk: "per-lane by construction: the delegation lane wraps the parent's approver with spawn provenance before it becomes the child's seat, and a lane with no approver must hand down none",
|
|
6
7
|
hooks: "a deployment seat, not an inherited clamp — `tightenTaskSpec` refuses to merge two hook sets rather than composing them, so there is no tightening value to carry",
|
|
7
8
|
excludeTools: "roster control rather than containment: the lanes that copy a parent's roster do so beside these seats, and the workflow lane authors its child roster from the governance baseline instead of the host's",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE exit for an auto-mode classification that could not RULE (`unavailable`: the classifier leg failed, or its
|
|
3
|
+
* per-level budget ran out) — shared by both classifier stations, the leg's gate and an ancestor's frozen classifier
|
|
4
|
+
* in the policy fold, so the rule is written once.
|
|
5
|
+
*
|
|
6
|
+
* ONE fork, on the APPROVAL SEAT (does an ask this station hands on reach a person — a live approver, or a durable
|
|
7
|
+
* park): with one, the ask goes on, its text saying why auto mode is asking, and a person decides; without one, the
|
|
8
|
+
* call is refused with CC's `x1t` sentence and the classification's FORM (`unavailable`) is attested on the deny. The seat is
|
|
9
|
+
* the only input to the fork — not the cause, not the mode. The handed-on ask carries `requiresRealApproval`: no
|
|
10
|
+
* automatic lane may answer it in the classifier's stead — not a blanket `onAsk:"allow"`, not the sandbox admission, not a
|
|
11
|
+
* budget resolver over its park (the park mints the non-budgetable kind). CC refuses either way; asking where a person can be
|
|
12
|
+
* asked is a declared departure (a classifier that is slow or down must not turn an attended session into a string
|
|
13
|
+
* of refusals). Neither arm is a block: the denial limit counts classifier RULINGS, and a person's refusal of the
|
|
14
|
+
* handed-on ask is a person's, not the classifier's.
|
|
15
|
+
*
|
|
16
|
+
* The observer hears every such classification with where the call went, except while the call is being aborted — a halt,
|
|
17
|
+
* or the run's end (the classifier did not fail; the call ended). It is observe-only: a throw never changes the arm.
|
|
18
|
+
*/
|
|
19
|
+
import { type AutoModeClassifierRound, type AutoModeUnavailableCause } from "./auto-mode.js";
|
|
20
|
+
import { type PermissionResult, type ToolCallRequest } from "./tool-policy.js";
|
|
21
|
+
/** Where an `unavailable` classification sent the call: `asked` = on to the approval seat, `denied` = refused. */
|
|
22
|
+
export type ClassifierUnavailableDisposition = "asked" | "denied";
|
|
23
|
+
/** What the stations' observer receives — the facts the `classifier.unavailable` notice is composed from. */
|
|
24
|
+
export interface ClassifierUnavailableInfo {
|
|
25
|
+
toolName: string;
|
|
26
|
+
toolCallId: string;
|
|
27
|
+
cause: AutoModeUnavailableCause;
|
|
28
|
+
disposition: ClassifierUnavailableDisposition;
|
|
29
|
+
}
|
|
30
|
+
export type ClassifierUnavailableObserver = (info: ClassifierUnavailableInfo) => void;
|
|
31
|
+
/** The station's two facts for the fork: whether an ask it hands on reaches a person (`true` only — anything else is
|
|
32
|
+
* no seat), and who hears it. */
|
|
33
|
+
export interface ClassifierUnavailableSeat {
|
|
34
|
+
approvalSeat?: boolean;
|
|
35
|
+
onUnavailable?: ClassifierUnavailableObserver;
|
|
36
|
+
}
|
|
37
|
+
/** The arm the fork chose: the ask to hand on, or the refusal. */
|
|
38
|
+
export type ClassifierUnavailableArm<A> = {
|
|
39
|
+
kind: "asked";
|
|
40
|
+
ask: A;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "denied";
|
|
43
|
+
deny: PermissionResult;
|
|
44
|
+
};
|
|
45
|
+
export declare function classifierUnavailableArm<A extends Extract<PermissionResult, {
|
|
46
|
+
action: "ask";
|
|
47
|
+
}>>(args: {
|
|
48
|
+
ask: A;
|
|
49
|
+
req: ToolCallRequest;
|
|
50
|
+
cause: AutoModeUnavailableCause;
|
|
51
|
+
/** The ladder's attribution, for the refusal's sentence (it names both rungs when the ladder moved). */
|
|
52
|
+
attribution?: AutoModeClassifierRound;
|
|
53
|
+
seat: ClassifierUnavailableSeat | undefined;
|
|
54
|
+
/** The call is being aborted (its per-call signal — a halt, or the run's end): the observer is not told (the arm is chosen
|
|
55
|
+
* as ever). */
|
|
56
|
+
aborted: boolean;
|
|
57
|
+
}): ClassifierUnavailableArm<A>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { classifierUnavailableAskMessage, classifierUnavailableDenyMessage } from "./auto-mode.js";
|
|
2
|
+
import { attestClassifierDenyCause } from "./tool-policy.js";
|
|
3
|
+
export function classifierUnavailableArm(args) {
|
|
4
|
+
const { ask, req, cause } = args;
|
|
5
|
+
const seated = args.seat?.approvalSeat === true;
|
|
6
|
+
if (!args.aborted) {
|
|
7
|
+
try {
|
|
8
|
+
args.seat?.onUnavailable?.({ toolName: req.toolName, toolCallId: req.toolCallId, cause, disposition: seated ? "asked" : "denied" });
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (seated)
|
|
14
|
+
return { kind: "asked", ask: { ...ask, message: classifierUnavailableAskMessage(ask.message ?? `approval required for "${req.toolName}"`, cause), requiresRealApproval: true } };
|
|
15
|
+
const deny = { action: "deny", message: classifierUnavailableDenyMessage(req.toolName, cause, args.attribution), decisionReason: "classifier" };
|
|
16
|
+
return { kind: "denied", deny: attestClassifierDenyCause(deny, "unavailable", req) };
|
|
17
|
+
}
|
|
@@ -101,3 +101,15 @@ export type PathTargetReading = {
|
|
|
101
101
|
* against names no file, and claiming it names `/<spelling>` was a fabrication no fence should judge.
|
|
102
102
|
*/
|
|
103
103
|
export declare function effectivePathTargetOf(call: ToolCallLike, pt: ToolCallPathTarget | undefined, bases: ToolPathBases): PathTargetReading;
|
|
104
|
+
/**
|
|
105
|
+
* The SELECTION half of {@link effectivePathTargetOf}, before any identity is applied: the spelling the tool opens and
|
|
106
|
+
* the base it resolves that spelling against — the slot's own value, or an absolute pattern's base directory — or,
|
|
107
|
+
* with `spelled` absent, the base itself (the tool searches all of it). A caller that resolves canonically (the tool,
|
|
108
|
+
* the read face's own station) reads this; the rule lanes read the lexical identity above. Same order as above.
|
|
109
|
+
*/
|
|
110
|
+
export declare function effectivePathSpellingOf(call: ToolCallLike, pt: ToolCallPathTarget | undefined, bases: ToolPathBases): {
|
|
111
|
+
readonly spelled?: string;
|
|
112
|
+
readonly base: string | undefined;
|
|
113
|
+
} | {
|
|
114
|
+
readonly refused: Win32NamespaceRefusal;
|
|
115
|
+
} | undefined;
|
|
@@ -23,26 +23,37 @@ export function splitAbsoluteGlobPattern(pattern, family) {
|
|
|
23
23
|
return { baseDir, relativePattern: spelled.slice(cut + 1) };
|
|
24
24
|
}
|
|
25
25
|
export function effectivePathTargetOf(call, pt, bases) {
|
|
26
|
+
const spelling = effectivePathSpellingOf(call, pt, bases);
|
|
27
|
+
if (spelling === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
if ("refused" in spelling)
|
|
30
|
+
return { kind: "refused", reason: spelling.refused };
|
|
31
|
+
if (spelling.spelled !== undefined)
|
|
32
|
+
return resolveSpelling(spelling.spelled, spelling.base, bases);
|
|
33
|
+
const declaredBase = spelling.base;
|
|
34
|
+
if (declaredBase === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const family = pathFamilyOf(bases);
|
|
37
|
+
return isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
|
|
38
|
+
}
|
|
39
|
+
export function effectivePathSpellingOf(call, pt, bases) {
|
|
26
40
|
if (pt === undefined)
|
|
27
41
|
return undefined;
|
|
28
42
|
const declaredBase = pathTargetBaseOf(pt, bases);
|
|
29
43
|
const spelled = pathTargetValue(call.args, pt);
|
|
30
44
|
if (spelled !== undefined)
|
|
31
|
-
return
|
|
45
|
+
return { spelled, base: declaredBase };
|
|
32
46
|
if ((pt.absent ?? "none") === "none")
|
|
33
47
|
return undefined;
|
|
34
48
|
if (pt.patternParam !== undefined) {
|
|
35
49
|
const pattern = pathTargetValue(call.args, { param: pt.patternParam });
|
|
36
50
|
const split = pattern === undefined ? undefined : splitAbsoluteGlobPattern(pattern, pathFamilyOf(bases));
|
|
37
51
|
if (split !== undefined && "refused" in split)
|
|
38
|
-
return {
|
|
52
|
+
return { refused: split.refused };
|
|
39
53
|
if (split !== undefined)
|
|
40
|
-
return
|
|
54
|
+
return { spelled: split.baseDir, base: declaredBase };
|
|
41
55
|
}
|
|
42
|
-
|
|
43
|
-
return undefined;
|
|
44
|
-
const family = pathFamilyOf(bases);
|
|
45
|
-
return isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
|
|
56
|
+
return { base: declaredBase };
|
|
46
57
|
}
|
|
47
58
|
function resolveSpelling(spelled, declaredBase, bases) {
|
|
48
59
|
const family = pathFamilyOf(bases);
|
|
@@ -222,6 +222,18 @@ export interface EngineNotice {
|
|
|
222
222
|
* ladder position (0 the seat, 1 the model behind it), since two rungs may share an id. A ruling from
|
|
223
223
|
* that rung resets its count; an `unavailable` round neither counts nor resets. Once per run per rung,
|
|
224
224
|
* and by construction before the denial limit's third block hands the user its card.
|
|
225
|
+
* - `"classifier.unavailable"` (`src/core/runner/classifier-ladder.ts`, `classifierUnavailableNotice`; minted by
|
|
226
|
+
* both classifier stations — the leg's gate and an ancestor's frozen classifier in the policy fold) — a
|
|
227
|
+
* classification could NOT RULE (the classifier leg failed, or its per-level budget
|
|
228
|
+
* `RunnerDeps.autoMode.timeoutMs` ran out) and says where the call went: `disposition: "asked"` = on down the
|
|
229
|
+
* ask chain to the leg's approval seat (a live approver or a durable park decides; the handed-on ask carries
|
|
230
|
+
* `requiresRealApproval`, so no automatic lane answers it; a declared departure from CC, which refuses either
|
|
231
|
+
* way), `"denied"` = refused with the classifier's own sentence (no approval seat).
|
|
232
|
+
* `detail: { toolName, toolCallId, cause, disposition, sessionId?, runId? }` — `cause` is the
|
|
233
|
+
* `AutoModeUnavailableCause` word. Once per classification that could not answer (a call judged by an ancestor's
|
|
234
|
+
* classifier and the leg's own can carry two); not minted while the call is being aborted (a halt, or the run's end). Audience
|
|
235
|
+
* `user`: on a lane with no approval face this line is the only place the user hears why the call did not
|
|
236
|
+
* run. It is not a block — the denial limit does not count it.
|
|
225
237
|
* - `"route.base_url_changed_key_unchanged"` (key↔URL pairing) — `Runner.swapDeps` moved a
|
|
226
238
|
* same-name entry's `baseUrl` while its Model-visible credential half (auth-bearing headers)
|
|
227
239
|
* did not change: legal (a provider changing domains), but worth one loud line — if the
|
|
@@ -281,6 +293,44 @@ export interface EngineNotice {
|
|
|
281
293
|
* secret-shaped keys from a spawned child's environment (a tool's shell, or a language server).
|
|
282
294
|
* Once per PROCESS, shared by every scrub site ({@link import("./secret-env.js").announceSecretEnvScrub});
|
|
283
295
|
* `detail: { names }` — every withheld key's name, never a value.
|
|
296
|
+
* - `"config.permission_mode_unavailable"` — the permission mode a task requested is not the mode the leg runs
|
|
297
|
+
* under: the deployment denies it (`by: "operator_deny"` — `RuntimeCaps.bypassPermissions` / `autoMode` is
|
|
298
|
+
* `false`), a requested `auto` has no classifier face (`by: "face_absent"`), or a child's declaration was folded
|
|
299
|
+
* under its parent's ceiling (`by: "inherited_ceiling"`, minted on the PARENT's stream with the child's
|
|
300
|
+
* `sessionId` beside the parent's — a child's own manifest is not shown to the person). The leg runs in
|
|
301
|
+
* `effective` (always `default` for the first two arms), announced once per leg and once per steer that folds;
|
|
302
|
+
* `detail: { requested, effective, by, sessionId, runId }`. Audience `user`.
|
|
303
|
+
* - `"config.permission_mode_invalid"`, `"config.spec_retired_key"`, `"config.shell_gate_value_retired"` and
|
|
304
|
+
* `"resume.legacy_mode_seed"` are ERROR codes, not notices (named here only to keep the mode family's spellings
|
|
305
|
+
* in one place): a `permissionMode` word outside the closed set (on the spec or on a checkpoint row), a spec
|
|
306
|
+
* carrying a retired mode key (`autoModeRequested`, `approverPosture`), a `shellGate` value other than
|
|
307
|
+
* `"always"`, and a checkpoint row whose pre-mode seed the migration cannot read. Each is a door refusal: the
|
|
308
|
+
* code rides the thrown error (the run fails, or the resume is refused) and never crosses
|
|
309
|
+
* {@link deliverEngineNotice}, so all four are deliberately absent from {@link ENGINE_NOTICE_CODES} /
|
|
310
|
+
* {@link NOTICE_AUDIENCE}, like `config.deps_retired_key`. A consumer diffing its own table against the
|
|
311
|
+
* catalog must not add a row for them; it reads them on the error's `code`.
|
|
312
|
+
* - `"permission.ask_released_by_mode"` — the `bypassPermissions` mode released a surviving ask instead of
|
|
313
|
+
* putting it to anyone; once per (mode, askClass) per run, `detail: { mode, askClass, origin?, mandate?, count,
|
|
314
|
+
* firstToolCallId, sessionId, runId }` — `count` is how many releases of that pair the run had made when the
|
|
315
|
+
* notice was minted (the first). The per-call record is `tool_end.gate.disposition.released`. Audience `user`.
|
|
316
|
+
* - `"permission.read_deny_unapplied"` — the mode released a call whose reads the shell read station could not
|
|
317
|
+
* establish (`unestablishable` / `declined`) while a read-deny table is in force: no row of the table was applied
|
|
318
|
+
* to what it reads (C-R124, "released and disclosed"). Every such call, `detail: { mode, toolCallId, tiers, rows,
|
|
319
|
+
* rowCount, sessionId, runId }` (`rows` = the first eight non-built-in rows). Audience `user`.
|
|
320
|
+
* - `"permission.rule_store_unavailable"` — the person's persisted permission rules could not be read (a read
|
|
321
|
+
* failure, a corrupt or unreadable store, a read that did not answer in time), so that read's rows were SKIPPED:
|
|
322
|
+
* none of their allow, ask or deny rows took part in the decisions it served — a deny row among them protected
|
|
323
|
+
* nothing meanwhile — and no card is raised for it. `detail.at` names the read that found it first: `"prepare"`
|
|
324
|
+
* (the path rows the run's file and shell checks take at run start — the next run reads them again) or `"call"`
|
|
325
|
+
* (one call's own rule check — every later call reads again, so the rows apply as soon as a read succeeds). Once
|
|
326
|
+
* per run, whichever read fails first; the trace keeps every read's line (`permission.rule_store_unreadable`).
|
|
327
|
+
* `detail: { at, sessionId, runId }`. Audience `user`.
|
|
328
|
+
* - `"resume.legacy_mode_seed_migrated"` — a checkpoint row written before the one-key mode (7.34.0) carried
|
|
329
|
+
* `autoModeRequested` / `approverPosture` / a doctrine `shellGate`; the resume leg read them once into
|
|
330
|
+
* `permissionMode` (`autoModeRequested: true` ⇒ `auto`; a posture is dropped — it never authorized anything;
|
|
331
|
+
* `shellGate` `"off"` / `"classify"` are dropped, `"always"` kept) and the next suspend writes the new member.
|
|
332
|
+
* `detail: { migrated: { permissionMode?, droppedKeys }, sessionId, runId }`. Audience `operator`. A row the
|
|
333
|
+
* migration cannot read refuses the resume instead (`resume.legacy_mode_seed`, the error-code row above).
|
|
284
334
|
* - `"config.durable_gate_unavailable"` — a durable-approval MANDATE is in force on a leg with NO
|
|
285
335
|
* checkpoint store: the per-principal `forceDurableGate` entitlement, or the task's own
|
|
286
336
|
* `durableApproval` opt-in with no live approver seat (`durableMandateSourceOf`, the predicate the
|
|
@@ -686,8 +736,9 @@ export interface EngineNotice {
|
|
|
686
736
|
* facility (an in-fold deny, no settlement on the frame). The deny itself is unchanged (the
|
|
687
737
|
* fail-closed sentence as the tool result); this is its person-facing half, minted at the gate's ONE
|
|
688
738
|
* deny exit, once per tool call, by `ask-unresolvable-notice.ts`. `detail: { sessionId, toolName,
|
|
689
|
-
* toolCallId, cause, parkLaneExisted }` — `parkLaneExisted` says whether a park lane was armed and
|
|
690
|
-
* declined/failed (`true`) or none existed (`false`, the usual shape)
|
|
739
|
+
* toolCallId, cause, parkLaneExisted, remedy? }` — `parkLaneExisted` says whether a park lane was armed and
|
|
740
|
+
* declined/failed (`true`) or none existed (`false`, the usual shape); `remedy` (the headless auto-deny only)
|
|
741
|
+
* is the one sentence naming the caller-side change that would have let the call run, as the tool text carries it. Audience `"user"`: the refused
|
|
691
742
|
* work is the session user's own; the operator hears it through the sink like every code.
|
|
692
743
|
*
|
|
693
744
|
* - `"memory.consolidation_recommended"` (design/339 §2.2/§6.2) — the engine-minted per-scope
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../internal/harness-types.js";
|
|
2
|
-
import type { ToolPolicy } from "./tool-policy.js";
|
|
2
|
+
import type { PermissionResult, ToolPolicy } from "./tool-policy.js";
|
|
3
3
|
/** design/388 B22 — the identity triple the exemption probe hands the store beside the name. */
|
|
4
4
|
export interface ToolCallIdentity {
|
|
5
5
|
capabilityId: string;
|
|
@@ -31,6 +31,10 @@ export interface FsWriteGatePolicyOptions {
|
|
|
31
31
|
* to the ask-resolution chain (live onAsk / durable park / headless deny); `"allow"` is the
|
|
32
32
|
* acceptEdits-mode shape (domain checks still run so a sensitive-path deny composes unchanged). */
|
|
33
33
|
defaultWrite: "ask" | "allow";
|
|
34
|
+
/** The ask the default verdict mints in place of the gate's ordinary write question — a permission mode's own
|
|
35
|
+
* sentence over the resolved `path`, with the gate's domain note (a configured domain that was not honored) to
|
|
36
|
+
* append when there is one. Read only where `defaultWrite` is `"ask"`. */
|
|
37
|
+
defaultAsk?: (path: string, note: string | undefined) => PermissionResult;
|
|
34
38
|
/**
|
|
35
39
|
* Session-exemption lookup ("allow all for this session" grants recorded by the deployment's approval
|
|
36
40
|
* surface). Consulted ONCE per covered write, after the domain checks and immediately BEFORE the gate
|
|
@@ -109,7 +109,10 @@ export function createFsWriteGatePolicy(opts) {
|
|
|
109
109
|
}
|
|
110
110
|
if (defaultWrite === "allow")
|
|
111
111
|
return { action: "allow" };
|
|
112
|
-
|
|
112
|
+
const note = refusedDomain === undefined ? undefined : ` — note: the configured write domain ${refusedDomain} was not honored; a domain that holds the root would exempt every write under it`;
|
|
113
|
+
if (opts.defaultAsk !== undefined)
|
|
114
|
+
return opts.defaultAsk(path, note);
|
|
115
|
+
return ask(`approve write to "${path}"?${advisory}${note ?? ""}`);
|
|
113
116
|
},
|
|
114
117
|
};
|
|
115
118
|
}
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -175,6 +175,14 @@ function shellReadAskMessage(toolName, finding) {
|
|
|
175
175
|
}
|
|
176
176
|
function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
|
|
177
177
|
const account = verdict.sentence !== undefined && verdict.sentence.trim().length > 0 ? inlineUntrusted(verdict.sentence, PROBE_REASON_MAX) : undefined;
|
|
178
|
+
if (verdict.writesPlaced === true)
|
|
179
|
+
pass.shellWritesPlaced = true;
|
|
180
|
+
if (verdict.writeProtected === true)
|
|
181
|
+
pass.writeProtected = true;
|
|
182
|
+
if (verdict.unplacedOptions !== undefined)
|
|
183
|
+
pass.shellUnplacedOptions = verdict.unplacedOptions;
|
|
184
|
+
if (verdict.finding === "unestablishable" || verdict.finding === "declined" || verdict.unestablished === true)
|
|
185
|
+
pass.shellReadUnestablished = true;
|
|
178
186
|
switch (verdict.disposition) {
|
|
179
187
|
case "vouch":
|
|
180
188
|
return;
|
|
@@ -207,16 +215,24 @@ function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
|
|
|
207
215
|
return;
|
|
208
216
|
}
|
|
209
217
|
case "ordinary": {
|
|
218
|
+
verdict.admit?.();
|
|
219
|
+
if (verdict.details !== undefined)
|
|
220
|
+
pass.shellReadDetails = verdict.details;
|
|
210
221
|
if (pass.decision.action === "allow") {
|
|
222
|
+
const candidate = normalizeReadRootCandidate(verdict.readRootCandidate);
|
|
211
223
|
pass.decision = {
|
|
212
224
|
action: "ask",
|
|
213
225
|
message: shellReadAskMessage(toolName, verdict.finding),
|
|
214
226
|
decisionReason: "safety",
|
|
215
227
|
...(account !== undefined ? { probeReason: account } : {}),
|
|
228
|
+
...(candidate !== undefined ? { probeReadRootCandidate: candidate } : {}),
|
|
216
229
|
};
|
|
230
|
+
if (candidate !== undefined)
|
|
231
|
+
pass.shellReadOffer = true;
|
|
217
232
|
pass.tightenedBy = shellGated ? "shell_gate" : "safety";
|
|
218
233
|
}
|
|
219
|
-
|
|
234
|
+
if (verdict.finding === "declined")
|
|
235
|
+
pass.probeBoundaryDeclined = true;
|
|
220
236
|
return;
|
|
221
237
|
}
|
|
222
238
|
default: {
|