@sema-agent/core 7.38.3 → 7.39.1
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 +191 -6
- package/dist/core/ask-origin.d.ts +16 -7
- package/dist/core/ask-origin.js +4 -2
- package/dist/core/auto-mode-directive.d.ts +114 -19
- package/dist/core/auto-mode-directive.js +67 -19
- package/dist/core/checkpoint-store.d.ts +72 -41
- package/dist/core/checkpoint-store.js +28 -48
- package/dist/core/classifier-unavailable-arm.js +2 -2
- package/dist/core/decision-receipt.d.ts +2 -0
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/engine-notice.d.ts +41 -17
- package/dist/core/engine-notice.js +26 -14
- package/dist/core/gate-outcome.d.ts +80 -42
- package/dist/core/gate-outcome.js +21 -6
- package/dist/core/governance-codes.d.ts +1 -3
- package/dist/core/governance-codes.js +0 -3
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/hooks.js +2 -2
- package/dist/core/human-input-projection.d.ts +12 -0
- package/dist/core/human-input-projection.js +12 -0
- package/dist/core/memory-engine/engine.js +11 -11
- package/dist/core/memory-engine/layout.d.ts +21 -2
- package/dist/core/memory-engine/layout.js +17 -5
- package/dist/core/mode-policy.d.ts +26 -0
- package/dist/core/mode-policy.js +37 -1
- package/dist/core/output-style.d.ts +21 -0
- package/dist/core/output-style.js +29 -0
- package/dist/core/real-approval.d.ts +25 -0
- package/dist/core/real-approval.js +27 -0
- package/dist/core/reminder-disclosure.d.ts +2 -2
- package/dist/core/reminder-mint.d.ts +6 -9
- package/dist/core/reminder-mint.js +3 -14
- package/dist/core/retired-keys.d.ts +2 -1
- package/dist/core/retired-keys.js +3 -1
- package/dist/core/runner/assemble-result.d.ts +2 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/compaction-restate.d.ts +45 -4
- package/dist/core/runner/compaction-restate.js +89 -11
- package/dist/core/runner/contracts.d.ts +6 -6
- package/dist/core/runner/denial-limit-arms.js +4 -4
- package/dist/core/runner/inherited-ask-grants.d.ts +4 -2
- package/dist/core/runner/inherited-ask-grants.js +0 -3
- package/dist/core/runner/permission-rule-lanes.d.ts +18 -0
- package/dist/core/runner/permission-rule-lanes.js +35 -25
- package/dist/core/runner/prepare-context-lane.d.ts +1 -0
- package/dist/core/runner/prepare-context-lane.js +6 -0
- package/dist/core/runner/prepare-inherited-gate.js +2 -9
- package/dist/core/runner/prepare-task.js +10 -24
- package/dist/core/runner/prepare-wiring-manifest.js +2 -2
- package/dist/core/runner/prompt-block.d.ts +9 -0
- package/dist/core/runner/prompt-block.js +8 -0
- package/dist/core/runner/resume-admission.js +4 -1
- package/dist/core/runner/resume-apply.js +28 -18
- package/dist/core/runner/resume-policy-outcome.js +10 -4
- package/dist/core/runner/run-attachment-seats.js +1 -1
- package/dist/core/runner/run-identity-wiring.js +9 -2
- package/dist/core/runner/run-leg.js +42 -31
- package/dist/core/runner/run-settle-and-teardown.js +4 -3
- package/dist/core/runner/run-turn-boundary.js +4 -3
- package/dist/core/runner/runtask.d.ts +4 -2
- package/dist/core/runner/steer-admission.d.ts +9 -0
- package/dist/core/runner/steer-admission.js +8 -0
- package/dist/core/runner/stream-steer-verb.js +16 -11
- package/dist/core/runner/synthetic-tools.d.ts +4 -0
- package/dist/core/runner/tool-turn-repair-seat.js +1 -1
- package/dist/core/runner/turn-attachments.d.ts +16 -22
- package/dist/core/runner/turn-attachments.js +3 -20
- package/dist/core/runner-deps.d.ts +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +8 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
- package/dist/core/task-event.d.ts +7 -2
- package/dist/core/task-spec.d.ts +29 -7
- package/dist/core/terminal-cause.d.ts +44 -3
- package/dist/core/terminal-cause.js +5 -0
- package/dist/core/tool-policy.d.ts +55 -29
- package/dist/core/tool-policy.js +42 -31
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +17 -3
- package/dist/core/wiring-manifest.js +8 -3
- package/dist/engine/harness/agent-harness.d.ts +10 -0
- package/dist/engine/harness/agent-harness.js +12 -1
- package/dist/engine/llm/tool-turns.d.ts +15 -2
- package/dist/engine/llm/tool-turns.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/prompt-assembly/event-registry.js +2 -1
- package/dist/tools/fs/index.d.ts +2 -1
- package/dist/tools/fs/read-deny-home.d.ts +3 -2
- package/dist/tools/fs/read-deny-home.js +6 -4
- package/dist/tools/fs/shell-read-check.d.ts +10 -3
- package/dist/tools/fs/shell-read-check.js +2 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +21 -1
- package/wire-changes/core@7.39.0.json +999 -0
- package/wire-changes/core@7.39.1.json +74 -0
|
@@ -211,7 +211,7 @@ export interface FrameReceipt {
|
|
|
211
211
|
text: string;
|
|
212
212
|
/** Which frame this receipt settles. A boundary collector must not render a listing whose receipt is
|
|
213
213
|
* still pending (the frame is queued, not yet in the transcript). */
|
|
214
|
-
source: "git_status" | "agent_listing" | "skills_listing" | "date_change";
|
|
214
|
+
source: "git_status" | "agent_listing" | "skills_listing" | "date_change" | "output_style";
|
|
215
215
|
/** Runs inside the `message_end` walk (synchronous, never awaited): advances the run-local record
|
|
216
216
|
* and parks any session write for the next serialization point. */
|
|
217
217
|
commit: (entryId: string) => void;
|
|
@@ -283,8 +283,10 @@ export interface Prepared {
|
|
|
283
283
|
/** Whether the full shell is on this leg's roster (the inherited-gate phase's bit, asserted against the mount). */
|
|
284
284
|
fullShellReachable: boolean;
|
|
285
285
|
/** The run's approval-posture facts as prepared — the effective permission mode and the ask seam's effective
|
|
286
|
-
* answer. The
|
|
286
|
+
* answer. The posture composition renders them into the engine's own marked reminders; no gate decision keys on them. */
|
|
287
287
|
approvalPosture: import("../auto-mode-directive.js").ApprovalPostureFacts;
|
|
288
|
+
/** The output-style frame body, rendered once at prepare from the screened `TaskSpec.outputStyle` (absent = no seat). */
|
|
289
|
+
outputStyleFrame?: string;
|
|
288
290
|
/** The leg's live permission-mode seat: the effective mode every mode consumer reads, the steer verb writes. */
|
|
289
291
|
permissionMode: PermissionModeController;
|
|
290
292
|
/** The ISOLATION-AWARE working-tree root for this task (a worktree's cwd when `isolation: "worktree"`, else
|
|
@@ -1179,10 +1181,8 @@ export interface Prepared {
|
|
|
1179
1181
|
* handler matches on, and the re-assert closure the compaction landing + boundary retry call.
|
|
1180
1182
|
* Always present (empty object on a hands-less leg — the lane is then out of scope). */
|
|
1181
1183
|
gitStatusRef: GitStatusLaneRef;
|
|
1182
|
-
/** The posture
|
|
1183
|
-
postureFrameRef:
|
|
1184
|
-
protectedText?: string;
|
|
1185
|
-
};
|
|
1184
|
+
/** The posture composition's run-local half (the newest carrier's protection slot; the row last composed for). */
|
|
1185
|
+
postureFrameRef: import("../auto-mode-directive.js").PostureFrameRef;
|
|
1186
1186
|
/** G1 通告层 — narrow post-compact getter over the process task registry: THIS run's visible
|
|
1187
1187
|
* pending/running background tasks (same owner/scope/session identity the TaskOutput/TaskStop tools
|
|
1188
1188
|
* use), as a bounded display projection (id/description/status — never handles/env/abort). Called by
|
|
@@ -2,7 +2,7 @@ import { markTightenMessage } from "../safety-axis-vocab.js";
|
|
|
2
2
|
import { classifierParseFailureDenyMessage, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow } from "../auto-mode.js";
|
|
3
3
|
import { sanitizeAutoModeArmingRecipe, tightenDenialLimit } from "../auto-mode-arming.js";
|
|
4
4
|
import { classifierUnavailableArm } from "../classifier-unavailable-arm.js";
|
|
5
|
-
import { attestClassifierDenyCause, attestClassifierRound } from "../tool-policy.js";
|
|
5
|
+
import { attestClassifierDenyCause, attestClassifierRound, carryEngineAttestations } from "../tool-policy.js";
|
|
6
6
|
import { SETTLEMENT_NOBODY_JUDGED } from "../gate-outcome.js";
|
|
7
7
|
import { deliverEngineNotice } from "../types.js";
|
|
8
8
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
@@ -84,8 +84,8 @@ export const inheritedAskFallbackMessage = (toolName) => `approval required for
|
|
|
84
84
|
export async function judgeInheritedClassifier(opts) {
|
|
85
85
|
const { autoMode, req } = opts;
|
|
86
86
|
const facts = opts.facts ?? INHERITED_STATION_FACTS, m = facts.mandateMarks;
|
|
87
|
-
const frames =
|
|
88
|
-
const ask = (frames.length > 0 ? { ...opts.ask, message: [opts.ask.message ?? inheritedAskFallbackMessage(req.toolName), ...frames].join(" — ") } : opts.ask);
|
|
87
|
+
const frames = [...(m.egress === true ? [markTightenMessage(req.toolName, "egress")] : []), ...(m.irreversibility === "always" ? [markTightenMessage(req.toolName, "irreversible")] : [])];
|
|
88
|
+
const ask = (frames.length > 0 ? carryEngineAttestations(opts.ask, { ...opts.ask, message: [opts.ask.message ?? inheritedAskFallbackMessage(req.toolName), ...frames].join(" — ") }) : opts.ask);
|
|
89
89
|
const origin = askOriginOf(ask, facts);
|
|
90
90
|
const classification = classifyAsk(ask, facts);
|
|
91
91
|
const classifierStandsDown = () => {
|
|
@@ -123,7 +123,7 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
123
123
|
return { kind: "deny", result: attributed(verdict.kind === "parse_error" ? attestClassifierDenyCause(denied, "parse_error", req) : denied) };
|
|
124
124
|
}
|
|
125
125
|
const fallback = tracked.fallback;
|
|
126
|
-
const minted = { ...ask, message: denialLimitFallbackMessage(fallback, reason || category || req.toolName), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback };
|
|
126
|
+
const minted = carryEngineAttestations(ask, { ...ask, message: denialLimitFallbackMessage(fallback, reason || category || req.toolName), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback });
|
|
127
127
|
return { kind: "resolve", ask: minted, fallback, mintedHere: true, origin: askOriginOf(minted, facts), classification: classifyAsk(minted, facts) };
|
|
128
128
|
}
|
|
129
129
|
export function headlessDenyAtFold(r) {
|
|
@@ -31,8 +31,10 @@ export declare function askGrantShapeOf(args: unknown): string | undefined;
|
|
|
31
31
|
* the gate's own judges add (a protected target, an out-of-root read) was not on the card. Not covered ⇒ the gate
|
|
32
32
|
* asks its own question and the grant stays (it answers what it answered; the call-site sweep ends it). The class
|
|
33
33
|
* set is also the AUTHORITY test (a hook's ask carries `hook`, a fallback `denial_limit_fallback`, a marked tool
|
|
34
|
-
* `operator_forced`, a protected write `write_protection`, an out-of-root read `probe
|
|
35
|
-
*
|
|
34
|
+
* `operator_forced`, a protected write `write_protection`, an out-of-root read `probe`, the operator's shell floor
|
|
35
|
+
* `shell_floor` — a class no inherited station mints from its facts, so no station card answers it; the shell seat's classify-tier
|
|
36
|
+
* hesitation is `ordinary`, the one question a yes on the same bytes answers) — neither the ask's `decisionReason` nor
|
|
37
|
+
* its `mandate` word is read.
|
|
36
38
|
* · LEDGER: the ONE real consultation (timed on the wrapper arm) is booked here — one frame happened, one is booked.
|
|
37
39
|
* The grant lives within one gate evaluation (consumed on reuse, swept at the call site): a REOPENED checkpoint's
|
|
38
40
|
* re-adjudication never sees a stale grant.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { askMandateMember } from "../ask-origin.js";
|
|
2
1
|
import { primaryActivityArg } from "../arg-summary.js";
|
|
3
2
|
import { askApproverIdentity, carryEngineAttestations } from "../tool-policy.js";
|
|
4
3
|
export function askGrantCovers(granted, asked) {
|
|
@@ -18,8 +17,6 @@ export function consumeInheritedAskGrant(grants, onAsk, humanReviewRef, decision
|
|
|
18
17
|
return undefined;
|
|
19
18
|
if (!askGrantCovers(grant.classes, decision.action === "ask" ? decision.classes : undefined))
|
|
20
19
|
return undefined;
|
|
21
|
-
if (decision.action === "ask" && askMandateMember(decision).mandate === "operator_always")
|
|
22
|
-
return undefined;
|
|
23
20
|
grants.delete(req.toolCallId);
|
|
24
21
|
humanReviewRef.count += 1;
|
|
25
22
|
humanReviewRef.totalWaitMs += grant.waitMs;
|
|
@@ -120,6 +120,24 @@ export interface PermissionRuleLanes {
|
|
|
120
120
|
adjudicate: (req: ToolCallRequest) => Promise<OrgGateVerdict>;
|
|
121
121
|
} | undefined;
|
|
122
122
|
}
|
|
123
|
+
/** The ORG lane: the org's verdict about one call, off the store view `read` supplies — the gate's `orgRules` face over the
|
|
124
|
+
* per-call handoff ({@link createPermissionRuleLanes}), and {@link orgVerdictBeforeClaim} over a view of its own. */
|
|
125
|
+
export declare function orgRuleLane(cfg: {
|
|
126
|
+
read: (toolCallId: string) => Promise<EffectivePermissionRules>;
|
|
127
|
+
questionToolMounted: boolean;
|
|
128
|
+
questionToolName: string;
|
|
129
|
+
onRevisionDefect: (message: string) => void;
|
|
130
|
+
}): NonNullable<PermissionRuleLanes["org"]>;
|
|
131
|
+
/** The org's CURRENT verdict about a parked call, read by the resume entry before the claim (the row's session and principal,
|
|
132
|
+
* bounded like the gate's wait; unreadable ⇒ unavailable). The mount and a revision defect are the resumed leg's facts, so
|
|
133
|
+
* the reserved name is exempted and the defect left to that leg: this read can leave a question unasked, never invent one. */
|
|
134
|
+
export declare function orgVerdictBeforeClaim(cfg: {
|
|
135
|
+
provider: PermissionRuleStoreProvider;
|
|
136
|
+
principal: string | undefined;
|
|
137
|
+
localOwnerDeclared: boolean;
|
|
138
|
+
sessionId: string;
|
|
139
|
+
signal: AbortSignal | undefined;
|
|
140
|
+
}, req: ToolCallRequest): Promise<OrgGateVerdict>;
|
|
123
141
|
/**
|
|
124
142
|
* Build the gate's two permission-rule lanes over the unified store, resolved once per task.
|
|
125
143
|
*
|
|
@@ -5,7 +5,8 @@ import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
|
5
5
|
import { effectivePathTargetOf } from "../effective-path-target.js";
|
|
6
6
|
import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
|
|
7
7
|
import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
|
|
8
|
-
import { orgRuleVerdictFor } from "../permission-rule-org.js";
|
|
8
|
+
import { orgRuleVerdictFor, settleOrgVerdictWithin } from "../permission-rule-org.js";
|
|
9
|
+
import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
|
|
9
10
|
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
10
11
|
import { thrownText } from "../thrown-value.js";
|
|
11
12
|
import { strongestAnswer } from "../persisted-rule-arms.js";
|
|
@@ -46,6 +47,38 @@ export function pathRuleLaneAnswer(table, req, ctx) {
|
|
|
46
47
|
return undefined;
|
|
47
48
|
return persistedRuleAnswerOf(adjudicatePersistedPathRules(table, { tool: req.toolName, cwd: ctx.root, sessionId: ctx.sessionId }, { ...(tighten !== undefined ? { tighten } : {}), ...(allow !== undefined ? { allow } : {}) }, bases));
|
|
48
49
|
}
|
|
50
|
+
export function orgRuleLane(cfg) {
|
|
51
|
+
return {
|
|
52
|
+
adjudicate: async (req) => {
|
|
53
|
+
if (wireOf("offer.orgQuestionExemption", req) === cfg.questionToolName && cfg.questionToolMounted)
|
|
54
|
+
return { status: "available" };
|
|
55
|
+
let resolution;
|
|
56
|
+
try {
|
|
57
|
+
resolution = (await cfg.read(req.toolCallId)).org;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
return { status: "unavailable", disclosures: [`the permission-rule store threw while resolving the org partition: ${thrownText(err)}`] };
|
|
61
|
+
}
|
|
62
|
+
if (resolution.status === "unavailable" || resolution.status === "ungoverned") {
|
|
63
|
+
return { status: "unavailable", disclosures: resolution.status === "ungoverned" ? ["the store reports no org partition while the provider declares one — unreadable governance, never an empty one"] : resolution.disclosures };
|
|
64
|
+
}
|
|
65
|
+
const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
|
|
66
|
+
const command = req.args?.command;
|
|
67
|
+
if (!laneToolNamesOf(req).includes(PERSISTED_RULE_TOOL) || typeof command !== "string")
|
|
68
|
+
return { status: "available", ...revisionCell };
|
|
69
|
+
const verdict = orgRuleVerdictFor(resolution.rules, { tool: PERSISTED_RULE_TOOL, command });
|
|
70
|
+
return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function orgVerdictBeforeClaim(cfg, req) {
|
|
75
|
+
const unreadable = { status: "unavailable", disclosures: [] };
|
|
76
|
+
const read = () => {
|
|
77
|
+
const store = ownerStoreOf(cfg.provider, cfg.principal, cfg.localOwnerDeclared);
|
|
78
|
+
return orgRuleLane({ read: () => store.effective({ sessionId: cfg.sessionId }), questionToolMounted: true, questionToolName: ASK_USER_QUESTION_TOOL_NAME, onRevisionDefect: () => undefined }).adjudicate(req);
|
|
79
|
+
};
|
|
80
|
+
return settleOrgVerdictWithin(Promise.resolve().then(read).catch(() => unreadable), unreadable, { timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS, ...(cfg.signal !== undefined ? { signal: cfg.signal } : {}) });
|
|
81
|
+
}
|
|
49
82
|
export function createPermissionRuleLanes(cfg) {
|
|
50
83
|
const provider = cfg.provider;
|
|
51
84
|
if (cfg.home !== undefined && !isAbsolutePathForm(cfg.home)) {
|
|
@@ -90,30 +123,7 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
90
123
|
handed.delete(toolCallId);
|
|
91
124
|
return p;
|
|
92
125
|
};
|
|
93
|
-
const org = provider.partitions.org
|
|
94
|
-
? {
|
|
95
|
-
adjudicate: async (req) => {
|
|
96
|
-
if (wireOf("offer.orgQuestionExemption", req) === cfg.questionToolName && cfg.questionToolMounted)
|
|
97
|
-
return { status: "available" };
|
|
98
|
-
let resolution;
|
|
99
|
-
try {
|
|
100
|
-
resolution = (await hand(req.toolCallId)).org;
|
|
101
|
-
}
|
|
102
|
-
catch (err) {
|
|
103
|
-
return { status: "unavailable", disclosures: [`the permission-rule store threw while resolving the org partition: ${thrownText(err)}`] };
|
|
104
|
-
}
|
|
105
|
-
if (resolution.status === "unavailable" || resolution.status === "ungoverned") {
|
|
106
|
-
return { status: "unavailable", disclosures: resolution.status === "ungoverned" ? ["the store reports no org partition while the provider declares one — unreadable governance, never an empty one"] : resolution.disclosures };
|
|
107
|
-
}
|
|
108
|
-
const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
|
|
109
|
-
const command = req.args?.command;
|
|
110
|
-
if (!laneToolNamesOf(req).includes(PERSISTED_RULE_TOOL) || typeof command !== "string")
|
|
111
|
-
return { status: "available", ...revisionCell };
|
|
112
|
-
const verdict = orgRuleVerdictFor(resolution.rules, { tool: PERSISTED_RULE_TOOL, command });
|
|
113
|
-
return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
|
|
114
|
-
},
|
|
115
|
-
}
|
|
116
|
-
: undefined;
|
|
126
|
+
const org = provider.partitions.org ? orgRuleLane({ read: hand, questionToolMounted: cfg.questionToolMounted, questionToolName: cfg.questionToolName, onRevisionDefect: cfg.onRevisionDefect }) : undefined;
|
|
117
127
|
const personalAdjudicate = async (req) => {
|
|
118
128
|
if (anonymous && !cfg.localOwnerDeclared)
|
|
119
129
|
return undefined;
|
|
@@ -62,6 +62,7 @@ export interface PrepareContextLaneInput {
|
|
|
62
62
|
/** borrowed-mutable — the posture directive's newest engine carrier (the first-turn mint and each post-compaction restatement write it); the clip guard reads it. */
|
|
63
63
|
postureFrameRef: {
|
|
64
64
|
protectedText?: string;
|
|
65
|
+
unwritten?: string;
|
|
65
66
|
};
|
|
66
67
|
/** borrowed-mutable — the per-request lossy-projection flag. Writers here: the handler re-derives it at every request
|
|
67
68
|
* build, and the repair sink raises it when the provider boundary's law left recorded content out of the same request
|
|
@@ -105,6 +105,12 @@ export function prepareContextLane(input) {
|
|
|
105
105
|
if (kept.action === "kept" || kept.action === "reinserted")
|
|
106
106
|
trimmed = kept.messages;
|
|
107
107
|
}
|
|
108
|
+
if (postureFrameRef.unwritten !== undefined) {
|
|
109
|
+
let at = trimmed.length;
|
|
110
|
+
while (at > 0 && trimmed[at - 1].role === "user")
|
|
111
|
+
at--;
|
|
112
|
+
trimmed = [...trimmed.slice(0, at), { role: "user", content: [{ type: "text", text: postureFrameRef.unwritten }], timestamp: Date.now(), engineMinted: true }, ...trimmed.slice(at)];
|
|
113
|
+
}
|
|
108
114
|
requestLossyRef.current =
|
|
109
115
|
replayed !== healed ||
|
|
110
116
|
capped !== replayed ||
|
|
@@ -8,7 +8,7 @@ import { askApproverIdentity, durableMandateSourceOf } from "../tool-policy.js";
|
|
|
8
8
|
import { deliverEngineNotice } from "../types.js";
|
|
9
9
|
import { PermissionModeController, modeRequestAfterResume } from "../mode-policy.js";
|
|
10
10
|
import { SessionModeSeats } from "./session-mode-seats.js";
|
|
11
|
-
import {
|
|
11
|
+
import { recordedModeSeed } from "../checkpoint-store.js";
|
|
12
12
|
import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
|
|
13
13
|
import { asThrownError } from "../thrown-value.js";
|
|
14
14
|
export function prepareInheritedGate(input) {
|
|
@@ -32,14 +32,7 @@ export function prepareInheritedGate(input) {
|
|
|
32
32
|
const extras = liveRules.filter((l) => !seedRules.some((s) => s.sessionId === l.sessionId && s.principal === l.principal));
|
|
33
33
|
return [...merged, ...extras];
|
|
34
34
|
})();
|
|
35
|
-
const modeSeed =
|
|
36
|
-
if (modeSeed.migrated !== undefined) {
|
|
37
|
-
deliverEngineNotice(deps.onNotice, {
|
|
38
|
-
code: "resume.legacy_mode_seed_migrated",
|
|
39
|
-
message: `the resumed checkpoint row carried pre-mode keys (${modeSeed.migrated.droppedKeys.join(", ")}); read once into ${modeSeed.migrated.permissionMode !== undefined ? `permissionMode: "${modeSeed.migrated.permissionMode}"` : "no mode of its own (the chain's ceiling stands)"}${modeSeed.shellGate !== undefined ? ' with the shell floor "always" kept' : ""} — the next suspend writes the one key; this migration is kept through 7.38.x and removed in 7.39.0, which refuses a row still carrying these keys (resume or abandon such rows before upgrading)`,
|
|
40
|
-
detail: { migrated: { ...(modeSeed.migrated.permissionMode !== undefined ? { permissionMode: modeSeed.migrated.permissionMode } : {}), droppedKeys: [...modeSeed.migrated.droppedKeys] }, sessionId, runId },
|
|
41
|
-
});
|
|
42
|
-
}
|
|
35
|
+
const modeSeed = recordedModeSeed(seedInheritedGate);
|
|
43
36
|
const inheritedShellGate = liveInheritedGate?.shellGate ?? modeSeed.shellGate;
|
|
44
37
|
const inheritedParentConstraints = attachRebuiltDenialTrackers(liveInheritedGate?.parentConstraints, deps.autoMode?.denialLimit);
|
|
45
38
|
const parentSeat = liveInheritedGate?.permissionModeCeiling;
|
|
@@ -11,10 +11,10 @@ import { callerToolMembership } from "../../agents/inherited-tool-pool.js";
|
|
|
11
11
|
import { toolPolicyPathDenyRules, toolPolicyWriteTargetGuard } from "../tool-policy.js";
|
|
12
12
|
import { loadAnnounceOnceLedger } from "./announce-once-ledger.js";
|
|
13
13
|
import { isValidReminderMark, mintReminderMark } from "../reminder-mint.js";
|
|
14
|
-
import { clampPermissionMode
|
|
15
|
-
import { modePolicyOf, modeRequestAfterResume } from "../mode-policy.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
14
|
+
import { clampPermissionMode } from "../ask-origin.js";
|
|
15
|
+
import { modePolicyOf, modeRequestAfterResume, refusePermissionModeDoors } from "../mode-policy.js";
|
|
16
|
+
import { resolveOutputStyleFrame } from "../output-style.js";
|
|
17
|
+
import { recordedModeSeed } from "../checkpoint-store.js";
|
|
18
18
|
import { emitTrace } from "../trace.js";
|
|
19
19
|
import { persistedPathRowsOfTask, ruleStoreUnreadableNotice } from "./persisted-path-rows.js";
|
|
20
20
|
import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
|
|
@@ -81,6 +81,7 @@ import { selfOrchestrationFailClosedReason } from "../../orchestration/workflow-
|
|
|
81
81
|
import { remainingBudgetMicroUsd, resolveCheckpointStore } from "../checkpoint-store.js";
|
|
82
82
|
import { checkpointSeatOf } from "../checkpoint-seat.js";
|
|
83
83
|
import { handsWriteHalfMounts } from "../../tools/fs/index.js";
|
|
84
|
+
import { askSeatEffective } from "../wiring-manifest.js";
|
|
84
85
|
import { durableParkGapFor } from "../park-selfcheck.js";
|
|
85
86
|
import { deliverEngineNotice } from "../types.js";
|
|
86
87
|
import { ToolRosterDeltaSeat } from "../tool-roster.js";
|
|
@@ -266,22 +267,6 @@ function refuseRegistryScopeMismatch(spec, internals) {
|
|
|
266
267
|
function validateInputFaultNotice(onError, sessionId) {
|
|
267
268
|
return ({ toolName, toolCallId, error }) => onError?.(new Error(`tool input pre-validation (validateInput) threw for ${toolName} (call ${toolCallId}) — read as no verdict; the call proceeded to the gate: ${thrownText(error)}`), { phase: "hook", sessionId });
|
|
268
269
|
}
|
|
269
|
-
function refusePermissionModeDoors(spec) {
|
|
270
|
-
assertNoRetiredKeys(spec, RETIRED_TASK_SPEC_KEYS, "TaskSpec");
|
|
271
|
-
const mode = spec.permissionMode;
|
|
272
|
-
if (mode !== undefined && !isPermissionMode(mode)) {
|
|
273
|
-
const e = new Error(`permissionMode must be one of ${PERMISSION_MODES.map((w) => JSON.stringify(w)).join(", ")} when present (got ${JSON.stringify(mode)}) — omit it for the default mode; an unrecognized word is refused rather than read as any mode.`);
|
|
274
|
-
e.code = "config.permission_mode_invalid";
|
|
275
|
-
throw e;
|
|
276
|
-
}
|
|
277
|
-
const floor = spec.shellGate;
|
|
278
|
-
if (floor !== undefined && floor !== "always") {
|
|
279
|
-
const e = new Error(`shellGate must be the floor word "always" when present (got ${JSON.stringify(floor)}) — the doctrine values "off" / "classify" are retired: the shell's shape tier is installed on every full shell and the permission mode (permissionMode) decides what its asks become; omit shellGate for no operator floor.`);
|
|
280
|
-
e.code = "config.shell_gate_value_retired";
|
|
281
|
-
throw e;
|
|
282
|
-
}
|
|
283
|
-
return mode;
|
|
284
|
-
}
|
|
285
270
|
export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink, holdSessionLock) {
|
|
286
271
|
const runId = uuidv7();
|
|
287
272
|
if (runIdSink !== undefined)
|
|
@@ -291,8 +276,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
291
276
|
spec = doors.spec;
|
|
292
277
|
const checkpointStore = resolveCheckpointStore(spec, deps), checkpointSeat = checkpointSeatOf(spec, checkpointStore);
|
|
293
278
|
const permissionModeSeat = refusePermissionModeDoors(spec);
|
|
279
|
+
const outputStyleFrame = resolveOutputStyleFrame(spec.outputStyle);
|
|
294
280
|
const declaredModeAtDoor = (() => {
|
|
295
|
-
const requested = modeRequestAfterResume(permissionModeSeat,
|
|
281
|
+
const requested = modeRequestAfterResume(permissionModeSeat, recordedModeSeed(resume?.seed.inheritedGate), resume?.outcome);
|
|
296
282
|
const ceiling = internals?.inheritedGate?.permissionModeCeiling?.current ?? internals?.inheritedGate?.permissionMode;
|
|
297
283
|
return ceiling === undefined ? (requested ?? "default") : clampPermissionMode(requested, ceiling).effective;
|
|
298
284
|
})();
|
|
@@ -754,13 +740,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
754
740
|
const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, permissionMode, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
|
|
755
741
|
const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
|
|
756
742
|
const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef });
|
|
757
|
-
prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, readDenyInForce: () => ({ tiers: handsReadFace.readDenyBuiltinTiersActive, rows: [...readDenyAdditionsNormalized.map((e) => e.pattern), ...handsReadFaceInput.pathDenyRules.filter((r) => r.faces.includes("read")).map((r) => r.rule)] }), hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider: autoModeDeciderRef, autoModeDenialTracking: autoModeDenialTrackingRef, askRoute: wiringManifest.ask, permissionMode, toolMarkSources, ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
|
|
758
|
-
const postureFrameRef = {};
|
|
743
|
+
prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, readDenyInForce: () => ({ tiers: handsReadFace.readDenyBuiltinTiersActive, rows: [...readDenyAdditionsNormalized.map((e) => e.pattern), ...handsReadFaceInput.pathDenyRules.filter((r) => r.faces.includes("read")).map((r) => r.rule)] }), hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider: autoModeDeciderRef, autoModeDenialTracking: autoModeDenialTrackingRef, askRoute: { ...wiringManifest.ask, effective: askSeatEffective(wiringManifest) }, permissionMode, toolMarkSources, ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
|
|
744
|
+
const postureFrameRef = { report: (err) => deps.onError?.(err, { phase: "degraded", sessionId }) };
|
|
759
745
|
const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, toolRosterDeltas: rosterSeat, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, contextOverlay: legContextOverlay, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}) });
|
|
760
746
|
const preparedHolder = {};
|
|
761
747
|
const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
|
|
762
748
|
await onceLedger.settle(session, announcedListingsRef);
|
|
763
|
-
const buildPrepared = () => ({ harness, session, sessionId, runId, registryAccess: { owner: hostTaskId, scope: taskScope }, reminderMark, reminderDisclosureCounts, fullShellReachable, approvalPosture: { get mode() { return permissionMode.current; }, askEffective: wiringManifest
|
|
749
|
+
const buildPrepared = () => ({ harness, session, sessionId, runId, registryAccess: { owner: hostTaskId, scope: taskScope }, reminderMark, reminderDisclosureCounts, fullShellReachable, approvalPosture: { get mode() { return permissionMode.current; }, askEffective: askSeatEffective(wiringManifest) }, ...(outputStyleFrame !== undefined ? { outputStyleFrame } : {}), permissionMode, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, endModeCeiling, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, stopForDenialLimit, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, frameSettlement, ...(peerLaneRefs.boundaryRound !== undefined ? { peerBoundaryRound: peerLaneRefs.boundaryRound } : {}), gitStatusRef, postureFrameRef, listBackgroundTasks, hookIdentity, ...(sessionBoxBound !== undefined ? { sessionBox: sessionBoxBound } : {}), hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(shellReadCheck !== undefined ? { shellReadCheck } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
|
|
764
750
|
rollback.commit();
|
|
765
751
|
commitModeSeats(abortController.signal);
|
|
766
752
|
const prepared = buildPrepared();
|
|
@@ -11,7 +11,7 @@ import { inlineUntrusted } from "../untrusted-text.js";
|
|
|
11
11
|
import { mountWriteProtection } from "./write-protection-station.js";
|
|
12
12
|
import { autoRunOnRecommendationEffective } from "./memory-consolidation-driver.js";
|
|
13
13
|
import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
|
|
14
|
-
import { LSP_CONTRACT_ID, countElicitOptIns, deriveHandsSeam, deriveLspSeam, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
|
|
14
|
+
import { LSP_CONTRACT_ID, askSeatEffective, countElicitOptIns, deriveHandsSeam, deriveLspSeam, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
|
|
15
15
|
import { DEFAULT_IRREVERSIBLE_SCOPE } from "./checkpoint-scope.js";
|
|
16
16
|
export function mcpManifestEntries(entries, statuses) {
|
|
17
17
|
return (entries ?? []).map((entry) => {
|
|
@@ -222,7 +222,7 @@ export function prepareWiringManifest(input) {
|
|
|
222
222
|
hands: deriveHandsSeam(handsEnabled),
|
|
223
223
|
fileHistory: fileHistoryFact,
|
|
224
224
|
});
|
|
225
|
-
peerLaneRefs.askEffective = wiringManifest
|
|
225
|
+
peerLaneRefs.askEffective = askSeatEffective(wiringManifest);
|
|
226
226
|
peerLaneRefs.permissionMode = () => permissionMode.current;
|
|
227
227
|
const sessionBoxBound = sessionBox !== undefined && internals?.onTaskNotification !== undefined ? sessionBox : undefined;
|
|
228
228
|
if (sessionBoxBound !== undefined && internals?.onTaskNotification !== undefined) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PromptBlockCause } from "../terminal-cause.js";
|
|
2
|
+
import type { BlockedRef } from "./synthetic-tools.js";
|
|
3
|
+
/**
|
|
4
|
+
* Hold the task's first prompt back: `ref.reason` is `text` in the engine's reminder frame, `ref.detail` names `cause`.
|
|
5
|
+
* `text` is the hook's own words when `cause` is `"block"` — they also become `detail.hookReason`, folded to one line,
|
|
6
|
+
* bounded, envelope tags defused, with no reminder frame and no session mark; a reason that folds to nothing leaves the
|
|
7
|
+
* key out. For every other cause `text` is the engine's sentence about the hook's ending and `hookReason` is never minted.
|
|
8
|
+
*/
|
|
9
|
+
export declare function holdPrompt(ref: BlockedRef, mark: string | undefined, cause: PromptBlockCause, text: string): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { formatHookFeedback } from "../hooks.js";
|
|
2
|
+
import { inlineUntrusted } from "../untrusted-text.js";
|
|
3
|
+
const HOOK_REASON_MAX = 240;
|
|
4
|
+
export function holdPrompt(ref, mark, cause, text) {
|
|
5
|
+
ref.reason = formatHookFeedback(text, mark);
|
|
6
|
+
const hookReason = cause === "block" ? inlineUntrusted(String(text), HOOK_REASON_MAX) : "";
|
|
7
|
+
ref.detail = { kind: "prompt_blocked_by_hook", cause, ...(hookReason !== "" ? { hookReason } : {}) };
|
|
8
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { CheckpointError, readPendingSteerQueue, resolveCheckpointStore, validatePendingSteer } from "../checkpoint-store.js";
|
|
1
|
+
import { CheckpointError, readPendingSteerQueue, refuseRetiredModeSeed, resolveCheckpointStore, validatePendingSteer } from "../checkpoint-store.js";
|
|
2
|
+
import { refuseResumeDoors } from "../mode-policy.js";
|
|
2
3
|
import { describeSuppliedValue } from "./decide-continuation.js";
|
|
3
4
|
export async function resumeAdmission(input) {
|
|
4
5
|
const { token, outcome, taskConfig, runner, parentConstraintRegistry } = input;
|
|
@@ -36,6 +37,8 @@ export async function resumeAdmission(input) {
|
|
|
36
37
|
const outcomeGate = outcome.gate;
|
|
37
38
|
const resumeSignal = config.signal;
|
|
38
39
|
let suppliedMessage;
|
|
40
|
+
refuseRetiredModeSeed(cp.state);
|
|
41
|
+
refuseResumeDoors(config, cp.state.inheritedGate);
|
|
39
42
|
const parkedSteers = readPendingSteerQueue(cp.state);
|
|
40
43
|
if (outcomeGate === "wake") {
|
|
41
44
|
suppliedMessage = outcome.message;
|
|
@@ -2,12 +2,13 @@ import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
|
2
2
|
import { isAskOrigin } from "../ask-origin.js";
|
|
3
3
|
import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
|
|
4
4
|
import { CheckpointError, remainingBudgetMicroUsd } from "../checkpoint-store.js";
|
|
5
|
-
import {
|
|
5
|
+
import { settledByDecider } from "../gate-outcome.js";
|
|
6
6
|
import { cloneObserverInput, mintHookInvocationIdentity, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
|
|
7
|
-
import { checkToolPolicyProjection, refuseOutOfContractDecision } from "../tool-policy.js";
|
|
7
|
+
import { checkToolPolicyProjection, policyCannotAnswer, refuseOutOfContractDecision } from "../tool-policy.js";
|
|
8
8
|
import { callFaceSeat, parkedCallFaceOf } from "../tool-roster.js";
|
|
9
9
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
10
10
|
import { deepJsonEqual, describeSuppliedValue } from "./decide-continuation.js";
|
|
11
|
+
import { orgVerdictBeforeClaim } from "./permission-rule-lanes.js";
|
|
11
12
|
import { mintGateOutcome } from "./gate-exit.js";
|
|
12
13
|
import { asThrownError, thrownText } from "../thrown-value.js";
|
|
13
14
|
import { wireOf } from "../tool-name-readings.js";
|
|
@@ -75,6 +76,19 @@ export async function resumeApply(input) {
|
|
|
75
76
|
await recheckGovernanceWindow();
|
|
76
77
|
}
|
|
77
78
|
}
|
|
79
|
+
const editSignal = config.signal;
|
|
80
|
+
const raceEditAbort = async (p) => {
|
|
81
|
+
if (editSignal === undefined)
|
|
82
|
+
return p;
|
|
83
|
+
if (editSignal.aborted) {
|
|
84
|
+
throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the call before the claim — the checkpoint stays pending and is resumable with a live signal");
|
|
85
|
+
}
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const onAbort = () => reject(new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the call before the claim — the checkpoint stays pending and is resumable with a live signal"));
|
|
88
|
+
editSignal.addEventListener("abort", onAbort, { once: true });
|
|
89
|
+
p.then((v) => { editSignal.removeEventListener("abort", onAbort); resolve(v); }, (e) => { editSignal.removeEventListener("abort", onAbort); reject(e); });
|
|
90
|
+
});
|
|
91
|
+
};
|
|
78
92
|
if (plainPolicyOutcome !== undefined &&
|
|
79
93
|
plainPolicyOutcome.decision === "allow" &&
|
|
80
94
|
plainPolicyOutcome.updatedInput !== undefined &&
|
|
@@ -94,19 +108,6 @@ export async function resumeApply(input) {
|
|
|
94
108
|
...callFaceSeat(parkedCallFaceOf(cp.pendingAction)),
|
|
95
109
|
...(cp.state.handsCwd !== undefined ? { cwd: cp.state.handsCwd } : {}),
|
|
96
110
|
};
|
|
97
|
-
const editSignal = config.signal;
|
|
98
|
-
const raceEditAbort = async (p) => {
|
|
99
|
-
if (editSignal === undefined)
|
|
100
|
-
return p;
|
|
101
|
-
if (editSignal.aborted) {
|
|
102
|
-
throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the approver's edit — the checkpoint stays pending and is resumable with a live signal");
|
|
103
|
-
}
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
const onAbort = () => reject(new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the approver's edit — the checkpoint stays pending and is resumable with a live signal"));
|
|
106
|
-
editSignal.addEventListener("abort", onAbort, { once: true });
|
|
107
|
-
p.then((v) => { editSignal.removeEventListener("abort", onAbort); resolve(v); }, (e) => { editSignal.removeEventListener("abort", onAbort); reject(e); });
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
111
|
const ig = cp.state.inheritedGate;
|
|
111
112
|
if (ig?.requiresParentConstraint === true) {
|
|
112
113
|
const chain = ig.constraintChain;
|
|
@@ -148,6 +149,8 @@ export async function resumeApply(input) {
|
|
|
148
149
|
throw new CheckpointError("resume.constraint_rejected", `the approver EDITED this call's input and the deployment's resume-edit policy denies the edited args${rechecked.message ? `: ${rechecked.message}` : ""}. ` +
|
|
149
150
|
"Refused pre-CAS (the checkpoint stays pending and remains decidable)");
|
|
150
151
|
}
|
|
152
|
+
if (policyCannotAnswer(rechecked, plainPolicyOutcome.hostDecision.decidedBy))
|
|
153
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `the approver EDITED this call's input, the edited args raise a question that requires real approval, and the decision is the deployment's policy — a policy's answer is not a person's (decide the edit as "person", or approve the shown input); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
|
|
151
154
|
if (rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, editedArgs)) {
|
|
152
155
|
throw new CheckpointError("resume.constraint_rejected", `the deployment's resume-edit policy REWROTE the approver's edited args to a different value than was approved — the durable resume cannot execute either the un-adjudicated edit or an unapproved rewrite; ` +
|
|
153
156
|
`refused pre-CAS (the checkpoint stays pending); re-submit the edited action so the rewrite is adjudicated and approved synchronously`);
|
|
@@ -169,6 +172,15 @@ export async function resumeApply(input) {
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
}
|
|
175
|
+
const orgProvider = runner.deps.permissionRuleStore;
|
|
176
|
+
if (plainPolicyOutcome?.decision === "allow" && plainPolicyOutcome.hostDecision.decidedBy !== "person" && orgProvider?.partitions.org === true && cp.pendingAction.kind === "tool_approval") {
|
|
177
|
+
const call = { toolName: cp.pendingAction.toolName, args: plainPolicyOutcome.updatedInput !== undefined ? plainPolicyOutcome.updatedInput : cp.pendingAction.args, toolCallId: cp.pendingAction.toolCallId };
|
|
178
|
+
const orgNow = await raceEditAbort(orgVerdictBeforeClaim({ provider: orgProvider, principal: config.principal || cp.principal || undefined, localOwnerDeclared: runner.deps.localOwnerRules === true, sessionId: cp.sessionId, signal: editSignal }, call));
|
|
179
|
+
if (policyCannotAnswer(orgNow, plainPolicyOutcome.hostDecision.decidedBy)) {
|
|
180
|
+
const rule = orgNow.status === "available" ? orgNow.verdict?.rule : undefined;
|
|
181
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" decided by "${plainPolicyOutcome.hostDecision.decidedBy}" on a call an organization policy rule (${rule}) requires a person's approval for — a policy's answer is not a person's (decide it as "person", or refuse it as "policy"); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
172
184
|
if (plainPolicyOutcome !== undefined) {
|
|
173
185
|
const parkedOrigin = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.origin : undefined;
|
|
174
186
|
if (!isAskOrigin(parkedOrigin)) {
|
|
@@ -177,9 +189,7 @@ export async function resumeApply(input) {
|
|
|
177
189
|
const facts = plainPolicyOutcome.hostDecision;
|
|
178
190
|
const settlement = facts.decidedBy === "sla_timeout"
|
|
179
191
|
? { kind: "park_sla_expired", who: { party: "host", ...(facts.approver !== undefined ? { approver: facts.approver } : {}) }, when: Date.now() }
|
|
180
|
-
: facts.decidedBy === "
|
|
181
|
-
? { kind: "policy_refused", who: { party: "none" }, when: Date.now() }
|
|
182
|
-
: settledByPerson(plainPolicyOutcome.decision === "allow" ? "human_allowed" : "human_refused", facts.approver, plainPolicyOutcome.decision === "deny" ? plainPolicyOutcome.reason : undefined);
|
|
192
|
+
: settledByDecider(facts.decidedBy, plainPolicyOutcome.decision === "allow" ? "allowed" : "refused", facts.approver, plainPolicyOutcome.decision === "deny" ? plainPolicyOutcome.reason : undefined);
|
|
183
193
|
plainPolicyOutcome = { ...plainPolicyOutcome, gateOutcome: mintGateOutcome({ ...(plainPolicyOutcome.decision === "deny" ? { deniedBy: "ask_resolution" } : {}), settled: { settlement, origin: parkedOrigin } }) };
|
|
184
194
|
}
|
|
185
195
|
const outcomeForStore = plainPolicyOutcome ?? plainReviewOutcome ?? plainParkOutcome;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace } from "../ask-question.js";
|
|
2
2
|
import { CHECKPOINT_FEATURES, CheckpointError, checkpointVersionOf, winnerFromOutcome } from "../checkpoint-store.js";
|
|
3
|
-
import { screenApproverAttribution } from "../tool-policy.js";
|
|
3
|
+
import { policyCannotAnswer, screenApproverAttribution } from "../tool-policy.js";
|
|
4
4
|
import { sanitizeUntrustedText } from "../untrusted-text.js";
|
|
5
5
|
import { assertOutcomeText, describeSuppliedValue, pendingContentAskCallId, sameWinner } from "./decide-continuation.js";
|
|
6
6
|
const REFUSED_DECIDE_KEYS = [
|
|
@@ -55,9 +55,15 @@ export function resumePolicyOutcome(input) {
|
|
|
55
55
|
if (decidedBy !== "person" && decidedBy !== "sla_timeout" && decidedBy !== "policy") {
|
|
56
56
|
throw new CheckpointError("checkpoint.invalid_outcome", `resume hostDecision.decidedBy "${describeSuppliedValue(decidedBy)}" is outside the closed set — it is exactly "person", "sla_timeout" or "policy"; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const cannotAllow = decision !== "allow"
|
|
59
|
+
? undefined
|
|
60
|
+
: decidedBy === "sla_timeout"
|
|
61
|
+
? ` — that word names a wait that ended with nobody answering, and it cannot approve an action that EXECUTES (an allow is decided by "person" or "policy")`
|
|
62
|
+
: policyCannotAnswer(cp, decidedBy)
|
|
63
|
+
? ` on a row that requires real approval — a policy's answer is not a person's, and this row demands a person (decide it as "person", or refuse it as "policy")`
|
|
64
|
+
: undefined;
|
|
65
|
+
if (cannotAllow !== undefined)
|
|
66
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" decided by "${decidedBy}"${cannotAllow}; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
|
|
61
67
|
const attribution = screenApproverAttribution(hostDecisionRaw.approver);
|
|
62
68
|
if (attribution.defect !== undefined) {
|
|
63
69
|
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries an approver attribution this engine refuses: ${attribution.defect}; refusing pre-CAS, the checkpoint stays pending`, { field: "approver" });
|
|
@@ -15,7 +15,7 @@ export async function runAttachmentSeats(input) {
|
|
|
15
15
|
rs.attach.skillsListingOn = rs.attach.attachmentsCfg?.skillsListing !== false && eventDefaultOn("skills_listing");
|
|
16
16
|
const listingsLive = (rs.attach.agentListingOn && prepared.agentListing !== undefined) || (rs.attach.skillsListingOn && prepared.skillsListing !== undefined);
|
|
17
17
|
const backgroundTasksLive = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
|
|
18
|
-
rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive ? createAttachmentState() : undefined;
|
|
18
|
+
rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive || prepared.outputStyleFrame !== undefined ? createAttachmentState() : undefined;
|
|
19
19
|
rs.attach.dateState = prepared.dateChange !== undefined ? { announcedDate: prepared.dateChange.anchor?.current?.announcedDate ?? prepared.dateChange.legDate } : undefined;
|
|
20
20
|
rs.attach.instrProbe = runner.deps.probeInstructionSources;
|
|
21
21
|
rs.attach.instrState =
|
|
@@ -2,7 +2,9 @@ import { uuidv7 } from "../../internal/harness.js";
|
|
|
2
2
|
import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
|
|
3
3
|
import { recordFrameReceipt, toolResultReceiptKey } from "../frame-settlement.js";
|
|
4
4
|
import { discloseDroppedPending, renderTaskNotificationXml, taskNotificationDedupKey } from "../task-notification.js";
|
|
5
|
-
import {
|
|
5
|
+
import { buildAppliedHumanInputEvent } from "../human-input-projection.js";
|
|
6
|
+
import { announceAcceptance, nextHumanInputSeq } from "./steer-admission.js";
|
|
7
|
+
import { deliverDelegationLifecycle, deliverEngineNotice, parentLegIds, undrainedUserInputNotices } from "../types.js";
|
|
6
8
|
export function runIdentityWiring(input) {
|
|
7
9
|
const { spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, mcpRedialRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, notificationLane, pendingSessionNotifications, runner } = input;
|
|
8
10
|
notificationLane.harness = prepared.harness;
|
|
@@ -59,7 +61,7 @@ export function runIdentityWiring(input) {
|
|
|
59
61
|
undrainedUserAtEnd = counts;
|
|
60
62
|
return;
|
|
61
63
|
}
|
|
62
|
-
for (const notice of undrainedUserInputNotices(counts, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId)) {
|
|
64
|
+
for (const notice of undrainedUserInputNotices(counts, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId, parentLegIds(internals))) {
|
|
63
65
|
deliverEngineNotice(runner.deps.onNotice, notice);
|
|
64
66
|
}
|
|
65
67
|
};
|
|
@@ -71,6 +73,11 @@ export function runIdentityWiring(input) {
|
|
|
71
73
|
if (notificationLane.sessionId !== undefined && typeof payload?.task_id === "string" && typeof payload.status === "string")
|
|
72
74
|
pendingSessionNotifications.markDelivered(notificationLane.sessionId, taskNotificationDedupKey(payload));
|
|
73
75
|
};
|
|
76
|
+
prepared.harness.onUserInputConsumed = (record) => {
|
|
77
|
+
const steer = record;
|
|
78
|
+
const identity = announceAcceptance(steer);
|
|
79
|
+
queue.push({ ...buildAppliedHumanInputEvent({ carrier: "steer", source: "steer", sessionSeq: nextHumanInputSeq(prepared.harness), inputId: steer.inputId, actor: steer.actor, principal: spec.principal }), eventId: uuidv7(), ...identity });
|
|
80
|
+
};
|
|
74
81
|
prepared.harness.onFrameCommitted = (carrier, entryId) => {
|
|
75
82
|
const seam = prepared.frameSettlement;
|
|
76
83
|
if (seam.awaiting.size === 0)
|