sortie-dogs 0.16.3 → 0.16.5
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/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/operator-mission.d.ts +2 -0
- package/dist/core/operator-mission.js +1 -1
- package/dist/core/operator-runtime.d.ts +6 -0
- package/dist/core/operator-runtime.js +58 -2
- package/dist/plugin/index.js +52 -50
- package/dist/plugin/mission-review.d.ts +44 -0
- package/dist/plugin/mission-review.js +13 -9
- package/dist/plugin/native-background.js +29 -1
- package/dist/plugin/profiled.js +178 -127
- package/dist/plugin/runtime-bridge.d.ts +8 -0
- package/package.json +1 -1
package/dist/asset-version.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export declare const V010_RUNTIME_ASSET_VERSION = "0.16.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.16.5-mission-direct-retry-v2";
|
|
7
7
|
export declare const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
|
|
8
8
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION | typeof CODEX_SKILL_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export const V010_RUNTIME_ASSET_VERSION = "0.16.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.16.5-mission-direct-retry-v2";
|
|
7
7
|
export const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
|
|
@@ -64,6 +64,8 @@ export interface MissionAttempt {
|
|
|
64
64
|
/** Fingerprint of the settled scoped candidate against which a later Rescue is proposed. */
|
|
65
65
|
candidateID?: string;
|
|
66
66
|
kind: "implementation" | "normal_remediation" | "astra_rescue" | "reviewer_correction" | "direct_execution";
|
|
67
|
+
/** A direct execution may itself be the one ordinary remediation. */
|
|
68
|
+
normalRemediation?: boolean;
|
|
67
69
|
status: "pending" | "dispatched" | "succeeded" | "failed" | "cancelled" | "unconfirmed" | "handed-off";
|
|
68
70
|
callID?: string;
|
|
69
71
|
childSessionID?: string;
|
|
@@ -964,7 +964,7 @@ export function missionPacket(mission, run, includeWorkQuantity = true) {
|
|
|
964
964
|
: run?.phase === "awaiting-decision" ? (run.units.some(unit => unit.dispatchDenial)
|
|
965
965
|
? "Coordinator: inspect units[].dispatch_denial before changing the plan. Correct only its diagnosed cause; do not repeat an unchanged refused Task or replan for a host-state mismatch. Report an unresolved runtime mismatch with the loaded runtime identity; preserve requirements and cumulative spend."
|
|
966
966
|
: run.units.some(unit => unit.status === "failed" && unit.resultClass === "acceptance" && unit.failure?.outcome === "fail" && !unit.normalRemediationUsed)
|
|
967
|
-
?
|
|
967
|
+
? `Coordinator: one exact declared validation failed. Reconcile the ${run.units.some(unit => unit.status === "failed" && unit.resultClass === "acceptance" && unit.failure?.outcome === "fail" && !unit.normalRemediationUsed && unit.directExecution) ? "settled direct execution" : "returned Worker"} and cumulative budget, then call retry_mission_unit once for that unit before replanning. This preserves the same scope and acceptance.`
|
|
968
968
|
: run.units.some(unit => unit.status === "failed" && unit.resultClass === "acceptance" && unit.failure?.outcome === "fail" && unit.normalRemediationUsed && !unit.terminalRescue)
|
|
969
969
|
? "Coordinator: the one ordinary remediation failed the declared validation again. If its native terminal, writer release, exact candidate and cumulative budget permit, call rescue_mission_unit once; inspect and report a non_rescue reason, then continue ordinary correction within budget. Rescue does not bypass validation, review or root acceptance."
|
|
970
970
|
: "Coordinator: correct the cause and call plan_units with the remaining work and all requirements; budget is cumulative.")
|
|
@@ -155,6 +155,12 @@ interface UnitState {
|
|
|
155
155
|
handedOffAt: string;
|
|
156
156
|
settledCallID?: string;
|
|
157
157
|
})[];
|
|
158
|
+
/** Settled direct attempts are historical, not handoffs or validation for a later attempt. */
|
|
159
|
+
priorDirectExecutions?: (NonNullable<UnitState["directExecution"]> & {
|
|
160
|
+
callID: string;
|
|
161
|
+
resultClass: string | null;
|
|
162
|
+
failure?: SerialDispatchSettlement["failure"];
|
|
163
|
+
})[];
|
|
158
164
|
resultClass: string | null;
|
|
159
165
|
failure?: SerialDispatchSettlement["failure"];
|
|
160
166
|
normalRemediationUsed?: boolean;
|
|
@@ -355,7 +355,48 @@ export class OperatorRuntime {
|
|
|
355
355
|
// Display metadata must not turn an otherwise valid execution state into a refusal.
|
|
356
356
|
if (Buffer.byteLength(source) > OPERATOR_LIMITS.planBytes * 4) {
|
|
357
357
|
const { workQuantity: _display, ...execution } = parsed;
|
|
358
|
-
|
|
358
|
+
// Formal snapshot recipes are generated by the host and can legitimately contain
|
|
359
|
+
// many paths. Exclude those arrays only from this size check; retain the exact
|
|
360
|
+
// saved evidence and its freshness inputs in the state used by validation.
|
|
361
|
+
const sizedBinding = (binding) => {
|
|
362
|
+
if (!record(binding))
|
|
363
|
+
return binding;
|
|
364
|
+
const result = { ...binding };
|
|
365
|
+
for (const key of ["source_paths", "candidate_paths"])
|
|
366
|
+
if (Array.isArray(result[key]))
|
|
367
|
+
delete result[key];
|
|
368
|
+
if (record(result.freshness)) {
|
|
369
|
+
const freshness = { ...result.freshness };
|
|
370
|
+
for (const key of ["protected_paths", "scratch_paths", "incidental_control_paths"]) {
|
|
371
|
+
if (Array.isArray(freshness[key]))
|
|
372
|
+
delete freshness[key];
|
|
373
|
+
}
|
|
374
|
+
result.freshness = freshness;
|
|
375
|
+
}
|
|
376
|
+
return result;
|
|
377
|
+
};
|
|
378
|
+
const sizedChecks = (holder) => {
|
|
379
|
+
if (!record(holder) || !Array.isArray(holder.checks))
|
|
380
|
+
return holder;
|
|
381
|
+
return { ...holder, checks: holder.checks.map(check => record(check) && record(check.binding)
|
|
382
|
+
? { ...check, binding: sizedBinding(check.binding) } : check) };
|
|
383
|
+
};
|
|
384
|
+
const sized = { ...execution, units: Array.isArray(execution.units) ? execution.units.map(unit => {
|
|
385
|
+
if (!record(unit) || !Array.isArray(unit.evidence))
|
|
386
|
+
return unit;
|
|
387
|
+
return { ...unit, evidence: unit.evidence.map(item => {
|
|
388
|
+
if (!record(item) || !record(item.protected_binding))
|
|
389
|
+
return item;
|
|
390
|
+
return { ...item, protected_binding: sizedBinding(item.protected_binding) };
|
|
391
|
+
}),
|
|
392
|
+
...(unit.directExecution ? { directExecution: sizedChecks(unit.directExecution) } : {}),
|
|
393
|
+
...(unit.reviewerCorrection ? { reviewerCorrection: sizedChecks(unit.reviewerCorrection) } : {}),
|
|
394
|
+
...(unit.validationRegistration ? { validationRegistration: sizedChecks(unit.validationRegistration) } : {}),
|
|
395
|
+
...(Array.isArray(unit.directHandoffs) ? { directHandoffs: unit.directHandoffs.map(sizedChecks) } : {}),
|
|
396
|
+
...(Array.isArray(unit.priorDirectExecutions) ? { priorDirectExecutions: unit.priorDirectExecutions.map(sizedChecks) } : {}),
|
|
397
|
+
};
|
|
398
|
+
}) : execution.units };
|
|
399
|
+
if (Buffer.byteLength(JSON.stringify(sized)) > OPERATOR_LIMITS.planBytes * 4)
|
|
359
400
|
throw new Error("operator-state-too-large");
|
|
360
401
|
}
|
|
361
402
|
const repairGeneration = parsed.repairGeneration === undefined ? 0 : parsed.repairGeneration;
|
|
@@ -1960,11 +2001,19 @@ export class OperatorRuntime {
|
|
|
1960
2001
|
state.units.some(candidate => candidate.terminalRescue))
|
|
1961
2002
|
throw new Error("operator-mission-normal-remediation-unavailable");
|
|
1962
2003
|
await this.verifyControls(unit);
|
|
2004
|
+
if (unit.directExecution) {
|
|
2005
|
+
if (!unit.directExecution.finishedAt || !unit.callID)
|
|
2006
|
+
throw new Error("operator-mission-normal-remediation-unavailable");
|
|
2007
|
+
(unit.priorDirectExecutions ??= []).push({ ...structuredClone(unit.directExecution), callID: unit.callID,
|
|
2008
|
+
resultClass: unit.resultClass, failure: structuredClone(unit.failure) });
|
|
2009
|
+
delete unit.directExecution;
|
|
2010
|
+
}
|
|
1963
2011
|
// The ordinary retry keeps its exact scope and validation, but the next Worker needs the
|
|
1964
2012
|
// host-observed failed command/exit rather than reconstructing it from an earlier session.
|
|
1965
2013
|
unit.task = { ...unit.task, prompt: `${unit.task.prompt}\nnormal_remediation_prior_validation: ${JSON.stringify({
|
|
1966
2014
|
command: unit.failure.command, exit_code: unit.failure.exitCode
|
|
1967
|
-
})}
|
|
2015
|
+
})}${unit.validationRegistration
|
|
2016
|
+
? `\nnormal_remediation_validation: ${JSON.stringify({ validation: unitValidation(unit), validation_cwd: unitValidationCwd(unit) })}` : ""}` };
|
|
1968
2017
|
unit.normalRemediationUsed = true;
|
|
1969
2018
|
unit.status = "pending";
|
|
1970
2019
|
unit.callID = null;
|
|
@@ -2008,6 +2057,13 @@ export class OperatorRuntime {
|
|
|
2008
2057
|
await this.verifyControls(unit);
|
|
2009
2058
|
// The Rescue route is sealed in host-owned operator state and the returned Task reference.
|
|
2010
2059
|
// Do not alter the registered handoff or expose the stable runtime's rescue capability.
|
|
2060
|
+
if (unit.directExecution) {
|
|
2061
|
+
if (!unit.directExecution.finishedAt || !unit.callID)
|
|
2062
|
+
throw new Error("operator-mission-terminal-rescue-unavailable");
|
|
2063
|
+
(unit.priorDirectExecutions ??= []).push({ ...structuredClone(unit.directExecution), callID: unit.callID,
|
|
2064
|
+
resultClass: unit.resultClass, failure: structuredClone(unit.failure) });
|
|
2065
|
+
delete unit.directExecution;
|
|
2066
|
+
}
|
|
2011
2067
|
unit.task = { ...unit.task, prompt: unit.task.prompt.replace(header, line => `${line}\nterminal_rescue_attempt: ${input.attempt_id}`) };
|
|
2012
2068
|
unit.terminalRescue = { attempt_id: input.attempt_id, selected_model: input.selected_model,
|
|
2013
2069
|
selected_variant: input.selected_variant, accepted_base: structuredClone(base), prior_failure: structuredClone(unit.failure), status: "prepared" };
|
package/dist/plugin/index.js
CHANGED
|
@@ -7942,6 +7942,56 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
7942
7942
|
}
|
|
7943
7943
|
},
|
|
7944
7944
|
};
|
|
7945
|
+
// Both direct and Worker retries must prove the exact reservation settled and its writer released.
|
|
7946
|
+
// A direct actor is not a native child Task and must not acquire a fabricated terminal record.
|
|
7947
|
+
async function missionDispatchWriterReleased(root, taskID, callID, actor, writeScopes) {
|
|
7948
|
+
const snapshot = await (await goalLedger(root)).readGoal();
|
|
7949
|
+
const origin = await input.runtimeBridge?.missionDispatchOrigin?.(root, taskID, callID);
|
|
7950
|
+
const reservationID = goalFingerprint({ goal_id: snapshot.state.goal_id, unit_id: taskID, call_id: origin?.callID ?? callID });
|
|
7951
|
+
if (snapshot.state.outstanding_reservations.some(item => item.reservation_id === reservationID) ||
|
|
7952
|
+
!snapshot.records.some(({ event }) => event.kind === "unit.settled" && event.goal_id === snapshot.state.goal_id &&
|
|
7953
|
+
event.unit_id === taskID && event.reservation_id === reservationID)) {
|
|
7954
|
+
return { ready: false, reason: "exact_dispatch_settlement_missing" };
|
|
7955
|
+
}
|
|
7956
|
+
const active = activeSessions.get(actor), authorization = sessionAuthorizations.get(actor);
|
|
7957
|
+
if (authorization && (authorization.rootSessionID !== root || authorization.taskID !== taskID ||
|
|
7958
|
+
authorization.dispatchCallID !== callID))
|
|
7959
|
+
return { ready: false, reason: "writer_owned_by_other_dispatch" };
|
|
7960
|
+
if (active?.inFlightCalls.size) {
|
|
7961
|
+
const response = await input.client?.session?.messages?.({ path: { id: actor }, query: { directory: input.directory } }).catch(() => undefined);
|
|
7962
|
+
const history = isRecord(response) && Array.isArray(response.data) ? response.data : undefined;
|
|
7963
|
+
if (!history)
|
|
7964
|
+
return { ready: false, reason: "tool_terminal_records_unavailable" };
|
|
7965
|
+
for (const pendingID of active.inFlightCalls) {
|
|
7966
|
+
const parts = history.flatMap(message => isRecord(message) && isRecord(message.info) &&
|
|
7967
|
+
message.info.sessionID === actor && Array.isArray(message.parts) ? message.parts : [])
|
|
7968
|
+
.filter(part => isRecord(part) && part.type === "tool" && part.callID === pendingID);
|
|
7969
|
+
if (parts.length !== 1 || !isRecord(parts[0]) || !isRecord(parts[0].state) ||
|
|
7970
|
+
!["completed", "error"].includes(String(parts[0].state.status)))
|
|
7971
|
+
return { ready: false, reason: "tool_dispatch_active_or_unproven" };
|
|
7972
|
+
}
|
|
7973
|
+
active.inFlightCalls.clear();
|
|
7974
|
+
}
|
|
7975
|
+
const released = JSON.parse(await releaseWriteGate(actor));
|
|
7976
|
+
if (released.status === "denied")
|
|
7977
|
+
return { ready: false, reason: released.reason };
|
|
7978
|
+
if (active)
|
|
7979
|
+
active.released = true;
|
|
7980
|
+
const scopeRoot = await durableScopeRoot(project?.root ?? input.directory);
|
|
7981
|
+
if (scopeRoot === undefined)
|
|
7982
|
+
return active?.parallel === "valid"
|
|
7983
|
+
? { ready: false, reason: "writer_registry_unavailable" } : { ready: true };
|
|
7984
|
+
try {
|
|
7985
|
+
const local = writeScopes.map(path => relative(project?.root ?? input.directory, resolve(project?.root ?? input.directory, normalizeManifestScope(path).path)).replaceAll("\\", "/"))
|
|
7986
|
+
.filter(path => path !== ".." && !path.startsWith("../") && !isAbsolute(path));
|
|
7987
|
+
const scope = normalizeWorktreeScope({ read: [], write: local });
|
|
7988
|
+
return await new ScopeLeaseRegistry(scopeRoot).hasConflictingLease(scope)
|
|
7989
|
+
? { ready: false, reason: "writer_lease_owned_elsewhere" } : { ready: true };
|
|
7990
|
+
}
|
|
7991
|
+
catch {
|
|
7992
|
+
return { ready: false, reason: "writer_registry_unavailable" };
|
|
7993
|
+
}
|
|
7994
|
+
}
|
|
7945
7995
|
input.runtimeBridge?.connected?.({
|
|
7946
7996
|
renderReturnReport: async (root, text, expectedReceipt, missionReview, reviewEvidenceGaps) => {
|
|
7947
7997
|
let rendered;
|
|
@@ -8571,56 +8621,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
8571
8621
|
throw new Error("mission-activation-grant-stale");
|
|
8572
8622
|
}
|
|
8573
8623
|
}),
|
|
8574
|
-
missionWorkerTerminal:
|
|
8575
|
-
|
|
8576
|
-
const snapshot = await (await goalLedger(root)).readGoal();
|
|
8577
|
-
const origin = await input.runtimeBridge?.missionDispatchOrigin?.(root, terminal.taskID, terminal.callID);
|
|
8578
|
-
const reservationID = goalFingerprint({ goal_id: snapshot.state.goal_id, unit_id: terminal.taskID, call_id: origin?.callID ?? terminal.callID });
|
|
8579
|
-
if (snapshot.state.outstanding_reservations.some(item => item.reservation_id === reservationID) ||
|
|
8580
|
-
!snapshot.records.some(({ event }) => event.kind === "unit.settled" && event.goal_id === snapshot.state.goal_id &&
|
|
8581
|
-
event.unit_id === terminal.taskID && event.reservation_id === reservationID)) {
|
|
8582
|
-
return { ready: false, reason: "exact_dispatch_settlement_missing" };
|
|
8583
|
-
}
|
|
8584
|
-
const active = activeSessions.get(childSessionID), authorization = sessionAuthorizations.get(childSessionID);
|
|
8585
|
-
if (authorization && (authorization.rootSessionID !== root || authorization.taskID !== terminal.taskID ||
|
|
8586
|
-
authorization.dispatchCallID !== terminal.callID)) {
|
|
8587
|
-
return { ready: false, reason: "writer_owned_by_other_dispatch" };
|
|
8588
|
-
}
|
|
8589
|
-
if (active?.inFlightCalls.size) {
|
|
8590
|
-
const response = await input.client?.session?.messages?.({ path: { id: childSessionID }, query: { directory: input.directory } }).catch(() => undefined);
|
|
8591
|
-
const history = isRecord(response) && Array.isArray(response.data) ? response.data : undefined;
|
|
8592
|
-
if (!history)
|
|
8593
|
-
return { ready: false, reason: "tool_terminal_records_unavailable" };
|
|
8594
|
-
for (const callID of active.inFlightCalls) {
|
|
8595
|
-
const parts = history.flatMap(message => isRecord(message) && isRecord(message.info) &&
|
|
8596
|
-
message.info.sessionID === childSessionID && Array.isArray(message.parts) ? message.parts : [])
|
|
8597
|
-
.filter(part => isRecord(part) && part.type === "tool" && part.callID === callID);
|
|
8598
|
-
if (parts.length !== 1 || !isRecord(parts[0]) || !isRecord(parts[0].state) ||
|
|
8599
|
-
!["completed", "error"].includes(String(parts[0].state.status)))
|
|
8600
|
-
return { ready: false, reason: "tool_dispatch_active_or_unproven" };
|
|
8601
|
-
}
|
|
8602
|
-
active.inFlightCalls.clear();
|
|
8603
|
-
}
|
|
8604
|
-
const released = JSON.parse(await releaseWriteGate(childSessionID));
|
|
8605
|
-
if (released.status === "denied")
|
|
8606
|
-
return { ready: false, reason: released.reason };
|
|
8607
|
-
if (active)
|
|
8608
|
-
active.released = true;
|
|
8609
|
-
const scopeRoot = await durableScopeRoot(project?.root ?? input.directory);
|
|
8610
|
-
if (scopeRoot === undefined)
|
|
8611
|
-
return active?.parallel === "valid"
|
|
8612
|
-
? { ready: false, reason: "writer_registry_unavailable" } : { ready: true };
|
|
8613
|
-
try {
|
|
8614
|
-
const local = writeScopes.map(path => relative(project?.root ?? input.directory, resolve(project?.root ?? input.directory, normalizeManifestScope(path).path)).replaceAll("\\", "/"))
|
|
8615
|
-
.filter(path => path !== ".." && !path.startsWith("../") && !isAbsolute(path));
|
|
8616
|
-
const scope = normalizeWorktreeScope({ read: [], write: local });
|
|
8617
|
-
return await new ScopeLeaseRegistry(scopeRoot).hasConflictingLease(scope)
|
|
8618
|
-
? { ready: false, reason: "writer_lease_owned_elsewhere" } : { ready: true };
|
|
8619
|
-
}
|
|
8620
|
-
catch {
|
|
8621
|
-
return { ready: false, reason: "writer_registry_unavailable" };
|
|
8622
|
-
}
|
|
8623
|
-
},
|
|
8624
|
+
missionWorkerTerminal: (root, terminal, writeScopes) => missionDispatchWriterReleased(root, terminal.taskID, terminal.callID, terminal.childSessionID, writeScopes),
|
|
8625
|
+
missionDirectTerminal: (root, dispatch, writeScopes) => missionDispatchWriterReleased(root, dispatch.taskID, dispatch.callID, dispatch.actor, writeScopes),
|
|
8624
8626
|
expandMissionWriteGate,
|
|
8625
8627
|
registerGoalDeclaration: async (root, prompt, missionRevision) => {
|
|
8626
8628
|
if (!isCoordinatorSession(root) && !await recoverCoordinatorRoot(root))
|
|
@@ -1,8 +1,52 @@
|
|
|
1
1
|
import { type OperatorState } from "../core/operator-runtime.js";
|
|
2
|
+
import type { GoalEvidence } from "../core/goal-bound.js";
|
|
2
3
|
import { type MissionEvidenceExcerpt, type MissionReviewScope, type OperatorMission } from "../core/operator-mission.js";
|
|
3
4
|
import { type RuntimeProfile } from "../core/runtime-profile.js";
|
|
4
5
|
import type { DirectHandoffValidationCheck, ReviewerCorrectionCheck } from "./runtime-bridge.js";
|
|
5
6
|
export declare const validationDirectoryMatches: (commands: readonly string[], actual: string | undefined, directory?: string, directories?: Readonly<Record<string, string>>) => boolean;
|
|
7
|
+
/** Project measured evidence without copying the host's protected snapshot recipe. */
|
|
8
|
+
export declare function missionEvidenceView(items: readonly GoalEvidence[]): {
|
|
9
|
+
protected_binding_ref?: {
|
|
10
|
+
docker_workspace?: {
|
|
11
|
+
readonly container_id: string;
|
|
12
|
+
readonly cwd: string;
|
|
13
|
+
readonly actual_cwd?: string;
|
|
14
|
+
readonly repository_root: string;
|
|
15
|
+
readonly source: string;
|
|
16
|
+
readonly candidate: string;
|
|
17
|
+
} | undefined;
|
|
18
|
+
manifest_path: string;
|
|
19
|
+
manifest_hash: string;
|
|
20
|
+
source_policy: "project-files-v1" | "declared-paths-v1" | undefined;
|
|
21
|
+
} | undefined;
|
|
22
|
+
evidence_id: string;
|
|
23
|
+
goal_id: string;
|
|
24
|
+
goal_revision: number;
|
|
25
|
+
scope_epoch: number;
|
|
26
|
+
acceptance_fingerprint: string;
|
|
27
|
+
measurement: {
|
|
28
|
+
readonly criterion_ids: readonly string[];
|
|
29
|
+
readonly target: string;
|
|
30
|
+
readonly entrypoint: string;
|
|
31
|
+
readonly workload: string;
|
|
32
|
+
readonly oracle_coverage: readonly string[];
|
|
33
|
+
readonly build_boundary: "included" | "excluded" | "not-applicable";
|
|
34
|
+
};
|
|
35
|
+
identity: {
|
|
36
|
+
readonly source: string;
|
|
37
|
+
readonly candidate: string;
|
|
38
|
+
readonly fixture: string;
|
|
39
|
+
};
|
|
40
|
+
execution: {
|
|
41
|
+
readonly command: readonly string[];
|
|
42
|
+
readonly exit_code: number | null;
|
|
43
|
+
readonly outcome: "pass" | "fail" | "skip" | "cancel";
|
|
44
|
+
readonly started_at: string;
|
|
45
|
+
readonly ended_at: string;
|
|
46
|
+
readonly units: readonly string[];
|
|
47
|
+
};
|
|
48
|
+
proof_scope: "requested-full" | "supporting-proxy" | "document-deliverable" | "expected-negative";
|
|
49
|
+
}[];
|
|
6
50
|
/** Reviewer-facing evidence preserves execution/coverage; the host retains the full snapshot recipe. */
|
|
7
51
|
export declare function missionReviewValidation(run: OperatorState, statePath: string): {
|
|
8
52
|
details_ref: {
|
|
@@ -36,6 +36,18 @@ async function currentGitDiff(git, paths) {
|
|
|
36
36
|
}
|
|
37
37
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
38
38
|
export const validationDirectoryMatches = (commands, actual, directory, directories) => directory === undefined || commands.every(command => resolve(directory, directories?.[normalizeCommand(command)] ?? ".") === resolve(directory, actual ?? "."));
|
|
39
|
+
/** Project measured evidence without copying the host's protected snapshot recipe. */
|
|
40
|
+
export function missionEvidenceView(items) {
|
|
41
|
+
return items.map(({ protected_binding, ...evidence }) => ({
|
|
42
|
+
...evidence,
|
|
43
|
+
...(protected_binding ? { protected_binding_ref: {
|
|
44
|
+
manifest_path: protected_binding.manifest_path,
|
|
45
|
+
manifest_hash: protected_binding.manifest_hash,
|
|
46
|
+
source_policy: protected_binding.source_policy,
|
|
47
|
+
...(protected_binding.docker_workspace ? { docker_workspace: protected_binding.docker_workspace } : {}),
|
|
48
|
+
} } : {}),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
39
51
|
/** Reviewer-facing evidence preserves execution/coverage; the host retains the full snapshot recipe. */
|
|
40
52
|
export function missionReviewValidation(run, statePath) {
|
|
41
53
|
return run.units.map(unit => ({
|
|
@@ -44,15 +56,7 @@ export function missionReviewValidation(run, statePath) {
|
|
|
44
56
|
...(unitValidationCwd(unit) ? {
|
|
45
57
|
validation_cwd: unitValidationCwd(unit),
|
|
46
58
|
} : {}),
|
|
47
|
-
evidence: unit.evidence
|
|
48
|
-
...evidence,
|
|
49
|
-
...(protected_binding ? { protected_binding_ref: {
|
|
50
|
-
manifest_path: protected_binding.manifest_path,
|
|
51
|
-
manifest_hash: protected_binding.manifest_hash,
|
|
52
|
-
source_policy: protected_binding.source_policy,
|
|
53
|
-
...(protected_binding.docker_workspace ? { docker_workspace: protected_binding.docker_workspace } : {}),
|
|
54
|
-
} } : {}),
|
|
55
|
-
})),
|
|
59
|
+
evidence: missionEvidenceView(unit.evidence),
|
|
56
60
|
...(unit.reviewerCorrection ? { correction_checks: (unit.reviewerCorrection.checks ?? []).map(check => ({
|
|
57
61
|
call_id: check.callID, dispatch_call_id: check.dispatchCallID, child_session_id: check.childSessionID,
|
|
58
62
|
command: check.command, directory: check.directory, exit_code: check.exitCode, started_at: check.startedAt, ended_at: check.endedAt,
|
|
@@ -1,6 +1,34 @@
|
|
|
1
|
+
import { missionEvidenceView } from "./mission-review.js";
|
|
1
2
|
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2
3
|
const values = (value) => (Array.isArray(value) ? value : object(value) && Array.isArray(value.data) ? value.data : []).filter(object);
|
|
3
4
|
const key = (parent) => `v2-background-dispatches:${parent}`;
|
|
5
|
+
/** Project the captured Coordinator result, not today's potentially different run or evidence. */
|
|
6
|
+
function modelResult(dispatch, result) {
|
|
7
|
+
if (!object(dispatch.owner) || !object(dispatch.owner.profile) || dispatch.owner.profile.mission !== true)
|
|
8
|
+
return result;
|
|
9
|
+
let packet;
|
|
10
|
+
try {
|
|
11
|
+
packet = JSON.parse(result);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
if (!object(packet) || typeof packet.mission_id !== "string" || typeof packet.run_id !== "string" || !Array.isArray(packet.units))
|
|
17
|
+
return result;
|
|
18
|
+
let changed = false;
|
|
19
|
+
const units = packet.units.map(unit => {
|
|
20
|
+
if (!object(unit) || typeof unit.id !== "string" || !Array.isArray(unit.evidence) ||
|
|
21
|
+
!unit.evidence.every((item) => object(item) && (item.protected_binding === undefined || object(item.protected_binding))) ||
|
|
22
|
+
!unit.evidence.some((item) => object(item.protected_binding)))
|
|
23
|
+
return unit;
|
|
24
|
+
changed = true;
|
|
25
|
+
return { ...unit, evidence: missionEvidenceView(unit.evidence),
|
|
26
|
+
evidence_details_ref: { storage: "sortie-dogs.v010", key: key(dispatch.parent), call_id: dispatch.callID,
|
|
27
|
+
run_id: packet.run_id, unit_id: unit.id, field: "result.units[].evidence",
|
|
28
|
+
omitted: "captured host snapshot recipe; the original result remains in this native background dispatch" } };
|
|
29
|
+
});
|
|
30
|
+
return changed ? JSON.stringify({ ...packet, units }) : result;
|
|
31
|
+
}
|
|
4
32
|
/** Lifecycle transport for native Jobs, not a scheduler. The host owns execution and wakeup. */
|
|
5
33
|
export class NativeBackgroundLifecycle {
|
|
6
34
|
context;
|
|
@@ -135,7 +163,7 @@ export class NativeBackgroundLifecycle {
|
|
|
135
163
|
}
|
|
136
164
|
}
|
|
137
165
|
if (dispatch.result !== undefined)
|
|
138
|
-
additions.set(text, { callID: dispatch.callID, text: dispatch.result });
|
|
166
|
+
additions.set(text, { callID: dispatch.callID, text: modelResult(dispatch, dispatch.result) });
|
|
139
167
|
}
|
|
140
168
|
if (changed)
|
|
141
169
|
await this.write(parent, dispatches);
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -642,7 +642,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
642
642
|
// not by resetting the goal budget. This checkpoint grants no dispatch itself.
|
|
643
643
|
if (mission && (!["completed", "cancelled"].includes(mission.phase) ||
|
|
644
644
|
(mission.phase === "cancelled" && run))) {
|
|
645
|
-
return JSON.stringify(missionPacket(mission, run, false));
|
|
645
|
+
return JSON.stringify(compactMissionEvidence(missionPacket(mission, run, false), run, root));
|
|
646
646
|
}
|
|
647
647
|
return await operators.continuationCheckpoint(root) ?? await proposals.continuationCheckpoint(root);
|
|
648
648
|
},
|
|
@@ -940,7 +940,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
940
940
|
: undefined;
|
|
941
941
|
const attemptFailure = result.disposition === "cancelled"
|
|
942
942
|
? { category: "cancellation", code: "native-task-cancelled" }
|
|
943
|
-
: result.resultClass === "acceptance" && result.failure?.outcome === "fail" &&
|
|
943
|
+
: result.resultClass === "acceptance" && result.failure?.outcome === "fail" &&
|
|
944
|
+
(observed.outcome === "completed" || !!unit.directExecution?.finishedAt)
|
|
944
945
|
? { category: "implementation",
|
|
945
946
|
code: `validation-failed:${result.failure.exitCode ?? "unknown"}` }
|
|
946
947
|
: result.resultClass === "process-defect"
|
|
@@ -1085,10 +1086,28 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1085
1086
|
unit.childSessionID !== attempt.childSessionID || /^task_id: (.+)$/m.exec(unit.task.prompt)?.[1] !== attempt.taskID) {
|
|
1086
1087
|
return { status: "non_rescue", reason: "terminal_identity_conflict" };
|
|
1087
1088
|
}
|
|
1088
|
-
if (unit.directExecution)
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1089
|
+
if (unit.directExecution) {
|
|
1090
|
+
if (!unit.directExecution.finishedAt || unit.status === "running")
|
|
1091
|
+
return { status: "non_rescue", reason: "direct_unit_still_running" };
|
|
1092
|
+
if (unit.directExecution.actor !== attempt.childSessionID)
|
|
1093
|
+
return { status: "non_rescue", reason: "terminal_identity_conflict" };
|
|
1094
|
+
if (checkLiveOutcome) {
|
|
1095
|
+
try {
|
|
1096
|
+
await control.assertActiveGoal(root, await operators.completionGoalFingerprint(run));
|
|
1097
|
+
}
|
|
1098
|
+
catch {
|
|
1099
|
+
return { status: "non_rescue", reason: "goal_not_active" };
|
|
1100
|
+
}
|
|
1101
|
+
const budget = await control.currentBudget(root, { reconcileUsage: false });
|
|
1102
|
+
if (!budget || budget.reserved_units !== 0)
|
|
1103
|
+
return { status: "non_rescue", reason: "goal_reservation_unsettled" };
|
|
1104
|
+
const released = await control.missionDirectTerminal(root, { taskID: attempt.taskID, callID: attempt.callID,
|
|
1105
|
+
actor: unit.directExecution.actor }, unit.unit.write);
|
|
1106
|
+
if (!released.ready)
|
|
1107
|
+
return { status: "non_rescue", reason: released.reason ?? "writer_not_released" };
|
|
1108
|
+
}
|
|
1109
|
+
return { status: "ready", child: unit.directExecution.actor };
|
|
1110
|
+
}
|
|
1092
1111
|
const observed = await observeWorkerTerminal(mission, run, attempt, !checkLiveOutcome);
|
|
1093
1112
|
if (observed.status !== "ready")
|
|
1094
1113
|
return observed;
|
|
@@ -1306,7 +1325,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1306
1325
|
run.units[0]?.status === "failed" && run.units[0]?.resultClass === "acceptance" &&
|
|
1307
1326
|
run.units[0]?.failure?.outcome === "fail" && !run.units[0]?.normalRemediationUsed) {
|
|
1308
1327
|
return { ...packet, task: missions.task(mission),
|
|
1309
|
-
next_action: `Fast-lane: declared validation failed. After the Worker returns, for the same scope and command ` +
|
|
1328
|
+
next_action: `Fast-lane: declared validation failed. After the ${run.units[0]?.directExecution ? "direct execution settles" : "Worker returns"}, for the same scope and command ` +
|
|
1310
1329
|
`call ${retryMissionUnit} once, then ${startDirectUnit} to correct and formally validate here when you have the context; ` +
|
|
1311
1330
|
`delegate its Worker only when useful. For a changed contract use ${planUnits} with executor=self, reason and one ` +
|
|
1312
1331
|
`corrective unit. The Coordinator remains available for actual coordination. Keep cumulative budget and failed history.` };
|
|
@@ -2398,8 +2417,10 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2398
2417
|
const unit = run.units.find(item => item.directExecution?.actor === actor && item.status === "running");
|
|
2399
2418
|
const taskID = /^task_id: (.+)$/mu.exec(unit.task.prompt)[1];
|
|
2400
2419
|
await missions.update(root, mission => {
|
|
2420
|
+
const previous = [...(mission.attempts ?? [])].reverse().find(item => item.runID === run.runID && item.unitID === unit.unit.id);
|
|
2401
2421
|
(mission.attempts ??= []).push({ attemptID: randomUUID(), runID: run.runID, unitID: unit.unit.id,
|
|
2402
|
-
taskID, kind: "direct_execution",
|
|
2422
|
+
taskID, kind: "direct_execution", ...(unit.normalRemediationUsed ? { normalRemediation: true } : {}),
|
|
2423
|
+
...(previous ? { predecessorAttemptID: previous.attemptID } : {}), status: "dispatched", callID: unit.callID, childSessionID: actor });
|
|
2403
2424
|
});
|
|
2404
2425
|
direct = await directExecution(actor);
|
|
2405
2426
|
}
|
|
@@ -2416,7 +2437,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2416
2437
|
throw new Error("mission-run-registration-incomplete: retry plan_units with the same plan to finish goal registration and Mission binding; no unit admitted");
|
|
2417
2438
|
await operators.verifyContinuityControls(run);
|
|
2418
2439
|
}
|
|
2419
|
-
async function assertDirectPlanningQuiescent(actor, planningCallID) {
|
|
2440
|
+
async function assertDirectPlanningQuiescent(actor, planningCallID, planningTool = planUnits) {
|
|
2420
2441
|
await control.assertDirectUnitQuiescent(actor);
|
|
2421
2442
|
// Cold hook state is not proof. Only the exact current planner may be pending;
|
|
2422
2443
|
// a completed background launch or another Task remains an active/unproven writer.
|
|
@@ -2433,7 +2454,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2433
2454
|
throw new Error("mission-replan-terminal-unreconciled:tool_terminal_records_unavailable");
|
|
2434
2455
|
if (part.type !== "tool")
|
|
2435
2456
|
continue;
|
|
2436
|
-
if (part.callID === planningCallID && planningCallID && part.tool ===
|
|
2457
|
+
if (part.callID === planningCallID && planningCallID && part.tool === planningTool)
|
|
2437
2458
|
continue;
|
|
2438
2459
|
if (typeof part.callID !== "string")
|
|
2439
2460
|
throw new Error("mission-replan-terminal-unreconciled:tool_identity_unavailable");
|
|
@@ -2703,129 +2724,158 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2703
2724
|
return JSON.stringify({ status: "recorded", consultation: consultation.consultations?.at(-1) });
|
|
2704
2725
|
} };
|
|
2705
2726
|
const retryMissionUnit = `${profile.toolPrefix}retry_mission_unit`, rescueMissionUnit = `${profile.toolPrefix}rescue_mission_unit`;
|
|
2706
|
-
tools[retryMissionUnit] = { description: "Owning Coordinator or Fast-lane Operator: after a host-classified Worker validation failure, prepare one same-scope remediation. Use start_direct_unit to correct and formally validate in this session, or dispatch the returned Worker when useful.
|
|
2727
|
+
tools[retryMissionUnit] = { description: "Owning Coordinator or Fast-lane Operator: after a host-classified direct or Worker validation failure, prepare one same-scope remediation. Use start_direct_unit to correct and formally validate in this session, or dispatch the returned Worker when useful. Exact settlement, a quiescent and released writer, and remaining cumulative unit budget are required; acceptance and scope remain intact.",
|
|
2707
2728
|
args: { unit_id: stringSchema }, execute: async (args, context) => {
|
|
2708
|
-
const { root
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
attempt.failure?.category
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2729
|
+
const { root } = await missionAuthority(context.sessionID);
|
|
2730
|
+
return serializeDispatchTransition(root, async () => {
|
|
2731
|
+
const { mission } = await missionAuthority(context.sessionID);
|
|
2732
|
+
if (context.sessionID !== (mission.coordinator ?? root) ||
|
|
2733
|
+
(await identity(context.sessionID)).role !== (mission.coordinator ? "dog-operator" : "dog-coordinator")) {
|
|
2734
|
+
throw new Error("mission-remediation-coordinator-required");
|
|
2735
|
+
}
|
|
2736
|
+
const run = await operators.required(root), unitID = String(args.unit_id);
|
|
2737
|
+
const unit = run.units.find(item => item.unit.id === unitID);
|
|
2738
|
+
const attempt = [...(mission.attempts ?? [])].reverse().find(item => item.runID === run.runID && item.unitID === unitID);
|
|
2739
|
+
const oldDirectUnknown = attempt?.kind === "direct_execution" && attempt.failure?.category === "unknown" &&
|
|
2740
|
+
attempt.failure.code === "acceptance" && typeof unit?.failure?.exitCode === "number" && unit.failure.exitCode !== 0 &&
|
|
2741
|
+
unit.directExecution?.checks.some(check => check.exitCode === unit.failure?.exitCode &&
|
|
2742
|
+
JSON.stringify(check.command) === JSON.stringify(unit.failure?.command));
|
|
2743
|
+
if (!unit || unit.status !== "failed" || unit.resultClass !== "acceptance" || unit.failure?.outcome !== "fail" ||
|
|
2744
|
+
unit.normalRemediationUsed || !attempt || !["implementation", "direct_execution"].includes(attempt.kind) || attempt.status !== "failed" ||
|
|
2745
|
+
(attempt.failure?.category !== "implementation" && !oldDirectUnknown))
|
|
2746
|
+
throw new Error("operator-mission-normal-remediation-unavailable");
|
|
2747
|
+
if (attempt.kind === "direct_execution") {
|
|
2748
|
+
try {
|
|
2749
|
+
await assertDirectPlanningQuiescent(context.sessionID, context.callID, retryMissionUnit);
|
|
2750
|
+
}
|
|
2751
|
+
catch (error) {
|
|
2752
|
+
const packet = missionPacket(mission, run);
|
|
2753
|
+
return JSON.stringify({ ...packet, run_status: packet.status, status: "non_rescue",
|
|
2754
|
+
reason: error instanceof Error ? error.message : "direct_unit_tools_unreconciled" });
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
const terminal = await missionWorkerTerminalProof(root, mission, run, attempt, true);
|
|
2758
|
+
if (terminal.status !== "ready") {
|
|
2759
|
+
const packet = missionPacket(mission, run);
|
|
2760
|
+
return JSON.stringify({ ...packet, run_status: packet.status, status: "non_rescue", reason: terminal.reason });
|
|
2761
|
+
}
|
|
2762
|
+
const budget = await control.currentBudget(root);
|
|
2763
|
+
if (!budget || budget.remaining_units < 1) {
|
|
2764
|
+
const packet = missionPacket(mission, run);
|
|
2765
|
+
return JSON.stringify({ ...packet, run_status: packet.status, status: "non_rescue", reason: "budget_exhausted", budget });
|
|
2766
|
+
}
|
|
2767
|
+
const prepared = await operators.prepareMissionNormalRemediation(root, run.runID, unitID);
|
|
2768
|
+
control.enableUnits(root, 1);
|
|
2769
|
+
const next = await operators.next(root, context.sessionID);
|
|
2770
|
+
return JSON.stringify({ status: "normal_remediation_prepared", run_id: prepared.runID,
|
|
2771
|
+
unit_id: unitID, task: record(next) ? next.task : undefined, budget: await control.currentBudget(root) });
|
|
2772
|
+
});
|
|
2735
2773
|
} };
|
|
2736
2774
|
tools[rescueMissionUnit] = { description: "Coordinator: only after the same unit's one ordinary remediation failed the same declared validation, evaluate one optional Astra Rescue. The host requires the current Mission failure class, reconciled native terminal, exact current candidate/acceptance/scope/validation, discovered model availability, one-use and cumulative budget; the returned Worker must still pass ordinary validation, independent review, and root acceptance. This is not the legacy sortie_execute_terminal_rescue tool.",
|
|
2737
2775
|
args: { unit_id: stringSchema }, execute: async (args, context) => {
|
|
2738
|
-
const { root
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2776
|
+
const { root } = await missionAuthority(context.sessionID);
|
|
2777
|
+
return serializeDispatchTransition(root, async () => {
|
|
2778
|
+
const { mission } = await missionAuthority(context.sessionID);
|
|
2779
|
+
if (context.sessionID !== mission.coordinator || (await identity(context.sessionID)).role !== "dog-operator") {
|
|
2780
|
+
throw new Error("mission-rescue-coordinator-required");
|
|
2781
|
+
}
|
|
2782
|
+
const run = await operators.required(root), unitID = String(args.unit_id);
|
|
2783
|
+
const unit = run.units.find(item => item.unit.id === unitID);
|
|
2784
|
+
const attempt = [...(mission.attempts ?? [])].reverse().find(item => item.runID === run.runID && item.unitID === unitID);
|
|
2785
|
+
const nonRescue = async (reason) => {
|
|
2786
|
+
await missions.update(root, state => {
|
|
2787
|
+
state.rescue = { attemptID: attempt?.attemptID ?? randomUUID(),
|
|
2788
|
+
runID: run.runID, unitID, status: "non_rescue", reason };
|
|
2789
|
+
});
|
|
2790
|
+
const packet = missionPacket(await missions.required(root), await operators.read(root));
|
|
2791
|
+
return JSON.stringify({ ...packet, run_status: packet.status, status: "non_rescue", reason });
|
|
2792
|
+
};
|
|
2793
|
+
if (!unit || unit.status !== "failed" || unit.resultClass !== "acceptance" || unit.failure?.outcome !== "fail" ||
|
|
2794
|
+
unit.normalRemediationUsed !== true || unit.terminalRescue || !attempt ||
|
|
2795
|
+
!(attempt.kind === "normal_remediation" || attempt.kind === "direct_execution" && attempt.normalRemediation === true) ||
|
|
2796
|
+
attempt.status !== "failed" || attempt.resultClass !== "acceptance" || attempt.failure?.category !== "implementation") {
|
|
2797
|
+
return nonRescue("normal_remediation_not_exhausted");
|
|
2798
|
+
}
|
|
2799
|
+
if (attempt.kind === "direct_execution") {
|
|
2800
|
+
try {
|
|
2801
|
+
await assertDirectPlanningQuiescent(context.sessionID, context.callID, rescueMissionUnit);
|
|
2802
|
+
}
|
|
2803
|
+
catch (error) {
|
|
2804
|
+
return nonRescue(error instanceof Error ? error.message : "direct_unit_tools_unreconciled");
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
const terminal = await missionWorkerTerminalProof(root, mission, run, attempt, true);
|
|
2808
|
+
if (terminal.status !== "ready")
|
|
2809
|
+
return nonRescue(terminal.reason);
|
|
2810
|
+
const candidate = await missionReviewSource(input.directory, run, [], mission.reviewBaseline, mission.reviewScope);
|
|
2811
|
+
if (!attempt.candidateID || attempt.candidateID !== candidate.fingerprint)
|
|
2812
|
+
return nonRescue("accepted_candidate_changed");
|
|
2813
|
+
const budget = await control.currentBudget(root);
|
|
2814
|
+
if (!budget || budget.reserved_units !== 0)
|
|
2815
|
+
return nonRescue("budget_exhausted");
|
|
2816
|
+
const budgetDetails = budget;
|
|
2817
|
+
const hasTimeLimit = typeof budgetDetails.time_limit_ms === "number";
|
|
2818
|
+
const timeLimit = hasTimeLimit ? budgetDetails.time_limit_ms : Number.MAX_SAFE_INTEGER;
|
|
2819
|
+
const consumedTime = hasTimeLimit ? budgetDetails.settled_time_ms : 0;
|
|
2820
|
+
const hasCostLimit = typeof budgetDetails.cost_limit_usd === "number";
|
|
2821
|
+
const costLimit = hasCostLimit ? budgetDetails.cost_limit_usd : Number.MAX_VALUE;
|
|
2822
|
+
const consumedCost = hasCostLimit ? budgetDetails.settled_cost_usd : (budgetDetails.settled_cost_usd ?? 0);
|
|
2823
|
+
if (!Number.isSafeInteger(timeLimit) || timeLimit < 0 || !Number.isSafeInteger(consumedTime) || consumedTime < 0 ||
|
|
2824
|
+
!Number.isFinite(costLimit) || costLimit < 0 || typeof consumedCost !== "number" || !Number.isFinite(consumedCost) || consumedCost < 0) {
|
|
2825
|
+
return nonRescue("budget_exhausted");
|
|
2826
|
+
}
|
|
2827
|
+
if ((hasTimeLimit && consumedTime >= timeLimit) || (hasCostLimit && consumedCost >= costLimit)) {
|
|
2828
|
+
return nonRescue("budget_exhausted");
|
|
2829
|
+
}
|
|
2830
|
+
const nativeModels = await readHostModels(input.client);
|
|
2831
|
+
const target = nativeModels?.has(DEFAULT_TERMINAL_RESCUE_MODEL)
|
|
2832
|
+
? { model: DEFAULT_TERMINAL_RESCUE_MODEL, variant: null } : null;
|
|
2833
|
+
const terminalIdentity = { run_id: run.runID, unit_id: unitID, attempt_id: attempt.attemptID,
|
|
2834
|
+
predecessor_attempt_id: attempt.predecessorAttemptID ?? null, candidate_id: candidate.fingerprint,
|
|
2835
|
+
route_id: `${profile.id}:mission:${unitID}`, child_id: terminal.child, call_id: attempt.callID };
|
|
2836
|
+
const priorFailure = attempt.failure;
|
|
2837
|
+
const policy = proposeTerminalRescue({ prior_attempt: { identity: terminalIdentity, role: "implementation", recovery_kind: "normal_remediation",
|
|
2838
|
+
disposition: "failed", failure: priorFailure ? { category: priorFailure.category, code: priorFailure.code } : null },
|
|
2839
|
+
terminal_reconciliation: { current: terminalIdentity, observation: { identity: terminalIdentity, disposition: "failed" }, evidence: {
|
|
2840
|
+
terminal: "satisfied", tools_quiescent: "satisfied", artifact_window_closed: "satisfied", writer_released: "satisfied",
|
|
2841
|
+
gate_released: "satisfied", lease_released: "satisfied", worktree_released: "satisfied"
|
|
2842
|
+
} },
|
|
2843
|
+
accepted_base: { candidate_id: candidate.fingerprint, contract_id: run.acceptanceFingerprint, scope: [...unit.unit.write],
|
|
2844
|
+
acceptance: [...run.acceptance], validation: [...unit.unit.validation] }, available_target: target,
|
|
2845
|
+
explicit_override: (attempt.selectedModel !== undefined && attempt.selectedModel !== PREVIEW_WORKER_ROUTE.model) ||
|
|
2846
|
+
(attempt.observedModel !== undefined && attempt.observedModel !== PREVIEW_WORKER_ROUTE.model) ||
|
|
2847
|
+
(attempt.observedVariant !== undefined && attempt.observedVariant !== PREVIEW_WORKER_ROUTE.variant),
|
|
2848
|
+
unit_rescue_count: (mission.attempts ?? []).filter(item => item.runID === run.runID && item.unitID === unitID && item.kind === "astra_rescue").length,
|
|
2849
|
+
active_run_rescue_count: (mission.attempts ?? []).filter(item => item.runID === run.runID && item.kind === "astra_rescue").length,
|
|
2850
|
+
budget_limits: { counters: { recovery_actions: budget.max_units, probe_iterations: 0, model_attempts: budget.max_units },
|
|
2851
|
+
resources: { time_ms: timeLimit, cost_usd: costLimit } },
|
|
2852
|
+
budget_consumed: { counters: { recovery_actions: budget.consumed_units + budget.reserved_units, probe_iterations: 0,
|
|
2853
|
+
model_attempts: budget.consumed_units + budget.reserved_units },
|
|
2854
|
+
resources: { time_ms: consumedTime, cost_usd: consumedCost } },
|
|
2855
|
+
budget_request: { counters: { recovery_actions: 1, probe_iterations: 0, model_attempts: 1 },
|
|
2856
|
+
// Current Mission charges native time/cost through its cumulative goal ledger at normal dispatch settlement;
|
|
2857
|
+
// this policy reservation consumes one existing unit, not an invented parallel resource allowance.
|
|
2858
|
+
resources: { time_ms: 0, cost_usd: 0 } } });
|
|
2859
|
+
if (policy.status !== "proposed")
|
|
2860
|
+
return nonRescue(policy.reason);
|
|
2861
|
+
const rescueAttemptID = randomUUID();
|
|
2862
|
+
const prepared = await operators.prepareMissionTerminalRescue(root, run.runID, unitID, {
|
|
2863
|
+
attempt_id: rescueAttemptID, selected_model: policy.proposal.target.model,
|
|
2864
|
+
selected_variant: policy.proposal.target.variant,
|
|
2865
|
+
accepted_base: { candidate_id: policy.proposal.accepted_base_candidate_id, contract_id: policy.proposal.contract_id,
|
|
2866
|
+
scope: policy.proposal.scope, acceptance: policy.proposal.acceptance, validation: policy.proposal.validation },
|
|
2867
|
+
});
|
|
2868
|
+
control.enableUnits(root, 1);
|
|
2746
2869
|
await missions.update(root, state => {
|
|
2747
|
-
state.rescue = { attemptID:
|
|
2748
|
-
|
|
2870
|
+
state.rescue = { attemptID: rescueAttemptID, runID: run.runID, unitID,
|
|
2871
|
+
selectedModel: policy.proposal.target.model, selectedVariant: policy.proposal.target.variant, status: "prepared" };
|
|
2749
2872
|
});
|
|
2750
|
-
const
|
|
2751
|
-
return JSON.stringify({
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
attempt.status !== "failed" || attempt.resultClass !== "acceptance" || attempt.failure?.category !== "implementation") {
|
|
2756
|
-
return nonRescue("normal_remediation_not_exhausted");
|
|
2757
|
-
}
|
|
2758
|
-
const terminal = await missionWorkerTerminalProof(root, mission, run, attempt, true);
|
|
2759
|
-
if (terminal.status !== "ready")
|
|
2760
|
-
return nonRescue(terminal.reason);
|
|
2761
|
-
const candidate = await missionReviewSource(input.directory, run, [], mission.reviewBaseline, mission.reviewScope);
|
|
2762
|
-
if (!attempt.candidateID || attempt.candidateID !== candidate.fingerprint)
|
|
2763
|
-
return nonRescue("accepted_candidate_changed");
|
|
2764
|
-
const budget = await control.currentBudget(root);
|
|
2765
|
-
if (!budget || budget.reserved_units !== 0)
|
|
2766
|
-
return nonRescue("budget_exhausted");
|
|
2767
|
-
const budgetDetails = budget;
|
|
2768
|
-
const hasTimeLimit = typeof budgetDetails.time_limit_ms === "number";
|
|
2769
|
-
const timeLimit = hasTimeLimit ? budgetDetails.time_limit_ms : Number.MAX_SAFE_INTEGER;
|
|
2770
|
-
const consumedTime = hasTimeLimit ? budgetDetails.settled_time_ms : 0;
|
|
2771
|
-
const hasCostLimit = typeof budgetDetails.cost_limit_usd === "number";
|
|
2772
|
-
const costLimit = hasCostLimit ? budgetDetails.cost_limit_usd : Number.MAX_VALUE;
|
|
2773
|
-
const consumedCost = hasCostLimit ? budgetDetails.settled_cost_usd : (budgetDetails.settled_cost_usd ?? 0);
|
|
2774
|
-
if (!Number.isSafeInteger(timeLimit) || timeLimit < 0 || !Number.isSafeInteger(consumedTime) || consumedTime < 0 ||
|
|
2775
|
-
!Number.isFinite(costLimit) || costLimit < 0 || typeof consumedCost !== "number" || !Number.isFinite(consumedCost) || consumedCost < 0) {
|
|
2776
|
-
return nonRescue("budget_exhausted");
|
|
2777
|
-
}
|
|
2778
|
-
if ((hasTimeLimit && consumedTime >= timeLimit) || (hasCostLimit && consumedCost >= costLimit)) {
|
|
2779
|
-
return nonRescue("budget_exhausted");
|
|
2780
|
-
}
|
|
2781
|
-
const nativeModels = await readHostModels(input.client);
|
|
2782
|
-
const target = nativeModels?.has(DEFAULT_TERMINAL_RESCUE_MODEL)
|
|
2783
|
-
? { model: DEFAULT_TERMINAL_RESCUE_MODEL, variant: null } : null;
|
|
2784
|
-
const terminalIdentity = { run_id: run.runID, unit_id: unitID, attempt_id: attempt.attemptID,
|
|
2785
|
-
predecessor_attempt_id: attempt.predecessorAttemptID ?? null, candidate_id: candidate.fingerprint,
|
|
2786
|
-
route_id: `${profile.id}:mission:${unitID}`, child_id: terminal.child, call_id: attempt.callID };
|
|
2787
|
-
const priorFailure = attempt.failure;
|
|
2788
|
-
const policy = proposeTerminalRescue({ prior_attempt: { identity: terminalIdentity, role: "implementation", recovery_kind: "normal_remediation",
|
|
2789
|
-
disposition: "failed", failure: priorFailure ? { category: priorFailure.category, code: priorFailure.code } : null },
|
|
2790
|
-
terminal_reconciliation: { current: terminalIdentity, observation: { identity: terminalIdentity, disposition: "failed" }, evidence: {
|
|
2791
|
-
terminal: "satisfied", tools_quiescent: "satisfied", artifact_window_closed: "satisfied", writer_released: "satisfied",
|
|
2792
|
-
gate_released: "satisfied", lease_released: "satisfied", worktree_released: "satisfied"
|
|
2793
|
-
} },
|
|
2794
|
-
accepted_base: { candidate_id: candidate.fingerprint, contract_id: run.acceptanceFingerprint, scope: [...unit.unit.write],
|
|
2795
|
-
acceptance: [...run.acceptance], validation: [...unit.unit.validation] }, available_target: target,
|
|
2796
|
-
explicit_override: (attempt.selectedModel !== undefined && attempt.selectedModel !== PREVIEW_WORKER_ROUTE.model) ||
|
|
2797
|
-
(attempt.observedModel !== undefined && attempt.observedModel !== PREVIEW_WORKER_ROUTE.model) ||
|
|
2798
|
-
(attempt.observedVariant !== undefined && attempt.observedVariant !== PREVIEW_WORKER_ROUTE.variant),
|
|
2799
|
-
unit_rescue_count: (mission.attempts ?? []).filter(item => item.runID === run.runID && item.unitID === unitID && item.kind === "astra_rescue").length,
|
|
2800
|
-
active_run_rescue_count: (mission.attempts ?? []).filter(item => item.runID === run.runID && item.kind === "astra_rescue").length,
|
|
2801
|
-
budget_limits: { counters: { recovery_actions: budget.max_units, probe_iterations: 0, model_attempts: budget.max_units },
|
|
2802
|
-
resources: { time_ms: timeLimit, cost_usd: costLimit } },
|
|
2803
|
-
budget_consumed: { counters: { recovery_actions: budget.consumed_units + budget.reserved_units, probe_iterations: 0,
|
|
2804
|
-
model_attempts: budget.consumed_units + budget.reserved_units },
|
|
2805
|
-
resources: { time_ms: consumedTime, cost_usd: consumedCost } },
|
|
2806
|
-
budget_request: { counters: { recovery_actions: 1, probe_iterations: 0, model_attempts: 1 },
|
|
2807
|
-
// Current Mission charges native time/cost through its cumulative goal ledger at normal dispatch settlement;
|
|
2808
|
-
// this policy reservation consumes one existing unit, not an invented parallel resource allowance.
|
|
2809
|
-
resources: { time_ms: 0, cost_usd: 0 } } });
|
|
2810
|
-
if (policy.status !== "proposed")
|
|
2811
|
-
return nonRescue(policy.reason);
|
|
2812
|
-
const rescueAttemptID = randomUUID();
|
|
2813
|
-
const prepared = await operators.prepareMissionTerminalRescue(root, run.runID, unitID, {
|
|
2814
|
-
attempt_id: rescueAttemptID, selected_model: policy.proposal.target.model,
|
|
2815
|
-
selected_variant: policy.proposal.target.variant,
|
|
2816
|
-
accepted_base: { candidate_id: policy.proposal.accepted_base_candidate_id, contract_id: policy.proposal.contract_id,
|
|
2817
|
-
scope: policy.proposal.scope, acceptance: policy.proposal.acceptance, validation: policy.proposal.validation },
|
|
2818
|
-
});
|
|
2819
|
-
control.enableUnits(root, 1);
|
|
2820
|
-
await missions.update(root, state => {
|
|
2821
|
-
state.rescue = { attemptID: rescueAttemptID, runID: run.runID, unitID,
|
|
2822
|
-
selectedModel: policy.proposal.target.model, selectedVariant: policy.proposal.target.variant, status: "prepared" };
|
|
2873
|
+
const next = await operators.next(root, context.sessionID);
|
|
2874
|
+
return JSON.stringify({ status: "prepared", rescue: { attempt_id: rescueAttemptID, selected_model: policy.proposal.target.model,
|
|
2875
|
+
selected_variant: policy.proposal.target.variant, accepted_base_candidate_id: policy.proposal.accepted_base_candidate_id,
|
|
2876
|
+
obligations: policy.proposal.obligations }, run_id: prepared.runID, unit_id: unitID,
|
|
2877
|
+
task: record(next) ? next.task : undefined, budget: await control.currentBudget(root) });
|
|
2823
2878
|
});
|
|
2824
|
-
const next = await operators.next(root, context.sessionID);
|
|
2825
|
-
return JSON.stringify({ status: "prepared", rescue: { attempt_id: rescueAttemptID, selected_model: policy.proposal.target.model,
|
|
2826
|
-
selected_variant: policy.proposal.target.variant, accepted_base_candidate_id: policy.proposal.accepted_base_candidate_id,
|
|
2827
|
-
obligations: policy.proposal.obligations }, run_id: prepared.runID, unit_id: unitID,
|
|
2828
|
-
task: record(next) ? next.task : undefined, budget: await control.currentBudget(root) });
|
|
2829
2879
|
} };
|
|
2830
2880
|
function declaredMissionExecution(mission, execution) {
|
|
2831
2881
|
const operation = mission.execution;
|
|
@@ -5071,7 +5121,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
5071
5121
|
if ((await identity(request.sessionID)).role === "dog-operator") {
|
|
5072
5122
|
const mission = await missions.read(root);
|
|
5073
5123
|
if (mission?.coordinator === request.sessionID) {
|
|
5074
|
-
|
|
5124
|
+
const run = await operators.read(root);
|
|
5125
|
+
(output.context ??= []).push(`Mission continuation: ${JSON.stringify(compactMissionEvidence(missionPacket(mission, run, false), run, root))}. ` +
|
|
5075
5126
|
`Continue in this same Coordinator. Use ${status} or ${next}; retain original requirements and cumulative budget. No proposal/approval phase.`);
|
|
5076
5127
|
return;
|
|
5077
5128
|
}
|
|
@@ -239,6 +239,14 @@ export interface RuntimeBridge {
|
|
|
239
239
|
ready: boolean;
|
|
240
240
|
reason?: string;
|
|
241
241
|
}>;
|
|
242
|
+
missionDirectTerminal(rootSessionID: string, dispatch: {
|
|
243
|
+
taskID: string;
|
|
244
|
+
callID: string;
|
|
245
|
+
actor: string;
|
|
246
|
+
}, writeScopes: readonly string[]): Promise<{
|
|
247
|
+
ready: boolean;
|
|
248
|
+
reason?: string;
|
|
249
|
+
}>;
|
|
242
250
|
restoreReviewerCorrectionChild(rootSessionID: string, childSessionID: string, callID: string, taskID: string): Promise<void>;
|
|
243
251
|
activateMissionWorker(rootSessionID: string, childSessionID: string, handoffPath: string, admission: MissionImplementationAdmission): Promise<void>;
|
|
244
252
|
activateDirectUnit(rootSessionID: string, actorSessionID: string, prompt: string, handoffPath: string): Promise<void>;
|