@sema-agent/core 7.0.2 → 7.2.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 +58 -0
- package/dist/agents/cross-session-envelope.d.ts +138 -0
- package/dist/agents/cross-session-envelope.js +191 -0
- package/dist/agents/cross-session-judge.d.ts +119 -0
- package/dist/agents/cross-session-judge.js +184 -0
- package/dist/agents/cross-session-ref.d.ts +52 -0
- package/dist/agents/cross-session-ref.js +64 -0
- package/dist/agents/repair-loop.d.ts +8 -7
- package/dist/agents/roster-store.d.ts +7 -2
- package/dist/agents/send-message-tool.d.ts +13 -0
- package/dist/agents/send-message-tool.js +36 -12
- package/dist/brain/errors.d.ts +18 -0
- package/dist/brain/errors.js +3 -0
- package/dist/brain/stream-engine.js +6 -4
- package/dist/core/checkpoint-store.d.ts +189 -3
- package/dist/core/checkpoint-store.js +56 -16
- package/dist/core/context-edit.d.ts +3 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/hooks.d.ts +34 -7
- package/dist/core/hooks.js +14 -8
- package/dist/core/image-downsample.d.ts +4 -3
- package/dist/core/permission-rule-consent.d.ts +72 -23
- package/dist/core/permission-rule-consent.js +115 -26
- package/dist/core/permission-rule-model.d.ts +245 -51
- package/dist/core/permission-rule-model.js +312 -54
- package/dist/core/permission-rule-org.js +13 -6
- package/dist/core/remote-env.d.ts +8 -1
- package/dist/core/roles.d.ts +30 -8
- package/dist/core/roles.js +12 -8
- package/dist/core/runner/assemble-result.js +2 -1
- package/dist/core/runner/prepare-task.d.ts +41 -2
- package/dist/core/runner/prepare-task.js +353 -152
- package/dist/core/runner/prepare-workspace-restore.d.ts +6 -1
- package/dist/core/runner/prepare-workspace-restore.js +2 -1
- package/dist/core/runner/runtask.d.ts +12 -3
- package/dist/core/runner/runtask.js +45 -7
- package/dist/core/safety-axis-vocab.d.ts +1 -1
- package/dist/core/strategy-store.d.ts +4 -1
- package/dist/core/task-notification.d.ts +64 -5
- package/dist/core/task-notification.js +25 -4
- package/dist/core/task-registry-shared.d.ts +7 -3
- package/dist/core/tool-errors.d.ts +1 -1
- package/dist/core/tool-policy.d.ts +51 -7
- package/dist/core/tool-policy.js +63 -9
- package/dist/core/types.d.ts +110 -9
- package/dist/core/untrusted-text.js +17 -1
- package/dist/engine/compaction/compaction.js +6 -2
- package/dist/engine/harness/agent-harness.d.ts +28 -6
- package/dist/engine/harness/agent-harness.js +34 -2
- package/dist/engine/harness/messages.js +4 -0
- package/dist/engine/harness/types.d.ts +37 -0
- package/dist/engine/harness/types.js +5 -0
- package/dist/engine/session/session.js +3 -2
- package/dist/index.d.ts +6 -3
- package/dist/index.js +5 -2
- package/dist/internal/harness.d.ts +1 -0
- package/dist/internal/harness.js +1 -0
- package/dist/orchestration/builtin-workflows.d.ts +17 -9
- package/dist/orchestration/run-workflow-tool.js +7 -2
- package/dist/orchestration/workflow-governance.js +1 -1
- package/dist/orchestration/workflow-types.d.ts +1 -0
- package/dist/orchestration/workflow.js +1 -1
- package/dist/stores/file/mailbox-store.d.ts +2 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +125 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import { resolve as resolveFsPath } from "node:path";
|
|
4
|
-
import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, readCompactionActiveTools, summaryOutputBudgetTokens } from "../../internal/harness.js";
|
|
4
|
+
import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, isSyntheticApiErrorMessage, readCompactionActiveTools, summaryOutputBudgetTokens } from "../../internal/harness.js";
|
|
5
5
|
const PROMPT_HASH_SALT = randomBytes(16);
|
|
6
6
|
import { sanitizeCompactionSettings } from "../auto-compaction.js";
|
|
7
7
|
import { projectStaleToolResults, resolveStaleToolResultOffload } from "./compaction-call-options.js";
|
|
@@ -23,7 +23,8 @@ import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send
|
|
|
23
23
|
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
24
24
|
import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOfLayer, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
|
|
25
25
|
const PERSISTED_RULE_TOOL = "Bash";
|
|
26
|
-
|
|
26
|
+
const DIRECTORY_RULE_TOOL = "Read";
|
|
27
|
+
import { directoryRuleAdmits, eligiblePersisted, findAdmittingRule, lexicalNormalAbsolutePathOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
27
28
|
import { normalizePersistedRule } from "../permission-rule-store.js";
|
|
28
29
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
29
30
|
import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
|
|
@@ -76,7 +77,7 @@ import { capAggregateToolResults } from "../tool-result-budget.js";
|
|
|
76
77
|
import { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES } from "../media-byte-cap.js";
|
|
77
78
|
import { dropOrphanToolResults, guardBudget, insertTrimNotice, trimToBudget } from "../context-guard.js";
|
|
78
79
|
import { StubExecutionEnv } from "../stub-env.js";
|
|
79
|
-
import { hasDestroy, isIsolated, isRemoteExecutionEnv, isSuspendable, missingRestoreSurface } from "../remote-env.js";
|
|
80
|
+
import { hasDestroy, isIsolated, isRemoteExecutionEnv, isSuspendable, missingRestoreSurface, RemoteExecutionError } from "../remote-env.js";
|
|
80
81
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
81
82
|
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
82
83
|
import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../task-registry.js";
|
|
@@ -662,6 +663,144 @@ export function batchContextAt(messages, currentId) {
|
|
|
662
663
|
const completedCallIds = batch.filter((id) => id !== currentId && resolved.has(id));
|
|
663
664
|
return { batchToolCallIds: batch, completedCallIds };
|
|
664
665
|
}
|
|
666
|
+
function composeCallSignal(runSignal, callSignal) {
|
|
667
|
+
return callSignal !== undefined ? AbortSignal.any([runSignal, callSignal]) : runSignal;
|
|
668
|
+
}
|
|
669
|
+
function lateAskSettlementObserver(args) {
|
|
670
|
+
return (late) => {
|
|
671
|
+
if (late.kind === "approve") {
|
|
672
|
+
deliverEngineNotice(args.onNotice, {
|
|
673
|
+
code: "task.late_approval",
|
|
674
|
+
message: `an approval for "${args.toolName}" was not consumed: a run or turn interrupt released the ask wait, ` +
|
|
675
|
+
`so the tool did NOT run and the approval was not honored (an unconsumed answer is the approver ` +
|
|
676
|
+
`releasing its wait, never a verdict).`,
|
|
677
|
+
detail: {
|
|
678
|
+
toolName: args.toolName,
|
|
679
|
+
toolCallId: args.toolCallId,
|
|
680
|
+
sessionId: args.sessionId,
|
|
681
|
+
runId: args.runId,
|
|
682
|
+
...(args.taskId !== undefined ? { taskId: args.taskId } : {}),
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
args.onError?.(late.error, { phase: "hook", sessionId: args.sessionId });
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function askGrantShapeOf(args) {
|
|
691
|
+
try {
|
|
692
|
+
return JSON.stringify(args ?? null);
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
return undefined;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function consumeInheritedAskGrant(grants, onAsk, humanReviewRef, decision, req) {
|
|
699
|
+
if (decision.decisionReason !== undefined && decision.decisionReason !== "rule")
|
|
700
|
+
return undefined;
|
|
701
|
+
const grant = grants.get(req.toolCallId);
|
|
702
|
+
if (grant === undefined || askApproverIdentity(grant.approver) !== askApproverIdentity(onAsk) || grant.argsJson !== askGrantShapeOf(req.args))
|
|
703
|
+
return undefined;
|
|
704
|
+
grants.delete(req.toolCallId);
|
|
705
|
+
humanReviewRef.count += 1;
|
|
706
|
+
humanReviewRef.totalWaitMs += grant.waitMs;
|
|
707
|
+
const toolArg = primaryActivityArg(req.args);
|
|
708
|
+
humanReviewRef.gates.push({
|
|
709
|
+
kind: "human",
|
|
710
|
+
waitMs: grant.waitMs,
|
|
711
|
+
decision: "allow",
|
|
712
|
+
toolName: req.toolName,
|
|
713
|
+
...(toolArg !== undefined ? { toolArg } : {}),
|
|
714
|
+
});
|
|
715
|
+
return { action: "allow", presentedInput: grant.presented };
|
|
716
|
+
}
|
|
717
|
+
function raceSettlementAgainstSignal(p, signal) {
|
|
718
|
+
const settled = p.then((value) => ({ tag: "value", value }), (error) => ({ tag: "threw", error }));
|
|
719
|
+
if (signal.aborted)
|
|
720
|
+
return Promise.resolve({ tag: "aborted" });
|
|
721
|
+
return new Promise((resolve) => {
|
|
722
|
+
let done = false;
|
|
723
|
+
const finish = (r) => {
|
|
724
|
+
if (done)
|
|
725
|
+
return;
|
|
726
|
+
done = true;
|
|
727
|
+
signal.removeEventListener("abort", onAbortEvent);
|
|
728
|
+
resolve(r);
|
|
729
|
+
};
|
|
730
|
+
const onAbortEvent = () => finish({ tag: "aborted" });
|
|
731
|
+
signal.addEventListener("abort", onAbortEvent, { once: true });
|
|
732
|
+
void settled.then(finish);
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
const PARK_COMPENSATION_TIMEOUT_MS = 30_000;
|
|
736
|
+
export async function compensateUnparkedPause(remoteEnv, snapshotId, io) {
|
|
737
|
+
const boundCtl = new AbortController();
|
|
738
|
+
const boundTimer = setTimeout(() => boundCtl.abort(), io.boundMs);
|
|
739
|
+
const bound = boundCtl.signal;
|
|
740
|
+
let phase = "resumeVM";
|
|
741
|
+
let restoreAttempts = 1;
|
|
742
|
+
let vmRunning = false;
|
|
743
|
+
const work = (async () => {
|
|
744
|
+
const { outcome: back, attempts: backAttempts } = await restoreWorkspaceWithRetry(remoteEnv, snapshotId, { abortSignal: bound }, (attempt) => (restoreAttempts = attempt));
|
|
745
|
+
if (!back.ok) {
|
|
746
|
+
io.noteFailure(remoteEnvFailureNote("resumeVM", back.error, backAttempts));
|
|
747
|
+
io.disclose(back.error);
|
|
748
|
+
return { ok: false, reason: `resumeVM failed (${back.error.code}) while compensating an unparked pause` };
|
|
749
|
+
}
|
|
750
|
+
vmRunning = true;
|
|
751
|
+
if (bound.aborted) {
|
|
752
|
+
return { ok: false, reason: "resumeVM settled only after the decision bound" };
|
|
753
|
+
}
|
|
754
|
+
phase = "postResumeInit";
|
|
755
|
+
const init = await remoteEnv.postResumeInit({ abortSignal: bound });
|
|
756
|
+
if (!init.ok) {
|
|
757
|
+
io.noteFailure(remoteEnvFailureNote("postResumeInit", init.error, 1));
|
|
758
|
+
io.disclose(init.error);
|
|
759
|
+
return { ok: false, reason: `postResumeInit failed (${init.error.code}) while compensating an unparked pause` };
|
|
760
|
+
}
|
|
761
|
+
return { ok: true };
|
|
762
|
+
})();
|
|
763
|
+
const raced = await raceSettlementAgainstSignal(work, bound);
|
|
764
|
+
clearTimeout(boundTimer);
|
|
765
|
+
if (raced.tag === "value")
|
|
766
|
+
return raced.value;
|
|
767
|
+
if (raced.tag === "threw") {
|
|
768
|
+
io.noteFailure(remoteEnvFailureNote(phase, raced.error instanceof RemoteExecutionError
|
|
769
|
+
? raced.error
|
|
770
|
+
: new RemoteExecutionError("unknown", `the pause compensation's ${phase} call threw: ${raced.error instanceof Error ? raced.error.message : String(raced.error)}`), phase === "resumeVM" ? restoreAttempts : 1));
|
|
771
|
+
io.disclose(raced.error);
|
|
772
|
+
return {
|
|
773
|
+
ok: false,
|
|
774
|
+
reason: "the pause compensation threw (adapter contract violation; the deployment's error face carries the exception)",
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
const timedOut = new RemoteExecutionError("timeout", `the pause compensation's ${phase} call did not settle within the ${io.boundMs}ms decision bound`);
|
|
778
|
+
io.noteFailure(remoteEnvFailureNote(phase, timedOut, phase === "resumeVM" ? restoreAttempts : 1));
|
|
779
|
+
io.disclose(timedOut);
|
|
780
|
+
const destroyOwnerlessLate = () => {
|
|
781
|
+
io.disclose(new Error(`park compensation settled after its ${io.boundMs}ms decision bound: the VM is running with no run ` +
|
|
782
|
+
`left to own it — destroying the env (an ownerless running VM must not linger; the run already took ` +
|
|
783
|
+
`the fail-closed arm when the bound fired)`));
|
|
784
|
+
void Promise.resolve()
|
|
785
|
+
.then(() => remoteEnv.destroy())
|
|
786
|
+
.catch((destroyErr) => io.disclose(destroyErr));
|
|
787
|
+
};
|
|
788
|
+
void work.then((late) => {
|
|
789
|
+
if (vmRunning) {
|
|
790
|
+
destroyOwnerlessLate();
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
void late;
|
|
794
|
+
}, (lateErr) => {
|
|
795
|
+
io.disclose(lateErr);
|
|
796
|
+
if (vmRunning)
|
|
797
|
+
destroyOwnerlessLate();
|
|
798
|
+
});
|
|
799
|
+
return {
|
|
800
|
+
ok: false,
|
|
801
|
+
reason: `the pause compensation did not settle within ${io.boundMs}ms — treated as failed (a late-restored VM is destroyed; a late restore failure is disclosed)`,
|
|
802
|
+
};
|
|
803
|
+
}
|
|
665
804
|
function raceAbort(p, signal, onAbort) {
|
|
666
805
|
if (signal.aborted)
|
|
667
806
|
return Promise.resolve(onAbort());
|
|
@@ -818,6 +957,87 @@ function persistedRuleHitOf(admitting) {
|
|
|
818
957
|
? undefined
|
|
819
958
|
: { rules: admitting.map((r) => ({ rule: r.rule, dots: r.adds.map((a) => ({ actor: a.dot.actor, counter: a.dot.counter })) })) };
|
|
820
959
|
}
|
|
960
|
+
function directoryRuleLaneAnswer(table, args, ctx) {
|
|
961
|
+
const filePath = args?.file_path;
|
|
962
|
+
if (typeof filePath !== "string" || filePath === "")
|
|
963
|
+
return undefined;
|
|
964
|
+
const spelled = filePath.startsWith("/") ? filePath : `${(ctx.liveCwd ?? ctx.root ?? "").replace(/\/+$/, "")}/${filePath}`;
|
|
965
|
+
const target = lexicalNormalAbsolutePathOf(spelled);
|
|
966
|
+
if (target === undefined)
|
|
967
|
+
return undefined;
|
|
968
|
+
const hit = table.find((r) => eligiblePersisted(r, { tool: DIRECTORY_RULE_TOOL, cwd: ctx.root, sessionId: ctx.sessionId }) && directoryRuleAdmits(r, target));
|
|
969
|
+
return hit !== undefined ? persistedRuleHitOf([hit]) : undefined;
|
|
970
|
+
}
|
|
971
|
+
function makeRuleOffersOf(cfg) {
|
|
972
|
+
return (toolName, args, ask) => {
|
|
973
|
+
if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
|
|
974
|
+
return {};
|
|
975
|
+
if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
|
|
976
|
+
return {};
|
|
977
|
+
const closedDoor = (() => {
|
|
978
|
+
if (ask?.requiresRealApproval === true)
|
|
979
|
+
return "mandated";
|
|
980
|
+
if (ask?.persistedRuleShadowed !== undefined)
|
|
981
|
+
return "shadowed";
|
|
982
|
+
if (ask?.decisionReason === "hook")
|
|
983
|
+
return "mandated";
|
|
984
|
+
if (ask?.matchedAskRule !== undefined)
|
|
985
|
+
return "shadowed";
|
|
986
|
+
if (ask?.inheritedUnresolved === true)
|
|
987
|
+
return "mandated";
|
|
988
|
+
if (ask?.ancestorResolved === true)
|
|
989
|
+
return "mandated";
|
|
990
|
+
return undefined;
|
|
991
|
+
})();
|
|
992
|
+
if (closedDoor !== undefined)
|
|
993
|
+
return { ruleOffersAbsence: closedDoor };
|
|
994
|
+
if (persistedRuleMandateOf({
|
|
995
|
+
egress: cfg.egressTools.has(toolName),
|
|
996
|
+
irreversibility: cfg.irreversibilityTier.get(toolName),
|
|
997
|
+
shellGated: cfg.shellGatedBash,
|
|
998
|
+
probeMandated: ask?.probeMandated === true,
|
|
999
|
+
}) !== undefined) {
|
|
1000
|
+
return { ruleOffersAbsence: "mandated" };
|
|
1001
|
+
}
|
|
1002
|
+
const command = args?.command;
|
|
1003
|
+
if (typeof command !== "string")
|
|
1004
|
+
return { ruleOffersAbsence: "lane_cannot_speak" };
|
|
1005
|
+
const offers = suggestRulesForCommand(command, {
|
|
1006
|
+
...(ask?.segmentCoverage !== undefined ? { coverage: ask.segmentCoverage } : {}),
|
|
1007
|
+
...(cfg.taskRoot !== undefined ? { scope: { kind: "project", root: cfg.taskRoot }, cwd: cfg.taskRoot } : {}),
|
|
1008
|
+
...(cfg.cwdRef?.current !== undefined ? { execCwd: cfg.cwdRef.current } : {}),
|
|
1009
|
+
...(cfg.deniesDirectoryRead !== undefined ? { deniesDirectoryRead: cfg.deniesDirectoryRead } : {}),
|
|
1010
|
+
});
|
|
1011
|
+
return offers.length > 0
|
|
1012
|
+
? { ruleOffers: offers, ...(cfg.cwdRef?.current !== undefined ? { execCwd: cfg.cwdRef.current } : {}) }
|
|
1013
|
+
: { ruleOffersAbsence: "lane_cannot_speak" };
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
function makeOrgAdjudicationLane(overlay, questionToolMounted, onRevisionDefect) {
|
|
1017
|
+
if (overlay === undefined)
|
|
1018
|
+
return undefined;
|
|
1019
|
+
return {
|
|
1020
|
+
adjudicate: async (req) => {
|
|
1021
|
+
if (req.toolName === ASK_USER_QUESTION_TOOL_NAME && questionToolMounted)
|
|
1022
|
+
return { status: "available" };
|
|
1023
|
+
let resolution;
|
|
1024
|
+
try {
|
|
1025
|
+
resolution = await overlay.resolve();
|
|
1026
|
+
}
|
|
1027
|
+
catch (err) {
|
|
1028
|
+
return { status: "unavailable", disclosures: [`the org rule overlay threw: ${err instanceof Error ? err.message : String(err)}`] };
|
|
1029
|
+
}
|
|
1030
|
+
if (resolution.status === "unavailable")
|
|
1031
|
+
return { status: "unavailable", disclosures: resolution.disclosures };
|
|
1032
|
+
const revisionCell = orgRevisionEvidenceOf(resolution, onRevisionDefect);
|
|
1033
|
+
const command = req.args?.command;
|
|
1034
|
+
if (req.toolName !== PERSISTED_RULE_TOOL || typeof command !== "string")
|
|
1035
|
+
return { status: "available", ...revisionCell };
|
|
1036
|
+
const verdict = orgRuleVerdictFor(resolution.rules, { tool: req.toolName, command });
|
|
1037
|
+
return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
|
|
1038
|
+
},
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
821
1041
|
function cwdConflictsRestoreError(requestedCwd) {
|
|
822
1042
|
const e = new Error(`RunInternals.requestedCwd ("${requestedCwd}") cannot be combined with a checkpoint workspace restore — ` +
|
|
823
1043
|
`the restored workspace's own mount path is authoritative for the task root, so a requested cwd on this leg ` +
|
|
@@ -1666,7 +1886,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1666
1886
|
...(am.onBreakerOpen !== undefined ? { onBreakerOpen: am.onBreakerOpen } : {}),
|
|
1667
1887
|
classify: async (input, signal) => {
|
|
1668
1888
|
const ctx = await session.buildContext();
|
|
1669
|
-
const known = ctx.messages.filter((m) => m
|
|
1889
|
+
const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) &&
|
|
1890
|
+
(m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
|
|
1670
1891
|
const userPrompt = renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
|
|
1671
1892
|
const classifierAuth = await spec.getApiKeyAndHeaders?.(classifierModel);
|
|
1672
1893
|
const response = await classifierRuntime.completeSimple(classifierModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }] }, {
|
|
@@ -1985,10 +2206,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1985
2206
|
handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
|
|
1986
2207
|
shellGatedBash = handsReadFace.shellGatedBash;
|
|
1987
2208
|
shellGatedMonitor = handsReadFace.shellGatedMonitor;
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2209
|
+
const delegationSurfaceActive = backgroundTaskToolsActive || workflowToolsActive;
|
|
2210
|
+
if (delegationSurfaceActive || internals?.parentNotify !== undefined) {
|
|
2211
|
+
if (delegationSurfaceActive) {
|
|
2212
|
+
toolEffects.set("TaskOutput", "read");
|
|
2213
|
+
toolEffects.set("TaskStop", "write");
|
|
2214
|
+
tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
|
|
2215
|
+
}
|
|
1992
2216
|
if (runnerSelf && !(spec.tools ?? []).some((t) => t.name === SEND_MESSAGE_TOOL_NAME)) {
|
|
1993
2217
|
toolEffects.set(SEND_MESSAGE_TOOL_NAME, "write");
|
|
1994
2218
|
axisExplicitNegatives.set(SEND_MESSAGE_TOOL_NAME, { ...axisExplicitNegatives.get(SEND_MESSAGE_TOOL_NAME), egress: false });
|
|
@@ -2018,7 +2242,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2018
2242
|
owner: hostTaskId,
|
|
2019
2243
|
scope: taskScope,
|
|
2020
2244
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
2021
|
-
...(internals?.onTaskNotification !== undefined ? { notify: internals.onTaskNotification } : {}), ...(spec.oneShot !== undefined ? { oneShot: spec.oneShot } : {}),
|
|
2245
|
+
...(internals?.onTaskNotification !== undefined ? { notify: internals.onTaskNotification } : {}), ...(spec.oneShot !== undefined ? { oneShot: spec.oneShot } : {}), retrievalToolMounted: delegationSurfaceActive,
|
|
2022
2246
|
...(internals?.onSubagentSpawn !== undefined ? { sink: internals.onSubagentSpawn } : {}),
|
|
2023
2247
|
...(internals?.parentNotify !== undefined
|
|
2024
2248
|
? { uplink: internals.parentNotify, ...(internals.parentPeerRef !== undefined ? { uplinkRecipient: internals.parentPeerRef } : {}) }
|
|
@@ -2043,7 +2267,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2043
2267
|
detail: { handle, ...(scope !== undefined ? { scope } : {}) },
|
|
2044
2268
|
}),
|
|
2045
2269
|
})));
|
|
2046
|
-
if (!(spec.tools ?? []).some((t) => t.name === AGENT_TRANSCRIPT_TOOL_NAME)) {
|
|
2270
|
+
if (delegationSurfaceActive && !(spec.tools ?? []).some((t) => t.name === AGENT_TRANSCRIPT_TOOL_NAME)) {
|
|
2047
2271
|
tools.push(firstPartyOffload(createAgentTranscriptTool({
|
|
2048
2272
|
runner: runnerSelf,
|
|
2049
2273
|
registry: defaultTaskRegistry,
|
|
@@ -3487,14 +3711,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3487
3711
|
return true;
|
|
3488
3712
|
};
|
|
3489
3713
|
const inheritedAskGrants = new Map();
|
|
3490
|
-
const askGrantShapeOf = (args) => {
|
|
3491
|
-
try {
|
|
3492
|
-
return JSON.stringify(args ?? null);
|
|
3493
|
-
}
|
|
3494
|
-
catch {
|
|
3495
|
-
return undefined;
|
|
3496
|
-
}
|
|
3497
|
-
};
|
|
3498
3714
|
const recordInheritedAskGrant = (toolCallId, approver, presented, waitMs) => {
|
|
3499
3715
|
if (typeof approver !== "function")
|
|
3500
3716
|
return;
|
|
@@ -3615,10 +3831,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3615
3831
|
const anonymous = spec.principal === undefined || spec.principal === "";
|
|
3616
3832
|
if (anonymous && !localOwnerDeclared)
|
|
3617
3833
|
return undefined;
|
|
3618
|
-
if (req.toolName !== PERSISTED_RULE_TOOL)
|
|
3619
|
-
return undefined;
|
|
3620
|
-
const command = req.args?.command;
|
|
3621
|
-
if (typeof command !== "string")
|
|
3834
|
+
if (req.toolName !== PERSISTED_RULE_TOOL && req.toolName !== DIRECTORY_RULE_TOOL)
|
|
3622
3835
|
return undefined;
|
|
3623
3836
|
let listed;
|
|
3624
3837
|
try {
|
|
@@ -3638,76 +3851,32 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3638
3851
|
return { unreadable: true };
|
|
3639
3852
|
}
|
|
3640
3853
|
const table = await spliceSessionOverlayRows(deps.sessionPermissionRules, sessionId, listed.rules, deps.tracer, hostTaskId);
|
|
3641
|
-
|
|
3854
|
+
if (req.toolName === DIRECTORY_RULE_TOOL) {
|
|
3855
|
+
return directoryRuleLaneAnswer(table, req.args, { root, sessionId, liveCwd: handsCwdRef?.current });
|
|
3856
|
+
}
|
|
3857
|
+
const command = req.args?.command;
|
|
3858
|
+
if (typeof command !== "string")
|
|
3859
|
+
return undefined;
|
|
3860
|
+
const admitting = findAdmittingRule(table, { tool: req.toolName, command, cwd: root, sessionId, ...(handsCwdRef?.current !== undefined ? { execCwd: handsCwdRef.current } : {}) });
|
|
3642
3861
|
if (admitting !== undefined)
|
|
3643
3862
|
return persistedRuleHitOf(admitting);
|
|
3644
|
-
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: root, sessionId });
|
|
3863
|
+
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: root, sessionId, ...(handsCwdRef?.current !== undefined ? { execCwd: handsCwdRef.current } : {}) });
|
|
3645
3864
|
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
|
|
3646
3865
|
},
|
|
3647
3866
|
};
|
|
3648
3867
|
})();
|
|
3649
|
-
const permissionRuleOrgLane = (() => {
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
catch (err) {
|
|
3662
|
-
return { status: "unavailable", disclosures: [`the org rule overlay threw: ${err instanceof Error ? err.message : String(err)}`] };
|
|
3663
|
-
}
|
|
3664
|
-
if (resolution.status === "unavailable")
|
|
3665
|
-
return { status: "unavailable", disclosures: resolution.disclosures };
|
|
3666
|
-
const revisionCell = orgRevisionEvidenceOf(resolution, (message) => deps.onError?.(new Error(message), { phase: "config", sessionId }));
|
|
3667
|
-
const command = req.args?.command;
|
|
3668
|
-
if (req.toolName !== PERSISTED_RULE_TOOL || typeof command !== "string")
|
|
3669
|
-
return { status: "available", ...revisionCell };
|
|
3670
|
-
const verdict = orgRuleVerdictFor(resolution.rules, { tool: req.toolName, command });
|
|
3671
|
-
return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
|
|
3672
|
-
},
|
|
3673
|
-
};
|
|
3674
|
-
})();
|
|
3675
|
-
const ruleOffersOf = (toolName, args, ask) => {
|
|
3676
|
-
if (permissionRuleLane === undefined || toolName !== PERSISTED_RULE_TOOL)
|
|
3677
|
-
return {};
|
|
3678
|
-
if ((spec.principal === undefined || spec.principal === "") && deps.localOwnerRules !== true)
|
|
3679
|
-
return {};
|
|
3680
|
-
const closedDoor = (() => {
|
|
3681
|
-
if (ask?.requiresRealApproval === true)
|
|
3682
|
-
return "mandated";
|
|
3683
|
-
if (ask?.persistedRuleShadowed !== undefined)
|
|
3684
|
-
return "shadowed";
|
|
3685
|
-
if (ask?.decisionReason === "hook")
|
|
3686
|
-
return "mandated";
|
|
3687
|
-
if (ask?.matchedAskRule !== undefined)
|
|
3688
|
-
return "shadowed";
|
|
3689
|
-
if (ask?.inheritedUnresolved === true)
|
|
3690
|
-
return "mandated";
|
|
3691
|
-
if (ask?.ancestorResolved === true)
|
|
3692
|
-
return "mandated";
|
|
3693
|
-
return undefined;
|
|
3694
|
-
})();
|
|
3695
|
-
if (closedDoor !== undefined)
|
|
3696
|
-
return { ruleOffersAbsence: closedDoor };
|
|
3697
|
-
if (persistedRuleMandateOf({
|
|
3698
|
-
egress: egressTools.has(toolName),
|
|
3699
|
-
irreversibility: irreversibilityTier.get(toolName),
|
|
3700
|
-
shellGated: shellGatedBash,
|
|
3701
|
-
probeMandated: ask?.probeMandated === true,
|
|
3702
|
-
}) !== undefined) {
|
|
3703
|
-
return { ruleOffersAbsence: "mandated" };
|
|
3704
|
-
}
|
|
3705
|
-
const command = args?.command;
|
|
3706
|
-
if (typeof command !== "string")
|
|
3707
|
-
return { ruleOffersAbsence: "lane_cannot_speak" };
|
|
3708
|
-
const offers = suggestRulesForCommand(command, ask?.segmentCoverage !== undefined ? { coverage: ask.segmentCoverage } : undefined);
|
|
3709
|
-
return offers.length > 0 ? { ruleOffers: offers } : { ruleOffersAbsence: "lane_cannot_speak" };
|
|
3710
|
-
};
|
|
3868
|
+
const permissionRuleOrgLane = makeOrgAdjudicationLane(deps.permissionRuleOrg, questionToolMounted, (message) => deps.onError?.(new Error(message), { phase: "config", sessionId }));
|
|
3869
|
+
const ruleOffersOf = makeRuleOffersOf({
|
|
3870
|
+
laneArmed: permissionRuleLane !== undefined,
|
|
3871
|
+
principal: spec.principal,
|
|
3872
|
+
localOwnerDeclared: deps.localOwnerRules === true,
|
|
3873
|
+
egressTools,
|
|
3874
|
+
irreversibilityTier,
|
|
3875
|
+
shellGatedBash,
|
|
3876
|
+
taskRoot: taskRootFinal,
|
|
3877
|
+
cwdRef: handsCwdRef,
|
|
3878
|
+
deniesDirectoryRead: readDenyMatcher !== undefined ? (d) => readDenyMatcher.matchPath(d) !== null : undefined,
|
|
3879
|
+
});
|
|
3711
3880
|
const frozenClassifierExcluded = (d) => d.decisionReason === "hook" || d.matchedAskRule !== undefined;
|
|
3712
3881
|
const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider) => {
|
|
3713
3882
|
let editArgs = edit;
|
|
@@ -4159,14 +4328,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4159
4328
|
});
|
|
4160
4329
|
}
|
|
4161
4330
|
if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
|
|
4331
|
+
const composedCallSignal = (callSignal) => composeCallSignal(abortController.signal, callSignal);
|
|
4162
4332
|
const adjudicate = effectivePolicy
|
|
4163
|
-
? (req
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4333
|
+
? (req, callSignal) => {
|
|
4334
|
+
const signal = composedCallSignal(callSignal);
|
|
4335
|
+
return raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}) }, signal)), signal, () => ({
|
|
4336
|
+
action: "deny",
|
|
4337
|
+
message: "policy check aborted (task timed out or cancelled)",
|
|
4338
|
+
}));
|
|
4339
|
+
}
|
|
4167
4340
|
: undefined;
|
|
4168
4341
|
const approvalPreviewOf = (toolName, args) => resolveApprovalPreview(tools, toolName, args);
|
|
4169
|
-
const resolveAskBound = async (decision, req) => {
|
|
4342
|
+
const resolveAskBound = async (decision, req, callSignal) => {
|
|
4170
4343
|
if (inheritedUnavailableAsks.delete(req.toolCallId)) {
|
|
4171
4344
|
return {
|
|
4172
4345
|
action: "deny",
|
|
@@ -4177,23 +4350,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4177
4350
|
approverUnavailable: true,
|
|
4178
4351
|
};
|
|
4179
4352
|
}
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
inheritedAskGrants.delete(req.toolCallId);
|
|
4184
|
-
humanReviewRef.count += 1;
|
|
4185
|
-
humanReviewRef.totalWaitMs += grant.waitMs;
|
|
4186
|
-
const toolArg = primaryActivityArg(req.args);
|
|
4187
|
-
humanReviewRef.gates.push({
|
|
4188
|
-
kind: "human",
|
|
4189
|
-
waitMs: grant.waitMs,
|
|
4190
|
-
decision: "allow",
|
|
4191
|
-
toolName: req.toolName,
|
|
4192
|
-
...(toolArg !== undefined ? { toolArg } : {}),
|
|
4193
|
-
});
|
|
4194
|
-
return { action: "allow", presentedInput: grant.presented };
|
|
4195
|
-
}
|
|
4196
|
-
}
|
|
4353
|
+
const grantReuse = consumeInheritedAskGrant(inheritedAskGrants, onAsk, humanReviewRef, decision, req);
|
|
4354
|
+
if (grantReuse !== undefined)
|
|
4355
|
+
return grantReuse;
|
|
4197
4356
|
const t0 = now();
|
|
4198
4357
|
const resolved = await resolveAsk({
|
|
4199
4358
|
toolName: req.toolName,
|
|
@@ -4212,9 +4371,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4212
4371
|
...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
|
|
4213
4372
|
...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
|
|
4214
4373
|
...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
|
|
4215
|
-
}, onAsk,
|
|
4374
|
+
}, onAsk, composedCallSignal(callSignal), lateAskSettlementObserver({ toolName: req.toolName, toolCallId: req.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
|
|
4216
4375
|
const waitMs = Math.max(0, now() - t0);
|
|
4217
|
-
if (resolved.approverUnavailable !== true) {
|
|
4376
|
+
if (resolved.approverUnavailable !== true && resolved.resolution !== "task_aborted") {
|
|
4218
4377
|
humanReviewRef.count += 1;
|
|
4219
4378
|
humanReviewRef.totalWaitMs += waitMs;
|
|
4220
4379
|
const toolArg = primaryActivityArg(req.args);
|
|
@@ -4325,11 +4484,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4325
4484
|
placementRootSessionId: stampPlacementRootSessionId(placementRootResolved),
|
|
4326
4485
|
externalContentTarget: externalContentTargetActive ? true : undefined,
|
|
4327
4486
|
});
|
|
4328
|
-
const
|
|
4487
|
+
const compensatePausedVM = (remoteEnv, snapshotId) => compensateUnparkedPause(remoteEnv, snapshotId, {
|
|
4488
|
+
boundMs: PARK_COMPENSATION_TIMEOUT_MS,
|
|
4489
|
+
noteFailure: (note) => remoteEnvFailures.push(note),
|
|
4490
|
+
disclose: (err) => {
|
|
4491
|
+
try {
|
|
4492
|
+
deps.onError?.(err, { phase: "config", sessionId });
|
|
4493
|
+
}
|
|
4494
|
+
catch {
|
|
4495
|
+
}
|
|
4496
|
+
},
|
|
4497
|
+
});
|
|
4498
|
+
const commitSuspendSaga = async (token, cp, remoteEnv, remoteHandle, cutSignal) => {
|
|
4329
4499
|
if (!checkpointStore)
|
|
4330
|
-
return {
|
|
4500
|
+
return { tag: "absent" };
|
|
4331
4501
|
if (memoryEngineSession)
|
|
4332
4502
|
await memoryEngineSession.harvest("checkpoint");
|
|
4503
|
+
if (cutSignal?.aborted) {
|
|
4504
|
+
if (remoteEnv !== undefined && remoteHandle?.snapshotId !== undefined) {
|
|
4505
|
+
const back = await compensatePausedVM(remoteEnv, remoteHandle.snapshotId);
|
|
4506
|
+
if (!back.ok) {
|
|
4507
|
+
abortController.abort();
|
|
4508
|
+
void harness.abort();
|
|
4509
|
+
return { tag: "compensation_failed", reason: back.reason };
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
return { tag: "cut" };
|
|
4513
|
+
}
|
|
4333
4514
|
const announceCommittedScreeningPark = () => {
|
|
4334
4515
|
const committedCount = cp.state.inheritedGate?.parentConstraintCount;
|
|
4335
4516
|
if (cp.state.inheritedGate?.requiresParentConstraint === true &&
|
|
@@ -4367,7 +4548,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4367
4548
|
try {
|
|
4368
4549
|
await checkpointStore.put(token, cp);
|
|
4369
4550
|
announceCommittedScreeningPark();
|
|
4370
|
-
return {
|
|
4551
|
+
return { tag: "committed" };
|
|
4371
4552
|
}
|
|
4372
4553
|
catch (putErr) {
|
|
4373
4554
|
deps.onError?.(putErr, { phase: "config", sessionId });
|
|
@@ -4383,7 +4564,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4383
4564
|
catch {
|
|
4384
4565
|
}
|
|
4385
4566
|
announceCommittedScreeningPark();
|
|
4386
|
-
return {
|
|
4567
|
+
return { tag: "committed" };
|
|
4387
4568
|
}
|
|
4388
4569
|
if (confirmed === "unknown") {
|
|
4389
4570
|
const unknownReason = "the approval checkpoint's state cannot be established (the store rejected the write and then could not be read back; a committed-but-unacknowledged row may exist) — the run is stopped rather than continued past an approval whose durable record is unknown";
|
|
@@ -4398,29 +4579,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4398
4579
|
}
|
|
4399
4580
|
abortController.abort();
|
|
4400
4581
|
void harness.abort();
|
|
4401
|
-
return {
|
|
4582
|
+
return { tag: "unknown", reason: unknownReason };
|
|
4402
4583
|
}
|
|
4403
4584
|
const reason = "the approval checkpoint could not be persisted (the store rejected the write; the deployment's error face carries the store's own message)";
|
|
4404
4585
|
if (remoteEnv !== undefined && remoteHandle?.snapshotId !== undefined) {
|
|
4405
|
-
const
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
if (back.ok) {
|
|
4409
|
-
const init = await remoteEnv.postResumeInit();
|
|
4410
|
-
if (init.ok)
|
|
4411
|
-
return { ok: false, reason };
|
|
4412
|
-
remoteEnvFailures.push(remoteEnvFailureNote("postResumeInit", init.error, 1));
|
|
4413
|
-
deps.onError?.(init.error, { phase: "config", sessionId });
|
|
4414
|
-
}
|
|
4415
|
-
else {
|
|
4416
|
-
remoteEnvFailures.push(remoteEnvFailureNote("resumeVM", back.error, backAttempts));
|
|
4417
|
-
deps.onError?.(back.error, { phase: "config", sessionId });
|
|
4418
|
-
}
|
|
4586
|
+
const back = await compensatePausedVM(remoteEnv, remoteHandle.snapshotId);
|
|
4587
|
+
if (back.ok)
|
|
4588
|
+
return { tag: "absent", reason };
|
|
4419
4589
|
abortController.abort();
|
|
4420
4590
|
void harness.abort();
|
|
4421
|
-
return {
|
|
4591
|
+
return { tag: "compensation_failed", reason };
|
|
4422
4592
|
}
|
|
4423
|
-
return {
|
|
4593
|
+
return { tag: "absent", reason };
|
|
4424
4594
|
}
|
|
4425
4595
|
};
|
|
4426
4596
|
const suspendLoopCapHit = (count, cap, detail) => {
|
|
@@ -4502,7 +4672,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4502
4672
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
4503
4673
|
...(spec.principal ? { principal: spec.principal } : {}),
|
|
4504
4674
|
};
|
|
4505
|
-
if (
|
|
4675
|
+
if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
|
|
4506
4676
|
return false;
|
|
4507
4677
|
publishCommittedSuspend({ suspendRef, reviewRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
|
|
4508
4678
|
try {
|
|
@@ -4588,7 +4758,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4588
4758
|
...(spec.principal ? { principal: spec.principal } : {}),
|
|
4589
4759
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
4590
4760
|
};
|
|
4591
|
-
if (
|
|
4761
|
+
if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
|
|
4592
4762
|
return false;
|
|
4593
4763
|
publishCommittedSuspend({ suspendRef, reviewRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
|
|
4594
4764
|
try {
|
|
@@ -4602,7 +4772,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4602
4772
|
return true;
|
|
4603
4773
|
}
|
|
4604
4774
|
: undefined;
|
|
4605
|
-
const resolveContentAsk = async (req) => {
|
|
4775
|
+
const resolveContentAsk = async (req, callSignal) => {
|
|
4776
|
+
const contentAskSignal = composedCallSignal(callSignal);
|
|
4606
4777
|
if (!contentAskRoutable(req.toolCallId) || liveQuestionFace === undefined || mountedQuestionTool === undefined) {
|
|
4607
4778
|
return { kind: "unavailable", parkDeclined: false };
|
|
4608
4779
|
}
|
|
@@ -4633,7 +4804,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4633
4804
|
if (contentAskBindings.size >= CONTENT_ASK_BINDING_CAP && !contentAskBindings.has(req.toolCallId)) {
|
|
4634
4805
|
return { kind: "unavailable", parkDeclined: true };
|
|
4635
4806
|
}
|
|
4636
|
-
if (
|
|
4807
|
+
if (contentAskSignal.aborted) {
|
|
4637
4808
|
return { kind: "delivery_failure", code: "question.aborted", presentedInput: retained };
|
|
4638
4809
|
}
|
|
4639
4810
|
try {
|
|
@@ -4644,8 +4815,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4644
4815
|
sourceTaskId: sessionId,
|
|
4645
4816
|
boundInputHash: boundInputHashOf(retained),
|
|
4646
4817
|
deliveryId,
|
|
4647
|
-
},
|
|
4648
|
-
const settlement = await raceAbort(facePromise.then((outcome) => ({ tag: "outcome", outcome }), (error) => ({ tag: "threw", error })),
|
|
4818
|
+
}, contentAskSignal))();
|
|
4819
|
+
const settlement = await raceAbort(facePromise.then((outcome) => ({ tag: "outcome", outcome }), (error) => ({ tag: "threw", error })), contentAskSignal, () => ({ tag: "aborted" }));
|
|
4649
4820
|
if (settlement.tag === "aborted") {
|
|
4650
4821
|
void facePromise.then((late) => {
|
|
4651
4822
|
if (classifyQuestionOutcome(late).shape !== "answered")
|
|
@@ -4673,7 +4844,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4673
4844
|
bindOutcome({ kind: "failed", error }, retainedQuestionsHash);
|
|
4674
4845
|
return {
|
|
4675
4846
|
kind: "delivery_failure",
|
|
4676
|
-
code: settlement.tag === "aborted" ||
|
|
4847
|
+
code: settlement.tag === "aborted" || contentAskSignal.aborted ? "question.aborted" : "question.human_channel_failed",
|
|
4677
4848
|
presentedInput: retained,
|
|
4678
4849
|
};
|
|
4679
4850
|
}
|
|
@@ -4683,7 +4854,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4683
4854
|
}
|
|
4684
4855
|
};
|
|
4685
4856
|
const suspendAsk = parkLaneArmed && checkpointStore !== undefined
|
|
4686
|
-
? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated) => {
|
|
4857
|
+
? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated, callSignal) => {
|
|
4687
4858
|
const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
|
|
4688
4859
|
if (syncFirstEligible &&
|
|
4689
4860
|
runtimeCaps?.forceDurableGate !== true &&
|
|
@@ -4691,6 +4862,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4691
4862
|
!inheritedUnavailableAsks.has(req.toolCallId)) {
|
|
4692
4863
|
return undefined;
|
|
4693
4864
|
}
|
|
4865
|
+
const cutSignal = composedCallSignal(callSignal);
|
|
4694
4866
|
let token;
|
|
4695
4867
|
let gate;
|
|
4696
4868
|
let cp;
|
|
@@ -4726,13 +4898,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4726
4898
|
if (!presented.ok) {
|
|
4727
4899
|
throw new ParkRefusal(`the stored form of "${req.toolName}"'s arguments could not be presented for re-adjudication (${describeThrown(presented.cause)})`, { cause: presented.cause });
|
|
4728
4900
|
}
|
|
4729
|
-
const
|
|
4901
|
+
const reprojectedRaced = await raceSettlementAgainstSignal(Promise.resolve(basePolicyForResumeEdit.check({
|
|
4730
4902
|
toolName: req.toolName,
|
|
4731
4903
|
args: presented.value,
|
|
4732
4904
|
toolCallId: req.toolCallId,
|
|
4733
4905
|
budget: budgetSnapshot,
|
|
4734
4906
|
...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}),
|
|
4735
|
-
},
|
|
4907
|
+
}, cutSignal)), cutSignal);
|
|
4908
|
+
if (reprojectedRaced.tag === "aborted")
|
|
4909
|
+
return undefined;
|
|
4910
|
+
if (reprojectedRaced.tag === "threw")
|
|
4911
|
+
throw reprojectedRaced.error;
|
|
4912
|
+
const reprojected = refuseOutOfContractDecision(reprojectedRaced.value);
|
|
4736
4913
|
const demanded = reprojected.updatedInput !== undefined ? tryCloneArgs(reprojected.updatedInput) : undefined;
|
|
4737
4914
|
const rewroteTheFiledValue = reprojected.updatedInput !== undefined &&
|
|
4738
4915
|
!(demanded?.ok === true &&
|
|
@@ -4763,7 +4940,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4763
4940
|
}
|
|
4764
4941
|
const { messages } = await session.buildContext();
|
|
4765
4942
|
const { batchToolCallIds, completedCallIds } = batchContextAt(messages, req.toolCallId);
|
|
4766
|
-
if (
|
|
4943
|
+
if (cutSignal.aborted) {
|
|
4767
4944
|
return undefined;
|
|
4768
4945
|
}
|
|
4769
4946
|
if (suspendLoopCapHit(suspendChainBase(), maxSuspends, ` for tool "${req.toolName}" — likely a resume/restart loop.`))
|
|
@@ -4771,12 +4948,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4771
4948
|
if (remoteEnv !== undefined) {
|
|
4772
4949
|
if (hasBackgroundShell(remoteEnv))
|
|
4773
4950
|
await sweepBackgroundShells(remoteEnv, defaultTaskRegistry);
|
|
4774
|
-
const snap = await remoteEnv.suspendVM({ abortSignal:
|
|
4951
|
+
const snap = await remoteEnv.suspendVM({ abortSignal: cutSignal });
|
|
4775
4952
|
if (!snap.ok) {
|
|
4776
4953
|
remoteEnvFailures.push(remoteEnvFailureNote("suspendVM", snap.error, 1));
|
|
4777
4954
|
throw new Error(`suspendVM failed (${snap.error.code}): ${snap.error.message}`, { cause: snap.error });
|
|
4778
4955
|
}
|
|
4779
4956
|
remoteHandle = { ...remoteEnv.workspaceHandle(), snapshotId: snap.value };
|
|
4957
|
+
if (cutSignal.aborted) {
|
|
4958
|
+
const back = await compensatePausedVM(remoteEnv, snap.value);
|
|
4959
|
+
if (!back.ok) {
|
|
4960
|
+
abortController.abort();
|
|
4961
|
+
void harness.abort();
|
|
4962
|
+
}
|
|
4963
|
+
return undefined;
|
|
4964
|
+
}
|
|
4780
4965
|
}
|
|
4781
4966
|
else if (parkOnlyRemoteEnv !== undefined) {
|
|
4782
4967
|
if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
|
|
@@ -4882,16 +5067,31 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4882
5067
|
};
|
|
4883
5068
|
}
|
|
4884
5069
|
catch (err) {
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
:
|
|
4890
|
-
|
|
5070
|
+
const cutWon = cutSignal.aborted;
|
|
5071
|
+
const preselected = cutWon
|
|
5072
|
+
? undefined
|
|
5073
|
+
: {
|
|
5074
|
+
parkFailed: err instanceof ParkRefusal
|
|
5075
|
+
? err.message
|
|
5076
|
+
: "the durable approval park could not be prepared (the deployment's error face carries the exception)",
|
|
5077
|
+
};
|
|
5078
|
+
try {
|
|
5079
|
+
deps.onError?.(err, { phase: "config", sessionId });
|
|
5080
|
+
}
|
|
5081
|
+
catch {
|
|
5082
|
+
}
|
|
5083
|
+
return preselected;
|
|
4891
5084
|
}
|
|
4892
|
-
const committed = await commitSuspendSaga(token, cp, remoteEnv, remoteHandle);
|
|
4893
|
-
if (
|
|
4894
|
-
|
|
5085
|
+
const committed = await commitSuspendSaga(token, cp, remoteEnv, remoteHandle, cutSignal);
|
|
5086
|
+
if (committed.tag !== "committed") {
|
|
5087
|
+
if (committed.tag === "cut")
|
|
5088
|
+
return undefined;
|
|
5089
|
+
if (committed.tag === "absent") {
|
|
5090
|
+
if (cutSignal.aborted)
|
|
5091
|
+
return undefined;
|
|
5092
|
+
return committed.reason !== undefined ? { parkFailed: committed.reason } : undefined;
|
|
5093
|
+
}
|
|
5094
|
+
return { parkFailed: committed.reason };
|
|
4895
5095
|
}
|
|
4896
5096
|
publishCommittedSuspend({ suspendRef, reviewRef }, token, gate, cp.scope, remoteHandle, cp.checkpointId, cp.pendingAction);
|
|
4897
5097
|
try {
|
|
@@ -4956,6 +5156,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4956
5156
|
result = await runToolGate({
|
|
4957
5157
|
onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: hostTaskId, site: f.site, message: f.error.message, ts: Date.now() })),
|
|
4958
5158
|
event: e,
|
|
5159
|
+
...(e.signal !== undefined ? { callSignal: e.signal } : {}),
|
|
4959
5160
|
identity: hookIdentity,
|
|
4960
5161
|
reminderMark,
|
|
4961
5162
|
preToolUse: ownGatePreToolUse,
|