@sema-agent/core 7.12.0 → 7.13.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 +44 -0
- package/dist/core/ask-origin.d.ts +55 -0
- package/dist/core/ask-origin.js +21 -0
- package/dist/core/engine-notice.d.ts +18 -0
- package/dist/core/gate-lanes.d.ts +0 -40
- package/dist/core/gate-lanes.js +3 -17
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/hooks.js +1 -1
- package/dist/core/runner/assemble-result.d.ts +45 -1
- package/dist/core/runner/assemble-result.js +4 -1
- package/dist/core/runner/compaction-seams.d.ts +42 -0
- package/dist/core/runner/compaction-seams.js +80 -0
- package/dist/core/runner/contracts.d.ts +44 -6
- package/dist/core/runner/denial-limit-arms.js +3 -2
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
- package/dist/core/runner/permission-rule-lanes.js +2 -1
- package/dist/core/runner/prepare-policy-chain.js +3 -2
- package/dist/core/runner/prepare-protocol-tools.js +5 -0
- package/dist/core/runner/prepare-safety-scan.js +11 -16
- package/dist/core/runner/prepare-task.d.ts +9 -1
- package/dist/core/runner/prepare-task.js +6 -1
- package/dist/core/runner/resume-claim.d.ts +2 -2
- package/dist/core/runner/resume-preflight.d.ts +2 -2
- package/dist/core/runner/run-attachment-seats.d.ts +2 -2
- package/dist/core/runner/run-git-lane.d.ts +1 -1
- package/dist/core/runner/run-harness-handlers.js +2 -0
- package/dist/core/runner/run-identity-wiring.d.ts +7 -0
- package/dist/core/runner/run-identity-wiring.js +3 -1
- package/dist/core/runner/run-leg.d.ts +7 -5
- package/dist/core/runner/run-leg.js +256 -5
- package/dist/core/runner/run-notification-lane.d.ts +4 -3
- package/dist/core/runner/run-recovery-lanes.d.ts +4 -17
- package/dist/core/runner/run-recovery-lanes.js +5 -4
- package/dist/core/runner/run-settle-and-teardown.d.ts +14 -10
- package/dist/core/runner/run-settle-and-teardown.js +112 -4
- package/dist/core/runner/run-stop-and-final-verify.d.ts +2 -2
- package/dist/core/runner/run-terminal-adoption.d.ts +16 -16
- package/dist/core/runner/run-terminal-adoption.js +87 -7
- package/dist/core/runner/run-turn-boundary.js +5 -4
- package/dist/core/runner/runtask.d.ts +10 -90
- package/dist/core/runner/runtask.js +77 -596
- package/dist/core/runner/stream-lifecycle-verbs.js +10 -1
- package/dist/core/task-stream.d.ts +10 -1
- package/dist/core/tool-face.d.ts +8 -0
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +5 -3
- package/dist/core/tool-registry.d.ts +11 -3
- package/dist/core/tool-registry.js +7 -1
- package/dist/core/tool-roster.d.ts +26 -0
- package/dist/core/tool-roster.js +38 -7
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/server/http.js +2 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +11 -1
|
@@ -652,6 +652,24 @@ export interface Prepared {
|
|
|
652
652
|
callIssuedAtRef: {
|
|
653
653
|
current?: number;
|
|
654
654
|
};
|
|
655
|
+
/** #677 — was the run's WORK cut by a stop? Two observation points, one record, read by the answer-settled
|
|
656
|
+
* verdict (`answerSettled`) beside the harness's own undrained-input account:
|
|
657
|
+
* - `continuation` — the loop decided to CONTINUE past its last assistant turn and the abort caught it
|
|
658
|
+
* before that continuation dispatched. Written by the loop-trace sink from the loop's own steps: a
|
|
659
|
+
* `continue` step (a drained steer / follow-up, a truncated-output or thinking-only or degenerate-output
|
|
660
|
+
* nudge, a next tool turn) sets it; the terminal step clears it unless the loop exited at its
|
|
661
|
+
* pre-dispatch abort guard (`aborted_before_stream`) — the one exit where `final` is still the PREVIOUS
|
|
662
|
+
* turn's message. A clean "stop" the loop itself had decided was not the end is not a settled answer.
|
|
663
|
+
* - `abortedBeforeTurnEnd` — the run's abort was already in force when a turn ENDED (written by the
|
|
664
|
+
* `turn_end` handler before the turns cap fires its own abort). The ruling's line is the last
|
|
665
|
+
* `turn_end`: a stop that landed before it landed on work — even when the brain ignored the signal
|
|
666
|
+
* and delivered a full "stop" answer through it (the loop then ends at its own decision, so the
|
|
667
|
+
* continuation bit never sets); a stop that landed after it landed on the tail.
|
|
668
|
+
* Always present; both false on a run nothing stopped. */
|
|
669
|
+
workCutRef: {
|
|
670
|
+
continuation: boolean;
|
|
671
|
+
abortedBeforeTurnEnd: boolean;
|
|
672
|
+
};
|
|
655
673
|
/** RB-458 — records the FIRST brain call this run's outer guardrail gave up on (see
|
|
656
674
|
* {@link import("../../brain/timeout.js").withBrainCallGuardrail}). Always present; `timedOut`
|
|
657
675
|
* stays absent unless the guardrail fired. The run loop reads it AFTER the loop settles and gives
|
|
@@ -2332,13 +2350,11 @@ export interface TurnBoundaryDeps {
|
|
|
2332
2350
|
drainManualCompact: (outcome: CompactOutcome) => void;
|
|
2333
2351
|
runnerHooks: {
|
|
2334
2352
|
onError: RunnerDeps["onError"];
|
|
2335
|
-
seamCCompactionOptions: (prepared: Prepared) => Pick<MaybeCompactOptions, "summaryProvider" | "onCompaction" | "maxConsecutiveProviderReuse" | "consecutiveProviderReuse"> | undefined;
|
|
2336
|
-
compactionHookOptions: (spec: TaskSpec, sessionId: string, trigger: "auto" | "manual" | "forced") => Pick<MaybeCompactOptions, "trigger" | "preCompact" | "postCompact">;
|
|
2337
|
-
recordCompactionReuse: (prepared: Prepared, comp: {
|
|
2338
|
-
compacted: boolean;
|
|
2339
|
-
reused?: boolean;
|
|
2340
|
-
}) => void;
|
|
2341
2353
|
};
|
|
2354
|
+
/** The Runner's deployment deps, read LIVE ({@link RunnerDepsSeat}): the compaction seams (compaction-seams.ts) the
|
|
2355
|
+
* boundary's compaction pass reaches down for read the summary provider, the hooks slot and the error sink through it
|
|
2356
|
+
* on every pass — the three Runner methods that used to sit on `runnerHooks` as delegates (design/393 S7). */
|
|
2357
|
+
runner: RunnerDepsSeat;
|
|
2342
2358
|
}
|
|
2343
2359
|
/** design/157 B15 尾件 — R5(harness 事件处理器族)的依赖包。全部为 runLocked 内声明顺序早于
|
|
2344
2360
|
* 工厂调用点的 const 稳定引用 + 三个 runLocked 形参;this 面(deps.onError)经 runnerHooks 打包
|
|
@@ -2423,6 +2439,28 @@ export interface RunnerSelfSeat {
|
|
|
2423
2439
|
/** Streaming form of {@link RunnerSelfSeat.resume}: the pre-CAS guards and the CAS run first, then the live stream is returned. */
|
|
2424
2440
|
resumeStream(token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, internals?: RunInternals): Promise<TaskStream>;
|
|
2425
2441
|
}
|
|
2442
|
+
/**
|
|
2443
|
+
* design/393 S7 (#675) — the orchestrator's entry (`prepareTask`, prepare-task.ts) as ONE contract: the notification lane's
|
|
2444
|
+
* `prepareTask` seat names it, and the orchestrator pins its own declaration against it (`PrepareTaskIsTheContract`), so a
|
|
2445
|
+
* signature change reds at the declaration — not at the driver's hand-in, and not as a seat spelled a second time. It lives
|
|
2446
|
+
* on the floor because a lane may not name the orchestrator (docs/LAYERING.json: layer 4 sits above the lanes). Positional,
|
|
2447
|
+
* exactly as the entry is declared — the seat carries the orchestrator's function itself, not a lane Input.
|
|
2448
|
+
*/
|
|
2449
|
+
export type PrepareTaskFn = (spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, internals?: RunInternals, runnerSelf?: RunnerSelfSeat, runIdSink?: {
|
|
2450
|
+
runId?: string;
|
|
2451
|
+
}) => Promise<Prepared>;
|
|
2452
|
+
/**
|
|
2453
|
+
* design/393 S7 (#670) — the Result of an INSTALLING phase: a lane whose every product is installed on a seat it borrowed
|
|
2454
|
+
* (the harness's stop gate and recovery chain, the run state's counter groups, the stream's result setter, the claimed
|
|
2455
|
+
* row's registries) hands nothing back, and says so by extending this marker. The marker is machine-read by gate:phase-api
|
|
2456
|
+
* (design/238 R-1's third clause): a Result with no members MUST extend it, a Result with members MAY NOT, and no Result
|
|
2457
|
+
* inherits anything else — so an empty Result is a stated fact about the phase, never an interface someone forgot to
|
|
2458
|
+
* fill, and a phase that starts handing a product back must drop the declaration in the same edit. Six lanes carry it:
|
|
2459
|
+
* the attachment seats, the stop gate, the recovery lanes, the settle and teardown (run-), the preflight and the claim
|
|
2460
|
+
* (resume-).
|
|
2461
|
+
*/
|
|
2462
|
+
export interface InstallingPhaseResult {
|
|
2463
|
+
}
|
|
2426
2464
|
/** The live-task handle `runLocked` publishes once the harness exists (design/47): the harness + abort
|
|
2427
2465
|
* controller, the loop-liveness latch (`ended` flips when the single `harness.prompt` settles; `userInterrupted`
|
|
2428
2466
|
* / `userHalted` are the interrupt and halt verbs' attribution seats), the run's reminder mark, its session and
|
|
@@ -2,7 +2,7 @@ import { CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, classifierUnavailableDenyMessage
|
|
|
2
2
|
import { sanitizeAutoModeArmingRecipe, tightenDenialLimit } from "../auto-mode-arming.js";
|
|
3
3
|
import { deliverEngineNotice } from "../types.js";
|
|
4
4
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
5
|
-
import { askOriginOf, classifierMayAnswer } from "../ask-origin.js";
|
|
5
|
+
import { askOriginOf, classifierMayAnswer, probeMandatedAsk } from "../ask-origin.js";
|
|
6
6
|
export function attachRebuiltDenialTrackers(entries, denialLimit) {
|
|
7
7
|
if (entries === undefined)
|
|
8
8
|
return undefined;
|
|
@@ -76,8 +76,9 @@ export function inheritedAskCarry(judged, liveApprover, tracker) {
|
|
|
76
76
|
export async function judgeInheritedClassifier(opts) {
|
|
77
77
|
const { autoMode, ask, req } = opts;
|
|
78
78
|
const origin = askOriginOf(ask, INHERITED_STATION_FACTS);
|
|
79
|
-
if (autoMode === undefined || !classifierMayAnswer(origin))
|
|
79
|
+
if (autoMode === undefined || !classifierMayAnswer(origin) || probeMandatedAsk(ask)) {
|
|
80
80
|
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
81
|
+
}
|
|
81
82
|
const verdict = await autoMode.decider
|
|
82
83
|
.decide({ req, ...(ask.message !== undefined ? { askMessage: ask.message } : {}) }, opts.signal)
|
|
83
84
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
* silence the ungated-write warning for deployments that wired no policy at all.
|
|
27
27
|
*/
|
|
28
28
|
import type { AskRuleEvidence, ToolCallRequest } from "../tool-policy.js";
|
|
29
|
-
import { persistedRuleMandateOf
|
|
29
|
+
import { persistedRuleMandateOf } from "../ask-origin.js";
|
|
30
|
+
import { type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit, type PersistedRuleUnreadable } from "../hooks.js";
|
|
30
31
|
import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage } from "../permission-rule-model.js";
|
|
31
32
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
32
33
|
import type { PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { persistedRuleMandateOf } from "../
|
|
1
|
+
import { persistedRuleMandateOf } from "../ask-origin.js";
|
|
2
|
+
import {} from "../hooks.js";
|
|
2
3
|
import { adjudicatePersistedPathRules, adjudicatePersistedRules, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
3
4
|
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
4
5
|
import { effectivePathTargetOf } from "../effective-path-target.js";
|
|
@@ -10,6 +10,7 @@ import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
|
|
|
10
10
|
import { NAMESPACED_NAME_SHAPES, protocolOf } from "../protocol-table.js";
|
|
11
11
|
import { emitTrace } from "../trace.js";
|
|
12
12
|
import { createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
13
|
+
import { probeMandatedAsk } from "../ask-origin.js";
|
|
13
14
|
import { headlessDenyAtFold, headlessDenyAtRecheck, inheritedAskCarry, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js";
|
|
14
15
|
import { askGrantShapeOf } from "./inherited-ask-grants.js";
|
|
15
16
|
import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js";
|
|
@@ -374,7 +375,7 @@ export async function preparePolicyChain(input) {
|
|
|
374
375
|
if (judged.kind === "deny")
|
|
375
376
|
return judged.result;
|
|
376
377
|
const { ask: inheritedAsk, fallback, mintedHere } = judged;
|
|
377
|
-
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
|
|
378
|
+
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && !probeMandatedAsk(inheritedAsk)) {
|
|
378
379
|
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
379
380
|
return { action: "allow" };
|
|
380
381
|
}
|
|
@@ -454,7 +455,7 @@ export async function preparePolicyChain(input) {
|
|
|
454
455
|
if (judged.kind === "deny")
|
|
455
456
|
return judged.result;
|
|
456
457
|
const { ask: inheritedAsk, fallback, mintedHere } = judged;
|
|
457
|
-
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName)) {
|
|
458
|
+
if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && !probeMandatedAsk(inheritedAsk)) {
|
|
458
459
|
recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
|
|
459
460
|
return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
|
|
460
461
|
}
|
|
@@ -3,12 +3,17 @@ import { materializeMcpTools } from "../mcp.js";
|
|
|
3
3
|
import { materializeA2aTools } from "../a2a.js";
|
|
4
4
|
import { deliverEngineNotice } from "../types.js";
|
|
5
5
|
import { RosterBuilder } from "../tool-roster.js";
|
|
6
|
+
import { TOOL_WIRE_NAME_MAX_CHARS } from "../tool-face.js";
|
|
6
7
|
import { REFRESH_MCP_TOOLS_TOOL_NAME, toolFace } from "../tool-catalog-entries.js";
|
|
7
8
|
import { MCP_NAMESPACE, A2A_NAMESPACE } from "../protocol-table.js";
|
|
8
9
|
import { mintNamespacePrefix } from "../protocol-naming.js";
|
|
9
10
|
import { applyMcpToolFaces } from "./tool-face-overlay.js";
|
|
10
11
|
export async function prepareProtocolTools(input) {
|
|
11
12
|
const { lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback } = input;
|
|
13
|
+
const overPeer = (lockedPreflight.mcp ?? []).find((s) => s.name.length > TOOL_WIRE_NAME_MAX_CHARS);
|
|
14
|
+
if (overPeer !== undefined) {
|
|
15
|
+
throw Object.assign(new Error(`MCP server name ${JSON.stringify(overPeer.name.slice(0, 40) + "…")} is ${overPeer.name.length} characters; a peer rides every roster row of its tools and the roster's bound on a name is ${TOOL_WIRE_NAME_MAX_CHARS}.`), { code: "config.tool_name_too_long" });
|
|
16
|
+
}
|
|
12
17
|
const mcp = lockedPreflight.mcp?.length
|
|
13
18
|
? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
|
|
14
19
|
: { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { NAMESPACED_NAME_SHAPES } from "../protocol-table.js";
|
|
2
2
|
import { engineCardTypes, handBandEffects } from "../tool-registry.js";
|
|
3
|
-
import {
|
|
4
|
-
import { callerCardIdOf, offendingResultCard, schemaKeyBoundProblem } from "../tool-roster.js";
|
|
3
|
+
import { callerCardIdOf, offendingResultCard, rosterMemberBoundProblem, schemaKeyBoundProblem } from "../tool-roster.js";
|
|
5
4
|
import { toolFaceProblem } from "./tool-face-overlay.js";
|
|
6
5
|
import { OFFLOAD_TOOL_NAME } from "../tool-result-store.js";
|
|
7
6
|
import { PRESENT_PLAN_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME } from "../present-plan-tool.js";
|
|
@@ -16,23 +15,19 @@ export function prepareSafetyScan(input) {
|
|
|
16
15
|
const axisExplicitNegatives = new Map();
|
|
17
16
|
const reversibilityProbes = new Map();
|
|
18
17
|
for (const t of spec.tools ?? []) {
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
e
|
|
18
|
+
{
|
|
19
|
+
const bound = rosterMemberBoundProblem({
|
|
20
|
+
name: t.name,
|
|
21
|
+
aliases: t.aliases,
|
|
22
|
+
...(t.contract !== undefined ? { contractId: t.contract.contractId, implementationRevision: t.contract.implementationRevision } : {}),
|
|
23
|
+
...(typeof t.modelGate === "string" ? { modelGate: t.modelGate } : {}),
|
|
24
|
+
});
|
|
25
|
+
if (bound !== undefined) {
|
|
26
|
+
const e = new Error(`Tool "${t.name.slice(0, 40)}" cannot ride a roster row: ${bound.message}.`);
|
|
27
|
+
e.code = bound.code;
|
|
28
28
|
throw e;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
if (t.contract !== undefined && (t.contract.contractId.length > TOOL_CONTRACT_MAX_CHARS || t.contract.implementationRevision.length > TOOL_CONTRACT_MAX_CHARS)) {
|
|
32
|
-
const e = new Error(`Tool "${t.name}" declares a contract member over ${TOOL_CONTRACT_MAX_CHARS} characters (contractId ${t.contract.contractId.length}, implementationRevision ${t.contract.implementationRevision.length}) — the roster's bound.`);
|
|
33
|
-
e.code = "config.tool_contract_too_long";
|
|
34
|
-
throw e;
|
|
35
|
-
}
|
|
36
31
|
{
|
|
37
32
|
const keyProblem = schemaKeyBoundProblem(t.parameters);
|
|
38
33
|
if (keyProblem !== undefined) {
|
|
@@ -8,7 +8,7 @@ export { mcpManifestEntries } from "./prepare-wiring-manifest.js";
|
|
|
8
8
|
export { __resetMaterializeEnvAnnouncements } from "./prepare-tool-disclosure-mount.js";
|
|
9
9
|
export { fileHistoryFilesystemIdentity, resolveFileHistoryScope } from "./prepare-file-history.js";
|
|
10
10
|
import type { RunnerSelfSeat } from "./contracts.js";
|
|
11
|
-
import type { Prepared, PrepareResume, RunInternals } from "./contracts.js";
|
|
11
|
+
import type { Prepared, PrepareResume, PrepareTaskFn, RunInternals } from "./contracts.js";
|
|
12
12
|
export type { FileHistoryBoundarySeat, InheritedGate, Prepared, PreparedMicroCompact, PrepareResume, ResolvedWorkspace, RunInternals, UsageGovernance } from "./contracts.js";
|
|
13
13
|
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
14
14
|
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
@@ -114,3 +114,11 @@ runnerSelf?: RunnerSelfSeat,
|
|
|
114
114
|
runIdSink?: {
|
|
115
115
|
runId?: string;
|
|
116
116
|
}): Promise<Prepared>;
|
|
117
|
+
/** design/393 S7 (#675) — the entry IS the contract the notification lane's seat names ({@link PrepareTaskFn}, contracts.ts):
|
|
118
|
+
* a signature change reds HERE, at the declaration, as `false` failing the `true` bound. IDENTITY, not assignability (review
|
|
119
|
+
* round 1): the parameter tuple (arity, optionality, each type) and the return type must be the SAME type in both directions —
|
|
120
|
+
* one-way `extends` let a dropped trailing parameter, an added optional one or a narrowed return slip through. Type-level only —
|
|
121
|
+
* nothing is emitted. */
|
|
122
|
+
type Holds<T extends true> = T;
|
|
123
|
+
type Same<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
124
|
+
export type PrepareTaskIsTheContract = Holds<Same<Parameters<typeof prepareTask>, Parameters<PrepareTaskFn>>> & Holds<Same<ReturnType<typeof prepareTask>, ReturnType<PrepareTaskFn>>> & Holds<Same<ThisParameterType<typeof prepareTask>, ThisParameterType<PrepareTaskFn>>> & Holds<Same<typeof prepareTask, PrepareTaskFn>>;
|
|
@@ -429,6 +429,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
429
429
|
const rosterSeat = new ToolRosterDeltaSeat();
|
|
430
430
|
const { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef } = await prepareProtocolTools({ lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback });
|
|
431
431
|
const callIssuedAtRef = {};
|
|
432
|
+
const workCutRef = { continuation: false, abortedBeforeTurnEnd: false };
|
|
432
433
|
const memoryWriteGateRef = {};
|
|
433
434
|
const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
|
|
434
435
|
const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
|
|
@@ -617,6 +618,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
617
618
|
},
|
|
618
619
|
...(spec.resilience !== undefined ? { resilience: spec.resilience } : {}),
|
|
619
620
|
loopTrace: (step) => {
|
|
621
|
+
if (step.kind === "continue")
|
|
622
|
+
workCutRef.continuation = true;
|
|
623
|
+
else
|
|
624
|
+
workCutRef.continuation = workCutRef.continuation && step.reason === "aborted_before_stream";
|
|
620
625
|
if (step.kind !== "continue")
|
|
621
626
|
return;
|
|
622
627
|
if (step.reason === "next_turn" || step.reason === "steer_injected" || step.reason === "follow_up_injected")
|
|
@@ -686,7 +691,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
686
691
|
const preparedHolder = {};
|
|
687
692
|
const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, 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, readDenyAdditionsNormalized, preparedHolder });
|
|
688
693
|
await onceLedger.settle(session, announcedListingsRef);
|
|
689
|
-
const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, gateStopRef, suspendForReview, 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 } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
694
|
+
const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, suspendForReview, 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 } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
690
695
|
rollback.commit();
|
|
691
696
|
const prepared = buildPrepared();
|
|
692
697
|
preparedHolder.current = prepared;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* — the post-consume hook fires on the tick the win was read — and returns what that rung returns.
|
|
10
10
|
*/
|
|
11
11
|
import { type Checkpoint, type CheckpointStore, type CheckpointToken } from "../checkpoint-store.js";
|
|
12
|
-
import type { InheritedGate, ResumeRun } from "./contracts.js";
|
|
12
|
+
import type { InheritedGate, InstallingPhaseResult, ResumeRun } from "./contracts.js";
|
|
13
13
|
export interface ResumeClaimInput<R> {
|
|
14
14
|
/** borrowed-readonly — the checkpoint token being claimed. */
|
|
15
15
|
token: CheckpointToken;
|
|
@@ -27,6 +27,6 @@ export interface ResumeClaimInput<R> {
|
|
|
27
27
|
next: (claimed: ResumeClaimResult) => Promise<R>;
|
|
28
28
|
}
|
|
29
29
|
/** The claim's completion is its whole result: a lost CAS throws, so reaching `next` IS the win. */
|
|
30
|
-
export interface ResumeClaimResult {
|
|
30
|
+
export interface ResumeClaimResult extends InstallingPhaseResult {
|
|
31
31
|
}
|
|
32
32
|
export declare function resumeClaim<R>(input: ResumeClaimInput<R>): Promise<R>;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* returns what that rung returns.
|
|
11
11
|
*/
|
|
12
12
|
import { type Checkpoint, type CheckpointStore, type CheckpointToken } from "../checkpoint-store.js";
|
|
13
|
-
import type { InheritedGate, RunnerDepsSeat, SuspendReap } from "./contracts.js";
|
|
13
|
+
import type { InheritedGate, InstallingPhaseResult, RunnerDepsSeat, SuspendReap } from "./contracts.js";
|
|
14
14
|
export interface ResumePreflightInput<R> {
|
|
15
15
|
/** borrowed-readonly — the checkpoint token (handed to the deployment and to the settling CAS). */
|
|
16
16
|
token: CheckpointToken;
|
|
@@ -35,6 +35,6 @@ export interface ResumePreflightInput<R> {
|
|
|
35
35
|
next: (screened: ResumePreflightResult) => Promise<R>;
|
|
36
36
|
}
|
|
37
37
|
/** The preflight's completion is its whole result: every refusal throws, so reaching `next` IS the fact. */
|
|
38
|
-
export interface ResumePreflightResult {
|
|
38
|
+
export interface ResumePreflightResult extends InstallingPhaseResult {
|
|
39
39
|
}
|
|
40
40
|
export declare function resumePreflight<R>(input: ResumePreflightInput<R>): Promise<R>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TaskSpec } from "../types.js";
|
|
2
|
-
import type { Prepared, ResumeRun, RunnerDepsSeat, RunState } from "./contracts.js";
|
|
2
|
+
import type { InstallingPhaseResult, Prepared, ResumeRun, RunnerDepsSeat, RunState } from "./contracts.js";
|
|
3
3
|
export interface RunAttachmentSeatsInput<R> {
|
|
4
4
|
/** borrowed-readonly — the task spec: the attachments config and the `finalVerification` opt-in. */
|
|
5
5
|
spec: TaskSpec;
|
|
@@ -17,6 +17,6 @@ export interface RunAttachmentSeatsInput<R> {
|
|
|
17
17
|
next: (seats: RunAttachmentSeatsResult) => Promise<R>;
|
|
18
18
|
}
|
|
19
19
|
/** Nothing comes back: the lane's products are the `counters` / `attach` groups it wrote on the borrowed run state. */
|
|
20
|
-
export interface RunAttachmentSeatsResult {
|
|
20
|
+
export interface RunAttachmentSeatsResult extends InstallingPhaseResult {
|
|
21
21
|
}
|
|
22
22
|
export declare function runAttachmentSeats<R>(input: RunAttachmentSeatsInput<R>): Promise<R>;
|
|
@@ -52,7 +52,7 @@ export interface RunGitLaneInput {
|
|
|
52
52
|
runner: RunnerDepsSeat;
|
|
53
53
|
}
|
|
54
54
|
export interface RunGitLaneResult {
|
|
55
|
-
/** The parked-mirror flush — awaited at the serialization points (the
|
|
55
|
+
/** The parked-mirror flush — awaited at the serialization points: the `turn_boundary` subscription here is the regular station (the final boundary of a gracefully ending run included); the leg's post-prompt call is the fallback for a terminal that skipped it. */
|
|
56
56
|
flushGitMirror: () => Promise<void>;
|
|
57
57
|
/** The owed-frame retry's `turn_boundary` subscription; the driver's teardown releases it first. */
|
|
58
58
|
unsubGitRetry: () => void;
|
|
@@ -281,6 +281,8 @@ export function createHarnessHandlers(input) {
|
|
|
281
281
|
};
|
|
282
282
|
const onTurnEnd = () => {
|
|
283
283
|
stats.turns += 1;
|
|
284
|
+
if (prepared.abortController.signal.aborted)
|
|
285
|
+
prepared.workCutRef.abortedBeforeTurnEnd = true;
|
|
284
286
|
const stopExtra = rs.turn.turnStopReason !== undefined ? { stopReason: rs.turn.turnStopReason } : {};
|
|
285
287
|
queue.push(rs.turn.turnUsage
|
|
286
288
|
? { type: "turn_end", usage: rs.turn.turnUsage, ...(rs.turn.turnUsageMissing ? { usageMissing: true } : {}), ...stopExtra, ...ident() }
|
|
@@ -73,5 +73,12 @@ export interface RunIdentityWiringResult {
|
|
|
73
73
|
followUp: number;
|
|
74
74
|
} | undefined;
|
|
75
75
|
};
|
|
76
|
+
/** #677 — did the run END with a person's steer / follow-up still pending (queued, or drained at the final boundary and
|
|
77
|
+
* never served)? A READ FACE over this lane's own `let`, written by the same undrained-inputs sink on BOTH its arms
|
|
78
|
+
* (park or not): the terminal-adoption lane folds it into the answer-settled verdict — a "stop" answer the person had
|
|
79
|
+
* already asked to continue past is not a settled answer, so a stop that cut that continuation is a stop on work. */
|
|
80
|
+
userInputLostAtEnd: {
|
|
81
|
+
readonly current: boolean;
|
|
82
|
+
};
|
|
76
83
|
}
|
|
77
84
|
export declare function runIdentityWiring(input: RunIdentityWiringInput): RunIdentityWiringResult;
|
|
@@ -41,7 +41,9 @@ export function runIdentityWiring(input) {
|
|
|
41
41
|
};
|
|
42
42
|
prepared.harness.engineInjectionsHeld = () => prepared.batchHaltRef.current !== undefined;
|
|
43
43
|
let undrainedUserAtEnd;
|
|
44
|
+
let userInputLostAtEnd = false;
|
|
44
45
|
prepared.harness.onUndrainedUserInputs = (counts) => {
|
|
46
|
+
userInputLostAtEnd = counts.steer + counts.followUp > 0;
|
|
45
47
|
if (prepared.pausedRef.current !== undefined) {
|
|
46
48
|
undrainedUserAtEnd = counts;
|
|
47
49
|
return;
|
|
@@ -81,5 +83,5 @@ export function runIdentityWiring(input) {
|
|
|
81
83
|
const loopLatch = { ended: false, userInterrupted: false, userHalted: false };
|
|
82
84
|
onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId, runId: prepared.runId, hookTimeoutMs: prepared.hookTimeoutMs, hookIdentity: prepared.hookIdentity });
|
|
83
85
|
const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
|
|
84
|
-
return { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats, undrainedUserAtEnd: { get current() { return undrainedUserAtEnd; } } };
|
|
86
|
+
return { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats, undrainedUserAtEnd: { get current() { return undrainedUserAtEnd; } }, userInputLostAtEnd: { get current() { return userInputLostAtEnd; } } };
|
|
85
87
|
}
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* assembly — orphaned background tasks, the git frame, the parked steer queue and the wake message each under its own trust
|
|
6
6
|
* framing and redelivery screen — and the prompt itself) and the OBJECTIVE leg (the UserPromptSubmit screen, the git
|
|
7
7
|
* frame's primary delivery, the first-frame listings, the pre-call budget / token / entry-governance gates, the image
|
|
8
|
-
* conversion, the objective's human-input frame and the prompt itself). The
|
|
8
|
+
* conversion, the objective's human-input frame and the prompt itself). The resumed decision's application
|
|
9
|
+
* (`applyResumeDecision`) and the gated call's resolver (`resolvePendingCall` — the post-CAS belts, the execution record)
|
|
10
|
+
* are the lane's own module-private functions since design/393 S7, verbatim from the Runner's private methods: the resume
|
|
11
|
+
* leg is their one caller, and a sibling file would be a lane-to-lane reach (the S6 legs' reading). The tail captures the abort facts; the catch
|
|
9
12
|
* re-throws the resume-equivalent env failures after a bounded teardown and adopts everything else as the leg's throw;
|
|
10
13
|
* the finally releases the timer, the diagnostics, the signal, the content-ask bindings, flips the notification lane
|
|
11
14
|
* and releases the four subscriptions in their order.
|
|
@@ -79,11 +82,10 @@ export interface RunLegInput<R> {
|
|
|
79
82
|
unsubBoundary: () => void;
|
|
80
83
|
/** borrowed-readonly — the git lane's harness-event release, called third. */
|
|
81
84
|
unsub: () => void;
|
|
82
|
-
/** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError`, `hooks`, `onNotice`, `allowImageUrl
|
|
83
|
-
* why it is a seat and not a
|
|
85
|
+
/** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError`, `hooks`, `onNotice`, `allowImageUrl`; the resumed
|
|
86
|
+
* decision's resolver files its execution record's word through `onNotice` too); the one home of why it is a seat and not a
|
|
87
|
+
* captured object is {@link RunnerDepsSeat}. */
|
|
84
88
|
runner: RunnerDepsSeat;
|
|
85
|
-
/** borrowed-readonly — the Runner's application of the resumed decision (its private method, as a delegate). */
|
|
86
|
-
applyResumeDecision: (prepared: Prepared, resume: ResumeRun, emit: (e: TaskEvent) => void, emitCommitted: (entryId: string, role: "user" | "assistant" | "toolResult", toolCallId?: string) => void, onResolvedToolSuccess?: (toolName: string, details: unknown) => void, onExecuteStart?: (toolCallId: string) => void) => Promise<void>;
|
|
87
89
|
/** borrowed-readonly — the terminal adoption, entered in this lane's last continuation with the leg's facts. */
|
|
88
90
|
next: (leg: RunLegResult) => Promise<R>;
|
|
89
91
|
}
|