@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
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
2
|
+
import { READ_RULE_TOOL, eligiblePersisted, isUsablePathBase, ruleBasesNeeded } from "../permission-rule-model.js";
|
|
3
|
+
import { isAbsolutePathForm, pathFamilyOf } from "../../tools/fs/safety.js";
|
|
4
|
+
import { PATH_DENY_RULE_FACES, pathRuleDenyRowOf } from "../permission-rules.js";
|
|
5
|
+
import { inlineUntrusted } from "../untrusted-text.js";
|
|
6
|
+
import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
7
|
+
import { thrownText } from "../thrown-value.js";
|
|
8
|
+
import { emitTrace } from "../trace.js";
|
|
9
|
+
import { deliverEngineNotice } from "../engine-notice.js";
|
|
10
|
+
export async function persistedPathRowsOf(cfg) {
|
|
11
|
+
const none = { readDirectories: [], denyRows: [] };
|
|
12
|
+
const provider = cfg.provider;
|
|
13
|
+
if (provider === undefined || !readsPersistedPathRows(provider, cfg.principal, cfg.localOwnerDeclared))
|
|
14
|
+
return none;
|
|
15
|
+
let view;
|
|
16
|
+
try {
|
|
17
|
+
view = await withGateTimeout(ownerStoreOf(provider, cfg.principal, cfg.localOwnerDeclared).effective({ sessionId: cfg.sessionId }), DEFAULT_GATE_AWAIT_TIMEOUT_MS, cfg.signal, "persistedPathRows");
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
return gateWaitFailureOf(err) === "aborted" ? none : skipped(cfg, thrownText(err));
|
|
21
|
+
}
|
|
22
|
+
if (view.unreadable !== undefined)
|
|
23
|
+
skipped(cfg, view.unreadable, view.unreadableSources);
|
|
24
|
+
const call = { tool: READ_RULE_TOOL, cwd: cfg.root, sessionId: cfg.sessionId };
|
|
25
|
+
const bases = { root: cfg.root, ...(cfg.home !== undefined && isAbsolutePathForm(cfg.home) ? { home: cfg.home } : {}) };
|
|
26
|
+
const family = pathFamilyOf(bases);
|
|
27
|
+
const readDirectories = [];
|
|
28
|
+
const denyRows = [];
|
|
29
|
+
for (const rule of view.rules) {
|
|
30
|
+
if (rule.status !== "live")
|
|
31
|
+
continue;
|
|
32
|
+
if (rule.behavior === "allow" && rule.tool === READ_RULE_TOOL && rule.match === "subpath" && eligiblePersisted(rule, call, "allow")) {
|
|
33
|
+
if (!readDirectories.includes(rule.command))
|
|
34
|
+
readDirectories.push(rule.command);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const faces = PATH_DENY_RULE_FACES[rule.tool];
|
|
38
|
+
if (rule.behavior !== "deny" || faces === undefined || (rule.match !== "path" && rule.match !== "subpath") || !eligiblePersisted(rule, { ...call, tool: rule.tool }, "deny"))
|
|
39
|
+
continue;
|
|
40
|
+
const missing = ruleBasesNeeded(rule).filter((b) => !isUsablePathBase(b === "cwd" ? bases.root : bases[b], family));
|
|
41
|
+
if (missing.length > 0) {
|
|
42
|
+
cfg.onDisclosure(`the persisted deny rule ${inlineUntrusted(rule.rule, 200)} needs a ${missing.join(" / ")} base this task does not declare — the shell's file commands and the search tools do not judge it on this leg`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
denyRows.push(pathRuleDenyRowOf({ parsed: rule, ruleText: rule.rule, faces }, bases));
|
|
46
|
+
}
|
|
47
|
+
return { readDirectories, denyRows };
|
|
48
|
+
}
|
|
49
|
+
function skipped(cfg, why, sources) {
|
|
50
|
+
cfg.onDisclosure(`the persisted path rules could not be read at prepare, so this run's read directories and read / write deny tables take none of ${sources !== undefined ? `the ${sourcesWord(sources)} rows` : "their rows"} (the next prepare reads again): ${why}`);
|
|
51
|
+
cfg.onUnread("prepare", sources);
|
|
52
|
+
return { readDirectories: [], denyRows: [] };
|
|
53
|
+
}
|
|
54
|
+
function sourcesWord(sources) {
|
|
55
|
+
return sources.length <= 1 ? sources.join("") : `${sources.slice(0, -1).join(", ")} and ${sources[sources.length - 1]}`;
|
|
56
|
+
}
|
|
57
|
+
const SKIPPED_STORE_SENTENCE = {
|
|
58
|
+
prepare: (which) => `${which} could not be read when this run started, so this run's file and shell checks skip their path rows (Read / Edit deny rows and Read directory rows) — a deny row among them protects nothing on this run. Each call's own rule check reads the rules again (a call whose own read fails too is judged without them), and the next run reads the path rows again.`,
|
|
59
|
+
call: (which) => `${which} could not be read for a call on this run, so that call's own rule check skipped them: none of their allow, ask or deny rows took part in it, and a deny row among them protected nothing there (path rows the run took when it started still apply to its file and shell checks). Every later call reads them again, and they apply again as soon as a read succeeds.`,
|
|
60
|
+
};
|
|
61
|
+
export function ruleStoreUnreadableNotice(onNotice, ids) {
|
|
62
|
+
let sent = false;
|
|
63
|
+
return (at, sources) => {
|
|
64
|
+
if (sent)
|
|
65
|
+
return;
|
|
66
|
+
sent = true;
|
|
67
|
+
const which = sources !== undefined && sources.length > 0 ? `your persisted permission rules from the ${sourcesWord(sources)} sources` : "your persisted permission rules";
|
|
68
|
+
deliverEngineNotice(onNotice, { code: "permission.rule_store_unavailable", message: SKIPPED_STORE_SENTENCE[at](which), detail: { at, sessionId: ids.sessionId, runId: ids.runId } });
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function readsPersistedPathRows(provider, principal, localOwnerDeclared) {
|
|
72
|
+
return provider.partitions.durable && ((principal !== undefined && principal !== "") || localOwnerDeclared);
|
|
73
|
+
}
|
|
74
|
+
export function persistedPathRowsOfTask(t) {
|
|
75
|
+
const provider = t.deps.permissionRuleStore;
|
|
76
|
+
if (!t.handsEnabled || provider === undefined || !readsPersistedPathRows(provider, t.principal, t.deps.localOwnerRules === true))
|
|
77
|
+
return undefined;
|
|
78
|
+
return persistedPathRowsOf({
|
|
79
|
+
provider,
|
|
80
|
+
localOwnerDeclared: t.deps.localOwnerRules === true,
|
|
81
|
+
principal: t.principal,
|
|
82
|
+
sessionId: t.sessionId,
|
|
83
|
+
root: t.root,
|
|
84
|
+
home: t.home,
|
|
85
|
+
onDisclosure: (message) => emitTrace(t.deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId: t.taskId, message, ts: Date.now() })),
|
|
86
|
+
onUnread: t.onUnread,
|
|
87
|
+
signal: AbortSignal.any([...t.signals]),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* publishes the declared store into the run's {@link ArtifactStoreRef} for that second point.
|
|
13
13
|
* Synchronous: the stretch has no await (the file read is a closure the tool calls later).
|
|
14
14
|
*/
|
|
15
|
+
import type { PermissionModeController } from "../mode-policy.js";
|
|
15
16
|
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
16
17
|
import type { OnAsk } from "../tool-policy.js";
|
|
17
18
|
import { type ArtifactPlane } from "../artifact-host.js";
|
|
@@ -22,7 +23,7 @@ import { type ArtifactStoreRef } from "./read-root-grant.js";
|
|
|
22
23
|
import type { Prepared } from "./contracts.js";
|
|
23
24
|
export interface PrepareArtifactInput {
|
|
24
25
|
/** borrowed-readonly — the deployment's hosting seat and the notice sink the refusal arm announces on. */
|
|
25
|
-
deps: Pick<RunnerDeps, "artifactHost" | "onNotice">;
|
|
26
|
+
deps: Pick<RunnerDeps, "artifactHost" | "onNotice" | "tracer">;
|
|
26
27
|
/** borrowed-readonly — the engine-minted run id: the host's `onMount` key and every call context's `runId`. */
|
|
27
28
|
runId: string;
|
|
28
29
|
/** borrowed-readonly — the host task id, the call context's audit attribution. */
|
|
@@ -34,6 +35,9 @@ export interface PrepareArtifactInput {
|
|
|
34
35
|
/** borrowed-readonly — the run's effective approver (`spec.onAsk ?? deps.onAsk`, the seat the tool gate resolves at):
|
|
35
36
|
* the tool's shared-read consent gate rides it as a mount dep (a core mount's execute ctx is minimal). */
|
|
36
37
|
onAsk: OnAsk | undefined;
|
|
38
|
+
/** borrowed-readonly — the leg's live permission-mode seat; the tool's shared-read consent gate arbitrates at it before
|
|
39
|
+
* the approver is consulted (a released read is delivered, undisclosed to nobody: the gate records it). */
|
|
40
|
+
permissionMode: PermissionModeController;
|
|
37
41
|
/** borrowed-readonly — the run's resolved env: the byte source of a published local file. */
|
|
38
42
|
executionEnv: ExecutionEnv;
|
|
39
43
|
/** borrowed-readonly — the canonical task root the fs fence contains reads to; undefined ⇒ no local-file publish. */
|
|
@@ -6,11 +6,12 @@ import { defineTool } from "../tools.js";
|
|
|
6
6
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
7
7
|
import { physicalPathOfExisting } from "../physical-path.js";
|
|
8
8
|
import { resolveKey, violationDetails, violationText } from "../../tools/fs/safety.js";
|
|
9
|
-
import { rootAdmittingArtifactStore } from "./read-root-grant.js";
|
|
9
|
+
import { rootAdmittingArtifactStore, withdrawStandingGrantsAdmitting } from "./read-root-grant.js";
|
|
10
|
+
import { emitTrace } from "../trace.js";
|
|
10
11
|
import { createArtifactTool } from "../../tools/artifact/artifact-tool.js";
|
|
11
12
|
import { thrownText } from "../thrown-value.js";
|
|
12
13
|
export function prepareArtifact(input) {
|
|
13
|
-
const { deps, runId, taskId, sessionId, agentName, onAsk, executionEnv, rootCanonical, additionalRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames, artifactStoreRef } = input;
|
|
14
|
+
const { deps, runId, taskId, sessionId, agentName, onAsk, permissionMode, executionEnv, rootCanonical, additionalRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames, artifactStoreRef } = input;
|
|
14
15
|
const host = deps.artifactHost;
|
|
15
16
|
if (host === undefined)
|
|
16
17
|
return { artifactPlane: undefined };
|
|
@@ -29,6 +30,11 @@ export function prepareArtifact(input) {
|
|
|
29
30
|
const storePhysical = storageDir !== undefined && rootCanonical !== undefined ? physicalPathOfExisting(storageDir) : undefined;
|
|
30
31
|
if (artifactStoreRef !== undefined)
|
|
31
32
|
artifactStoreRef.current = storePhysical;
|
|
33
|
+
if (artifactStoreRef !== undefined && storePhysical !== undefined) {
|
|
34
|
+
for (const entry of withdrawStandingGrantsAdmitting(storePhysical, artifactStoreRef)) {
|
|
35
|
+
emitTrace(deps.tracer, () => ({ kind: "config.additional_directory_skipped", version: 1, taskId, entry, field: "persistedReadRule", reason: `admits_artifact_store: the local artifact store ${storageDir} lies inside it or it inside the store — the Artifact tool's ledger and stored files stay outside every root the file tools reach`, ts: Date.now() }));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
32
38
|
if (storePhysical !== undefined && rootCanonical !== undefined) {
|
|
33
39
|
const overlapping = rootAdmittingArtifactStore(storePhysical, [rootCanonical, ...additionalRootsCanonical()]);
|
|
34
40
|
if (overlapping !== undefined)
|
|
@@ -60,7 +66,7 @@ export function prepareArtifact(input) {
|
|
|
60
66
|
return { ok: false, reason: "too_large", size: r.value.byteLength, limit: maxBytes, judgedOn: "bytes" };
|
|
61
67
|
return { ok: true, key: rk.key, bytes: r.value };
|
|
62
68
|
};
|
|
63
|
-
const artifactSpec = createArtifactTool({ host, capabilities: verdict.capabilities, runId, taskId, sessionId, ...(agentName !== undefined ? { agentName } : {}), readLocalFile, ...(onAsk !== undefined ? { onAsk } : {}) });
|
|
69
|
+
const artifactSpec = createArtifactTool({ host, capabilities: verdict.capabilities, runId, taskId, sessionId, ...(agentName !== undefined ? { agentName } : {}), readLocalFile, ...(onAsk !== undefined ? { onAsk } : {}), permissionMode: () => permissionMode.current, ...(deps.onNotice !== undefined ? { onNotice: deps.onNotice } : {}) });
|
|
64
70
|
roster.mount(firstPartyOffload(defineTool(artifactSpec)), { source: "builtin", mountedBy: "optional" });
|
|
65
71
|
envHandToolNames.add(ARTIFACT_TOOL_NAME);
|
|
66
72
|
return { artifactPlane: verdict.capabilities.plane };
|
|
@@ -77,15 +77,17 @@ export interface PrepareAskLaneInput {
|
|
|
77
77
|
isDelegatedChild?: true;
|
|
78
78
|
};
|
|
79
79
|
/** borrowed-readonly — the policy-chain phase's per-tool risk-axes projection (`riskAxes` on the ask). */
|
|
80
|
-
riskAxesOf: (
|
|
80
|
+
riskAxesOf: (call: Pick<ToolCallRequest, "toolName" | "face">) => {
|
|
81
81
|
riskAxes?: {
|
|
82
82
|
irreversible?: boolean;
|
|
83
83
|
egress?: boolean;
|
|
84
84
|
};
|
|
85
85
|
};
|
|
86
|
-
/** borrowed-mutable — the auto-mode denial tracker
|
|
87
|
-
* window on it iff the seat about to be called is live (the tracker's one writer). */
|
|
88
|
-
autoModeDenialTracking:
|
|
86
|
+
/** borrowed-mutable — the auto-mode denial tracker's live seat (undefined until an arming). Writer here: the carry mint
|
|
87
|
+
* arms the auto-deny window on it iff the seat about to be called is live (the tracker's one writer). */
|
|
88
|
+
autoModeDenialTracking: {
|
|
89
|
+
readonly current: AutoModeDenialTracker | undefined;
|
|
90
|
+
};
|
|
89
91
|
/** borrowed-readonly — the REBOUND spec; only `taskId` is read (the late-settlement observer's tag). */
|
|
90
92
|
spec: Pick<TaskSpec, "taskId">;
|
|
91
93
|
/** borrowed-readonly — the deployment seats this lane reports on, as a Pick over the SAME `deps` object (receiver
|
|
@@ -96,14 +96,14 @@ export function prepareAskLane(input) {
|
|
|
96
96
|
const p = approvalPreviewOf(req.toolName, req.args);
|
|
97
97
|
return { ...(p.preview !== undefined ? { preview: p.preview } : {}), ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}) };
|
|
98
98
|
})(),
|
|
99
|
-
...ruleOffersOf(req
|
|
99
|
+
...ruleOffersOf(req, req.args, decision.action === "ask" ? decision : undefined),
|
|
100
100
|
message: decision.message ?? `approval required for "${req.toolName}"`,
|
|
101
101
|
...askSourceIdentity(),
|
|
102
|
-
...riskAxesOf(req
|
|
102
|
+
...riskAxesOf(req),
|
|
103
103
|
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
104
104
|
...(decision.action === "ask" ? askMandateMember(decision) : {}),
|
|
105
105
|
...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
|
|
106
|
-
...(decision.action === "ask" ? gateAskCarry(decision, isLiveApproverSeat(onAsk), autoModeDenialTracking) : {}),
|
|
106
|
+
...(decision.action === "ask" ? gateAskCarry(decision, isLiveApproverSeat(onAsk), autoModeDenialTracking.current) : {}),
|
|
107
107
|
...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
|
|
108
108
|
...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
|
|
109
109
|
...(decision.action === "ask" && decision.probeReadRootCandidate !== undefined ? { readRootCandidate: decision.probeReadRootCandidate } : {}),
|
|
@@ -24,6 +24,7 @@ import { forkGovernanceDenial } from "../../agents/subagent.js";
|
|
|
24
24
|
import type { PromptEpochArtifact } from "../../prompt-assembly/artifact.js";
|
|
25
25
|
import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
26
26
|
import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
27
|
+
import type { PermissionModeController } from "../mode-policy.js";
|
|
27
28
|
import { type AutoModeDecider, type AutoModeDenialTracker } from "../auto-mode.js";
|
|
28
29
|
import { type CheckpointSeat } from "../checkpoint-seat.js";
|
|
29
30
|
import { type ComplianceCapability } from "../compliance.js";
|
|
@@ -109,10 +110,23 @@ export interface PrepareCapsAndWorkflowInput {
|
|
|
109
110
|
requestReview: (opts?: {
|
|
110
111
|
reason?: string;
|
|
111
112
|
}) => void;
|
|
112
|
-
/** borrowed-readonly — the plan-mode primitive the EnterPlanMode tool calls
|
|
113
|
-
enterPlanMode: () => void
|
|
114
|
-
/** borrowed-readonly — the
|
|
115
|
-
|
|
113
|
+
/** borrowed-readonly — the plan-mode primitive the EnterPlanMode tool calls: a steer of the leg's mode seat into `plan`. */
|
|
114
|
+
enterPlanMode: () => Promise<void>;
|
|
115
|
+
/** borrowed-readonly — the leg's live permission-mode seat: the arming's first arm is `declared === "auto"`, and the
|
|
116
|
+
* arming facts (the deny bits, the face, the re-entrant arm) are bound onto it here. */
|
|
117
|
+
permissionMode: PermissionModeController;
|
|
118
|
+
/** borrowed-readonly — LATE-BOUND: the effective read-deny table the hands phase compiles (the rows the classifier's deny
|
|
119
|
+
* paragraph renders); read at ARMING, which the driver runs after that phase — never at this phase's own call. */
|
|
120
|
+
readDenyEntries: () => {
|
|
121
|
+
readonly entries: ReadonlyArray<{
|
|
122
|
+
readonly pattern: string;
|
|
123
|
+
readonly caseSensitive?: boolean;
|
|
124
|
+
}>;
|
|
125
|
+
readonly ruleRows: ReadonlyArray<{
|
|
126
|
+
readonly tool: string;
|
|
127
|
+
readonly pattern: string;
|
|
128
|
+
}>;
|
|
129
|
+
};
|
|
116
130
|
/** borrowed-readonly — the peer lane's mount verdict (the classifier's cross-session rule fills only while mounted). */
|
|
117
131
|
peerLaneActive: boolean;
|
|
118
132
|
/** borrowed-readonly — whether the BUILT-IN SendMessage is on this face (the lane rule's second term). */
|
|
@@ -153,15 +167,26 @@ export interface PrepareCapsAndWorkflowResult {
|
|
|
153
167
|
agentForkDenial: ReturnType<typeof forkGovernanceDenial>;
|
|
154
168
|
/** owned — the observers grant (`allowObservers === true`, explicit opt-in). */
|
|
155
169
|
observersActive: boolean;
|
|
156
|
-
/** owned — the arming outcome as a closed reason (the manifest's auto-mode face)
|
|
157
|
-
|
|
158
|
-
autoModeArmReason: AutoModeArmFact;
|
|
159
|
-
/** owned — the armed classifier
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
/** owned — the arming outcome as a closed reason (the manifest's auto-mode face), read LIVE: a leg steered into `auto`
|
|
171
|
+
* after prepare arms then, and the fact follows. */
|
|
172
|
+
autoModeArmReason: () => AutoModeArmFact;
|
|
173
|
+
/** owned — the armed classifier's live seat (`current` undefined until an arming succeeds — at prepare for an `auto`
|
|
174
|
+
* leg, or at a steer into `auto`); every consumer reads `current` at its own call. */
|
|
175
|
+
autoModeDeciderRef: {
|
|
176
|
+
readonly current: AutoModeDecider | undefined;
|
|
177
|
+
};
|
|
178
|
+
/** owned — the denial tracker built beside the decider (same face, same arming), same live seat. */
|
|
179
|
+
autoModeDenialTrackingRef: {
|
|
180
|
+
readonly current: AutoModeDenialTracker | undefined;
|
|
181
|
+
};
|
|
182
|
+
/** owned — the serializable arming recipe (persistArming opt-in only), same live seat. */
|
|
183
|
+
autoModeArmingRef: {
|
|
184
|
+
readonly current: AutoModeArmingRecipe | undefined;
|
|
185
|
+
};
|
|
186
|
+
/** owned — arm the classifier for THIS leg (idempotent; a no-op unless the effective mode is `auto` and the face and the
|
|
187
|
+
* deny bit admit it). The driver calls it once after the hands phase (the deny table must exist first); the mode seat
|
|
188
|
+
* calls it again on a steer into `auto`. */
|
|
189
|
+
armAutoMode: () => Promise<void>;
|
|
165
190
|
/** owned — self-orchestration is ACTIVE for this leg (deployment ready ∧ not entitlement-denied); drives the prompt injection. */
|
|
166
191
|
selfOrchestrationActive: boolean;
|
|
167
192
|
/** owned — the Workflow tool is on the roster (the delegation surface's second door). */
|
|
@@ -192,6 +217,7 @@ declare const RUNTIME_CAP_KEYS: {
|
|
|
192
217
|
readonly forceDurableGate: true;
|
|
193
218
|
readonly autoMode: true;
|
|
194
219
|
readonly autoModeDenySource: true;
|
|
220
|
+
readonly bypassPermissions: true;
|
|
195
221
|
readonly allowMemoryOptOut: true;
|
|
196
222
|
};
|
|
197
223
|
export type RuntimeCapKeysCoverEveryCap = AssertAllKeysHandled<Exclude<keyof RuntimeCaps, keyof typeof RUNTIME_CAP_KEYS>>;
|
|
@@ -7,8 +7,9 @@ import { createRunWorkflowTool } from "../../orchestration/run-workflow-tool.js"
|
|
|
7
7
|
import { isSelfOrchestrationActive } from "../../orchestration/workflow-script-runner.js";
|
|
8
8
|
import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
9
9
|
import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { AUTO_MODE_CLASSIFIER_MAX_RETRIES } from "../auto-mode-defaults.js";
|
|
11
|
+
import { buildAutoModePrompt, readDenyClassifierRules, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
|
|
12
|
+
import { autoModeClassifierFallbackOf, createAutoModeDecider, createAutoModeDenialTracker, screenAutoModeKnobs } from "../auto-mode.js";
|
|
12
13
|
import { checkpointStoreOfSeat } from "../checkpoint-seat.js";
|
|
13
14
|
import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, resolveComplianceDenies } from "../compliance.js";
|
|
14
15
|
import { createPresentPlanTool, createEnterPlanModeTool } from "../present-plan-tool.js";
|
|
@@ -16,7 +17,7 @@ import { brainToRuntime } from "../runtime.js";
|
|
|
16
17
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
17
18
|
import { defineTool, isDefineToolProduct, rebindDefineToolCtx } from "../tools.js";
|
|
18
19
|
const MOUNT_REMEDY = "author it as a plain-object ToolSpec (own `name` and `execute`), hand the defineTool product itself, or wrap a product through the supported wrapper form (a fresh object re-stamped with `stampDefineToolBrand` whose rebind re-wraps the rebound product)";
|
|
19
|
-
import {
|
|
20
|
+
import { isAutoModeDenySource } from "../runtime-caps.js";
|
|
20
21
|
import { derivedRouteFallsBack } from "./derived-route-fallback.js";
|
|
21
22
|
import { CLASSIFIER_ATTEMPT_SUFFIX, CLASSIFIER_THINK_SEAT, announceClassifierRoleDefaulted, classifierLadderRungs, classifierReplyAsksAgain, classifierReplyFailure, classifierReplyVerdictText, classifierRoundObserver, classifierRungSeat, classifierSeatModel } from "./classifier-ladder.js";
|
|
22
23
|
import { REPORT_FINDINGS_TOOL_NAME, createReportBlockedTool, createReportFindingsTool } from "./synthetic-tools.js";
|
|
@@ -38,6 +39,7 @@ const RUNTIME_CAP_KEYS = {
|
|
|
38
39
|
forceDurableGate: true,
|
|
39
40
|
autoMode: true,
|
|
40
41
|
autoModeDenySource: true,
|
|
42
|
+
bypassPermissions: true,
|
|
41
43
|
allowMemoryOptOut: true,
|
|
42
44
|
};
|
|
43
45
|
function snapshotRuntimeCaps(record) {
|
|
@@ -54,7 +56,7 @@ function screenAutoModeCap(caps, onError, sessionId) {
|
|
|
54
56
|
return { caps, faulted: false };
|
|
55
57
|
if (typeof caps !== "object" || caps === null || Array.isArray(caps)) {
|
|
56
58
|
onError?.(new Error(`runtimeCapsResolver returned a non-record value (${JSON.stringify(caps)}) — read as a resolver fault: every per-principal capability is DENIED for this run (auto mode is not armed; asks flow the original chain). Return a RuntimeCaps object or undefined.`), { phase: "config", sessionId });
|
|
57
|
-
return { caps: { allowWorkflows: false, allowFork: false, autoMode: false }, faulted: true };
|
|
59
|
+
return { caps: { allowWorkflows: false, allowFork: false, autoMode: false, bypassPermissions: false }, faulted: true };
|
|
58
60
|
}
|
|
59
61
|
let record;
|
|
60
62
|
try {
|
|
@@ -62,22 +64,34 @@ function screenAutoModeCap(caps, onError, sessionId) {
|
|
|
62
64
|
}
|
|
63
65
|
catch (err) {
|
|
64
66
|
onError?.(new Error(`runtimeCapsResolver's record threw while being read (${thrownText(err)}) — read as a resolver fault: every per-principal capability is DENIED for this run (auto mode is not armed; asks flow the original chain).`), { phase: "config", sessionId });
|
|
65
|
-
return { caps: { allowWorkflows: false, allowFork: false, autoMode: false }, faulted: true };
|
|
67
|
+
return { caps: { allowWorkflows: false, allowFork: false, autoMode: false, bypassPermissions: false }, faulted: true };
|
|
66
68
|
}
|
|
69
|
+
let screened = record;
|
|
70
|
+
let faulted = false;
|
|
71
|
+
const fault = (message) => {
|
|
72
|
+
onError?.(new Error(`runtimeCapsResolver returned ${message}`), { phase: "config", sessionId });
|
|
73
|
+
faulted = true;
|
|
74
|
+
};
|
|
67
75
|
const { autoMode, autoModeDenySource } = record;
|
|
68
76
|
if (autoModeDenySource !== undefined && (!isAutoModeDenySource(autoModeDenySource) || autoMode !== false)) {
|
|
69
|
-
|
|
70
|
-
const { autoModeDenySource: _refused, ...rest } =
|
|
71
|
-
|
|
77
|
+
fault(`autoModeDenySource=${JSON.stringify(autoModeDenySource)} beside autoMode=${JSON.stringify(autoMode)} — read as a resolver fault: auto mode is DENIED for this run (the classifier is not armed; asks flow to the approver / park)`);
|
|
78
|
+
const { autoModeDenySource: _refused, ...rest } = screened;
|
|
79
|
+
screened = { ...rest, autoMode: false };
|
|
72
80
|
}
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
else if (autoMode !== undefined && typeof autoMode !== "boolean") {
|
|
82
|
+
fault(`a non-boolean autoMode (${JSON.stringify(autoMode)}) — read as a resolver fault: auto mode is DENIED for this run (the classifier is not armed; asks flow the same way they do with no auto-mode face)`);
|
|
83
|
+
screened = { ...screened, autoMode: false };
|
|
84
|
+
}
|
|
85
|
+
if (record.bypassPermissions !== undefined && typeof record.bypassPermissions !== "boolean") {
|
|
86
|
+
fault(`a non-boolean bypassPermissions (${JSON.stringify(record.bypassPermissions)}) — read as a resolver fault: bypassPermissions is DENIED for this run (a requested bypass folds back to default)`);
|
|
87
|
+
screened = { ...screened, bypassPermissions: false };
|
|
88
|
+
}
|
|
89
|
+
return { caps: screened, faulted };
|
|
77
90
|
}
|
|
78
|
-
function mintPersistedArming(am, laneRule, classifierSystemPrompt, onError, sessionId) {
|
|
91
|
+
function mintPersistedArming(am, laneRule, classifierSystemPrompt, derivedDenyRules, onError, sessionId) {
|
|
79
92
|
const arming = autoModeArmingRecipeOf(laneRule ? { ...am, crossSessionMessagesRule: true } : am, {
|
|
80
93
|
promptDigest: `apv1:${createHash("sha256").update(classifierSystemPrompt).digest("hex")}`,
|
|
94
|
+
derivedDenyRules,
|
|
81
95
|
});
|
|
82
96
|
if (arming === undefined) {
|
|
83
97
|
onError?.(new Error("RunnerDeps.autoMode.persistArming is set but the auto-mode face did not canonicalize into an arming recipe " +
|
|
@@ -100,7 +114,7 @@ function autoModeArmReasonOf(intent, facePresent, caps, capsFaulted) {
|
|
|
100
114
|
return { armed: true, reason: "armed" };
|
|
101
115
|
}
|
|
102
116
|
export async function prepareCapsAndWorkflow(input) {
|
|
103
|
-
const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode,
|
|
117
|
+
const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, permissionMode, readDenyEntries, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
|
|
104
118
|
const roster = new RosterBuilder(toolEffects);
|
|
105
119
|
for (const t of spec.tools ?? []) {
|
|
106
120
|
const mounted = mountCallerTool(t);
|
|
@@ -151,7 +165,7 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
151
165
|
}
|
|
152
166
|
catch (err) {
|
|
153
167
|
deps.onError?.(asThrownError(err), { phase: "config", sessionId });
|
|
154
|
-
runtimeCaps = { allowWorkflows: false, allowFork: false, autoMode: false };
|
|
168
|
+
runtimeCaps = { allowWorkflows: false, allowFork: false, autoMode: false, bypassPermissions: false };
|
|
155
169
|
runtimeCapsFaulted = true;
|
|
156
170
|
}
|
|
157
171
|
const screened = screenAutoModeCap(runtimeCaps, deps.onError, sessionId);
|
|
@@ -193,11 +207,17 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
193
207
|
}
|
|
194
208
|
const agentForkDenial = forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork);
|
|
195
209
|
const observersActive = runtimeCaps?.allowObservers === true;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const autoModeArmReason = autoModeArmReasonOf(
|
|
200
|
-
if (
|
|
210
|
+
const autoModeDeciderRef = { current: undefined };
|
|
211
|
+
const autoModeDenialTrackingRef = { current: undefined };
|
|
212
|
+
const autoModeArmingRef = { current: undefined };
|
|
213
|
+
const autoModeArmReason = () => autoModeArmReasonOf(permissionMode.declared === "auto", deps.autoMode !== undefined, runtimeCaps, runtimeCapsFaulted);
|
|
214
|
+
if (deps.autoMode !== undefined)
|
|
215
|
+
screenAutoModeKnobs(deps.autoMode);
|
|
216
|
+
const armAutoMode = async () => {
|
|
217
|
+
if (autoModeDeciderRef.current !== undefined)
|
|
218
|
+
return;
|
|
219
|
+
if (permissionMode.declared !== "auto" || deps.autoMode === undefined || runtimeCaps?.autoMode === false)
|
|
220
|
+
return;
|
|
201
221
|
const am = deps.autoMode;
|
|
202
222
|
const classifierSeat = classifierSeatModel({ deps, main: model });
|
|
203
223
|
let classifierModel = classifierSeat.model;
|
|
@@ -206,11 +226,14 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
206
226
|
}
|
|
207
227
|
announceClassifierRoleDefaulted({ onNotice: deps.onNotice, seat: { ...classifierSeat, model: classifierModel }, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) });
|
|
208
228
|
const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
|
|
209
|
-
const
|
|
229
|
+
const table = readDenyEntries();
|
|
230
|
+
const derivedDenyRules = readDenyClassifierRules(table.entries, table.ruleRows);
|
|
231
|
+
let classifierSystemPromptMemo;
|
|
232
|
+
const classifierSystemPrompt = () => (classifierSystemPromptMemo ??= buildAutoModePrompt({ ...am, derivedDenyRules, ...(classifierLaneRule ? { crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : {}) }));
|
|
210
233
|
const classifierRuntime = brainToRuntime(deps.brain);
|
|
211
234
|
const classifierRungs = classifierLadderRungs(am.classifierFallback, classifierModel, model);
|
|
212
|
-
|
|
213
|
-
|
|
235
|
+
const denialTracking = createAutoModeDenialTracker(am.denialLimit);
|
|
236
|
+
const decider = createAutoModeDecider({
|
|
214
237
|
...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
|
|
215
238
|
candidates: classifierRungs.map((m) => m.id),
|
|
216
239
|
onClassified: classifierRoundObserver({ tracer: deps.tracer, onNotice: deps.onNotice, taskId: hostTaskId, seatModelId: classifierModel.id, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) }),
|
|
@@ -229,8 +252,9 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
229
252
|
}
|
|
230
253
|
const classifierAuth = await spec.getApiKeyAndHeaders?.(rungModel);
|
|
231
254
|
const request = async (seat, suffix) => {
|
|
232
|
-
const response = await classifierRuntime.completeSimple(rungModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt + suffix, timestamp: Date.now() }] }, {
|
|
255
|
+
const response = await classifierRuntime.completeSimple(rungModel, { systemPrompt: classifierSystemPrompt(), messages: [{ role: "user", content: userPrompt + suffix, timestamp: Date.now() }] }, {
|
|
233
256
|
signal,
|
|
257
|
+
maxRetries: AUTO_MODE_CLASSIFIER_MAX_RETRIES,
|
|
234
258
|
...seat,
|
|
235
259
|
...(classifierAuth?.apiKey !== undefined ? { apiKey: classifierAuth.apiKey } : {}),
|
|
236
260
|
...(classifierAuth?.headers !== undefined ? { headers: classifierAuth.headers } : {}),
|
|
@@ -246,9 +270,14 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
246
270
|
return classifierReplyVerdictText(response);
|
|
247
271
|
},
|
|
248
272
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
273
|
+
const arming = am.persistArming === true ? mintPersistedArming(am, classifierLaneRule, classifierSystemPrompt(), derivedDenyRules, deps.onError, sessionId) : undefined;
|
|
274
|
+
if (autoModeDeciderRef.current !== undefined)
|
|
275
|
+
return;
|
|
276
|
+
autoModeDeciderRef.current = decider;
|
|
277
|
+
autoModeDenialTrackingRef.current = denialTracking;
|
|
278
|
+
autoModeArmingRef.current = arming;
|
|
279
|
+
};
|
|
280
|
+
permissionMode.bindArming({ caps: runtimeCaps, autoFacePresent: deps.autoMode !== undefined, armAuto: armAutoMode, onArmFault: (e) => deps.onError?.(asThrownError(e), { phase: "config", sessionId }) });
|
|
252
281
|
const deploymentWorkflowReady = runnerSelf !== undefined && isSelfOrchestrationActive(spec, deps);
|
|
253
282
|
const selfOrchestrationActive = deploymentWorkflowReady && runtimeCaps?.allowWorkflows !== false;
|
|
254
283
|
if (deploymentWorkflowReady && runtimeCaps?.allowWorkflows === false) {
|
|
@@ -313,7 +342,7 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
313
342
|
const co = memoryEngineSession?.captureOptOut;
|
|
314
343
|
return assembleParentCaptureState(co?.optedOut() ?? false, co?.indeterminate() ?? false, ctl, [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }]);
|
|
315
344
|
},
|
|
316
|
-
autoModeReview: () => (
|
|
345
|
+
autoModeReview: () => (autoModeDeciderRef.current !== undefined ? { decider: autoModeDeciderRef.current } : undefined),
|
|
317
346
|
workflowDepth: internals?.workflowDepth,
|
|
318
347
|
parentCwd: taskRootFinal,
|
|
319
348
|
parentThinking: () => childThinkingSeat(undefined, deploymentSubagentThinking(runnerSelf.agentCatalog?.roles), harnessRef.current?.getThinkingLevel() ?? thinking).thinking,
|
|
@@ -332,5 +361,5 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
332
361
|
inheritedGateForChildren,
|
|
333
362
|
}), { source: "builtin", mountedBy: "workflow" });
|
|
334
363
|
}
|
|
335
|
-
return { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason,
|
|
364
|
+
return { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDeciderRef, autoModeDenialTrackingRef, autoModeArmingRef, armAutoMode, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline };
|
|
336
365
|
}
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* before the move (a minted ownedEnv is destroyed there, the session view forgotten). The phase acquires
|
|
13
13
|
* no resource of its own — the two seats it returns are closures over the store the deployment owns.
|
|
14
14
|
*
|
|
15
|
-
* The
|
|
15
|
+
* The request door (the bad-value doors and every refusal the request decides alone) lives in
|
|
16
|
+
* `./file-history-request.ts` and runs BEFORE the session is acquired; this phase receives its resolution.
|
|
17
|
+
* The module-level helpers below the phase (`restoredAbsPathsOf`,
|
|
16
18
|
* the refusal's epoch-tail wording helper, `adoptForkedFileHistory` with its per-store carry memo,
|
|
17
19
|
* `createFileHistoryBoundarySeat`, `resolveFileHistoryCoordinates` / `resolveFileHistoryScope` /
|
|
18
20
|
* `fileHistoryFilesystemIdentity`, `childScopeRewindRefusal`) moved with it unchanged; the two the test
|
|
@@ -22,13 +24,17 @@ import type { ExecutionEnv } from "../../internal/harness.js";
|
|
|
22
24
|
import type { StoredSession } from "../../engine/session/session.js";
|
|
23
25
|
import type { RunnerDeps, TaskResult, TaskSpec, TrackFileEditHook } from "../types.js";
|
|
24
26
|
import type { FileHistoryBoundarySeat, RunInternals } from "./contracts.js";
|
|
27
|
+
import type { FileHistoryRequest } from "./file-history-request.js";
|
|
25
28
|
export interface PrepareFileHistoryInput {
|
|
26
29
|
/** borrowed-readonly — the deployment seats this phase reads: the first-touch failure policy, the history
|
|
27
30
|
* store and its capture switch (all validated HERE, once), the incident sink, and the retired-epoch diagnostic probe. */
|
|
28
|
-
deps: Pick<RunnerDeps, "
|
|
31
|
+
deps: Pick<RunnerDeps, "onError">;
|
|
29
32
|
/** borrowed-readonly — the REBOUND spec's rewind request fields and off-switch word (the retired `rewindFiles`
|
|
30
33
|
* spelling is read off the same object as an unknown key) and the caller's cancel signal for the restore. */
|
|
31
|
-
spec: Pick<TaskSpec, "resumeAt" | "
|
|
34
|
+
spec: Pick<TaskSpec, "resumeAt" | "restoreFiles" | "acceptPartialRestore" | "signal">;
|
|
35
|
+
/** borrowed-readonly — the request door's resolution ({@link resolveFileHistoryRequest}), judged before the session was
|
|
36
|
+
* acquired: every refusal the request and the deployment's seats decide on their own has already thrown. */
|
|
37
|
+
request: FileHistoryRequest;
|
|
32
38
|
/** borrowed-readonly — the trusted lineage a delegating parent handed this run (absent for a root run
|
|
33
39
|
* and for a host's own fork), read for the recording-scope decision only. */
|
|
34
40
|
internals: Pick<RunInternals, "fileHistoryLineage"> | undefined;
|
|
@@ -2,45 +2,16 @@ import { NodeExecutionEnv } from "../../engine/execution-env/node-execution-env.
|
|
|
2
2
|
import { isRemoteExecutionEnv } from "../remote-env.js";
|
|
3
3
|
import { resolveTrackKey } from "../file-history-store.js";
|
|
4
4
|
export async function prepareFileHistory(input) {
|
|
5
|
-
const { deps, spec, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, abortController } = input;
|
|
5
|
+
const { deps, spec, request, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, abortController } = input;
|
|
6
6
|
const rewindNotes = [];
|
|
7
|
-
const onTrackFailure
|
|
8
|
-
if (
|
|
9
|
-
const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
|
|
10
|
-
e.code = "config.invalid_on_track_failure";
|
|
11
|
-
throw e;
|
|
12
|
-
}
|
|
13
|
-
const wiredSeat = resolveWiredFileHistoryStore(deps.fileHistoryStore);
|
|
14
|
-
const runDeclaredOff = resolveRunFileHistoryWord(spec.fileHistory);
|
|
15
|
-
const captureOff = resolveFileHistoryCaptureWord(deps.fileHistoryCapture, wiredSeat);
|
|
16
|
-
const declaredOff = wiredSeat === "disabled" || runDeclaredOff;
|
|
17
|
-
const fileHistoryStore = declaredOff ? undefined : wiredSeat;
|
|
18
|
-
const captureStore = captureOff ? undefined : fileHistoryStore;
|
|
19
|
-
const legacyRewindFiles = spec.rewindFiles;
|
|
20
|
-
if (legacyRewindFiles === true) {
|
|
21
|
-
if (spec.resumeAt !== undefined) {
|
|
22
|
-
const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is on whenever a RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off.)`);
|
|
23
|
-
e.code = "rewind.rewind_files_retired";
|
|
24
|
-
throw e;
|
|
25
|
-
}
|
|
7
|
+
const { onTrackFailure, captureOff, declaredOff, fileHistoryStore, captureStore, rewindTarget, rewindBefore } = request;
|
|
8
|
+
if (spec.rewindFiles === true) {
|
|
26
9
|
try {
|
|
27
10
|
deps.onError?.(new Error(`TaskSpec.rewindFiles (capture sense) is retired and was ignored: per-edited-file first-touch tracking is on whenever RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off, so a per-turn capture request has nothing left to ask for. Drop the field; the restore sense moved to TaskSpec.restoreFiles.`), { phase: "config", sessionId });
|
|
28
11
|
}
|
|
29
12
|
catch {
|
|
30
13
|
}
|
|
31
14
|
}
|
|
32
|
-
if (spec.restoreFiles === true && spec.resumeAt === undefined) {
|
|
33
|
-
const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
|
|
34
|
-
e.code = "rewind.invalid_spec";
|
|
35
|
-
throw e;
|
|
36
|
-
}
|
|
37
|
-
if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
|
|
38
|
-
const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
|
|
39
|
-
e.code = "rewind.conflicting_targets";
|
|
40
|
-
throw e;
|
|
41
|
-
}
|
|
42
|
-
const rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
|
|
43
|
-
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
|
|
44
15
|
if (spec.resumeAt !== undefined && spec.restoreFiles !== true) {
|
|
45
16
|
rewindNotes.push({
|
|
46
17
|
code: "conversation_only",
|
|
@@ -64,18 +35,7 @@ export async function prepareFileHistory(input) {
|
|
|
64
35
|
await adoptForkedFileHistory(captureStore, session, sessionId, deps.onError);
|
|
65
36
|
if (rewindTarget !== undefined && historyScope !== sessionId)
|
|
66
37
|
throw childScopeRewindRefusal(historyScope, rewindTarget);
|
|
67
|
-
if (fileHistoryStore
|
|
68
|
-
if (rewindTarget !== undefined && runDeclaredOff)
|
|
69
|
-
throw captureDisabledRefusal(rewindTarget);
|
|
70
|
-
if (rewindTarget !== undefined) {
|
|
71
|
-
const e = new Error(wiredSeat === "disabled"
|
|
72
|
-
? `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment declared file history disabled (RunnerDeps.fileHistoryStore: "disabled"), so there is no history to restore — the files were NOT rewound`
|
|
73
|
-
: `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
|
|
74
|
-
e.code = "rewind.store_unconfigured";
|
|
75
|
-
throw e;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
else if (!handsEnabled) {
|
|
38
|
+
if (fileHistoryStore !== undefined && !handsEnabled) {
|
|
79
39
|
if (rewindTarget !== undefined) {
|
|
80
40
|
rewindNotes.push({
|
|
81
41
|
code: "files_env_unsupported",
|
|
@@ -168,52 +128,6 @@ function restoredAbsPathsOf(restored, root) {
|
|
|
168
128
|
}
|
|
169
129
|
return out;
|
|
170
130
|
}
|
|
171
|
-
function resolveWiredFileHistoryStore(candidate) {
|
|
172
|
-
if (candidate === undefined || candidate === "disabled")
|
|
173
|
-
return candidate;
|
|
174
|
-
const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
|
|
175
|
-
const verbOf = (v) => {
|
|
176
|
-
try {
|
|
177
|
-
return candidate[v];
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
|
|
184
|
-
const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
|
|
185
|
-
if (missing.length > 0) {
|
|
186
|
-
const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
|
|
187
|
-
const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Write "disabled" (or omit the key) to run without file history.`);
|
|
188
|
-
e.code = "config.invalid_file_history_store";
|
|
189
|
-
throw e;
|
|
190
|
-
}
|
|
191
|
-
return candidate;
|
|
192
|
-
}
|
|
193
|
-
function resolveRunFileHistoryWord(word) {
|
|
194
|
-
if (word === undefined || word === "disabled")
|
|
195
|
-
return word === "disabled";
|
|
196
|
-
const e = new Error(`rewind-files: TaskSpec.fileHistory holds ${shownWord(word)} — the only value is "disabled"; omit the key to leave the deployment's setting in force`);
|
|
197
|
-
e.code = "rewind.invalid_spec";
|
|
198
|
-
throw e;
|
|
199
|
-
}
|
|
200
|
-
function resolveFileHistoryCaptureWord(word, wiredSeat) {
|
|
201
|
-
if (word === undefined || (word === "off" && wiredSeat !== undefined && wiredSeat !== "disabled"))
|
|
202
|
-
return word === "off";
|
|
203
|
-
const e = new Error(word === "off"
|
|
204
|
-
? `RunnerDeps.fileHistoryCapture: "off" requires a wired RunnerDeps.fileHistoryStore — nothing to switch off; to drop history entirely write fileHistoryStore: "disabled"`
|
|
205
|
-
: `RunnerDeps.fileHistoryCapture holds ${shownWord(word)} — the only value is "off"; omit the key to record whenever a fileHistoryStore is wired`);
|
|
206
|
-
e.code = "config.invalid_file_history_capture";
|
|
207
|
-
throw e;
|
|
208
|
-
}
|
|
209
|
-
function shownWord(word) {
|
|
210
|
-
return typeof word === "string" ? JSON.stringify(word.slice(0, 40)) : word === null || typeof word === "boolean" || typeof word === "number" ? String(word) : `a ${typeof word}`;
|
|
211
|
-
}
|
|
212
|
-
function captureDisabledRefusal(target) {
|
|
213
|
-
const e = new Error(`rewind-files: this run set TaskSpec.fileHistory: "disabled", so it neither records nor restores file history — restoring files to entry "${target}" was refused and the files were NOT rewound`);
|
|
214
|
-
e.code = "rewind.capture_disabled";
|
|
215
|
-
return e;
|
|
216
|
-
}
|
|
217
131
|
function rewindRefusalTail() {
|
|
218
132
|
return `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that recorded history (a RunnerDeps.fileHistoryStore wired, capture on, and the run's file history not disabled); a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`;
|
|
219
133
|
}
|