@sema-agent/core 7.38.2 → 7.39.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 +175 -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-mint.d.ts +0 -9
- package/dist/core/reminder-mint.js +0 -11
- 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/stores/file/fs-atomic.d.ts +29 -4
- package/dist/stores/file/fs-atomic.js +55 -6
- 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.38.2.json +20 -2
- package/wire-changes/core@7.38.3.json +36 -0
- package/wire-changes/core@7.39.0.json +845 -0
|
@@ -5,21 +5,22 @@ import { CheckpointError, readPendingSteerQueue, realApprovalOrgFact, remainingB
|
|
|
5
5
|
import { normalizePersistedRuleHit } from "../gate-lanes.js";
|
|
6
6
|
import { decidersNote, SETTLEMENT_NOBODY_JUDGED } from "../gate-outcome.js";
|
|
7
7
|
import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
|
|
8
|
-
import { buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
|
|
8
|
+
import { buildAppliedHumanInputEvent, buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
|
|
9
9
|
import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "../permission-rule-org.js";
|
|
10
10
|
import { applyPersistedTightening, disclosedRuleSet } from "../persisted-rule-arms.js";
|
|
11
11
|
import { computeCostMicroUsd } from "../pricing.js";
|
|
12
|
-
import {
|
|
13
|
-
import { mintSystemReminder, statedReminderBodiesOf } from "../reminder-mint.js";
|
|
12
|
+
import { mintSystemReminder } from "../reminder-mint.js";
|
|
14
13
|
import { hasDestroy } from "../remote-env.js";
|
|
15
|
-
import { advertisedWritableRootsSeat, policyRefusalMessage, refuseOutOfContractDecision } from "../tool-policy.js";
|
|
14
|
+
import { advertisedWritableRootsSeat, policyCannotAnswer, policyRefusalMessage, refuseOutOfContractDecision } from "../tool-policy.js";
|
|
16
15
|
import { callFaceSeat, judgeParkedToolIdentity, parkedRowLiveFace, rosterEntryNamed } from "../tool-roster.js";
|
|
17
16
|
import { deliverEngineNotice } from "../types.js";
|
|
18
17
|
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
19
18
|
import { errorCodeOf } from "./assemble-result.js";
|
|
20
19
|
import { DEFAULT_PRECALL_OUTPUT_TOKENS, platformLimitTerminal } from "./clock-and-limits.js";
|
|
20
|
+
import { mintOutputStyleReminder, owedPostureText } from "./compaction-restate.js";
|
|
21
21
|
import { deepJsonEqual, resumeContinuation } from "./decide-continuation.js";
|
|
22
22
|
import { settleExecutionRecord } from "./execution-record.js";
|
|
23
|
+
import { holdPrompt } from "./prompt-block.js";
|
|
23
24
|
import { mintGateOutcome } from "./gate-exit.js";
|
|
24
25
|
import { withGateTimeout } from "../gate-bounded-await.js";
|
|
25
26
|
import { resolveGitLegDelivery, wrapGitFrame } from "./git-leg-delivery.js";
|
|
@@ -137,6 +138,12 @@ export async function runLeg(input) {
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
}
|
|
141
|
+
const postureText = await owedPostureText(prepared, "always");
|
|
142
|
+
if (postureText !== undefined)
|
|
143
|
+
continuation += "\n\n" + postureText;
|
|
144
|
+
const outputStyleWrapped = mintOutputStyleReminder(prepared);
|
|
145
|
+
if (outputStyleWrapped !== undefined)
|
|
146
|
+
continuation += "\n\n" + outputStyleWrapped;
|
|
140
147
|
const engineSegments = continuation.length > 0 ? [{ start: 0, end: continuation.length }] : [];
|
|
141
148
|
const parkedSteers = readPendingSteerQueue(resume.cp.state);
|
|
142
149
|
const resumeFrames = [
|
|
@@ -223,16 +230,7 @@ export async function runLeg(input) {
|
|
|
223
230
|
if (steer.trusted)
|
|
224
231
|
engineSegments.push({ start, end: continuation.length });
|
|
225
232
|
appliedEvents.push({
|
|
226
|
-
...
|
|
227
|
-
carrier: source,
|
|
228
|
-
source,
|
|
229
|
-
delivery: "applied",
|
|
230
|
-
sessionSeq: nextHumanInputSeq(prepared.harness),
|
|
231
|
-
inputId: steer.inputId,
|
|
232
|
-
...(steer.actor !== undefined ? { actor: steer.actor } : {}),
|
|
233
|
-
...(steer.actor?.issuer !== undefined ? { issuer: steer.actor.issuer } : {}),
|
|
234
|
-
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
235
|
-
}),
|
|
233
|
+
...buildAppliedHumanInputEvent({ carrier: source, source, sessionSeq: nextHumanInputSeq(prepared.harness), inputId: steer.inputId, actor: steer.actor, principal: spec.principal }),
|
|
236
234
|
...ident(),
|
|
237
235
|
});
|
|
238
236
|
}
|
|
@@ -260,6 +258,10 @@ export async function runLeg(input) {
|
|
|
260
258
|
},
|
|
261
259
|
});
|
|
262
260
|
}
|
|
261
|
+
if (outputStyleWrapped !== undefined) {
|
|
262
|
+
const preview = redactThenCut(prepared.outputStyleFrame, 220);
|
|
263
|
+
prepared.frameReceipts.pending.push({ source: "output_style", text: continuation, commit: () => void (queue.push({ type: "steering_injected", source: "output_style", preview, ...ident() }), (rs.attach.attachmentsInjected += 1)) });
|
|
264
|
+
}
|
|
263
265
|
final = await withBrainSinks(() => prepared.harness.prompt(continuation, {
|
|
264
266
|
engineSegments,
|
|
265
267
|
settleUnansweredToolCalls: rejectionHalt !== undefined ? { halt: rejectionHalt } : {},
|
|
@@ -292,16 +294,18 @@ export async function runLeg(input) {
|
|
|
292
294
|
catch {
|
|
293
295
|
}
|
|
294
296
|
}
|
|
295
|
-
|
|
297
|
+
const timedOut = promptSeat.cause === "timeout";
|
|
298
|
+
holdPrompt(prepared.blockedRef, prepared.reminderMark, timedOut ? "timeout" : "cancelled", timedOut
|
|
296
299
|
? `the deployment's userPromptSubmit hook did not answer within its ${prepared.hookTimeoutMs}ms bound while screening this prompt; ` +
|
|
297
300
|
`the prompt was NOT submitted (fail-closed)`
|
|
298
301
|
: `the task was cancelled while the deployment's userPromptSubmit hook was still screening this prompt; ` +
|
|
299
|
-
`the prompt was NOT submitted (fail-closed)
|
|
302
|
+
`the prompt was NOT submitted (fail-closed)`);
|
|
300
303
|
promptBlocked = true;
|
|
301
304
|
}
|
|
302
305
|
const decision = promptSeat.expired ? undefined : promptSeat.value;
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
const blockWords = decision?.block;
|
|
307
|
+
if (blockWords) {
|
|
308
|
+
holdPrompt(prepared.blockedRef, prepared.reminderMark, "block", blockWords);
|
|
305
309
|
promptBlocked = true;
|
|
306
310
|
}
|
|
307
311
|
else if (decision?.additionalContext) {
|
|
@@ -315,8 +319,7 @@ export async function runLeg(input) {
|
|
|
315
319
|
}
|
|
316
320
|
catch {
|
|
317
321
|
}
|
|
318
|
-
prepared.blockedRef.
|
|
319
|
-
`the prompt was NOT submitted (fail-closed)`, prepared.reminderMark);
|
|
322
|
+
holdPrompt(prepared.blockedRef, prepared.reminderMark, "crashed", `the deployment's userPromptSubmit hook crashed while screening this prompt (${err.message}); ` + `the prompt was NOT submitted (fail-closed)`);
|
|
320
323
|
promptBlocked = true;
|
|
321
324
|
}
|
|
322
325
|
}
|
|
@@ -383,16 +386,14 @@ export async function runLeg(input) {
|
|
|
383
386
|
}
|
|
384
387
|
}
|
|
385
388
|
}
|
|
389
|
+
if (prepared.outputStyleFrame !== undefined)
|
|
390
|
+
firstFrames.push({ source: "output_style", body: prepared.outputStyleFrame, commit: () => undefined, disclose: () => undefined });
|
|
386
391
|
if (firstFrames.length > 0) {
|
|
387
392
|
effectiveObjective = `${firstFrames.map((f) => mintSystemReminder(sanitizeUntrustedText(f.body, attachmentEnvelopeTags(f.source)), prepared.reminderMark)).join("\n")}\n${effectiveObjective}`;
|
|
388
393
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
prepared.postureFrameRef.protectedText = mintSystemReminder(postureDirective, prepared.reminderMark);
|
|
393
|
-
effectiveObjective = `${prepared.postureFrameRef.protectedText}\n${effectiveObjective}`;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
394
|
+
const postureText = await owedPostureText(prepared, "always");
|
|
395
|
+
if (postureText !== undefined)
|
|
396
|
+
effectiveObjective = `${postureText}\n${effectiveObjective}`;
|
|
396
397
|
const gitQueuedChars = gitLegDelivered !== undefined && gitLegDelivered.standalone ? gitLegDelivered.wrapped.length : 0;
|
|
397
398
|
const precallMicroUsd = rs.budget.maxCostMicroUsd === undefined
|
|
398
399
|
? 0
|
|
@@ -615,10 +616,17 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
615
616
|
...advertisedWritableRootsSeat(prepared.advertisedWritableDirs),
|
|
616
617
|
...callFaceSeat(parkedRowLiveFace(prepared.toolRosterDeltas.current, pendingAction)),
|
|
617
618
|
}, prepared.abortController.signal));
|
|
619
|
+
const personUnanswered = policyCannotAnswer(rechecked, outcome.hostDecision.decidedBy);
|
|
618
620
|
const beltUnsafe = rechecked.action === "deny" ||
|
|
619
|
-
(rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs))
|
|
621
|
+
(rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs)) ||
|
|
622
|
+
personUnanswered;
|
|
620
623
|
if (beltUnsafe) {
|
|
621
|
-
const
|
|
624
|
+
const why = rechecked.action === "deny"
|
|
625
|
+
? (rechecked.message ? `: ${rechecked.message}` : "")
|
|
626
|
+
: personUnanswered
|
|
627
|
+
? " (the edited call requires a person's approval, and this decision was the deployment's policy)"
|
|
628
|
+
: " (the policy rewrote the edit to a value that was not approved)";
|
|
629
|
+
const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is refused by the deployment's tool policy and was not executed${why}.`, prepared.reminderMark);
|
|
622
630
|
await settleEnd(true, { content: editedDenial }, vetoed("policy"));
|
|
623
631
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
|
|
624
632
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
@@ -646,6 +654,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
646
654
|
orgWaitCancelled = cause === "aborted";
|
|
647
655
|
},
|
|
648
656
|
});
|
|
657
|
+
const unanswered = policyCannotAnswer(org, outcome.hostDecision.decidedBy);
|
|
649
658
|
const blocked = org.status === "unavailable"
|
|
650
659
|
? realApprovalOrgFact(gateRealApproval) === "unavailable"
|
|
651
660
|
? undefined
|
|
@@ -654,12 +663,14 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
|
|
|
654
663
|
: "this deployment is org-governed and cannot currently adjudicate against an organization policy snapshot"
|
|
655
664
|
: org.verdict?.behavior === "deny"
|
|
656
665
|
? `an organization policy rule (${org.verdict.rule}) denies it`
|
|
657
|
-
:
|
|
666
|
+
: unanswered
|
|
667
|
+
? `an organization policy rule (${org.verdict?.rule})${org.verdict?.unreadable !== undefined ? ` may reach this call, which could not be read against it (${org.verdict.unreadable}), so it` : ""} requires a person's approval for this call, and the decision recorded for it was the deployment's policy, not a person's`
|
|
668
|
+
: undefined;
|
|
658
669
|
if (blocked !== undefined) {
|
|
659
670
|
const orgDisclosures = org.status === "unavailable" && org.disclosures.length > 0
|
|
660
671
|
? ` Governance disclosures: ${inlineUntrusted(org.disclosures.join("; "), ORG_DISCLOSURE_MAX_CHARS)}.`
|
|
661
672
|
: "";
|
|
662
|
-
const orgDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${blocked}.${orgDisclosures} This approval is spent — the call has to be re-issued and approved again once organization policy permits it.`, prepared.reminderMark);
|
|
673
|
+
const orgDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${blocked}.${orgDisclosures} This approval is spent — the call has to be re-issued and ${unanswered ? "decided by a person" : "approved again once organization policy permits it"}.`, prepared.reminderMark);
|
|
663
674
|
await settleEnd(true, { content: orgDenial }, vetoed("org"));
|
|
664
675
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, orgDenial, true));
|
|
665
676
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
@@ -10,7 +10,7 @@ import { hasDestroy, isIsolated } from "../remote-env.js";
|
|
|
10
10
|
import { resolveTaskModel } from "../roles.js";
|
|
11
11
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
12
12
|
import { emitTrace } from "../trace.js";
|
|
13
|
-
import { deliverEngineNotice, settleOwedUserInput } from "../types.js";
|
|
13
|
+
import { deliverEngineNotice, parentLegIds, settleOwedUserInput } from "../types.js";
|
|
14
14
|
import { assembleResult } from "./assemble-result.js";
|
|
15
15
|
import { discloseUnconsumedHalt } from "./halt-attribution.js";
|
|
16
16
|
import { forwardSafely } from "./forward-sink.js";
|
|
@@ -111,6 +111,7 @@ export async function runSettleAndTeardown(input) {
|
|
|
111
111
|
budgetHit: rs.limits.budgetHit,
|
|
112
112
|
budgetAxis: rs.limits.budgetAxis,
|
|
113
113
|
blockedReason: prepared.blockedRef.reason,
|
|
114
|
+
blockedDetail: prepared.blockedRef.detail,
|
|
114
115
|
conflict: prepared.conflictRef.hit,
|
|
115
116
|
gitCoreOverBudget: prepared.gitStatusRef.terminalCode === "irreducible_core_over_budget",
|
|
116
117
|
outputInvalid: rs.degrade.outputInvalid,
|
|
@@ -147,7 +148,7 @@ export async function runSettleAndTeardown(input) {
|
|
|
147
148
|
amendTerminal(result, "resume.reopen_failed_unstarted", (m) => `${m ?? "the resume leg did not complete"} — the approved action never started, and reopening the checkpoint FAILED: its state is unprovable from here. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead`);
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
|
-
settleOwedUserInput(owedUserInput, rowReturned, { taskId: spec.taskId ?? prepared.sessionId, sessionId: prepared.sessionId, runId: prepared.runId }, runner.deps.onNotice);
|
|
151
|
+
settleOwedUserInput(owedUserInput, rowReturned, { taskId: spec.taskId ?? prepared.sessionId, sessionId: prepared.sessionId, runId: prepared.runId, parent: parentLegIds(internals) }, runner.deps.onNotice);
|
|
151
152
|
prepared.sealReadStateSeat?.(ownCommittedTailRef.current);
|
|
152
153
|
{
|
|
153
154
|
const wss = prepared.workspaceStateSettle;
|
|
@@ -293,7 +294,7 @@ export async function runSettleAndTeardown(input) {
|
|
|
293
294
|
}
|
|
294
295
|
}
|
|
295
296
|
finally {
|
|
296
|
-
settleOwedUserInput(owedUserInput, !prepared.suspendProgressRef.executedApproved, { taskId: spec.taskId ?? prepared.sessionId, sessionId: prepared.sessionId, runId: prepared.runId }, runner.deps.onNotice);
|
|
297
|
+
settleOwedUserInput(owedUserInput, !prepared.suspendProgressRef.executedApproved, { taskId: spec.taskId ?? prepared.sessionId, sessionId: prepared.sessionId, runId: prepared.runId, parent: parentLegIds(internals) }, runner.deps.onNotice);
|
|
297
298
|
const writeDateAnchor = owedDateAnchor(prepared.session, prepared.dateChange, rs.attach.dateState);
|
|
298
299
|
if (writeDateAnchor !== undefined)
|
|
299
300
|
await settleTeardownLeg(writeDateAnchor, "date anchor (settle leg)", (e) => runner.deps.onError?.(e, { phase: "degraded", sessionId: prepared.sessionId }));
|
|
@@ -13,7 +13,7 @@ import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.j
|
|
|
13
13
|
import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
|
|
14
14
|
import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
|
|
15
15
|
import { awaitChargeWithSlowDisclosure, discloseUnevaluableWindow, ENV_DUE_GOVERNANCE_READ_BUDGET_MS, GOVERNANCE_READ_STALLED, LIMIT_APPROACH_DEFAULT_THRESHOLDS, limitApproachFrames, platformLimitTerminal, raceUntilDeadline } from "./clock-and-limits.js";
|
|
16
|
-
import { compactionRestateOptions } from "./compaction-restate.js";
|
|
16
|
+
import { appendBoundaryFrames, compactionRestateOptions } from "./compaction-restate.js";
|
|
17
17
|
import { COMPACTION_FREED_EPSILON, COMPACTION_REGROWTH_FACTOR, MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js";
|
|
18
18
|
import { compactionHookOptions, recordCompactionReuse, seamCCompactionOptions } from "./compaction-seams.js";
|
|
19
19
|
import { contextUsageSections } from "../../prompt-assembly/section-usage.js";
|
|
@@ -197,6 +197,8 @@ export function createTurnBoundary(input) {
|
|
|
197
197
|
}
|
|
198
198
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
|
|
199
199
|
}
|
|
200
|
+
if (!noNextRequest() && !prepared.abortController.signal.aborted)
|
|
201
|
+
await appendBoundaryFrames(prepared, rs.turn.lastTurnHadToolCalls);
|
|
200
202
|
if (prepared.lspDiagnostics &&
|
|
201
203
|
!prepared.lspDiagnostics.registry.isEmpty() &&
|
|
202
204
|
!prepared.abortController.signal.aborted &&
|
|
@@ -283,13 +285,11 @@ export function createTurnBoundary(input) {
|
|
|
283
285
|
cadenceTurn: rs.counters.cadenceTurns,
|
|
284
286
|
todoMounted: todoToolMounted,
|
|
285
287
|
taskListMounted: taskToolsMounted,
|
|
286
|
-
planActive: prepared.permissionMode.current === "plan",
|
|
287
288
|
config: {
|
|
288
289
|
todoReminder: rs.attach.attachmentsCfg?.todoReminder === true,
|
|
289
290
|
...(rs.attach.attachmentsCfg?.todoReminderMode !== undefined ? { todoReminderMode: rs.attach.attachmentsCfg.todoReminderMode } : {}),
|
|
290
291
|
toolSearchReminder: toolSearchReminderOn,
|
|
291
292
|
changedFiles: changedFilesOn,
|
|
292
|
-
planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
|
|
293
293
|
budgetUsd: budgetUsdOn,
|
|
294
294
|
totalTokensReminder: totalTokensOn,
|
|
295
295
|
...(rs.attach.attachmentsCfg?.totalTokensReminderMode !== undefined
|
|
@@ -301,6 +301,7 @@ export function createTurnBoundary(input) {
|
|
|
301
301
|
skillsListing: rs.attach.skillsListingOn,
|
|
302
302
|
mcpInstructions: mcpInstructionsOn,
|
|
303
303
|
},
|
|
304
|
+
...(prepared.outputStyleFrame !== undefined && rs.turn.lastTurnHadToolCalls ? { outputStyle: prepared.outputStyleFrame } : {}),
|
|
304
305
|
...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
|
|
305
306
|
? { undiscoveredTools, deferDirectCall: prepared.deferDirectCall }
|
|
306
307
|
: {}),
|
|
@@ -198,8 +198,10 @@ export declare class Runner implements RunnerSelfSeat {
|
|
|
198
198
|
* during the resume — pre-CAS/edit-re-adjudication legs leave the checkpoint pending and resumable,
|
|
199
199
|
* the post-claim leg leaves it consumed; the message says which), `reopen_failed` (an aborted resume
|
|
200
200
|
* needed to reopen the checkpoint and the store refused, or the reopen failed in flight — the message
|
|
201
|
-
* distinguishes terminally-consumed from state-unprovable)
|
|
202
|
-
*
|
|
201
|
+
* distinguishes terminally-consumed from state-unprovable), `config_refused` (pre-CAS: the re-supplied
|
|
202
|
+
* config or a mode the row recorded is one prepare would refuse — `detail.code` is prepare's own code,
|
|
203
|
+
* `detail.field` says whether the config or the row carries it; the checkpoint stays pending and
|
|
204
|
+
* unchanged). Each code's full contract is on {@link CheckpointError.code}.
|
|
203
205
|
*/
|
|
204
206
|
resume(token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig,
|
|
205
207
|
/** Same TRUSTED run-internals seam as {@link resumeStream} — the convenience wrapper
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { AcceptedSteerInput } from "./contracts.js";
|
|
12
12
|
export declare function nextHumanInputSeq(key: object): number;
|
|
13
|
+
/** The identity keys a published `queued` frame carried beyond its own `eventId` (`parentToolCallId`, `sourceTaskId`): one fact, stated
|
|
14
|
+
* once by the frame's publisher and handed to whoever mints that input's other frames, so two frames of one input are never stamped
|
|
15
|
+
* by two computations. */
|
|
16
|
+
export type AcceptanceIdentity = {
|
|
17
|
+
parentToolCallId?: string;
|
|
18
|
+
sourceTaskId?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function armAcceptance(record: object, publish: () => AcceptanceIdentity): void;
|
|
21
|
+
export declare function announceAcceptance(record: object): AcceptanceIdentity | undefined;
|
|
13
22
|
/** Is a repeat under an already-accepted `inputId` the SAME
|
|
14
23
|
* instruction (an idempotent retry) or a different one wearing a used key? Compared field for field,
|
|
15
24
|
* exactly like the parked leg's replay check: the key alone is never evidence of a replay, and two
|
|
@@ -7,6 +7,14 @@ export function nextHumanInputSeq(key) {
|
|
|
7
7
|
}
|
|
8
8
|
return ++box.n;
|
|
9
9
|
}
|
|
10
|
+
const acceptanceSeats = new WeakMap();
|
|
11
|
+
export function armAcceptance(record, publish) {
|
|
12
|
+
let published;
|
|
13
|
+
acceptanceSeats.set(record, () => (published ??= publish()));
|
|
14
|
+
}
|
|
15
|
+
export function announceAcceptance(record) {
|
|
16
|
+
return acceptanceSeats.get(record)?.();
|
|
17
|
+
}
|
|
10
18
|
export function sameAcceptedSteerInput(a, b) {
|
|
11
19
|
return (a.payload === b.payload &&
|
|
12
20
|
a.trusted === b.trusted &&
|
|
@@ -6,7 +6,7 @@ import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "
|
|
|
6
6
|
import { isSystemInjectionPriority, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
|
|
7
7
|
import { deliverEngineNotice } from "../types.js";
|
|
8
8
|
import { inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
9
|
-
import { nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js";
|
|
9
|
+
import { announceAcceptance, armAcceptance, nextHumanInputSeq, sameAcceptedSteerInput } from "./steer-admission.js";
|
|
10
10
|
import { asThrownError } from "../thrown-value.js";
|
|
11
11
|
export function streamSteerVerb(input) {
|
|
12
12
|
const { spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner } = input;
|
|
@@ -44,11 +44,8 @@ export function streamSteerVerb(input) {
|
|
|
44
44
|
let payload;
|
|
45
45
|
let mintsAFrame;
|
|
46
46
|
let replay;
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
return;
|
|
50
|
-
if (typeof inputId === "string")
|
|
51
|
-
acceptedSteerInputs.set(inputId, replay);
|
|
47
|
+
const publishQueued = (h) => {
|
|
48
|
+
const identity = internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId, ...(spec.taskId !== undefined ? { sourceTaskId: spec.taskId } : {}) } : {};
|
|
52
49
|
queue.push({
|
|
53
50
|
...buildHumanInputEvent({
|
|
54
51
|
carrier: "steer",
|
|
@@ -61,10 +58,16 @@ export function streamSteerVerb(input) {
|
|
|
61
58
|
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
62
59
|
}),
|
|
63
60
|
eventId: uuidv7(),
|
|
64
|
-
...
|
|
65
|
-
? { parentToolCallId: internals.parentToolCallId, ...(spec.taskId !== undefined ? { sourceTaskId: spec.taskId } : {}) }
|
|
66
|
-
: {}),
|
|
61
|
+
...identity,
|
|
67
62
|
});
|
|
63
|
+
return identity;
|
|
64
|
+
};
|
|
65
|
+
const noteAccepted = () => {
|
|
66
|
+
if (!mintsAFrame)
|
|
67
|
+
return;
|
|
68
|
+
if (typeof inputId === "string")
|
|
69
|
+
acceptedSteerInputs.set(inputId, replay);
|
|
70
|
+
announceAcceptance(parkRecord);
|
|
68
71
|
};
|
|
69
72
|
const deliver = async () => {
|
|
70
73
|
if (live.resultValue)
|
|
@@ -130,13 +133,15 @@ export function streamSteerVerb(input) {
|
|
|
130
133
|
}
|
|
131
134
|
const injectFramed = async () => {
|
|
132
135
|
const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) };
|
|
136
|
+
if (mintsAFrame)
|
|
137
|
+
armAcceptance(parkRecord, () => publishQueued(h));
|
|
133
138
|
if (priority === "later") {
|
|
134
139
|
await h.harness.followUp(payload, noteOptions);
|
|
135
|
-
noteAccepted(
|
|
140
|
+
noteAccepted();
|
|
136
141
|
return;
|
|
137
142
|
}
|
|
138
143
|
const frame = await h.harness.steer(payload, { ...noteOptions, ...(priority === "now" ? { immediate: true } : {}) });
|
|
139
|
-
noteAccepted(
|
|
144
|
+
noteAccepted();
|
|
140
145
|
if (priority === "now" && frame !== undefined && h.harness.interruptTurn(frame)) {
|
|
141
146
|
deliverEngineNotice(runner.deps.onNotice, {
|
|
142
147
|
code: "task.turn_interrupted",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type TSchema } from "typebox";
|
|
2
2
|
import type { AgentTool } from "../../internal/harness-types.js";
|
|
3
3
|
import type { SkillManifest } from "../types.js";
|
|
4
|
+
import type { TerminalBlockedDetail } from "../terminal-cause.js";
|
|
4
5
|
import { type ResolvedSkillListingBudget } from "../skill-listing-budget.js";
|
|
5
6
|
import type { ActiveSkillScope } from "./active-skill-scope.js";
|
|
6
7
|
/** Reserved name of the injected structured-output tool (CC 2.1.187 `StructuredOutput`; batch-A rename — old
|
|
@@ -21,6 +22,9 @@ export declare const OUTPUT_DEFAULT_RETRIES = 2;
|
|
|
21
22
|
export declare function resolveOutputRetries(outputRetries?: number): number;
|
|
22
23
|
export interface BlockedRef {
|
|
23
24
|
reason?: string;
|
|
25
|
+
/** Written by the engine when a hook seat kept the prompt back (never by the `ReportBlocked` tool): the terminal's
|
|
26
|
+
* `detail`. Absent = the model's own report. */
|
|
27
|
+
detail?: TerminalBlockedDetail;
|
|
24
28
|
}
|
|
25
29
|
/** Captures the validated object a `submit_output` call submitted (structured output, 1.41). `set` is
|
|
26
30
|
* flipped so we can tell "model submitted {}" from "never called". Mirrors `BlockedRef`. */
|
|
@@ -40,7 +40,7 @@ export function createToolTurnRepairSeat(onNotice, ids) {
|
|
|
40
40
|
const announced = new Set();
|
|
41
41
|
const incomplete = createToolTurnIncompleteSeat(onNotice, ids);
|
|
42
42
|
return (repairs) => {
|
|
43
|
-
const missing = repairs.filter((repair) => repair.form === "missing_result").flatMap((repair) => repair.toolCallIds);
|
|
43
|
+
const missing = repairs.filter((repair) => repair.form === "missing_result" && repair.beforeRun !== true).flatMap((repair) => repair.toolCallIds);
|
|
44
44
|
if (missing.length > 0)
|
|
45
45
|
incomplete.announce("missing_result_synthesized", missing.length, missing);
|
|
46
46
|
const fresh = repairs.filter((repair) => {
|
|
@@ -18,10 +18,6 @@ export declare const TODO_REMINDER_CONFIG: {
|
|
|
18
18
|
readonly TURNS_SINCE_WRITE: 10;
|
|
19
19
|
readonly TURNS_BETWEEN_REMINDERS: 10;
|
|
20
20
|
};
|
|
21
|
-
export declare const PLAN_MODE_ATTACHMENT_CONFIG: {
|
|
22
|
-
readonly TURNS_BETWEEN_ATTACHMENTS: 5;
|
|
23
|
-
readonly FULL_REMINDER_EVERY_N_ATTACHMENTS: 5;
|
|
24
|
-
};
|
|
25
21
|
/** CC 2.1.220 tool-search reminder cadence, the shipped defaults of its `juniper_shoal`
|
|
26
22
|
* config block (`sru = 15` / `aru = 10` @185913-185914, read as `{everyNTurns, maxNames}` @185836-185849).
|
|
27
23
|
* `EVERY_N_TURNS` gates BOTH windows (since-last-ToolSearch and since-last-reminder, @608920);
|
|
@@ -80,7 +76,7 @@ export declare const CHANGED_FILES_MTIME_EPS_MS = 2000;
|
|
|
80
76
|
/** Per-boundary byte cap over the bundle's CLIPPABLE frames (weak-model context economics). Intact-class
|
|
81
77
|
* frames ride outside it — see {@link ATTACHMENT_DELIVERY_CLASS}. */
|
|
82
78
|
export declare const ATTACHMENT_BYTE_CAP: number;
|
|
83
|
-
export type AttachmentSource = "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "
|
|
79
|
+
export type AttachmentSource = "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "output_style" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "total_tokens_reminder" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
84
80
|
/** How the bundle cap treats a source — see {@link ATTACHMENT_DELIVERY_CLASS}. */
|
|
85
81
|
export type AttachmentDeliveryClass = {
|
|
86
82
|
readonly class: "clippable";
|
|
@@ -114,7 +110,7 @@ export declare const ATTACHMENT_DELIVERY_CLASS: {
|
|
|
114
110
|
readonly changed_files: {
|
|
115
111
|
readonly class: "clippable";
|
|
116
112
|
};
|
|
117
|
-
readonly
|
|
113
|
+
readonly output_style: {
|
|
118
114
|
readonly class: "clippable";
|
|
119
115
|
};
|
|
120
116
|
readonly budget_usd: {
|
|
@@ -238,10 +234,6 @@ export interface AttachmentState {
|
|
|
238
234
|
/** Cadence-clock value at the last tool_search_usage_reminder emitted (CC 220 `cFy`'s
|
|
239
235
|
* `turnsSinceLastReminder`, found by scanning back for the attachment entry itself). */
|
|
240
236
|
lastToolSearchReminderTurn: number;
|
|
241
|
-
/** plan_mode attachments emitted so far — drives CC's full/sparse rotation (1st, 6th, 11th… = full).
|
|
242
|
-
* 0 also encodes "never attached": CC always attaches on the FIRST plan-mode boundary, then throttles. */
|
|
243
|
-
planAttachmentCount: number;
|
|
244
|
-
lastPlanReminderTurn: number;
|
|
245
237
|
/** changed_files per-path dedup: the last mtime already surfaced for a path — the same (path, mtime)
|
|
246
238
|
* is never reported twice. Evicted alongside readFileState's ENOENT eviction (LOW-9: bounded by it). */
|
|
247
239
|
surfacedMtime: Map<string, number>;
|
|
@@ -296,7 +288,7 @@ export interface AttachmentState {
|
|
|
296
288
|
* leg's real remaining — the CC re-anchor arm, not a floor violation; the floor deliberately
|
|
297
289
|
* does NOT ride the checkpoint, since a carried floor against a fresh slice window would freeze
|
|
298
290
|
* the readout at "0 tokens left" over an allowance the run genuinely holds). Same lifecycle
|
|
299
|
-
* reasoning
|
|
291
|
+
* reasoning applies to every per-leg counter here.
|
|
300
292
|
* - the ROLLOVER (`rollOverContext`, which adds each compacted-away context back so the count keeps
|
|
301
293
|
* advancing) is likewise not owed: sema feeds a CUMULATIVE spend coordinate (`stats.tokens`),
|
|
302
294
|
* which a compaction never rewinds, where CC feeds the live context size.
|
|
@@ -412,17 +404,19 @@ export interface AttachmentInputs {
|
|
|
412
404
|
* multi-block turn consumes one unit PER block, so the windows run 2-3× faster than physical
|
|
413
405
|
* turns on real transcripts (text+tool_use is the norm). Write anchors ({@link stampWriteAnchor})
|
|
414
406
|
* are stamped in this SAME coordinate. Omitted ⇒ falls back to `turn` (correct only for the
|
|
415
|
-
* degenerate one-non-thinking-block-per-turn transcript).
|
|
416
|
-
* a recorded sema deviation already — assistant turns, not CC human turns). */
|
|
407
|
+
* degenerate one-non-thinking-block-per-turn transcript). */
|
|
417
408
|
cadenceTurn?: number;
|
|
418
409
|
/** Whether TodoWrite is mounted (CC gate: no tool ⇒ no reminder, ever). */
|
|
419
410
|
todoMounted: boolean;
|
|
420
411
|
/** Whether the task-family tools are mounted. Both mounted ⇒ task_reminder wins (CC isTodoV2 branch). */
|
|
421
412
|
taskListMounted: boolean;
|
|
422
|
-
/**
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
|
|
413
|
+
/**
|
|
414
|
+
* The output-style frame's body, rendered once at prepare from `TaskSpec.outputStyle` (output-style.ts);
|
|
415
|
+
* present ⇒ the frame is due at this boundary. The reference implementation's producer has no window: it
|
|
416
|
+
* attaches at every attachment cycle while a non-default style is active, so the run loop passes it at every
|
|
417
|
+
* continuing boundary and this producer holds no state for it.
|
|
418
|
+
*/
|
|
419
|
+
outputStyle?: string;
|
|
426
420
|
/** SR-3 budget_usd lane (CC 198 `QFm` producer :480424-480428 + renderer :600225; 220 `_Fy` @609096
|
|
427
421
|
* with the mount at @607474): the run's cost progress against its $ ceiling, in USD. Only passed
|
|
428
422
|
* when `config.budgetUsd` opted in AND the task actually carries a ceiling (CC gate
|
|
@@ -466,7 +460,6 @@ export interface AttachmentInputs {
|
|
|
466
460
|
* `toolSearchReminder` config resolves non-null, @607450-607456 / `cXi` @185859). */
|
|
467
461
|
toolSearchReminder?: boolean;
|
|
468
462
|
changedFiles: boolean;
|
|
469
|
-
planModeReminder: boolean;
|
|
470
463
|
/** SR-3: budget_usd progressive reminder lane (opt-in, default OFF like every 133 member). CC fires
|
|
471
464
|
* it on EVERY attachment cycle while `maxBudgetUsd` is set; there is NO threshold ladder and no
|
|
472
465
|
* throttle window (QFm/`_Fy` are unconditional) — the "progression" is the numbers advancing with
|
|
@@ -625,11 +618,11 @@ export interface AttachmentInputs {
|
|
|
625
618
|
* splitting "decide" from "mark sent" would just invite a drift between the two.
|
|
626
619
|
*
|
|
627
620
|
* Returns attachments in EVIDENCE-STRENGTH order (todo/task → tool_search_usage_reminder →
|
|
628
|
-
*
|
|
621
|
+
* output_style → budget_usd → total_tokens_reminder → background_tasks → tools_delta → agent_listing →
|
|
629
622
|
* skills_listing → mcp_instructions → mcp_dropped_tools → changed_files), which is also the §R3
|
|
630
623
|
* MED-6 truncation order read backwards: under the 8KB cap changed_files is sacrificed first, the
|
|
631
624
|
* todo/task list last. The G1/续批 members are registry/engine ground truth (strong), but the
|
|
632
|
-
* todo/
|
|
625
|
+
* todo/task lane keeps priority: it carries the run's own work-tracking state, which the model can
|
|
633
626
|
* least afford to lose.
|
|
634
627
|
*/
|
|
635
628
|
export declare function collectDueAttachments(state: AttachmentState, inp: AttachmentInputs): readonly TurnAttachment[];
|
|
@@ -856,8 +849,9 @@ export declare function agentListingDeltaHeader(toolName: string): string;
|
|
|
856
849
|
*
|
|
857
850
|
* ShowConcurrencyNote, honest attribution: `entries.length > 1` is a sema APPROXIMATION, not
|
|
858
851
|
* CC semantics. 220's real predicate (@607882) is `Aa() !== "pro" && Cq() === "default"` — subscription
|
|
859
|
-
* tier is not "pro" AND the output style is the default one.
|
|
860
|
-
* subscription plane
|
|
852
|
+
* tier is not "pro" AND the output style is the default one. The tier axis does not exist in core (BYOM has no
|
|
853
|
+
* subscription plane) and the style axis is only a declared seat (`TaskSpec.outputStyle`), so the predicate is
|
|
854
|
+
* untranslatable as a whole, and
|
|
861
855
|
* 220's intent — "show the parallel-launch hint to users who have not already been told it elsewhere" —
|
|
862
856
|
* has no sema carrier either. The roster-size proxy is kept because it is the one honest thing core can
|
|
863
857
|
* say: a single-type roster cannot be launched concurrently with a different type, so the note would be
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { PRESENT_PLAN_TOOL_NAME } from "../present-plan-tool.js";
|
|
2
1
|
import { readMember } from "../thrown-value.js";
|
|
3
2
|
import { delimitUntrustedWithClip, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
4
3
|
import { protocolOf } from "../protocol-table.js";
|
|
@@ -10,10 +9,6 @@ export const TODO_REMINDER_CONFIG = {
|
|
|
10
9
|
TURNS_SINCE_WRITE: 10,
|
|
11
10
|
TURNS_BETWEEN_REMINDERS: 10,
|
|
12
11
|
};
|
|
13
|
-
export const PLAN_MODE_ATTACHMENT_CONFIG = {
|
|
14
|
-
TURNS_BETWEEN_ATTACHMENTS: 5,
|
|
15
|
-
FULL_REMINDER_EVERY_N_ATTACHMENTS: 5,
|
|
16
|
-
};
|
|
17
12
|
export const TOOL_SEARCH_REMINDER_CONFIG = {
|
|
18
13
|
EVERY_N_TURNS: 15,
|
|
19
14
|
MAX_NAMES: 10,
|
|
@@ -35,7 +30,7 @@ export const ATTACHMENT_DELIVERY_CLASS = {
|
|
|
35
30
|
task_reminder: { class: "clippable" },
|
|
36
31
|
tool_search_usage_reminder: { class: "clippable" },
|
|
37
32
|
changed_files: { class: "clippable" },
|
|
38
|
-
|
|
33
|
+
output_style: { class: "clippable" },
|
|
39
34
|
budget_usd: { class: "clippable" },
|
|
40
35
|
total_tokens_reminder: { class: "clippable" },
|
|
41
36
|
background_tasks: { class: "clippable" },
|
|
@@ -68,8 +63,6 @@ export function createAttachmentState() {
|
|
|
68
63
|
lastTodoReminderTurn: 0,
|
|
69
64
|
lastTaskReminderTurn: 0,
|
|
70
65
|
lastToolSearchReminderTurn: 0,
|
|
71
|
-
planAttachmentCount: 0,
|
|
72
|
-
lastPlanReminderTurn: 0,
|
|
73
66
|
surfacedMtime: new Map(),
|
|
74
67
|
postCompactPending: false,
|
|
75
68
|
};
|
|
@@ -193,16 +186,8 @@ export function collectDueAttachments(state, inp) {
|
|
|
193
186
|
(out ??= []).push({ source: "tool_search_usage_reminder", body: renderToolSearchUsageReminder(inp.undiscoveredTools, inp.deferDirectCall !== false) });
|
|
194
187
|
}
|
|
195
188
|
}
|
|
196
|
-
if (inp.
|
|
197
|
-
|
|
198
|
-
inp.turn - state.lastPlanReminderTurn >= PLAN_MODE_ATTACHMENT_CONFIG.TURNS_BETWEEN_ATTACHMENTS;
|
|
199
|
-
if (due) {
|
|
200
|
-
state.planAttachmentCount += 1;
|
|
201
|
-
state.lastPlanReminderTurn = inp.turn;
|
|
202
|
-
const full = state.planAttachmentCount % PLAN_MODE_ATTACHMENT_CONFIG.FULL_REMINDER_EVERY_N_ATTACHMENTS === 1;
|
|
203
|
-
(out ??= []).push({ source: "plan_mode", body: full ? PLAN_MODE_FULL_BODY : PLAN_MODE_SPARSE_BODY });
|
|
204
|
-
}
|
|
205
|
-
}
|
|
189
|
+
if (inp.outputStyle !== undefined)
|
|
190
|
+
(out ??= []).push({ source: "output_style", body: inp.outputStyle });
|
|
206
191
|
if (inp.config.budgetUsd && inp.budgetUsd !== undefined) {
|
|
207
192
|
(out ??= []).push({ source: "budget_usd", body: renderBudgetUsd(inp.budgetUsd.used, inp.budgetUsd.total) });
|
|
208
193
|
}
|
|
@@ -761,8 +746,6 @@ function mcpDroppedToolsBound(inp) {
|
|
|
761
746
|
const single = first === undefined ? 0 : MCP_DROPPED_FIXED_MAX_BYTES + bytes(first.tool) + bytes(first.server) + bytes(first.reason) + 32;
|
|
762
747
|
return Math.max(MCP_DROPPED_BATCH_MAX_BYTES, single);
|
|
763
748
|
}
|
|
764
|
-
const PLAN_MODE_FULL_BODY = `Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system — every such call asks the user first, and is refused where nobody can answer. This supercedes any other instructions you have received. Instead, you should: research and analyze with read-only tools, and when you have formed a concrete plan, present it with the ${PRESENT_PLAN_TOOL_NAME} tool for approval. That tool inherently requests plan approval — never ask for approval via plain text.`;
|
|
765
|
-
const PLAN_MODE_SPARSE_BODY = `Plan mode still active (see full instructions earlier in conversation). Read-only: no edits, commits, or config changes. When your plan is ready, present it with the ${PRESENT_PLAN_TOOL_NAME} tool for approval. Never ask about plan approval via plain text.`;
|
|
766
749
|
const TRUNCATION_MARKER = "\n…[truncated]";
|
|
767
750
|
const MIN_KEEP_BYTES = 160;
|
|
768
751
|
function capAttachments(atts) {
|
|
@@ -718,7 +718,7 @@ export interface RunnerDeps {
|
|
|
718
718
|
*/
|
|
719
719
|
/**
|
|
720
720
|
* The deployment's A2A peer switches. `isolatePeers: true` makes every A2A peer skill's egress default an
|
|
721
|
-
* OPERATOR mandate: its ask is `operator_forced` (kept under `bypassPermissions
|
|
721
|
+
* OPERATOR mandate: its ask is `operator_forced` (kept under `bypassPermissions`; `dontAsk` refuses it like every ask)
|
|
722
722
|
* instead of the releasable `tool_marks_a2a`. Absent ⇒ the default (a permission mode may release it, disclosed).
|
|
723
723
|
*/
|
|
724
724
|
a2a?: {
|
|
@@ -27,6 +27,14 @@ export declare const ALLOW: Extract<ResumeOutcome, {
|
|
|
27
27
|
export declare const EXECUTED_ALLOW: GateOutcome;
|
|
28
28
|
export declare const EXECUTED_ALLOW_REORDERED: GateOutcome;
|
|
29
29
|
export declare const VETOED_BY_POLICY: GateOutcome;
|
|
30
|
+
/** The other decider: the record a `policy_ask` allow the deployment's own POLICY decided files
|
|
31
|
+
* (`policy_allowed`, `who.party: "policy"` with the seat that answered) — the decision's own record on the
|
|
32
|
+
* resolved winner, and the execution record once the call ran — and the decide that carries it. A backend keeps
|
|
33
|
+
* the record verbatim; nothing in it may be re-derived from the person's shape. */
|
|
34
|
+
export declare const POLICY_ALLOWED: GateOutcome;
|
|
35
|
+
export declare const POLICY_ALLOW: Extract<ResumeOutcome, {
|
|
36
|
+
gate: "policy_ask";
|
|
37
|
+
}>;
|
|
30
38
|
/**
|
|
31
39
|
* The full cross-backend contract for {@link CheckpointStore} — the SAME assertions for every
|
|
32
40
|
* backend. Entries are a 1:1 port of the vitest originals (assertion semantics unchanged).
|
|
@@ -41,6 +41,12 @@ const SETTLED = { kind: "human_allowed", who: { party: "person" }, when: 1_700_0
|
|
|
41
41
|
export const EXECUTED_ALLOW = Object.freeze({ disposition: Object.freeze({ kind: "allowed" }), settlement: Object.freeze({ ...SETTLED, who: Object.freeze({ ...SETTLED.who }) }), origin: "policy" });
|
|
42
42
|
export const EXECUTED_ALLOW_REORDERED = Object.freeze({ origin: "policy", settlement: Object.freeze({ when: 1_700_000_000_500, who: Object.freeze({ party: "person" }), kind: "human_allowed" }), disposition: Object.freeze({ kind: "allowed" }) });
|
|
43
43
|
export const VETOED_BY_POLICY = Object.freeze({ disposition: Object.freeze({ kind: "denied", deniedBy: "policy" }), settlement: Object.freeze({ ...SETTLED, who: Object.freeze({ ...SETTLED.who }) }), origin: "policy" });
|
|
44
|
+
export const POLICY_ALLOWED = Object.freeze({
|
|
45
|
+
disposition: Object.freeze({ kind: "allowed" }),
|
|
46
|
+
settlement: Object.freeze({ kind: "policy_allowed", who: Object.freeze({ party: "policy", approver: "unattended_class_answer" }), when: 1_700_000_000_500 }),
|
|
47
|
+
origin: "policy",
|
|
48
|
+
});
|
|
49
|
+
export const POLICY_ALLOW = { ...ALLOW, hostDecision: { decidedBy: "policy", approver: "unattended_class_answer" }, gateOutcome: POLICY_ALLOWED };
|
|
44
50
|
export async function checkpointStoreContract(make, runAssertion) {
|
|
45
51
|
const { run, settle } = beginContract(runAssertion);
|
|
46
52
|
run("kit prerequisites: reopen/setPendingSteer/listByScope are implemented (REQUIRED by this kit)", async () => {
|
|
@@ -399,6 +405,23 @@ export async function checkpointStoreContract(make, runAssertion) {
|
|
|
399
405
|
assert.equal("askClass" in bare, false, "an absent askClass must stay OMITTED on the row");
|
|
400
406
|
assert.equal("classes" in bare, false, "an absent classes must stay OMITTED on the row");
|
|
401
407
|
});
|
|
408
|
+
run("decider pair: a policy-decided allow resolves like a person's — its `policy_allowed` record (party `policy`, the attribution) round-trips verbatim on the resolved winner and as the execution record; the same record is already_recorded, a person's record for that row conflicts", async () => {
|
|
409
|
+
const store = make();
|
|
410
|
+
const cp = createCheckpointFixture();
|
|
411
|
+
await store.put(cp.token, cp);
|
|
412
|
+
assert.equal(await store.resolve(cp.token, cp.scope, POLICY_ALLOW), true, "a policy-decided allow is a decision the CAS takes like any other");
|
|
413
|
+
const resolved = (await store.get(cp.token));
|
|
414
|
+
assert.equal(resolved.status, "resolved");
|
|
415
|
+
assert.equal(resolved.resolvedOutcome.decision, "allow");
|
|
416
|
+
assert.deepEqual(resolved.resolvedOutcome.gateOutcome, POLICY_ALLOWED, "the decision's record rides the winner verbatim — the party word and the attribution included");
|
|
417
|
+
assert.equal(await store.recordExecutionOutcome(cp.token, cp.scope, POLICY_ALLOWED), "recorded");
|
|
418
|
+
const filed = (await store.get(cp.token));
|
|
419
|
+
assert.deepEqual(filed.executionOutcome, POLICY_ALLOWED, "the policy's execution record is kept verbatim");
|
|
420
|
+
assert.deepEqual(executionVerdict(filed), { kind: "executed", gate: POLICY_ALLOWED });
|
|
421
|
+
assert.equal(await store.recordExecutionOutcome(cp.token, cp.scope, structuredClone(POLICY_ALLOWED)), "already_recorded", "an unfrozen structural twin is the same record");
|
|
422
|
+
await assert.rejects(store.recordExecutionOutcome(cp.token, cp.scope, EXECUTED_ALLOW), (e) => e.code === "checkpoint.execution_outcome_conflict", "a person's record for a row the policy decided is a different record");
|
|
423
|
+
assert.deepEqual((await store.get(cp.token)).executionOutcome, POLICY_ALLOWED);
|
|
424
|
+
});
|
|
402
425
|
await settle();
|
|
403
426
|
}
|
|
404
427
|
const sortByToken = (a, b) => a.token.localeCompare(b.token);
|