@sema-agent/core 5.28.0 → 5.30.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 +99 -3
- package/dist/agents/cascade.d.ts +1 -1
- package/dist/agents/observer.d.ts +3 -3
- package/dist/agents/peer-admission.d.ts +1 -1
- package/dist/agents/send-message-tool.js +2 -0
- package/dist/agents/subagent.d.ts +2 -0
- package/dist/agents/subagent.js +6 -0
- package/dist/agents/teacher.d.ts +1 -1
- package/dist/agents/teacher.js +2 -0
- package/dist/agents/verify.d.ts +6 -6
- package/dist/agents/verify.js +2 -0
- package/dist/bench/metrics.d.ts +1 -1
- package/dist/brain/retry.d.ts +1 -1
- package/dist/core/arg-summary.d.ts +1 -1
- package/dist/core/ask-class.d.ts +2 -2
- package/dist/core/ask-question.d.ts +1 -1
- package/dist/core/ask-question.js +14 -1
- package/dist/core/auto-compaction.d.ts +5 -1
- package/dist/core/auto-compaction.js +10 -1
- package/dist/core/background-shell.d.ts +5 -5
- package/dist/core/checkpoint-store.d.ts +65 -19
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/file-snapshot-store.d.ts +1 -1
- package/dist/core/hooks.d.ts +15 -4
- package/dist/core/hooks.js +8 -2
- package/dist/core/human-input-projection.d.ts +2 -2
- package/dist/core/memory-admission.d.ts +2 -2
- package/dist/core/memory-engine/engine.d.ts +1 -1
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/header-hints.d.ts +1 -1
- package/dist/core/memory-engine/layout.d.ts +2 -2
- package/dist/core/memory-recall.d.ts +1 -1
- package/dist/core/memory.d.ts +2 -2
- package/dist/core/oracle-isolation.d.ts +2 -2
- package/dist/core/permission-rule-consent.d.ts +1 -1
- package/dist/core/permission-rule-model.d.ts +1 -1
- package/dist/core/permission-rule-org.d.ts +1 -1
- package/dist/core/permission-rule-store.d.ts +1 -1
- package/dist/core/permission-rules.js +2 -2
- package/dist/core/runner/active-skill-scope.d.ts +1 -1
- package/dist/core/runner/memory-consolidation.d.ts +1 -1
- package/dist/core/runner/prepare-task.d.ts +29 -8
- package/dist/core/runner/prepare-task.js +118 -22
- package/dist/core/runner/runtask.d.ts +1 -1
- package/dist/core/runner/runtask.js +34 -8
- package/dist/core/runner/session-rule-policy.d.ts +4 -3
- package/dist/core/runner/teardown-bounded.d.ts +1 -1
- package/dist/core/runner/tool-disclosure.d.ts +1 -1
- package/dist/core/runner/tool-output-projection.js +1 -1
- package/dist/core/scheduler.d.ts +4 -4
- package/dist/core/sensitive-path-policy.js +5 -16
- package/dist/core/session-policy-store.d.ts +3 -3
- package/dist/core/shared-memory/normalize.d.ts +1 -1
- package/dist/core/skill-tool-specifier.d.ts +1 -1
- package/dist/core/store-contracts/tool-result-store-contract.js +23 -0
- package/dist/core/task-notification.d.ts +2 -2
- package/dist/core/task-registry-agent.d.ts +9 -9
- package/dist/core/task-registry-shared.d.ts +4 -4
- package/dist/core/tighten-task-spec.js +18 -0
- package/dist/core/tool-errors.d.ts +2 -2
- package/dist/core/tool-policy.d.ts +64 -12
- package/dist/core/tool-policy.js +68 -8
- package/dist/core/tool-result-store.d.ts +2 -2
- package/dist/core/tool-result-store.js +3 -1
- package/dist/core/trace.d.ts +1 -1
- package/dist/core/types.d.ts +68 -13
- package/dist/core/untrusted-text.d.ts +6 -0
- package/dist/core/untrusted-text.js +1 -0
- package/dist/engine/compaction/compaction.d.ts +1 -1
- package/dist/engine/harness/messages.d.ts +1 -1
- package/dist/engine/harness/types.d.ts +12 -2
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/types.d.ts +2 -2
- package/dist/engine/session/import-validate.d.ts +1 -1
- package/dist/engine/session/log-digest.d.ts +1 -1
- package/dist/fixtures/index.d.ts +18 -2
- package/dist/fixtures/index.js +11 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/orchestration/goal.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +21 -0
- package/dist/orchestration/run-workflow-tool.js +6 -3
- package/dist/orchestration/workflow-primitives.d.ts +10 -1
- package/dist/orchestration/workflow-primitives.js +12 -1
- package/dist/orchestration/workflow-types.d.ts +5 -4
- package/dist/orchestration/workflow.js +11 -3
- package/dist/prompt-assembly/artifact.d.ts +1 -1
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/types.d.ts +4 -0
- package/dist/prompts/default.d.ts +14 -9
- package/dist/prompts/default.js +14 -4
- package/dist/scenarios/scenario-registry.d.ts +3 -3
- package/dist/stores/file/tool-result-store.d.ts +2 -2
- package/dist/tools/fs/bash-readonly-classifier.d.ts +23 -2
- package/dist/tools/fs/bash-readonly-classifier.js +11 -0
- package/dist/tools/fs/fs-bash.d.ts +8 -1
- package/dist/tools/fs/fs-bash.js +8 -3
- package/dist/tools/fs/fs-pdf.d.ts +1 -1
- package/dist/tools/fs/fs-pdf.js +2 -2
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-read.js +11 -7
- package/dist/tools/fs/fs-search-tools.d.ts +5 -3
- package/dist/tools/fs/fs-search-tools.js +15 -8
- package/dist/tools/fs/fs-shared.d.ts +6 -2
- package/dist/tools/fs/fs-shared.js +8 -3
- package/dist/tools/fs/index.d.ts +18 -0
- package/dist/tools/fs/index.js +13 -2
- package/dist/tools/fs/read-deny.d.ts +105 -0
- package/dist/tools/fs/read-deny.js +151 -0
- package/dist/tools/fs/read-face.d.ts +43 -0
- package/dist/tools/fs/read-face.js +38 -0
- package/dist/tools/fs/repo-map.d.ts +3 -1
- package/dist/tools/fs/repo-map.js +11 -5
- package/dist/tools/fs/safety.d.ts +34 -12
- package/dist/tools/fs/safety.js +88 -7
- package/dist/tools/fs/search.d.ts +54 -5
- package/dist/tools/fs/search.js +103 -21
- package/dist/tools/scheduler-tools.d.ts +1 -1
- package/dist/tools/task-list.d.ts +1 -1
- package/dist/tools/web.d.ts +3 -3
- package/package.json +1 -1
|
@@ -307,14 +307,14 @@ export interface Prepared {
|
|
|
307
307
|
* threaded to every compaction-boundary epoch selection so boundary re-pins hash the same
|
|
308
308
|
* artifact identity as the prepare-time pin. Empty on declaration-free runs. */
|
|
309
309
|
epochDeclaredSections: import("../../prompt-assembly/epoch.js").EpochDeclaredSections;
|
|
310
|
+
/** design/148 S3 — the LIVE nine-element identity face (refreshed by deferred materialization and
|
|
311
|
+
* the RB-31 adoption swap); the manifest holds the prepare-time initial copy. */
|
|
312
|
+
turnSnapshot?: import("../../prompt-assembly/turn-snapshot.js").TurnPromptSnapshot;
|
|
310
313
|
/** RB-31 (design/148 S2) — the compaction-boundary center-adoption seam (undefined = no source
|
|
311
314
|
* wired or session not center-pinned). Returns the maybeCompact `centerAdoption` fragment or
|
|
312
315
|
* undefined; `apply` runs post-CAS only (auto-compaction owns the ordering). Both
|
|
313
316
|
* `epochDeclaredSections` and `promptOverheadTokens` on THIS object are mutated by a successful
|
|
314
317
|
* adoption (read them at call time, never capture early). */
|
|
315
|
-
/** design/148 S3 — the LIVE nine-element identity face (refreshed by deferred materialization and
|
|
316
|
-
* the RB-31 adoption swap); the manifest holds the prepare-time initial copy. */
|
|
317
|
-
turnSnapshot?: import("../../prompt-assembly/turn-snapshot.js").TurnPromptSnapshot;
|
|
318
318
|
centerCompactionCandidate?: () => {
|
|
319
319
|
centerArtifactDigest?: string;
|
|
320
320
|
sourceRevision?: string;
|
|
@@ -573,8 +573,15 @@ export interface Prepared {
|
|
|
573
573
|
/** Narrow workspace reader for compaction working-file attachments (LONGRUN-2): reads a task file
|
|
574
574
|
* via the SAME env the hands ran against (so remote/k8s/E2B tasks read the container's tree, not
|
|
575
575
|
* the control plane's). Present only when the hands are enabled — without an env there is no
|
|
576
|
-
* workspace to re-read. null = unreadable (deleted/binary/transport error); callers skip it.
|
|
577
|
-
|
|
576
|
+
* workspace to re-read. null = unreadable (deleted/binary/transport error); callers skip it.
|
|
577
|
+
* design/199 件B: `{ withheld }` = the target matches the sensitive-path read deny list — the
|
|
578
|
+
* attachment is deliberately withheld and the consumer must SAY so (an annotation, never a silent
|
|
579
|
+
* skip: a silently missing attachment reads as "file gone", which is a different fact). */
|
|
580
|
+
readTaskFile?: (path: string) => Promise<string | null | {
|
|
581
|
+
withheld: {
|
|
582
|
+
pattern: string;
|
|
583
|
+
};
|
|
584
|
+
}>;
|
|
578
585
|
/** CC post-compact restore parity (2026-07-03): the task's READ files, most recent
|
|
579
586
|
* first (from the hands' readFileState `lastReadAt` stamps). The compaction working-file
|
|
580
587
|
* attachment prefers this over the modified set — CC restores what the model RECENTLY READ,
|
|
@@ -864,7 +871,7 @@ export interface InheritedGate {
|
|
|
864
871
|
* Org-memory admission freeze (ruled 2026-08-05): the spawning chain's FROZEN admitted org-scope
|
|
865
872
|
* set — every org memory scope the parent actually mounted (deployment-origin + admitted request).
|
|
866
873
|
* A delegated child's REQUEST-origin org scopes must be a subset (intersection ≠ request ⇒ the
|
|
867
|
-
* child's prepare refuses, `memory.admission_denied`); deployment-origin scopes
|
|
874
|
+
* child's prepare refuses, `memory.admission_denied`); deployment-origin scopes circumvent the freeze
|
|
868
875
|
* (operator authority is deployment-wide). ALWAYS emitted by chain assembly (empty array = parent
|
|
869
876
|
* mounted no org plane), so an ABSENT field discriminates an older-shape chain — which the child
|
|
870
877
|
* side reads fail-closed as the empty set. Serializable (plain strings): persists on the
|
|
@@ -872,7 +879,7 @@ export interface InheritedGate {
|
|
|
872
879
|
*/
|
|
873
880
|
admittedOrgScopes?: readonly string[];
|
|
874
881
|
/**
|
|
875
|
-
* The MONOTONIC org-governance provenance bit (
|
|
882
|
+
* The MONOTONIC org-governance provenance bit (falsification-style round 5): `true` when any leg of this
|
|
876
883
|
* tree ran with an org-admission surface configured (resolver / deployment scope list) or
|
|
877
884
|
* inherited the bit. It never clears once set (a governed tree stays governed), and it counts as
|
|
878
885
|
* governance evidence at the admission door EVEN when every admitted set en route is empty — a
|
|
@@ -1055,7 +1062,7 @@ export interface RunInternals {
|
|
|
1055
1062
|
* arbitration is decided) and BEFORE the resumed leg starts. The parked-resume caller uses it to
|
|
1056
1063
|
* flip the reserved row `parked→running` (guarded CAS) + adopt the live handle/lane — the ONLY
|
|
1057
1064
|
* legal site for that flip (a pre-consume flip would let TaskStop hit the plain running arm and
|
|
1058
|
-
*
|
|
1065
|
+
* circumvent the checkpoint arbitration entirely). A THROW here aborts the resume (propagates out of
|
|
1059
1066
|
* `resumeStream`): the checkpoint is already consumed, so the caller's compensation
|
|
1060
1067
|
* (`rollbackParkedClaim`) reads `resolved` and settles the honest failed/outcome-unknown terminal
|
|
1061
1068
|
* — never a silent run without an adopted row. Deployment/trusted-caller channel only, mirroring
|
|
@@ -1442,6 +1449,11 @@ export interface ResolvedWorkspace {
|
|
|
1442
1449
|
cwd: string;
|
|
1443
1450
|
/** True only for an accepted `isolation: "worktree"` request (⇒ `cwd` is this agent's own worktree). */
|
|
1444
1451
|
isolated: boolean;
|
|
1452
|
+
/** True when the task's execution env is REMOTE (#197 BGW-7): `cwd` then names a path INSIDE the remote
|
|
1453
|
+
* target (a per-task sandbox on the shape-(d) exemption leg, e.g. `/workspace`), which does not exist —
|
|
1454
|
+
* or names an unrelated directory — on the host. `isolated && !remote` is the only combination under
|
|
1455
|
+
* which `cwd` is a host path a consumer may diff / merge / remove. */
|
|
1456
|
+
remote: boolean;
|
|
1445
1457
|
}
|
|
1446
1458
|
/**
|
|
1447
1459
|
* From the resumed/active transcript, the batch position of `currentId` (design/45 §4.ter): the tool-call
|
|
@@ -1478,6 +1490,15 @@ export declare function rebaseWorkspacePath(p: string, fromRaw: string, toRaw: s
|
|
|
1478
1490
|
* which case the earlier — more specific — spelling is the intended one. Exported for direct unit pinning.
|
|
1479
1491
|
*/
|
|
1480
1492
|
export declare function rebaseWorkspacePathAcross(p: string, froms: readonly string[], to: string): string;
|
|
1493
|
+
/**
|
|
1494
|
+
* Build everything a task run needs (council design/34 ②: a free function with EXPLICIT deps, not a
|
|
1495
|
+
* Runner method — testable and decoupled). Resolves the model/role/thinking, acquires + reconciles the
|
|
1496
|
+
* session (with the conflict-detecting Proxy + bounded re-wake on a lost CAS), assembles the tool set
|
|
1497
|
+
* (caller tools + offload wrap + report_blocked + submit_output + MCP + memory + read_tool_result),
|
|
1498
|
+
* builds the system prompt, constructs the harness, wires the tool-policy gate + context-edit/guard
|
|
1499
|
+
* hooks + external-signal cancellation, and sets up the cache-break detector. **No run-loop control
|
|
1500
|
+
* flow** lives here — that stays in the Runner loop.
|
|
1501
|
+
*/
|
|
1481
1502
|
export declare function prepareTask(spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, internals?: RunInternals,
|
|
1482
1503
|
/** design/98 §3.1 (S8c): a TRUSTED self-reference to the Runner, passed by the Runner itself (never a
|
|
1483
1504
|
* TaskSpec field) so the `run_workflow` tool can execute child tasks via `runner.runTask`. Undefined when
|
|
@@ -57,7 +57,7 @@ import { foldAdmissionFreeze } from "../memory-admission.js";
|
|
|
57
57
|
import { prepareMemory } from "./prepare-memory.js";
|
|
58
58
|
import { defaultPromptProvider, buildEnvironmentContext, buildGitSnapshot, formatLocalDate, isValidTimeZone, PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
|
|
59
59
|
import { assemblePrompt } from "../../prompt-assembly/assemble.js";
|
|
60
|
-
import { auditToolCollisions, projectToolManifest } from "../../prompt-assembly/tool-catalog.js";
|
|
60
|
+
import { auditToolCollisions, getToolContract, projectToolManifest } from "../../prompt-assembly/tool-catalog.js";
|
|
61
61
|
import { resolveEpochAgainstBundled } from "../../prompt-assembly/epoch.js";
|
|
62
62
|
import { artifactDeclarations } from "../../prompt-assembly/artifact.js";
|
|
63
63
|
import { buildTurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js";
|
|
@@ -74,7 +74,7 @@ import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.j
|
|
|
74
74
|
import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../task-registry.js";
|
|
75
75
|
import { createMonitorTool } from "../../tools/monitor.js";
|
|
76
76
|
import { createWorktreeTools } from "../../tools/worktree.js";
|
|
77
|
-
import { applyCompactionToReadFileState, bashReversibilityProbe, createHandsToolkit, isReadDedupStubResult, seedReadFileStateFromContext, seedReadFileStateFromTranscript, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
|
|
77
|
+
import { applyCompactionToReadFileState, bashReversibilityProbe, compileReadDeny, createHandsToolkit, isReadDedupStubResult, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, FULL_SHELL_CONTRACT_ID, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
|
|
78
78
|
import { decodeTextBytes } from "../../tools/fs/encoding.js";
|
|
79
79
|
import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool, classifyQuestionOutcome, isLiveQuestionFace, validateAskQuestions, } from "../ask-question.js";
|
|
80
80
|
import { createSchedulerTools } from "../../tools/scheduler-tools.js";
|
|
@@ -85,7 +85,7 @@ import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-
|
|
|
85
85
|
import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
|
|
86
86
|
import { resolveKey } from "../../tools/fs/safety.js";
|
|
87
87
|
import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
|
|
88
|
-
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
|
|
88
|
+
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
|
|
89
89
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
90
90
|
import { countElicitOptIns, deriveAskEffective, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
|
|
91
91
|
import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
|
|
@@ -219,6 +219,9 @@ export function resolveModelPromptTraits(model, spec, internals) {
|
|
|
219
219
|
fableMitigations: isFableFamilyModelId(model.id),
|
|
220
220
|
};
|
|
221
221
|
}
|
|
222
|
+
function hasConversationContent(branch) {
|
|
223
|
+
return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
|
|
224
|
+
}
|
|
222
225
|
function isDelegatedNonForkChild(internals) {
|
|
223
226
|
return internals?.isDelegatedChild === true && internals?.insideFork !== true;
|
|
224
227
|
}
|
|
@@ -910,7 +913,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
910
913
|
}
|
|
911
914
|
if (internals?.onWorkspaceResolved !== undefined) {
|
|
912
915
|
try {
|
|
913
|
-
|
|
916
|
+
const workspaceEnv = ownedEnv ?? deps.executionEnv;
|
|
917
|
+
internals.onWorkspaceResolved({
|
|
918
|
+
cwd: taskRootPath,
|
|
919
|
+
isolated: internals.isolation === "worktree",
|
|
920
|
+
remote: workspaceEnv !== undefined &&
|
|
921
|
+
(isRemoteExecutionEnv(workspaceEnv) || workspaceEnv.hostLocalPaths === false),
|
|
922
|
+
});
|
|
914
923
|
}
|
|
915
924
|
catch {
|
|
916
925
|
}
|
|
@@ -1073,6 +1082,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1073
1082
|
const memoryAdmittedOrgScopesRef = { current: [] };
|
|
1074
1083
|
const ownOrgVerdictRef = { current: undefined };
|
|
1075
1084
|
const orgAdmissionCheckpointState = () => inheritedAdmittedOrgScopes !== undefined || ownOrgVerdictRef.current !== undefined || orgGovernedProvenance;
|
|
1085
|
+
const faceCheckpointSection = () => {
|
|
1086
|
+
if (resolvedReadFace === undefined) {
|
|
1087
|
+
const seed = resume?.seed.readFace;
|
|
1088
|
+
return seed !== undefined ? { face: seed.face, ...(seed.denyEntries !== undefined ? { denyEntries: seed.denyEntries.map((e) => ({ ...e })) } : {}) } : undefined;
|
|
1089
|
+
}
|
|
1090
|
+
if (resolvedReadFace === "open" || readDenyAdditionsNormalized.length > 0) {
|
|
1091
|
+
return { face: resolvedReadFace, ...(readDenyAdditionsNormalized.length > 0 ? { denyEntries: readDenyAdditionsNormalized.map((e) => ({ ...e })) } : {}) };
|
|
1092
|
+
}
|
|
1093
|
+
return undefined;
|
|
1094
|
+
};
|
|
1095
|
+
const faceCheckpointState = () => faceCheckpointSection() !== undefined;
|
|
1076
1096
|
const f012CheckpointState = () => (inheritedParentConstraints?.length ?? 0) > 0 ||
|
|
1077
1097
|
seedInheritedGate?.constraintChain !== undefined ||
|
|
1078
1098
|
internals?.delegationProvenance !== undefined;
|
|
@@ -1143,6 +1163,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1143
1163
|
};
|
|
1144
1164
|
let agentForkDenial;
|
|
1145
1165
|
let observersActive = false;
|
|
1166
|
+
let resolvedReadFace;
|
|
1167
|
+
let readDenyAdditionsNormalized = [];
|
|
1168
|
+
const fullShellReachable = handsEnabled &&
|
|
1169
|
+
!(executionEnv instanceof StubExecutionEnv) &&
|
|
1170
|
+
spec.handsReadOnly !== true &&
|
|
1171
|
+
!(toolFaceSnapshot.exclude?.includes("Bash") ?? false);
|
|
1146
1172
|
let autoModeDecider;
|
|
1147
1173
|
const enrichSpecToolCtx = (ctx) => ({
|
|
1148
1174
|
...ctx,
|
|
@@ -1153,6 +1179,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1153
1179
|
...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
|
|
1154
1180
|
...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
|
|
1155
1181
|
...(spec.handsReadOnly === true ? { handsReadOnly: true } : {}),
|
|
1182
|
+
...(resolvedReadFace === "roots" ? { readFace: "roots" } : {}),
|
|
1183
|
+
...(readDenyAdditionsNormalized.length > 0 ? { readDenyPatterns: Object.freeze(readDenyAdditionsNormalized.map((e) => ({ ...e }))) } : {}),
|
|
1156
1184
|
...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
|
|
1157
1185
|
oneShot: spec.oneShot,
|
|
1158
1186
|
clientContext: spec.clientContext,
|
|
@@ -1358,6 +1386,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1358
1386
|
workflowDepth: internals?.workflowDepth,
|
|
1359
1387
|
parentCwd: taskRootPath,
|
|
1360
1388
|
parentThinking: () => harnessRef.current?.getThinkingLevel() ?? thinking,
|
|
1389
|
+
parentReadFace: () => resolvedReadFace,
|
|
1390
|
+
parentReadDenyPatterns: () => (readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized : undefined),
|
|
1391
|
+
parentCheckpointStoreDisabled: spec.checkpointStore === null,
|
|
1392
|
+
parentCenterArtifactDigest: () => centerAdoption?.artifact.artifactDigest,
|
|
1393
|
+
parentCenterSourceRevision: () => centerAdoption?.sourceRevision,
|
|
1361
1394
|
...(forwardEvent ? { forwardEvent } : {}),
|
|
1362
1395
|
inheritedGateForChildren,
|
|
1363
1396
|
}));
|
|
@@ -1570,6 +1603,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1570
1603
|
const additionalRootsCanonical = [];
|
|
1571
1604
|
const additionalReadRootsCanonical = [];
|
|
1572
1605
|
let attachmentRootCanonical;
|
|
1606
|
+
let readDenyMatcher;
|
|
1573
1607
|
if (handsEnabled) {
|
|
1574
1608
|
const rootRaw = taskRootPath;
|
|
1575
1609
|
const canon = await executionEnv.canonicalPath(rootRaw);
|
|
@@ -1621,14 +1655,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1621
1655
|
if (resume === undefined && spec.sessionId !== undefined) {
|
|
1622
1656
|
const prior = await session.buildContext().catch(() => undefined);
|
|
1623
1657
|
for (const rec of wholeFileRecordsFromTranscript(prior?.messages ?? [])) {
|
|
1624
|
-
const rk = await resolveKey(executionEnv, rootCanonical, rec.path, undefined, undefined, additionalRootsCanonical);
|
|
1658
|
+
const rk = await resolveKey(executionEnv, rootCanonical, rec.path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical]);
|
|
1625
1659
|
if (rk.ok)
|
|
1626
1660
|
seedReadFileStateFromTranscript(readFileState, rk.key, rec.content, rec.at);
|
|
1627
1661
|
}
|
|
1628
1662
|
}
|
|
1629
1663
|
seedContextFiles = async (files) => {
|
|
1630
1664
|
for (const f of files) {
|
|
1631
|
-
const rk = await resolveKey(executionEnv, rootCanonical, f.path, undefined, undefined, additionalRootsCanonical);
|
|
1665
|
+
const rk = await resolveKey(executionEnv, rootCanonical, f.path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical]);
|
|
1632
1666
|
if (rk.ok)
|
|
1633
1667
|
seedReadFileStateFromContext(readFileState, rk.key, f.content);
|
|
1634
1668
|
}
|
|
@@ -1650,9 +1684,28 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1650
1684
|
...(resume !== undefined ? { baselineUnknown: true } : {}),
|
|
1651
1685
|
};
|
|
1652
1686
|
}
|
|
1687
|
+
const seedReadFaceSection = resume?.seed.readFace;
|
|
1688
|
+
const readDenyAdditions = [...(deps.readDenyPatterns ?? []), ...(spec.readDenyPatterns ?? []), ...(seedReadFaceSection?.denyEntries ?? [])];
|
|
1689
|
+
readDenyMatcher = compileReadDeny(readDenyAdditions, "readDenyPatterns");
|
|
1690
|
+
{
|
|
1691
|
+
const builtinCount = compileReadDeny([], "built-in").entries.length;
|
|
1692
|
+
readDenyAdditionsNormalized = readDenyMatcher.entries.slice(builtinCount);
|
|
1693
|
+
}
|
|
1694
|
+
let liveReadFace = resolveReadFace({
|
|
1695
|
+
specReadFace: spec.readFace,
|
|
1696
|
+
depsReadFace: deps.readFace,
|
|
1697
|
+
readOnlyMount: handsReadOnly,
|
|
1698
|
+
orgGoverned: deps.permissionRuleOrg !== undefined,
|
|
1699
|
+
fullShellReachable,
|
|
1700
|
+
});
|
|
1701
|
+
if (resume !== undefined && (seedReadFaceSection === undefined || seedReadFaceSection.face === "roots"))
|
|
1702
|
+
liveReadFace = "roots";
|
|
1703
|
+
resolvedReadFace = liveReadFace;
|
|
1653
1704
|
const band = createHandsToolkit(executionEnv, readFileState, rootCanonical, {
|
|
1654
1705
|
...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
|
|
1655
1706
|
...(additionalReadRootsCanonical.length > 0 ? { additionalReadRoots: additionalReadRootsCanonical } : {}),
|
|
1707
|
+
...(readDenyAdditions.length > 0 ? { readDenyPatterns: readDenyAdditions } : {}),
|
|
1708
|
+
readFace: liveReadFace,
|
|
1656
1709
|
includeShell: handsIncludeShell,
|
|
1657
1710
|
readOnly: handsReadOnly,
|
|
1658
1711
|
...(handsCwdRef ? { cwdRef: handsCwdRef } : {}),
|
|
@@ -1706,6 +1759,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1706
1759
|
const shellReadBoundary = () => ({
|
|
1707
1760
|
roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
|
|
1708
1761
|
...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
|
|
1762
|
+
denyMatch: (p) => readDenyMatcher?.matchPath(p)?.pattern ?? null,
|
|
1763
|
+
...(resolvedReadFace !== undefined ? { face: resolvedReadFace } : {}),
|
|
1709
1764
|
});
|
|
1710
1765
|
if (shellGate === "classify" && shellGatedBash)
|
|
1711
1766
|
reversibilityProbes.set("Bash", bashReversibilityProbe(undefined, shellReadBoundary));
|
|
@@ -2017,7 +2072,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2017
2072
|
if (spec.sessionId) {
|
|
2018
2073
|
try {
|
|
2019
2074
|
const branch = await session.getBranch();
|
|
2020
|
-
const hasConversation = branch
|
|
2075
|
+
const hasConversation = hasConversationContent(branch);
|
|
2021
2076
|
if (!hasConversation)
|
|
2022
2077
|
projectMemoryPhase = "fresh";
|
|
2023
2078
|
else {
|
|
@@ -2156,6 +2211,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2156
2211
|
policyEnabled: promptPolicyEnabled,
|
|
2157
2212
|
hooksEnabled: promptHooksEnabled,
|
|
2158
2213
|
isolationEnabled: isIsolated(executionEnv),
|
|
2214
|
+
readFaceOpen: resolvedReadFace === "open",
|
|
2159
2215
|
orchestrationEnabled: selfOrchestrationActive,
|
|
2160
2216
|
orchestrationDeferred: selfOrchestrationActive && (toolFaceSnapshot.defer?.includes("Workflow") ?? false),
|
|
2161
2217
|
promptProfile,
|
|
@@ -2208,6 +2264,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2208
2264
|
if (additionalRootsCanonical.length > 0) {
|
|
2209
2265
|
envFacts.additionalDirectories = [...additionalRootsCanonical];
|
|
2210
2266
|
}
|
|
2267
|
+
if (resolvedReadFace !== undefined)
|
|
2268
|
+
envFacts.readFace = resolvedReadFace;
|
|
2211
2269
|
if (additionalReadRootsCanonical.length > 0) {
|
|
2212
2270
|
envFacts.additionalReadDirectories = [...additionalReadRootsCanonical];
|
|
2213
2271
|
}
|
|
@@ -2301,6 +2359,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2301
2359
|
if (excluded.has(tools[i].name))
|
|
2302
2360
|
tools.splice(i, 1);
|
|
2303
2361
|
}
|
|
2362
|
+
if (handsEnabled) {
|
|
2363
|
+
const fullShellOnRoster = tools.some((t) => t.name === "Bash" && getToolContract(t, "core").contractId === FULL_SHELL_CONTRACT_ID);
|
|
2364
|
+
if (fullShellOnRoster !== fullShellReachable) {
|
|
2365
|
+
const e = new Error(`internal invariant: fullShellReachable=${String(fullShellReachable)} but the post-exclusion roster ` +
|
|
2366
|
+
`${fullShellOnRoster ? "carries" : "does not carry"} the full shell (${FULL_SHELL_CONTRACT_ID}). ` +
|
|
2367
|
+
`A new roster-affecting mechanism was added without updating the predicate.`);
|
|
2368
|
+
e.code = "internal.full_shell_reachable_mismatch";
|
|
2369
|
+
throw e;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2304
2372
|
if (promptProfile === "classic") {
|
|
2305
2373
|
for (let i = 0; i < tools.length; i++) {
|
|
2306
2374
|
const t = tools[i];
|
|
@@ -2498,7 +2566,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2498
2566
|
}
|
|
2499
2567
|
else if (resume === undefined && internals?.parentTaskId === undefined && internals?.parentSessionId === undefined) {
|
|
2500
2568
|
const candidate = deps.promptSource?.current();
|
|
2501
|
-
if (candidate !== undefined && (await session.getBranch())
|
|
2569
|
+
if (candidate !== undefined && !hasConversationContent(await session.getBranch())) {
|
|
2502
2570
|
centerAdoption = { artifact: candidate.artifact, sourceRevision: candidate.sourceRevision };
|
|
2503
2571
|
centerAdoptionFresh = true;
|
|
2504
2572
|
}
|
|
@@ -2577,7 +2645,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2577
2645
|
const resolved = resolveEpochAgainstBundled(pinned, SEMA_DEFAULT_PACK, epochDeclaredSections);
|
|
2578
2646
|
epochArtifactDigestForSnapshot = resolved.descriptor.artifactDigest;
|
|
2579
2647
|
if (resolved.migrated) {
|
|
2580
|
-
const
|
|
2648
|
+
const priorConversation = pinned === undefined ? hasConversationContent(await session.getBranch()) : true;
|
|
2649
|
+
const activatedBy = pinned === undefined && !priorConversation ? "session_start" : "legacy_migration";
|
|
2581
2650
|
await session.appendPromptEpoch({
|
|
2582
2651
|
...resolved.descriptor,
|
|
2583
2652
|
activatedBy,
|
|
@@ -3185,6 +3254,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3185
3254
|
...((internals?.explicitAgentName ?? internals?.agentName) !== undefined
|
|
3186
3255
|
? { sourceAgentName: internals?.explicitAgentName ?? internals?.agentName }
|
|
3187
3256
|
: {}),
|
|
3257
|
+
...(internals?.isDelegatedChild === true ? { isDelegatedChild: true } : {}),
|
|
3188
3258
|
});
|
|
3189
3259
|
const riskAxesOf = (toolName) => {
|
|
3190
3260
|
const tier = irreversibilityTier.get(toolName);
|
|
@@ -3290,6 +3360,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3290
3360
|
const suggestions = suggestRulesForCommand(command);
|
|
3291
3361
|
return suggestions.length > 0 ? { ruleSuggestions: suggestions } : {};
|
|
3292
3362
|
};
|
|
3363
|
+
const frozenClassifierExcluded = (d) => d.decisionReason === "hook" || d.matchedAskRule !== undefined;
|
|
3293
3364
|
const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider) => {
|
|
3294
3365
|
let editArgs = edit;
|
|
3295
3366
|
for (let round = 0;; round++) {
|
|
@@ -3316,7 +3387,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3316
3387
|
}
|
|
3317
3388
|
if (re.updatedInput !== undefined)
|
|
3318
3389
|
editArgs = re.updatedInput;
|
|
3319
|
-
if (ancestorDecider !== undefined) {
|
|
3390
|
+
if (ancestorDecider !== undefined && !frozenClassifierExcluded(re)) {
|
|
3320
3391
|
const verdict = await ancestorDecider
|
|
3321
3392
|
.decide({ req: { ...creq, args: editArgs }, ...(re.message !== undefined ? { askMessage: re.message } : {}) }, csignal ?? abortController.signal)
|
|
3322
3393
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
@@ -3395,7 +3466,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3395
3466
|
`ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
|
|
3396
3467
|
};
|
|
3397
3468
|
}
|
|
3398
|
-
if (pc.autoMode !== undefined) {
|
|
3469
|
+
if (pc.autoMode !== undefined && !frozenClassifierExcluded(first)) {
|
|
3399
3470
|
const verdict = await pc.autoMode.decider
|
|
3400
3471
|
.decide({ req: creq, ...(first.message !== undefined ? { askMessage: first.message } : {}) }, csignal ?? abortController.signal)
|
|
3401
3472
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
@@ -3482,7 +3553,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3482
3553
|
};
|
|
3483
3554
|
}
|
|
3484
3555
|
const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
|
|
3485
|
-
if (pc.autoMode !== undefined) {
|
|
3556
|
+
if (pc.autoMode !== undefined && !frozenClassifierExcluded(decision)) {
|
|
3486
3557
|
const verdict = await pc.autoMode.decider
|
|
3487
3558
|
.decide({ req: { ...creq, args: presentedArgs }, ...(decision.message !== undefined ? { askMessage: decision.message } : {}) }, csignal ?? abortController.signal)
|
|
3488
3559
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
@@ -3892,6 +3963,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3892
3963
|
nestedStats: { ...nestedStats },
|
|
3893
3964
|
consolidationNotes: undefined,
|
|
3894
3965
|
readFileState: readFileStateForCheckpoint ? [...readFileStateForCheckpoint.entries()] : undefined,
|
|
3966
|
+
readFace: faceCheckpointSection(),
|
|
3895
3967
|
repairBundle: repairBundleForCheckpoint(parkedSpendMicroUsd),
|
|
3896
3968
|
workspaceHandle,
|
|
3897
3969
|
handsCwd: handsCwdRef?.current,
|
|
@@ -4111,7 +4183,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4111
4183
|
const mintedAt = Date.now();
|
|
4112
4184
|
const cp = {
|
|
4113
4185
|
token,
|
|
4114
|
-
version: f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
|
|
4186
|
+
version: faceCheckpointState() ? FACE_CHECKPOINT_VERSION : f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
|
|
4115
4187
|
scope,
|
|
4116
4188
|
sessionId,
|
|
4117
4189
|
leafId,
|
|
@@ -4193,7 +4265,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4193
4265
|
const mintedAt = Date.now();
|
|
4194
4266
|
const cp = {
|
|
4195
4267
|
token,
|
|
4196
|
-
version: f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
|
|
4268
|
+
version: faceCheckpointState() ? FACE_CHECKPOINT_VERSION : f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
|
|
4197
4269
|
scope,
|
|
4198
4270
|
sessionId,
|
|
4199
4271
|
leafId,
|
|
@@ -4445,11 +4517,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4445
4517
|
const scope = durableApproval?.scope || checkpointScopeOf({ principal: spec.principal });
|
|
4446
4518
|
const ttlMs = sanitizedTtlMs(durableApproval?.ttlMs);
|
|
4447
4519
|
const checkpointState = serializeCheckpointState(remoteHandle, inFlightSpendMicroUsd());
|
|
4520
|
+
if (realApproval !== undefined && checkpointState.readFace !== undefined) {
|
|
4521
|
+
checkpointState.readFace = { ...checkpointState.readFace, realApproval: true };
|
|
4522
|
+
}
|
|
4448
4523
|
const approvalLedger = debitLedger(priorLedger, liveSpendRef.get?.() ?? { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: 0 }, resourceTotal, { countSlice: false });
|
|
4449
4524
|
const mintedAt = Date.now();
|
|
4450
4525
|
cp = {
|
|
4451
4526
|
token,
|
|
4452
|
-
version: realApproval !== undefined ? REAL_APPROVAL_CHECKPOINT_VERSION : f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
|
|
4527
|
+
version: faceCheckpointState() ? FACE_CHECKPOINT_VERSION : realApproval !== undefined ? REAL_APPROVAL_CHECKPOINT_VERSION : f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : approvalLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
|
|
4453
4528
|
scope,
|
|
4454
4529
|
sessionId,
|
|
4455
4530
|
leafId,
|
|
@@ -4799,21 +4874,42 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4799
4874
|
promptManifest.lowering = turnSnapshot.lowering;
|
|
4800
4875
|
const promptOverheadTokens = Math.ceil((systemPrompt.length +
|
|
4801
4876
|
harnessTools.reduce((a, t) => a + t.name.length + (t.description?.length ?? 0) + JSON.stringify(t.parameters ?? {}).length, 0)) / charsPerToken);
|
|
4877
|
+
const announceAttachmentSkip = (path, reason) => {
|
|
4878
|
+
deps.onError?.(new Error(`working-file attachment skipped — ${path}: ${reason}`), {
|
|
4879
|
+
phase: "degraded",
|
|
4880
|
+
sessionId,
|
|
4881
|
+
classification: "attachment-read",
|
|
4882
|
+
});
|
|
4883
|
+
};
|
|
4802
4884
|
const readTaskFile = handsEnabled
|
|
4803
4885
|
? async (path) => {
|
|
4804
4886
|
try {
|
|
4887
|
+
let key = path;
|
|
4805
4888
|
if (attachmentRootCanonical !== undefined) {
|
|
4806
|
-
const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, undefined, undefined, additionalRootsCanonical);
|
|
4807
|
-
if (!rk.ok)
|
|
4889
|
+
const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, readDenyMatcher, resolvedReadFace);
|
|
4890
|
+
if (!rk.ok) {
|
|
4891
|
+
announceAttachmentSkip(path, rk.violation.message);
|
|
4892
|
+
if (rk.violation.code === "read_path_denied" && rk.violation.pattern !== undefined) {
|
|
4893
|
+
return { withheld: { pattern: rk.violation.pattern } };
|
|
4894
|
+
}
|
|
4808
4895
|
return null;
|
|
4896
|
+
}
|
|
4897
|
+
key = rk.key;
|
|
4809
4898
|
}
|
|
4810
|
-
const r = await executionEnv.readBinaryFile(
|
|
4811
|
-
if (!r.ok)
|
|
4899
|
+
const r = await executionEnv.readBinaryFile(key);
|
|
4900
|
+
if (!r.ok) {
|
|
4901
|
+
announceAttachmentSkip(path, r.error.message);
|
|
4812
4902
|
return null;
|
|
4903
|
+
}
|
|
4813
4904
|
const d = decodeTextBytes(r.value);
|
|
4814
|
-
|
|
4905
|
+
if (d.malformed) {
|
|
4906
|
+
announceAttachmentSkip(path, "malformed text encoding (truncated UTF-16 body)");
|
|
4907
|
+
return null;
|
|
4908
|
+
}
|
|
4909
|
+
return d.text;
|
|
4815
4910
|
}
|
|
4816
|
-
catch {
|
|
4911
|
+
catch (err) {
|
|
4912
|
+
announceAttachmentSkip(path, err instanceof Error ? err.message : String(err));
|
|
4817
4913
|
return null;
|
|
4818
4914
|
}
|
|
4819
4915
|
}
|
|
@@ -4823,7 +4919,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4823
4919
|
if (attachmentRootCanonical === undefined)
|
|
4824
4920
|
return path;
|
|
4825
4921
|
try {
|
|
4826
|
-
const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, undefined, handsCwdRef?.current, additionalRootsCanonical);
|
|
4922
|
+
const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, undefined, handsCwdRef?.current, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, undefined, resolvedReadFace);
|
|
4827
4923
|
return rk.ok ? rk.key : path;
|
|
4828
4924
|
}
|
|
4829
4925
|
catch {
|
|
@@ -300,7 +300,7 @@ export declare class Runner {
|
|
|
300
300
|
* `inheritedManifestScope` and every other RunInternals field) — the library caller is trusted to
|
|
301
301
|
* re-supply the genuine chain; a caller able to invoke resumeStream already holds full Runner
|
|
302
302
|
* authority, so constraint substitution here is outside the enforced threat model (the count check
|
|
303
|
-
* catches shape MISTAKES, not a
|
|
303
|
+
* catches shape MISTAKES, not a untrusted trusted caller). */
|
|
304
304
|
internals?: RunInternals): Promise<TaskStream>;
|
|
305
305
|
/**
|
|
306
306
|
* design/45 resume step: with the harness idle and the branch rewound to the suspension leaf, resolve
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { persistedReadDenyEntryProblem } from "../../tools/fs/read-deny.js";
|
|
1
2
|
import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, uuidv7 } from "../../internal/harness.js";
|
|
2
3
|
import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
3
|
-
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
|
|
4
|
+
import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
|
|
4
5
|
import { engineVersion } from "../version.js";
|
|
5
6
|
import { CONFIG_CATALOG_VERSION, declarationReasons, resolveEffectiveConfig } from "../../config/catalog.js";
|
|
6
7
|
import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
|
|
@@ -35,7 +36,7 @@ import { hasDestroy, isIsolated } from "../remote-env.js";
|
|
|
35
36
|
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
36
37
|
import { cloneObserverInput, formatHookFeedback } from "../hooks.js";
|
|
37
38
|
import { buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
|
|
38
|
-
import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
39
|
+
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
39
40
|
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
40
41
|
import { RunnerSharedToolResultStore } from "../tool-result-store.js";
|
|
41
42
|
import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
|
|
@@ -245,7 +246,7 @@ function resumeContinuation(resume) {
|
|
|
245
246
|
if (resume.outcome.gate === "dry_run_review") {
|
|
246
247
|
const verdict = resume.outcome.decision === "approve"
|
|
247
248
|
? "Your predicted change was REVIEWED and APPROVED; it has been applied"
|
|
248
|
-
: `Your predicted change was REVIEWED and REJECTED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason)}` : ""}; it was NOT applied`;
|
|
249
|
+
: `Your predicted change was REVIEWED and REJECTED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : ""}; it was NOT applied`;
|
|
249
250
|
return formatHookFeedback(`You were resumed after a dry-run review. ${verdict}. Before continuing, re-orient from the workspace ` +
|
|
250
251
|
"(run `git status` and review your recent changes) to confirm the current state, then continue the " +
|
|
251
252
|
"remaining work. Do NOT restart the task or re-run work that is already done.");
|
|
@@ -265,7 +266,7 @@ function resumeContinuation(resume) {
|
|
|
265
266
|
"or restart from scratch, just carry out the revised plan.\n\nThe REVISED plan to follow is:\n" +
|
|
266
267
|
delimitUntrusted("revised plan", resume.outcome.editedPlan));
|
|
267
268
|
}
|
|
268
|
-
const why = resume.outcome.reason ? ` Reviewer note: ${delimitUntrusted("reviewer note", resume.outcome.reason)}` : "";
|
|
269
|
+
const why = resume.outcome.reason ? ` Reviewer note: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : "";
|
|
269
270
|
return formatHookFeedback(`Your proposed PLAN was REVIEWED and REJECTED; it was NOT executed.${why} Produce a NEW plan that ` +
|
|
270
271
|
"addresses the concern, then continue. This is a RESUMED task — re-plan from the current state; do " +
|
|
271
272
|
"NOT execute the rejected plan.");
|
|
@@ -276,7 +277,7 @@ function resumeContinuation(resume) {
|
|
|
276
277
|
}
|
|
277
278
|
const decided = resume.outcome.decision === "allow"
|
|
278
279
|
? "was APPROVED and has now been executed — its result is in the tool results above"
|
|
279
|
-
: `was DENIED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason)}` : ""}`;
|
|
280
|
+
: `was DENIED${resume.outcome.reason ? `: ${delimitUntrusted("reviewer note", resume.outcome.reason, REVIEWER_NOTE_MAX_BODY)}` : ""}`;
|
|
280
281
|
const remaining = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !pendingAction.completedCallIds.includes(id)).length;
|
|
281
282
|
const batchNote = remaining > 0
|
|
282
283
|
? ` ${remaining} other tool call(s) from that batch were returned as deferred and not run — re-issue any you still need.`
|
|
@@ -3718,11 +3719,36 @@ export class Runner {
|
|
|
3718
3719
|
(preCasGateBit.origin === "org_rule" ||
|
|
3719
3720
|
preCasGateBit.origin === "org_unavailable" ||
|
|
3720
3721
|
preCasGateBit.origin === "policy");
|
|
3721
|
-
|
|
3722
|
+
const faceSaysGoverned = (cp.state.readFace?.realApproval) === true;
|
|
3723
|
+
const preCasBitViolation = checkpointVersionOf(cp) === REAL_APPROVAL_CHECKPOINT_VERSION
|
|
3724
|
+
? !preCasBitWellFormed
|
|
3725
|
+
: checkpointVersionOf(cp) < REAL_APPROVAL_CHECKPOINT_VERSION
|
|
3726
|
+
? preCasGateBit !== undefined
|
|
3727
|
+
: faceSaysGoverned
|
|
3728
|
+
? !preCasBitWellFormed
|
|
3729
|
+
: preCasGateBit !== undefined && !preCasBitWellFormed;
|
|
3730
|
+
if (preCasBitViolation) {
|
|
3722
3731
|
throw new CheckpointError("checkpoint.invalid_outcome", checkpointVersionOf(cp) >= REAL_APPROVAL_CHECKPOINT_VERSION
|
|
3723
|
-
? `a v${checkpointVersionOf(cp)} checkpoint must
|
|
3732
|
+
? `a v${checkpointVersionOf(cp)} checkpoint's realApproval gate bit must be well-formed (origin org_rule/org_unavailable/policy)${checkpointVersionOf(cp) === REAL_APPROVAL_CHECKPOINT_VERSION ? " and present on an irreversible_ask gate" : ""} — this row's is not; refusing to resume a damaged real-approval row (corruption / downgrade guard), the checkpoint stays pending`
|
|
3724
3733
|
: `a v${checkpointVersionOf(cp)} checkpoint carries a realApproval gate bit no release of that version ever minted — refusing to honor a fabricated origin (corruption / forgery guard), the checkpoint stays pending`, { reason: checkpointVersionOf(cp) >= REAL_APPROVAL_CHECKPOINT_VERSION ? "real_approval_damaged" : "real_approval_forged" });
|
|
3725
3734
|
}
|
|
3735
|
+
{
|
|
3736
|
+
const faceSectionRaw = cp.state.readFace;
|
|
3737
|
+
const faceCandidate = faceSectionRaw !== undefined && typeof faceSectionRaw === "object" && faceSectionRaw !== null
|
|
3738
|
+
? faceSectionRaw
|
|
3739
|
+
: undefined;
|
|
3740
|
+
const faceWellFormed = faceCandidate !== undefined &&
|
|
3741
|
+
(faceCandidate.face === "open" || faceCandidate.face === "roots") &&
|
|
3742
|
+
(faceCandidate.realApproval === undefined || faceCandidate.realApproval === true) &&
|
|
3743
|
+
(faceCandidate.denyEntries === undefined ||
|
|
3744
|
+
(Array.isArray(faceCandidate.denyEntries) &&
|
|
3745
|
+
faceCandidate.denyEntries.every((e) => persistedReadDenyEntryProblem(e) === null)));
|
|
3746
|
+
if (checkpointVersionOf(cp) >= FACE_CHECKPOINT_VERSION ? !faceWellFormed : faceSectionRaw !== undefined) {
|
|
3747
|
+
throw new CheckpointError("checkpoint.invalid_outcome", checkpointVersionOf(cp) >= FACE_CHECKPOINT_VERSION
|
|
3748
|
+
? `a v${checkpointVersionOf(cp)} checkpoint must carry a well-formed read-face section (state.readFace) — this row's is missing or malformed; refusing to resume a damaged read-face row (corruption / downgrade guard), the checkpoint stays pending`
|
|
3749
|
+
: `a v${checkpointVersionOf(cp)} checkpoint carries a read-face section no release of that version ever minted — refusing to honor a fabricated read-face state (corruption / forgery guard), the checkpoint stays pending`, { reason: checkpointVersionOf(cp) >= FACE_CHECKPOINT_VERSION ? "read_face_damaged" : "read_face_forged" });
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3726
3752
|
if ((preCasGateBit?.origin === "org_rule" || preCasGateBit?.origin === "org_unavailable") &&
|
|
3727
3753
|
this.deps.permissionRuleOrg === undefined) {
|
|
3728
3754
|
throw new CheckpointError("checkpoint.unsupported_version", `this checkpoint's approval was minted under organization governance (${preCasGateBit.origin}) and this worker has no org adjudication wiring (permissionRuleOrg) — a governed approval may only be redeemed where governance can be enforced; the checkpoint stays pending, resume it on an org-wired worker`, { reason: "governed_unwired" });
|
|
@@ -4004,7 +4030,7 @@ export class Runner {
|
|
|
4004
4030
|
: outcome.settledBy === "aborted"
|
|
4005
4031
|
? `The approval for the pending tool call "${pendingAction.toolName}" ended without anyone deciding it (it was cancelled or could not be delivered), so it was not executed.`
|
|
4006
4032
|
: `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
|
|
4007
|
-
const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason) : defaultDenial;
|
|
4033
|
+
const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason, REVIEWER_NOTE_MAX_BODY) : defaultDenial;
|
|
4008
4034
|
emitEnd(true, { content: formatHookFeedback(reason) });
|
|
4009
4035
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason), true));
|
|
4010
4036
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
@@ -53,10 +53,11 @@ export declare function isWithin(root: string, p: string): boolean;
|
|
|
53
53
|
* - `toolDeny` → deny the listed tools.
|
|
54
54
|
* - `toolAllow` (if set) → deny any tool NOT listed (a narrowing allowlist).
|
|
55
55
|
* - `commandAllow`/`commandDeny` → delegate to {@link createCoarseCommandNamePolicy} (argv[0] names; only
|
|
56
|
-
* speaks about shell tools)
|
|
57
|
-
* un-parseable
|
|
56
|
+
* speaks about shell tools) with `defaultAction:"deny"` in BOTH modes — so this policy structurally
|
|
57
|
+
* emits no ask. Allowlist mode denies the un-listed and the un-parseable; blocklist mode denies the
|
|
58
|
+
* listed and the un-parseable (a parseable command absent from the deny list stays allowed).
|
|
58
59
|
* - `allowDirs` (if set) → a WRITE tool must land inside one of the dirs (resolved via `canonicalizeTarget`,
|
|
59
|
-
* so a symlink can't
|
|
60
|
+
* so a symlink can't carry across a write out); a write-capable tool that cannot be path-confined (e.g. `bash`)
|
|
60
61
|
* is DENIED while `allowDirs` is set (fail-closed, mirrors active-skill-scope MAJOR-2).
|
|
61
62
|
* Returns `allow` when nothing constrains the call. `env`/`rootPath` are the task's real exec env + root, so
|
|
62
63
|
* matching sees the actual filesystem (a remote/E2B task resolves in its container). RB-108: a relative
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* mask the original error") — but a promise that never settles is not a throw, and one wedged
|
|
10
10
|
* adapter turned into: for-await done, `result()` never settles (②型, process alive), or — when the
|
|
11
11
|
* wedge happened before the queue closed — a zero-handle pure-logic hang (the 2.12.0 exit-13 shape).
|
|
12
|
-
* The session lock releases in the run IIFE's finally, so one wedged run also
|
|
12
|
+
* The session lock releases in the run IIFE's finally, so one wedged run also contaminated every later
|
|
13
13
|
* same-session resume.
|
|
14
14
|
*
|
|
15
15
|
* The remedy is the same doctrine as the workflow journal drain (batch-4 F2): a best-effort leg gets
|
|
@@ -223,7 +223,7 @@ export declare function resolveToolSearch(args: ToolSearchArgs, registry: Readon
|
|
|
223
223
|
* direct call (ruled 2026-08-03) activates too and is flagged as an error, so it lands in that same
|
|
224
224
|
* under-approximated class by construction: within the process the in-memory `active` set carries it, and
|
|
225
225
|
* a replay that loses it costs the corrected call — which succeeds, and is itself carrier ②. The one way to
|
|
226
|
-
*
|
|
226
|
+
* self-declare a positive is a deployment `tool_result` hook that rewrites a rejection's error flag to false,
|
|
227
227
|
* which is the deployment editing its own transcript.
|
|
228
228
|
*
|
|
229
229
|
* design/116 W1-2 note: pre-116 checkpoints whose KEYWORD queries activated up to 25 tools re-derive at
|
|
@@ -62,7 +62,7 @@ const CC_DETAIL_TYPES = new Set([
|
|
|
62
62
|
"agent", "task", "task-list", "task-output", "workflow-run",
|
|
63
63
|
"web-fetch", "web-search", "todo", "cron-create", "cron-delete", "cron-list", "image",
|
|
64
64
|
"task-stop", "tool-search", "repo-map", "fork", "enter-plan-mode", "exit-plan-mode",
|
|
65
|
-
"monitor-start", "path_not_in_root", "readonly_out_of_root", "bash_invalid_timeout",
|
|
65
|
+
"monitor-start", "path_not_in_root", "read_path_denied", "readonly_out_of_root", "bash_invalid_timeout",
|
|
66
66
|
"report-findings", "schedule-wakeup", "send-message", "agent-transcript", "a2a", "document",
|
|
67
67
|
]);
|
|
68
68
|
export const structuredFrom = (result) => {
|