@sema-agent/core 7.33.8 → 7.35.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 +462 -3
- 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/errors.d.ts +23 -1
- package/dist/brain/errors.js +19 -2
- package/dist/brain/open-responses.js +1 -0
- package/dist/brain/openai.js +1 -0
- package/dist/brain/status-sink.d.ts +7 -1
- package/dist/brain/stream-engine.d.ts +3 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/brain/stream-shared.d.ts +3 -1
- package/dist/brain/stream-shared.js +16 -4
- package/dist/core/a2a.d.ts +3 -1
- package/dist/core/a2a.js +1 -1
- package/dist/core/ask-origin.d.ts +379 -154
- package/dist/core/ask-origin.js +224 -45
- package/dist/core/ask-question.js +2 -1
- 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 +54 -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 +19 -3
- package/dist/core/gate-lanes.js +116 -50
- package/dist/core/gate-outcome.d.ts +40 -7
- package/dist/core/gate-outcome.js +26 -4
- package/dist/core/governance-codes.d.ts +18 -2
- package/dist/core/governance-codes.js +23 -1
- package/dist/core/hooks.d.ts +53 -29
- package/dist/core/hooks.js +1 -3
- package/dist/core/mcp.js +1 -1
- package/dist/core/message-utils.d.ts +2 -1
- package/dist/core/message-utils.js +2 -1
- package/dist/core/mode-policy.d.ts +185 -0
- package/dist/core/mode-policy.js +184 -0
- package/dist/core/oracle-isolation.d.ts +3 -3
- package/dist/core/permission-rule-model.d.ts +20 -3
- package/dist/core/permission-rule-model.js +40 -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 +56 -9
- package/dist/core/persisted-rule-arms.d.ts +14 -10
- package/dist/core/persisted-rule-arms.js +13 -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/active-skill-scope.js +5 -4
- package/dist/core/runner/assemble-result.js +2 -2
- 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 +62 -38
- package/dist/core/runner/denial-limit-arms.d.ts +35 -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/file-history-request.d.ts +26 -0
- package/dist/core/runner/file-history-request.js +90 -0
- 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 +11 -6
- package/dist/core/runner/permission-rule-lanes.js +55 -48
- 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 +6 -4
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- 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-file-history.d.ts +9 -3
- package/dist/core/runner/prepare-file-history.js +4 -90
- package/dist/core/runner/prepare-gate-stations.d.ts +37 -9
- package/dist/core/runner/prepare-gate-stations.js +46 -29
- 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 +30 -23
- package/dist/core/runner/prepare-inherited-gate.js +45 -25
- package/dist/core/runner/prepare-park-ask.d.ts +6 -4
- package/dist/core/runner/prepare-park-ask.js +7 -5
- package/dist/core/runner/prepare-policy-chain.d.ts +25 -4
- package/dist/core/runner/prepare-policy-chain.js +203 -180
- 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 +56 -24
- 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-brain-sinks.js +3 -1
- package/dist/core/runner/run-identity-wiring.js +1 -1
- package/dist/core/runner/run-leg.js +29 -18
- package/dist/core/runner/run-notification-lane.d.ts +3 -0
- package/dist/core/runner/run-notification-lane.js +3 -1
- 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.d.ts +1 -0
- package/dist/core/runner/runtask.js +8 -11
- package/dist/core/runner/session-mode-seats.d.ts +48 -0
- package/dist/core/runner/session-mode-seats.js +73 -0
- package/dist/core/runner/session-rule-policy.js +3 -2
- 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 +30 -58
- package/dist/core/secret-env.js +149 -586
- package/dist/core/sensitive-path-policy.d.ts +4 -2
- package/dist/core/sensitive-path-policy.js +41 -31
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- 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 +84 -30
- package/dist/core/tool-policy.js +76 -16
- package/dist/core/tool-registry.d.ts +38 -0
- package/dist/core/tool-registry.js +25 -1
- package/dist/core/tool-roster.d.ts +17 -2
- package/dist/core/tool-roster.js +11 -1
- package/dist/core/tool-spec.d.ts +8 -9
- package/dist/core/trace.d.ts +18 -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/node-execution-env.d.ts +6 -1
- package/dist/engine/execution-env/node-execution-env.js +16 -3
- package/dist/engine/harness/agent-harness.js +2 -1
- 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/engine/lsp/node-lsp-manager.js +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +15 -6
- 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/index.d.ts +5 -3
- package/dist/stores/file/session-store.d.ts +8 -2
- package/dist/stores/file/session-store.js +5 -5
- 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 +50 -0
- package/dist/tools/fs/file-read-check.js +79 -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 +52 -13
- package/dist/tools/fs/shell-read-check.js +199 -20
- 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 +1 -1
- package/test/export-surface.snapshot.json +117 -11
- package/wire-changes/core@7.34.0.json +2621 -0
- package/wire-changes/core@7.35.0.json +584 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AskEffective } from "./wiring-manifest.js";
|
|
2
|
+
/** The permission-mode words the composition reads (the engine's closed set, as the wire spells it). */
|
|
3
|
+
import type { PermissionMode } from "./ask-origin.js";
|
|
2
4
|
/** The auto-mode directive body (title + the two paragraphs). Rendered when the run's auto-mode
|
|
3
5
|
* INTENT is resolved true — the seat, the chain it inherited, or the bit a suspend recorded. */
|
|
4
6
|
export declare const AUTO_MODE_DIRECTIVE: string;
|
|
@@ -8,39 +10,25 @@ export declare const BASH_FIRST_DIRECTIVE: string;
|
|
|
8
10
|
/** The bypass-posture directive body: the reference implementation's heading over the shell-first
|
|
9
11
|
* paragraph, the exact pair its bypass arm renders. */
|
|
10
12
|
export declare const BYPASS_POSTURE_DIRECTIVE: string;
|
|
11
|
-
/** The
|
|
12
|
-
* TYPE is declared beside the seat it belongs to ({@link ApproverPosture}, task-spec.ts); this array
|
|
13
|
-
* is its runtime twin, and the assignment below is the compile-time tie between them. */
|
|
14
|
-
export declare const APPROVER_POSTURES: readonly ApproverPosture[];
|
|
15
|
-
/** Value-shape check for the word — used by the spec door below and by the chain/checkpoint readers. */
|
|
16
|
-
export declare function isApproverPosture(value: unknown): value is ApproverPosture;
|
|
17
|
-
/** The SEAT's door screen, declared beside the vocabulary it screens against. The renderer keys on the
|
|
18
|
-
* exact word, so any other present value would render zero bytes and be indistinguishable from "no
|
|
19
|
-
* posture declared"; it is refused instead. The value that passed is the value RETURNED — the render
|
|
20
|
-
* reads that snapshot, never the caller's spec object again. */
|
|
21
|
-
export declare function refuseInvalidApproverPosture(spec: {
|
|
22
|
-
approverPosture?: ApproverPosture;
|
|
23
|
-
}): ApproverPosture | undefined;
|
|
24
|
-
/** The resolved posture facts of one leg: both already folded across seat, chain and checkpoint. */
|
|
13
|
+
/** The mode facts of one leg, both the ENGINE's own resolution — never a requested value, never spec shape. */
|
|
25
14
|
export interface ApprovalPostureFacts {
|
|
26
|
-
/** The
|
|
27
|
-
|
|
28
|
-
/** The
|
|
29
|
-
|
|
15
|
+
/** The leg's EFFECTIVE permission mode (a requested mode the deployment refused has already folded back). */
|
|
16
|
+
mode: PermissionMode;
|
|
17
|
+
/** The wiring manifest's ask derivation — `auto_allow` means no person answers an ask on this leg. */
|
|
18
|
+
askEffective: AskEffective;
|
|
30
19
|
}
|
|
31
20
|
/**
|
|
32
|
-
* The ONE composition, and the ONE invariant: **the segment the
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
21
|
+
* The ONE composition, and the ONE invariant: **the segment the leg's mode calls for is stated exactly
|
|
22
|
+
* once in the active model context**. `statedBodies` is the transcript's ENGINE REGIONS (read by metadata
|
|
23
|
+
* — a caller's own bytes are never in one, so a spliced copy cannot suppress the engine's); a segment
|
|
24
|
+
* already standing there is not restated, and a segment that has left the context — compacted away, or
|
|
25
|
+
* never delivered — is. Returns `undefined` when there is nothing left to say, which is both "no segment
|
|
26
|
+
* for this mode" (zero bytes) and "already said".
|
|
38
27
|
*
|
|
39
|
-
* Segment-wise rather than body-wise on purpose: a leg whose
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* owned by its own mint home.
|
|
28
|
+
* Segment-wise rather than body-wise on purpose: a leg whose mode CHANGES — it now runs under bypass, so the
|
|
29
|
+
* segment it owes changes — must state the new segment without restating one already standing, and must be
|
|
30
|
+
* able to read a body an earlier leg minted under a composition that joined two. The caller wraps the result
|
|
31
|
+
* in the engine's marked reminder shell; this module never wraps, so the mark stays owned by its own mint home.
|
|
44
32
|
*/
|
|
45
33
|
export declare function composeApprovalPostureDirective(facts: ApprovalPostureFacts, statedBodies?: readonly string[]): string | undefined;
|
|
46
34
|
/** SHA-256 of a directive body — the pins' lock port, so the lock is computed the one way. */
|
|
@@ -2,28 +2,19 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
export const AUTO_MODE_DIRECTIVE = "## Auto Mode Active\n\nBias toward working without stopping for clarifying questions — when you'd normally pause to check, make the reasonable call and keep going; they'll redirect you if needed. If the user, a skill, or the shape of the task suggests they want you to ask (with AskUserQuestion or otherwise), do so. And even absent that signal, it's still fine to stop when you're genuinely blocked — unclear direction, missing input, a decision only they can make.\n\nBefore any command that could discard uncommitted work — `git checkout`/`restore`/`reset`/`clean`, `rm -rf` in the repo, restoring from a snapshot — run `git status` first and stash (with `-u` for untracked) or commit anything that's there. When staging or committing, review what's included (`git status` after a broad `git add`), and if you see anything suspicious that might reveal secrets — even if the filename looks innocuous — double-check the file's contents before pushing.";
|
|
3
3
|
export const BASH_FIRST_DIRECTIVE = "Do your work through the Bash tool wherever it can accomplish the job: read files with cat, head, or sed -n, search with grep and find, and make file changes with sed, heredocs, or short scripts, rather than using the dedicated Read, Edit, or Write tools. Fall back to a dedicated tool only when Bash genuinely cannot do the job.";
|
|
4
4
|
export const BYPASS_POSTURE_DIRECTIVE = `While bypass permissions mode is active:\n\n${BASH_FIRST_DIRECTIVE}`;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export function refuseInvalidApproverPosture(spec) {
|
|
10
|
-
if (spec.approverPosture !== undefined && !isApproverPosture(spec.approverPosture)) {
|
|
11
|
-
const e = new Error(`approverPosture must be one of ${APPROVER_POSTURES.map((w) => JSON.stringify(w)).join(", ")} when present (got ${JSON.stringify(spec.approverPosture)}) — omit it to declare no posture; an unrecognized value is refused rather than read as absence.`);
|
|
12
|
-
e.code = "config.approver_posture_invalid";
|
|
13
|
-
throw e;
|
|
14
|
-
}
|
|
15
|
-
return spec.approverPosture;
|
|
5
|
+
function postureSegmentOf(facts) {
|
|
6
|
+
if (facts.mode === "bypassPermissions" || facts.askEffective === "auto_allow")
|
|
7
|
+
return BYPASS_POSTURE_DIRECTIVE;
|
|
8
|
+
return facts.mode === "auto" ? AUTO_MODE_DIRECTIVE : undefined;
|
|
16
9
|
}
|
|
17
10
|
export function composeApprovalPostureDirective(facts, statedBodies = []) {
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
11
|
+
const owed = postureSegmentOf(facts);
|
|
12
|
+
if (owed === undefined)
|
|
20
13
|
return undefined;
|
|
21
|
-
const stated = new Set();
|
|
22
14
|
for (const body of statedBodies)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return owed.length === 0 ? undefined : owed.join("\n\n");
|
|
15
|
+
if (postureSegmentsOf(body)?.includes(owed) === true)
|
|
16
|
+
return undefined;
|
|
17
|
+
return owed;
|
|
27
18
|
}
|
|
28
19
|
const POSTURE_SEGMENTS = [AUTO_MODE_DIRECTIVE, BYPASS_POSTURE_DIRECTIVE];
|
|
29
20
|
function postureSegmentsOf(body) {
|
|
@@ -17,8 +17,13 @@ export interface AutoModeRules {
|
|
|
17
17
|
export interface BuildAutoModePromptOptions {
|
|
18
18
|
rules?: AutoModeRules;
|
|
19
19
|
/** Settings-plane deny rules (tool[/argument-pattern] strings) — rendered into the
|
|
20
|
-
* `<settings_deny_rules>` slot as the CC circumvention-catching paragraph. */
|
|
20
|
+
* `<settings_deny_rules>` slot as the CC circumvention-catching paragraph, after the engine's own lines. */
|
|
21
21
|
settingsDenyRules?: readonly string[];
|
|
22
|
+
/** The ENGINE's deny lines — its effective read-deny table as rules ({@link readDenyClassifierRules}).
|
|
23
|
+
* Rendered in the same paragraph, ahead of `settingsDenyRules`. A separate member (not pre-merged by the
|
|
24
|
+
* caller) because a persisted arming records the two halves separately: the deployment's half is compared
|
|
25
|
+
* against the redeeming deployment's own settings, the engine's half against the row's own table. */
|
|
26
|
+
derivedDenyRules?: readonly DerivedDenyRule[];
|
|
22
27
|
/** Extra session-context facts (e.g. the CC user-identity line) — appended as a
|
|
23
28
|
* `## Session Context` bullet block after the assembled document. */
|
|
24
29
|
sessionContext?: readonly string[];
|
|
@@ -28,6 +33,62 @@ export interface BuildAutoModePromptOptions {
|
|
|
28
33
|
* text may carry `$`). */
|
|
29
34
|
crossSessionMessagesRule?: string;
|
|
30
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* One engine deny line: a `Read(...)` rule in the engine's own path-rule grammar (`//` = the filesystem root,
|
|
38
|
+
* `*` within one segment, a whole-segment `**` across segments, and a rule reaches the path it names and
|
|
39
|
+
* everything under it — `parseRuleText` / `pathRuleReachOf`), plus whether ASCII letter case is ignored, which
|
|
40
|
+
* that grammar cannot spell and the paragraph states beside the rule.
|
|
41
|
+
*/
|
|
42
|
+
export interface DerivedDenyRule {
|
|
43
|
+
readonly rule: string;
|
|
44
|
+
readonly anyCase?: true;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The effective READ-deny table as the classifier's deny lines — one line per row, the row's reach in the
|
|
48
|
+
* engine's own `Read(...)` path-rule grammar.
|
|
49
|
+
*
|
|
50
|
+
* A read-deny row is a run of path SEGMENTS matched anywhere in a path — the entry itself and everything under
|
|
51
|
+
* it — and its `*` never crosses a `/` (read-deny.ts: `.ssh` refuses `~/.ssh/id_rsa` and `/srv/x/.ssh/config`
|
|
52
|
+
* alike; a `**` segment matches exactly one segment). The rule grammar reaches the path a pattern names and
|
|
53
|
+
* everything under it, so the line is `//**` + `/` + the row with every run of stars written `*` (one segment,
|
|
54
|
+
* as in the row): `.ssh` ⇒ `Read(//**\/.ssh)`, `**\/secrets/**` ⇒ `Read(//**\/*\/secrets/*)`, `*` (every
|
|
55
|
+
* path) ⇒ `Read(//**\/*)`. A row that names no segment at all (`""`, `/`) is not stated. A HOME-ANCHORED row
|
|
56
|
+
* (`~/<run>`, read-deny.ts `READ_DENY_HOME_WORD`) is the rule grammar's home word with the same reach:
|
|
57
|
+
* `~/.sema/settings.json` ⇒ `Read(~/.sema/settings.json)`.
|
|
58
|
+
*
|
|
59
|
+
* The text of a row can come from a task (a spawned task's author included) and it is spliced into a trusted
|
|
60
|
+
* prompt, so a row is stated as a rule and nothing else: every character the grammar or the prompt must not
|
|
61
|
+
* carry (see NOT_STATED) is written `*`, and an over-long pattern is cut and closed with `*` — both wider than
|
|
62
|
+
* the row, never narrower, and the result still parses as a rule. No row can start a line, leave its code
|
|
63
|
+
* span, form a tag or a second rule, or read as a sentence; the engine enforces the row as compiled either way.
|
|
64
|
+
*
|
|
65
|
+
* A row matched without regard to ASCII letter case (the deny set's default) carries `anyCase` when its stated
|
|
66
|
+
* pattern has an ASCII letter. Duplicates are folded AFTER this encoding, first seen.
|
|
67
|
+
*/
|
|
68
|
+
export declare function readDenyClassifierRules(entries: readonly {
|
|
69
|
+
readonly pattern: string;
|
|
70
|
+
readonly caseSensitive?: boolean;
|
|
71
|
+
}[], rules?: readonly {
|
|
72
|
+
readonly tool: string;
|
|
73
|
+
readonly pattern: string;
|
|
74
|
+
}[]): DerivedDenyRule[];
|
|
75
|
+
/**
|
|
76
|
+
* The `<settings_deny_rules>` paragraph for one arming, from the engine's facts — the effective deny table
|
|
77
|
+
* (`readDeny`: the compiled matcher's entries, built-in tiers + deployment + task additions; `readDenyRules`: its
|
|
78
|
+
* permission-rule rows, both faces) and the deployment's `settingsDenyRules` — exactly as {@link buildAutoModePrompt}
|
|
79
|
+
* splices it. Empty inputs ⇒ "".
|
|
80
|
+
*/
|
|
81
|
+
export declare function renderClassifierDenyRules(input: {
|
|
82
|
+
readonly readDeny?: readonly {
|
|
83
|
+
readonly pattern: string;
|
|
84
|
+
readonly caseSensitive?: boolean;
|
|
85
|
+
}[];
|
|
86
|
+
readonly readDenyRules?: readonly {
|
|
87
|
+
readonly tool: string;
|
|
88
|
+
readonly pattern: string;
|
|
89
|
+
}[];
|
|
90
|
+
readonly settingsDenyRules?: readonly string[];
|
|
91
|
+
}): string;
|
|
31
92
|
/**
|
|
32
93
|
* Assemble the classifier SYSTEM prompt (CC `KRg`, content-equivalent single string — CC splits the
|
|
33
94
|
* session-context tail into a second uncached block purely as a prompt-cache optimization).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS, AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES, AUTO_MODE_DEFAULTS_SENTINEL } from "./auto-mode-defaults.js";
|
|
2
2
|
import { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./auto-mode-prompt-assets.js";
|
|
3
3
|
import { cutEnginePrefix, cutEngineSegments, flattenableUserText } from "./untrusted-text.js";
|
|
4
|
+
import { readDenyPatternRun } from "../tools/fs/read-deny.js";
|
|
4
5
|
export { AUTO_MODE_DEFAULTS_SENTINEL } from "./auto-mode-defaults.js";
|
|
5
6
|
function mergeRuleSection(user, defaultsInner) {
|
|
6
7
|
if (!user?.length)
|
|
@@ -20,10 +21,47 @@ function mergeRuleSection(user, defaultsInner) {
|
|
|
20
21
|
}
|
|
21
22
|
return out.join("\n");
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const DENY_LINE_PATTERN_MAX = 512;
|
|
25
|
+
const NOT_STATED = /[\s\p{Z}\p{Cf}`<>(){}[\]?\\\u0000-\u001f\u007f-\u009f]/u;
|
|
26
|
+
const notStated = (ch) => NOT_STATED.test(ch) || NOT_STATED.test(ch.normalize("NFKC"));
|
|
27
|
+
export function readDenyClassifierRules(entries, rules = []) {
|
|
28
|
+
const out = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const push = (line) => {
|
|
31
|
+
const key = `${line.rule}\u0000${line.anyCase === true}`;
|
|
32
|
+
if (seen.has(key))
|
|
33
|
+
return;
|
|
34
|
+
seen.add(key);
|
|
35
|
+
out.push(line);
|
|
36
|
+
};
|
|
37
|
+
for (const { pattern, caseSensitive } of entries) {
|
|
38
|
+
const { homeAnchored, run } = readDenyPatternRun(pattern);
|
|
39
|
+
const segments = run.split("/").filter((seg) => seg.length > 0);
|
|
40
|
+
if (segments.length === 0)
|
|
41
|
+
continue;
|
|
42
|
+
const stated = statedPattern(segments.join("/"));
|
|
43
|
+
push({ rule: homeAnchored ? `Read(~/${stated})` : `Read(//**/${stated})`, ...(caseSensitive !== true && /[A-Za-z]/.test(stated) ? { anyCase: true } : {}) });
|
|
44
|
+
}
|
|
45
|
+
for (const { tool, pattern } of rules) {
|
|
46
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(tool) || pattern.length === 0)
|
|
47
|
+
continue;
|
|
48
|
+
push({ rule: `${tool}(${pattern.split("/").map((seg) => (seg === "**" ? seg : statedPattern(seg))).join("/")})` });
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function statedPattern(pattern) {
|
|
53
|
+
const chars = [...pattern].map((ch) => (notStated(ch) ? "*" : ch));
|
|
54
|
+
const cut = chars.length > DENY_LINE_PATTERN_MAX ? [...chars.slice(0, DENY_LINE_PATTERN_MAX), "*"] : chars;
|
|
55
|
+
return cut.join("").replace(/\*+/g, "*");
|
|
56
|
+
}
|
|
57
|
+
export function renderClassifierDenyRules(input) {
|
|
58
|
+
return renderSettingsDenyRules(readDenyClassifierRules(input.readDeny ?? [], input.readDenyRules ?? []), input.settingsDenyRules ?? []);
|
|
59
|
+
}
|
|
60
|
+
function renderSettingsDenyRules(derived, settings) {
|
|
61
|
+
const items = [...derived.map((d) => `\`${d.rule}\`${d.anyCase === true ? " (any ASCII letter case)" : ""}`), ...settings.map((r) => `\`${r}\``)];
|
|
62
|
+
if (items.length === 0)
|
|
25
63
|
return "";
|
|
26
|
-
return (`- User Deny Rules: The user has configured these permission deny rules: ${
|
|
64
|
+
return (`- User Deny Rules: The user has configured these permission deny rules: ${items.join(", ")}. Each rule names a tool and (optionally) an argument pattern that is already hard-blocked for that tool. ` +
|
|
27
65
|
"Block the action if it accomplishes the same effect via a different tool — e.g. using Bash with " +
|
|
28
66
|
"`python -c`, `sed -i`, `cat >`, heredocs, or similar to write or edit a file that an Edit/Write/MultiEdit deny rule covers, or otherwise routing around a deny rule by switching tools. The named tool itself is enforced separately; your job here is to catch circumvention.");
|
|
29
67
|
}
|
|
@@ -39,7 +77,7 @@ export function buildAutoModePrompt(options) {
|
|
|
39
77
|
for (const [key, re] of PAIRED_SECTIONS) {
|
|
40
78
|
out = out.replace(re, (_m, inner) => mergeRuleSection(options?.rules?.[key], inner));
|
|
41
79
|
}
|
|
42
|
-
out = out.replace("<settings_deny_rules>", () => renderSettingsDenyRules(options?.settingsDenyRules ?? []));
|
|
80
|
+
out = out.replace("<settings_deny_rules>", () => renderSettingsDenyRules(options?.derivedDenyRules ?? [], options?.settingsDenyRules ?? []));
|
|
43
81
|
const context = options?.sessionContext ?? [];
|
|
44
82
|
if (context.length > 0) {
|
|
45
83
|
out += `\n\n## Session Context\n\n${context.map((f) => `- ${f}`).join("\n")}`;
|
|
@@ -28,7 +28,8 @@ export interface AutoModeRebuildOptions {
|
|
|
28
28
|
* an affirmative claim, "this is the run's first gated action", that would simply be false. A provider
|
|
29
29
|
* that returns `[]` is DECLARING the transcript empty, which is a different statement from an engine
|
|
30
30
|
* that silently had none to show. A provider that throws fails the classification closed
|
|
31
|
-
* (`unavailable` ⇒ a
|
|
31
|
+
* (`unavailable` ⇒ on to a person where the station has an approval seat, refused with a sentence that says
|
|
32
|
+
* so where it has none), never into a blind verdict.
|
|
32
33
|
*/
|
|
33
34
|
transcript: () => readonly Message[] | Promise<readonly Message[]>;
|
|
34
35
|
}
|
|
@@ -11,6 +11,7 @@ export function rebuildAutoModeDecider(opts) {
|
|
|
11
11
|
const promptOptions = {
|
|
12
12
|
...(effective.rules !== undefined ? { rules: effective.rules } : {}),
|
|
13
13
|
...(effective.settingsDenyRules !== undefined ? { settingsDenyRules: effective.settingsDenyRules } : {}),
|
|
14
|
+
...(effective.derivedDenyRules !== undefined ? { derivedDenyRules: effective.derivedDenyRules } : {}),
|
|
14
15
|
...(effective.sessionContext !== undefined ? { sessionContext: effective.sessionContext } : {}),
|
|
15
16
|
...(effective.crossSessionMessagesRule === true ? { crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : {}),
|
|
16
17
|
};
|
package/dist/core/auto-mode.d.ts
CHANGED
|
@@ -51,6 +51,14 @@ export declare function isAutoModeClassifierFallback(v: unknown): v is AutoModeC
|
|
|
51
51
|
* `config.auto_mode_classifier_fallback_invalid`. A typo'd knob must not read as the default: `"non"` would
|
|
52
52
|
* keep paying for the rung it meant to turn off. */
|
|
53
53
|
export declare function autoModeClassifierFallbackOf(v: unknown): AutoModeClassifierFallback;
|
|
54
|
+
/** Whether a present budget value is one the knob takes: a finite number of milliseconds in [1, 2147483647]. */
|
|
55
|
+
export declare function isAutoModeTimeoutMs(v: unknown): v is number;
|
|
56
|
+
/** Read the per-level budget knob (`RunnerDeps.autoMode.timeoutMs` / `AutoModeDeciderOptions.timeoutMs`):
|
|
57
|
+
* absent ⇒ {@link AUTO_MODE_DEFAULT_TIMEOUT_MS}; a finite number in [1, 2147483647] ⇒ itself; ANYTHING ELSE
|
|
58
|
+
* (NaN, ±Infinity, `0`, a negative, a value a host timer would fire at once, a string, an object) ⇒ a loud
|
|
59
|
+
* refusal carrying `config.auto_mode_timeout_invalid`. There is no OFF spelling: the budget bounds how long a
|
|
60
|
+
* permission gate waits, and a gate with no bound is not a setting this knob offers. */
|
|
61
|
+
export declare function autoModeTimeoutMsOf(v: unknown): number;
|
|
54
62
|
/** The classifier's verdict over one pending `ask`. Every arm may carry the classification's model
|
|
55
63
|
* attribution ({@link AutoModeClassifierRound}), present iff the decider was wired with a candidate ladder. */
|
|
56
64
|
export type AutoModeVerdict = ({
|
|
@@ -157,6 +165,12 @@ export declare function rungDidNotRule(v: AutoModeVerdict): AutoModeFallbackCaus
|
|
|
157
165
|
* NOT move keeps the CC text byte for byte.
|
|
158
166
|
*/
|
|
159
167
|
export declare function classifierUnavailableDenyMessage(toolName: string, cause: AutoModeUnavailableCause, attribution?: AutoModeClassifierRound): string;
|
|
168
|
+
/**
|
|
169
|
+
* The text an ask carries to a person when the classifier could not rule on it and the leg has an approval seat
|
|
170
|
+
* (the gate's classifier station and the inherited station): the ask's own text first — the card still says what
|
|
171
|
+
* is being asked — then why auto mode is asking at all. One writer for both stations.
|
|
172
|
+
*/
|
|
173
|
+
export declare function classifierUnavailableAskMessage(askText: string, cause: AutoModeUnavailableCause): string;
|
|
160
174
|
/**
|
|
161
175
|
* The model-facing deny text for a `parse_error` verdict — CC 2.1.259 `i7t` stem ("Auto mode could not evaluate
|
|
162
176
|
* this action and is blocking it for safety"; 2.1.250 `Ure`): the classifier answered, but not with a verdict,
|
|
@@ -256,7 +270,10 @@ export interface AutoModeDeciderOptions<T = void> {
|
|
|
256
270
|
/** Hard cap on one classification round-trip — one LADDER RUNG's DISPATCH, not the whole classification
|
|
257
271
|
* (see {@link candidates}) — and, separately, the one {@link assemble} call that precedes the walk (ONE
|
|
258
272
|
* number: a second knob would be a second answer to "how long may a classification make the gate wait").
|
|
259
|
-
*
|
|
273
|
+
* Omitted ⇒ {@link AUTO_MODE_DEFAULT_TIMEOUT_MS}. A present value that is not a finite number of at least 1
|
|
274
|
+
* and at most 2147483647 (a host timer fires at once past that) is REFUSED at construction
|
|
275
|
+
* (`config.auto_mode_timeout_invalid`, {@link autoModeTimeoutMsOf}) — never clamped, never read as the
|
|
276
|
+
* default, and there is no OFF: a permission gate always has a bound. */
|
|
260
277
|
timeoutMs?: number;
|
|
261
278
|
/**
|
|
262
279
|
* Fired once per `decide` call with the verdict the gate is about to act on and the wall time it waited
|
|
@@ -288,6 +305,10 @@ export interface AutoModeClassified {
|
|
|
288
305
|
export interface AutoModeDecider {
|
|
289
306
|
/** Never rejects. Any internal failure surfaces as `unavailable`/`parse_error` (fail-closed). */
|
|
290
307
|
decide(input: AutoModeClassifyInput, signal?: AbortSignal): Promise<AutoModeVerdict>;
|
|
308
|
+
/** The per-level budget this decider holds each rung's dispatch (and the assembly) to, in ms — the resolved
|
|
309
|
+
* {@link AutoModeDeciderOptions.timeoutMs}; what a notice about a timed-out classification names. Absent on a decider a
|
|
310
|
+
* host implements by hand. */
|
|
311
|
+
readonly timeoutMs?: number;
|
|
291
312
|
}
|
|
292
313
|
/** The per-run decider: timeout + fail-closed error mapping, nothing carried between rounds. One instance per
|
|
293
314
|
* run (it binds the run's model leg and assembled prompt); a round that failed says nothing about the next,
|
|
@@ -413,8 +434,16 @@ export interface AutoModeDenialTracker {
|
|
|
413
434
|
windowArmedAt?: number;
|
|
414
435
|
};
|
|
415
436
|
}
|
|
416
|
-
|
|
417
|
-
|
|
437
|
+
export declare function autoModeDenialLimitOf(v: unknown): Required<AutoModeDenialLimitOptions>;
|
|
438
|
+
/** The deployment face's three value knobs, read by their one readers — every leg's prepare runs it while
|
|
439
|
+
* `RunnerDeps.autoMode` is present, whatever mode the leg starts in, so a bad knob refuses the run before a steer or a
|
|
440
|
+
* parent's live ceiling can reach an arming that would have refused it. */
|
|
441
|
+
export declare function screenAutoModeKnobs(face: {
|
|
442
|
+
readonly timeoutMs?: unknown;
|
|
443
|
+
readonly classifierFallback?: unknown;
|
|
444
|
+
readonly denialLimit?: unknown;
|
|
445
|
+
}): void;
|
|
446
|
+
/** Build the per-run tracker over {@link autoModeDenialLimitOf}'s bounds (a bad knob refuses there). */
|
|
418
447
|
export declare function createAutoModeDenialTracker(opts?: AutoModeDenialLimitOptions): AutoModeDenialTracker;
|
|
419
448
|
/** The fallback ask's text — CC's sentence family, single-sourced for every mint site (the main gate
|
|
420
449
|
* and the inherited-lane arms): `Classifier denial limit exceeded, falling back to prompting: <limit
|
package/dist/core/auto-mode.js
CHANGED
|
@@ -31,6 +31,20 @@ export function autoModeClassifierFallbackOf(v) {
|
|
|
31
31
|
e.code = "config.auto_mode_classifier_fallback_invalid";
|
|
32
32
|
throw e;
|
|
33
33
|
}
|
|
34
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
35
|
+
export function isAutoModeTimeoutMs(v) {
|
|
36
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 1 && v <= MAX_TIMER_DELAY_MS;
|
|
37
|
+
}
|
|
38
|
+
export function autoModeTimeoutMsOf(v) {
|
|
39
|
+
if (v === undefined)
|
|
40
|
+
return AUTO_MODE_DEFAULT_TIMEOUT_MS;
|
|
41
|
+
if (isAutoModeTimeoutMs(v))
|
|
42
|
+
return v;
|
|
43
|
+
const e = new Error(`RunnerDeps.autoMode.timeoutMs must be a number of milliseconds between 1 and ${MAX_TIMER_DELAY_MS} ` +
|
|
44
|
+
`(got ${describeRejectedValue(v)}) — omit it for the default ${AUTO_MODE_DEFAULT_TIMEOUT_MS}`);
|
|
45
|
+
e.code = "config.auto_mode_timeout_invalid";
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
34
48
|
export const AUTO_MODE_ROUND_NOT_ASKED_CODE = "auto_mode.round_not_asked";
|
|
35
49
|
export function autoModeRoundNotAsked(reason) {
|
|
36
50
|
let detail;
|
|
@@ -70,6 +84,10 @@ export function classifierUnavailableDenyMessage(toolName, cause, attribution) {
|
|
|
70
84
|
"If it keeps failing, continue with other tasks that don't require this action and come back to it later. " +
|
|
71
85
|
"Note: reading files, searching code, and other read-only operations do not require the classifier and can still be used.");
|
|
72
86
|
}
|
|
87
|
+
export function classifierUnavailableAskMessage(askText, cause) {
|
|
88
|
+
const parenthetical = cause === "timeout" ? " (timed out)" : "";
|
|
89
|
+
return `${askText}\n\nThe auto-mode classifier is temporarily unavailable${parenthetical}, so auto mode could not judge this call; it needs an approval instead of the classifier's verdict.`;
|
|
90
|
+
}
|
|
73
91
|
export function classifierParseFailureDenyMessage(attribution) {
|
|
74
92
|
const stem = "Auto mode could not evaluate this action and is blocking it for safety";
|
|
75
93
|
return attribution === undefined ? `${stem} — the classifier's reply carried no verdict.` : `${stem} — the classifier model "${attribution.modelUsed}" replied without a verdict.`;
|
|
@@ -153,7 +171,7 @@ export function parseAutoModeResponse(text, options) {
|
|
|
153
171
|
return { kind: "block", category, reason };
|
|
154
172
|
}
|
|
155
173
|
export function createAutoModeDecider(opts) {
|
|
156
|
-
const timeoutMs = opts.timeoutMs
|
|
174
|
+
const timeoutMs = autoModeTimeoutMsOf(opts.timeoutMs);
|
|
157
175
|
if (opts.candidates !== undefined) {
|
|
158
176
|
if (opts.candidates.length === 0) {
|
|
159
177
|
throw new Error("AutoModeDeciderOptions.candidates must name at least one model — omit the member for the leg's own model, never an empty ladder");
|
|
@@ -183,7 +201,7 @@ export function createAutoModeDecider(opts) {
|
|
|
183
201
|
}
|
|
184
202
|
return verdict;
|
|
185
203
|
};
|
|
186
|
-
return { decide };
|
|
204
|
+
return { decide, timeoutMs };
|
|
187
205
|
async function decideUnreported(input, round, signal) {
|
|
188
206
|
const causes = [];
|
|
189
207
|
let verdict = { kind: "unavailable", cause: "error" };
|
|
@@ -243,42 +261,46 @@ export function createAutoModeDecider(opts) {
|
|
|
243
261
|
}
|
|
244
262
|
}
|
|
245
263
|
async function withDeadline(capMs, signal, call) {
|
|
264
|
+
if (signal?.aborted === true)
|
|
265
|
+
throw new Error("the classification was cancelled before it was dispatched");
|
|
246
266
|
let timer;
|
|
267
|
+
let onOuterAbort;
|
|
247
268
|
const inner = new AbortController();
|
|
248
|
-
const onOuterAbort = () => inner.abort();
|
|
249
|
-
if (signal !== undefined) {
|
|
250
|
-
if (signal.aborted)
|
|
251
|
-
inner.abort();
|
|
252
|
-
else
|
|
253
|
-
signal.addEventListener("abort", onOuterAbort, { once: true });
|
|
254
|
-
}
|
|
255
269
|
try {
|
|
256
270
|
return await new Promise((resolve, reject) => {
|
|
257
271
|
let settled = false;
|
|
272
|
+
const settle = (end) => {
|
|
273
|
+
if (settled)
|
|
274
|
+
return;
|
|
275
|
+
settled = true;
|
|
276
|
+
end();
|
|
277
|
+
};
|
|
278
|
+
onOuterAbort = () => {
|
|
279
|
+
inner.abort();
|
|
280
|
+
settle(() => reject(new Error("the classification was cancelled")));
|
|
281
|
+
};
|
|
282
|
+
signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
258
283
|
timer = setTimeout(() => {
|
|
259
|
-
|
|
260
|
-
settled = true;
|
|
284
|
+
settle(() => {
|
|
261
285
|
inner.abort();
|
|
262
286
|
reject(new AutoModeTimeout());
|
|
263
|
-
}
|
|
287
|
+
});
|
|
264
288
|
}, capMs);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
});
|
|
289
|
+
let pending;
|
|
290
|
+
try {
|
|
291
|
+
pending = call(inner.signal);
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
settle(() => reject(e));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
pending.then((v) => settle(() => resolve(v)), (e) => settle(() => reject(e)));
|
|
276
298
|
});
|
|
277
299
|
}
|
|
278
300
|
finally {
|
|
279
301
|
if (timer !== undefined)
|
|
280
302
|
clearTimeout(timer);
|
|
281
|
-
if (signal !== undefined)
|
|
303
|
+
if (signal !== undefined && onOuterAbort !== undefined)
|
|
282
304
|
signal.removeEventListener("abort", onOuterAbort);
|
|
283
305
|
}
|
|
284
306
|
}
|
|
@@ -305,25 +327,42 @@ export function unarmedWindow(fallback) {
|
|
|
305
327
|
function countsOf(fallback) {
|
|
306
328
|
return { consecutive: fallback.consecutive, total: fallback.total, limit: fallback.limit };
|
|
307
329
|
}
|
|
308
|
-
const
|
|
309
|
-
export function
|
|
310
|
-
const
|
|
311
|
-
|
|
330
|
+
const DENIAL_LIMIT_MEMBERS = new Set(["maxConsecutive", "maxTotal", "autoDenyAfterMs"]);
|
|
331
|
+
export function autoModeDenialLimitOf(v) {
|
|
332
|
+
const refuse = (what) => {
|
|
333
|
+
const e = new Error(`RunnerDeps.autoMode.denialLimit${what}`);
|
|
334
|
+
e.code = "config.auto_mode_denial_limit_invalid";
|
|
335
|
+
throw e;
|
|
336
|
+
};
|
|
337
|
+
if (v === undefined)
|
|
338
|
+
return { ...AUTO_MODE_DENIAL_LIMIT_DEFAULTS, autoDenyAfterMs: AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS };
|
|
339
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
340
|
+
return refuse(` must be a record of bounds (got ${describeRejectedValue(v)}) — omit it for the defaults`);
|
|
341
|
+
const opts = v;
|
|
342
|
+
const stray = Object.keys(opts).find((k) => !DENIAL_LIMIT_MEMBERS.has(k));
|
|
343
|
+
if (stray !== undefined)
|
|
344
|
+
return refuse(` has no member ${JSON.stringify(stray)} — its members are maxConsecutive, maxTotal and autoDenyAfterMs`);
|
|
345
|
+
const bound = (name, dflt) => {
|
|
346
|
+
const b = opts[name];
|
|
347
|
+
if (b === undefined)
|
|
312
348
|
return dflt;
|
|
313
|
-
|
|
314
|
-
throw new Error(`RunnerDeps.autoMode.denialLimit.${name} must be a positive integer (got ${JSON.stringify(v)}) — omit it for the default ${dflt}`);
|
|
315
|
-
}
|
|
316
|
-
return v;
|
|
349
|
+
return typeof b === "number" && Number.isInteger(b) && b >= 1 ? b : refuse(`.${name} must be a positive integer (got ${describeRejectedValue(b)}) — omit it for the default ${dflt}`);
|
|
317
350
|
};
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
351
|
+
const w = opts["autoDenyAfterMs"];
|
|
352
|
+
const autoDenyAfterMs = w === undefined
|
|
353
|
+
? AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS
|
|
354
|
+
: typeof w === "number" && Number.isInteger(w) && w >= 0 && w <= MAX_TIMER_DELAY_MS
|
|
355
|
+
? w
|
|
356
|
+
: refuse(`.autoDenyAfterMs must be an integer between 0 and ${MAX_TIMER_DELAY_MS} (got ${describeRejectedValue(w)}) — 0 disarms the window; omit it for the default ${AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS}`);
|
|
357
|
+
return { maxConsecutive: bound("maxConsecutive", AUTO_MODE_DENIAL_LIMIT_DEFAULTS.maxConsecutive), maxTotal: bound("maxTotal", AUTO_MODE_DENIAL_LIMIT_DEFAULTS.maxTotal), autoDenyAfterMs };
|
|
358
|
+
}
|
|
359
|
+
export function screenAutoModeKnobs(face) {
|
|
360
|
+
autoModeTimeoutMsOf(face.timeoutMs);
|
|
361
|
+
autoModeClassifierFallbackOf(face.classifierFallback);
|
|
362
|
+
autoModeDenialLimitOf(face.denialLimit);
|
|
363
|
+
}
|
|
364
|
+
export function createAutoModeDenialTracker(opts) {
|
|
365
|
+
const { maxConsecutive, maxTotal, autoDenyAfterMs } = autoModeDenialLimitOf(opts);
|
|
327
366
|
let consecutive = 0;
|
|
328
367
|
let total = 0;
|
|
329
368
|
let timedFallbackShown = false;
|