sortie-dogs 0.13.19 → 0.13.21
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/README.md +7 -7
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/operator-mission.d.ts +24 -1
- package/dist/core/operator-mission.js +127 -19
- package/dist/core/work-quantity-observations.d.ts +4 -0
- package/dist/core/work-quantity-observations.js +78 -6
- package/dist/core/work-quantity.js +41 -5
- package/dist/plugin/gate.d.ts +7 -0
- package/dist/plugin/gate.js +22 -2
- package/dist/plugin/profiled.js +302 -62
- package/dist/plugin/v2.js +96 -83
- package/package.json +1 -1
package/dist/plugin/profiled.js
CHANGED
|
@@ -11,12 +11,12 @@ import { BUILT_IN_MODEL_CATALOG } from "./model-routing.js";
|
|
|
11
11
|
import { goalFingerprint } from "../core/goal-bound.js";
|
|
12
12
|
import { decoratePreviewHeadings, returnReportPanel } from "./receipt-presentation.js";
|
|
13
13
|
import { sanitizeTerminalReport, terminalRunOutcome } from "./run-metrics.js";
|
|
14
|
-
import { normalizeCommand, canonicalObservedValidationMembers, extractWritePaths } from "./gate.js";
|
|
14
|
+
import { normalizeCommand, canonicalObservedValidationMembers, extractWritePaths, isShellTool } from "./gate.js";
|
|
15
15
|
import { normalizeExecutionScope, normalizeManifestScope, normalizeRelativePath } from "../core/path.js";
|
|
16
|
-
import { OperatorMissionRuntime, missionAcceptanceSummary, missionPacket, missionPlan, missionReviewAccepted, missionReviewIndependent, missionReviewScope, missionReviewTask, missionCommandOutcome, missionConversationContext, missionExecutionComplete, missionExecutionStatus, missionOperationSummary, missionValidationCommand, missionReviewTraces, missionReviewVerdict, missionSelfRecheckReport } from "../core/operator-mission.js";
|
|
16
|
+
import { OperatorMissionRuntime, missionAcceptanceSummary, missionPacket, missionPlan, missionReviewAccepted, missionReviewIndependent, missionReviewScope, missionReviewTask, missionCommandOutcome, missionConversationContext, missionExecutionComplete, missionExecutionRecoveryAction, missionExecutionStatus, missionOperationCommandMatch, missionOperationCommandDecorated, missionOperationDirectoryMatch, missionOperationSummary, missionValidationCommand, missionReviewTraces, missionReviewVerdict, missionSelfRecheckReport } from "../core/operator-mission.js";
|
|
17
17
|
import { publishMissionProgress } from "./mission-progress.js";
|
|
18
18
|
import { formatWorkQuantity, planWorkQuantity } from "../core/work-quantity.js";
|
|
19
|
-
import { cachedWorkProgress, observeActorWork, workRootForActor } from "../core/work-quantity-observations.js";
|
|
19
|
+
import { cachedWorkProgress, forgetWorkSession, observeActorWork, workRootForActor } from "../core/work-quantity-observations.js";
|
|
20
20
|
import { serializeOperatorStatus } from "./operator-status-output.js";
|
|
21
21
|
import { completedMissionReviewPrompts, initialMissionReviewPrompt, missionDeliveryObservation, missionReviewBaseline, missionReviewSource, observedMissionValidation, observedMissionValidationSummary, missionReviewValidation, reviewerCorrectionValidation, reviewerCorrectionValidationFresh, validationDirectoryMatches } from "./mission-review.js";
|
|
22
22
|
import { missionLocations, missionLocationPacket } from "./mission-location.js";
|
|
@@ -242,8 +242,14 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
242
242
|
return observedMissionValidationSummary(validation, child, typeof nativeSession?.messages === "function"
|
|
243
243
|
? () => session("messages", { path: { id: child }, query: { directory: input.directory } }) : undefined, notBefore, notBefore === undefined ? undefined : input.directory, direct?.unit.directExecution?.validationCwd ?? direct?.unit.unit.validation_cwd);
|
|
244
244
|
}
|
|
245
|
-
async function reviewMessages(id, notBefore) {
|
|
246
|
-
const
|
|
245
|
+
async function reviewMessages(id, notBefore, requireAvailable = false) {
|
|
246
|
+
const method = typeof nativeSession?.reviewMessages === "function" ? "reviewMessages" : "messages";
|
|
247
|
+
if (requireAvailable && typeof nativeSession?.[method] !== "function")
|
|
248
|
+
throw new Error("native-operation-history-api-unavailable");
|
|
249
|
+
const response = await session(method, { path: { id }, query: { directory: input.directory, ...(notBefore ? { notBefore } : {}) } });
|
|
250
|
+
const result = payload(response);
|
|
251
|
+
if (requireAvailable && (record(response) && response.error || !Array.isArray(result)))
|
|
252
|
+
throw new Error("native-operation-history-response-unavailable");
|
|
247
253
|
return Array.isArray(result) ? result.filter(record) : [];
|
|
248
254
|
}
|
|
249
255
|
async function observedSessionModel(id) {
|
|
@@ -551,7 +557,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
551
557
|
// not by resetting the goal budget. This checkpoint grants no dispatch itself.
|
|
552
558
|
if (mission && (!["completed", "cancelled"].includes(mission.phase) ||
|
|
553
559
|
(mission.phase === "cancelled" && run))) {
|
|
554
|
-
return JSON.stringify(missionPacket(mission, run));
|
|
560
|
+
return JSON.stringify(missionPacket(mission, run, false));
|
|
555
561
|
}
|
|
556
562
|
return await operators.continuationCheckpoint(root) ?? await proposals.continuationCheckpoint(root);
|
|
557
563
|
},
|
|
@@ -1617,6 +1623,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1617
1623
|
if (mission) {
|
|
1618
1624
|
const budget = await control.currentBudget(root);
|
|
1619
1625
|
const run = await operators.read(root);
|
|
1626
|
+
mission = await reconcileMissionExecution(root, mission, run);
|
|
1620
1627
|
const completion = run?.phase === "awaiting-acceptance" ? await control.completionReadiness(root) : undefined;
|
|
1621
1628
|
const acceptanceSummary = await missionAcceptanceSummary(mission, run, operators, acceptanceValidationObservation);
|
|
1622
1629
|
const packet = { acceptance_summary: acceptanceSummary, ...missionDispatchPacket(mission, run), budget,
|
|
@@ -1635,6 +1642,9 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1635
1642
|
delete packet.task;
|
|
1636
1643
|
delete packet.coordinator_dispatch;
|
|
1637
1644
|
}
|
|
1645
|
+
const recoveryAction = missionExecutionRecoveryAction(mission);
|
|
1646
|
+
if (recoveryAction)
|
|
1647
|
+
packet.next_action = recoveryAction;
|
|
1638
1648
|
if (view === "progress")
|
|
1639
1649
|
return serializeOperatorStatus(missionProgress(mission, run, budget, packet));
|
|
1640
1650
|
return serializeOperatorStatus(view === "full" ? packet : compactMissionEvidence(packet, run, root));
|
|
@@ -1766,10 +1776,12 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1766
1776
|
await requireRoot(context.sessionID);
|
|
1767
1777
|
const state = await operators.required(context.sessionID);
|
|
1768
1778
|
let mission = await missions.read(context.sessionID);
|
|
1779
|
+
if (mission)
|
|
1780
|
+
mission = await reconcileMissionExecution(context.sessionID, mission, state);
|
|
1769
1781
|
if (mission?.kind === "operation" && !missionExecutionComplete(mission))
|
|
1770
1782
|
return JSON.stringify({
|
|
1771
1783
|
status: "not-ready", operation_status: missionExecutionStatus(mission),
|
|
1772
|
-
next_action: "The requested operation has not completed. Preserve its real outcome; auxiliary checks and a bounded review do not complete it. Continue the declared operation or report its blocker.",
|
|
1784
|
+
next_action: missionExecutionRecoveryAction(mission) ?? "The requested operation has not completed. Preserve its real outcome; auxiliary checks and a bounded review do not complete it. Continue the declared operation or report its blocker.",
|
|
1773
1785
|
packet: missionPacket(mission, state)
|
|
1774
1786
|
});
|
|
1775
1787
|
if (mission && !["completed", "cancelled"].includes(mission.phase))
|
|
@@ -2518,6 +2530,225 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2518
2530
|
const commands = [...new Set(execution.commands.map(missionValidationCommand).map(normalizeCommand))];
|
|
2519
2531
|
return { commands, directory: resolve(input.directory, execution.directory), observations: operation?.observations ?? [] };
|
|
2520
2532
|
}
|
|
2533
|
+
async function reconcileMissionExecution(root, mission, run) {
|
|
2534
|
+
const execution = mission.execution;
|
|
2535
|
+
if (mission.kind !== "operation" || !execution || !run || run.runID !== mission.runID ||
|
|
2536
|
+
["completed", "cancelled"].includes(mission.phase))
|
|
2537
|
+
return mission;
|
|
2538
|
+
const owners = new Map();
|
|
2539
|
+
const recoveryErrors = [];
|
|
2540
|
+
const addOwner = (actor, window) => owners.set(actor, [...(owners.get(actor) ?? []), window]);
|
|
2541
|
+
for (const unit of run.units) {
|
|
2542
|
+
if (unit.directExecution?.actor) {
|
|
2543
|
+
addOwner(unit.directExecution.actor, { notBefore: unit.directExecution.startedAt,
|
|
2544
|
+
...(unit.directExecution.finishedAt ? { notAfter: unit.directExecution.finishedAt } : {}) });
|
|
2545
|
+
continue;
|
|
2546
|
+
}
|
|
2547
|
+
const child = unit.childSessionID;
|
|
2548
|
+
if (!child || !unit.callID)
|
|
2549
|
+
continue;
|
|
2550
|
+
const attempt = mission.attempts?.find(item => item.runID === run.runID && item.unitID === unit.unit.id &&
|
|
2551
|
+
item.callID === unit.callID && item.childSessionID === child);
|
|
2552
|
+
if (!attempt || attempt.status === "pending")
|
|
2553
|
+
continue;
|
|
2554
|
+
let info;
|
|
2555
|
+
try {
|
|
2556
|
+
const response = await session("get", { path: { id: child }, query: { directory: input.directory } });
|
|
2557
|
+
const value = payload(response);
|
|
2558
|
+
if (!record(value) || record(response) && response.error)
|
|
2559
|
+
throw new Error("native-operation-session-unavailable");
|
|
2560
|
+
info = value;
|
|
2561
|
+
}
|
|
2562
|
+
catch (error) {
|
|
2563
|
+
recoveryErrors.push({ sessionID: child, reason: error instanceof Error ? error.message : String(error) });
|
|
2564
|
+
continue;
|
|
2565
|
+
}
|
|
2566
|
+
if (info.id !== undefined && info.id !== child || info.parentID !== (run.operatorSessionID ?? root))
|
|
2567
|
+
continue;
|
|
2568
|
+
const created = record(info.time) ? info.time.created : undefined;
|
|
2569
|
+
const createdAt = typeof created === "number" && Number.isFinite(created) ? created
|
|
2570
|
+
: typeof created === "string" ? Date.parse(created) : NaN;
|
|
2571
|
+
// A reusable correction author belongs to this admission, not every earlier turn
|
|
2572
|
+
// since the conversation was created. Ordinary Workers have a new native session.
|
|
2573
|
+
const admittedAt = Date.parse(unit.reviewerCorrection?.admittedAt ?? run.createdAt);
|
|
2574
|
+
const settled = unit.reviewerCorrection && ["succeeded", "failed", "cancelled"].includes(unit.status)
|
|
2575
|
+
? [...mission.progress].reverse().find(item => item.unit === `${run.runID}/${unit.unit.id}` &&
|
|
2576
|
+
["succeeded", "failed", "cancelled"].includes(item.status) && Number.isFinite(Date.parse(item.at)))?.at
|
|
2577
|
+
: undefined;
|
|
2578
|
+
// The correction session is reusable after settlement; its later turns are
|
|
2579
|
+
// not this unit's operation. Reuse the existing recorded settlement boundary.
|
|
2580
|
+
if (unit.reviewerCorrection && unit.status !== "running" && !settled)
|
|
2581
|
+
continue;
|
|
2582
|
+
if (Number.isFinite(createdAt) && Number.isFinite(admittedAt)) {
|
|
2583
|
+
addOwner(child, { notBefore: new Date(Math.max(createdAt, admittedAt)).toISOString(),
|
|
2584
|
+
...(settled ? { notAfter: settled } : {}) });
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
if (owners.size === 0 && recoveryErrors.length === 0)
|
|
2588
|
+
return mission;
|
|
2589
|
+
const recovered = [];
|
|
2590
|
+
const observedInputs = [];
|
|
2591
|
+
const conflictingCalls = [];
|
|
2592
|
+
const laterStart = (saved, observed) => !saved || Date.parse(observed) > Date.parse(saved) ? observed : saved;
|
|
2593
|
+
for (const [sessionID, windows] of owners) {
|
|
2594
|
+
// A direct session has its own native admission time; a delegated session is bounded
|
|
2595
|
+
// by its native creation time when exposed by the existing session API.
|
|
2596
|
+
const notBefore = windows.map(window => window.notBefore).sort()[0];
|
|
2597
|
+
if (!notBefore)
|
|
2598
|
+
continue;
|
|
2599
|
+
let history;
|
|
2600
|
+
try {
|
|
2601
|
+
history = await reviewMessages(sessionID, notBefore, true);
|
|
2602
|
+
}
|
|
2603
|
+
catch (error) {
|
|
2604
|
+
recoveryErrors.push({ sessionID, reason: error instanceof Error ? error.message : String(error) });
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
const calls = new Map();
|
|
2608
|
+
const conflicts = new Set();
|
|
2609
|
+
const nativeStarts = new Map();
|
|
2610
|
+
const nativeInputs = new Map();
|
|
2611
|
+
for (const message of history) {
|
|
2612
|
+
const info = record(message.info) ? message.info : message;
|
|
2613
|
+
if (info.role !== "assistant" || info.sessionID !== sessionID || !Array.isArray(message.parts))
|
|
2614
|
+
continue;
|
|
2615
|
+
for (const part of message.parts) {
|
|
2616
|
+
if (!record(part) || part.type !== "tool" || !isShellTool(String(part.tool)) ||
|
|
2617
|
+
(part.sessionID !== undefined && part.sessionID !== sessionID) || typeof part.callID !== "string" ||
|
|
2618
|
+
!record(part.state) || !record(part.state.input))
|
|
2619
|
+
continue;
|
|
2620
|
+
const state = part.state;
|
|
2621
|
+
const nativeInput = state.input;
|
|
2622
|
+
if (typeof nativeInput.command !== "string")
|
|
2623
|
+
continue;
|
|
2624
|
+
const time = record(state.time) ? state.time : record(part.time) ? part.time : {};
|
|
2625
|
+
const date = (value) => typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString()
|
|
2626
|
+
: typeof value === "string" && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : undefined;
|
|
2627
|
+
const startedAt = date(time.start ?? time.ran);
|
|
2628
|
+
if (!startedAt)
|
|
2629
|
+
continue;
|
|
2630
|
+
// A boundary page may include tools from a later reusable-session turn.
|
|
2631
|
+
// Ignore them before comparing call input or terminal state as well.
|
|
2632
|
+
if (!windows.some(window => Date.parse(startedAt) >= Date.parse(window.notBefore) &&
|
|
2633
|
+
(!window.notAfter || Date.parse(startedAt) <= Date.parse(window.notAfter))))
|
|
2634
|
+
continue;
|
|
2635
|
+
const nativeDirectory = resolve(input.directory, typeof nativeInput.workdir === "string" ? nativeInput.workdir : ".");
|
|
2636
|
+
const directory = missionOperationDirectoryMatch(nativeDirectory, execution.directory) ? execution.directory : nativeDirectory;
|
|
2637
|
+
const key = part.callID;
|
|
2638
|
+
nativeStarts.set(key, laterStart(nativeStarts.get(key), startedAt));
|
|
2639
|
+
const nativeInputIdentity = { command: nativeInput.command, directory };
|
|
2640
|
+
const priorInput = nativeInputs.get(key);
|
|
2641
|
+
if (priorInput && (priorInput.command !== nativeInput.command || priorInput.directory !== directory)) {
|
|
2642
|
+
conflicts.add(key);
|
|
2643
|
+
}
|
|
2644
|
+
nativeInputs.set(key, nativeInputIdentity);
|
|
2645
|
+
const command = missionOperationCommandMatch(nativeInput.command, execution.commands, execution.directory);
|
|
2646
|
+
// Compare admitted inputs with persisted same-call observations even when this
|
|
2647
|
+
// record is no longer a matching command/cwd and will not yield a new result.
|
|
2648
|
+
observedInputs.push({ sessionID, callID: key, command, directory, startedAt });
|
|
2649
|
+
if (directory !== execution.directory)
|
|
2650
|
+
continue;
|
|
2651
|
+
if (!command)
|
|
2652
|
+
continue;
|
|
2653
|
+
const metadata = record(state.metadata) ? state.metadata : {};
|
|
2654
|
+
const nativeStatus = String(state.status ?? "").toLowerCase();
|
|
2655
|
+
const resultStatus = String(metadata.status ?? "").toLowerCase();
|
|
2656
|
+
if ((nativeStatus === "error" && resultStatus === "running") || (nativeStatus === "running" && resultStatus === "error")) {
|
|
2657
|
+
conflicts.add(key);
|
|
2658
|
+
}
|
|
2659
|
+
const running = nativeStatus === "running" || resultStatus === "running";
|
|
2660
|
+
const failed = nativeStatus === "error" || resultStatus === "error";
|
|
2661
|
+
if (!["completed", "error", "running"].includes(nativeStatus))
|
|
2662
|
+
continue;
|
|
2663
|
+
if (!running && !failed && nativeStatus !== "completed")
|
|
2664
|
+
continue;
|
|
2665
|
+
const completedAt = running ? undefined : date(time.end ?? time.completed);
|
|
2666
|
+
const exit = Number.isSafeInteger(metadata.exit) ? metadata.exit : undefined;
|
|
2667
|
+
const output = typeof state.output === "string" ? state.output : typeof part.output === "string" ? part.output : "";
|
|
2668
|
+
const status = failed ? "error" : running ? "running" : "completed";
|
|
2669
|
+
const outcome = conflicts.has(key) || running || !completedAt || exit === undefined ? undefined : missionCommandOutcome(output, exit, status);
|
|
2670
|
+
// A completed tool envelope without an exit/result does not prove a successful
|
|
2671
|
+
// process. Keep it unclassified instead of turning missing metadata into success.
|
|
2672
|
+
if (nativeInput.background === true && !running)
|
|
2673
|
+
continue;
|
|
2674
|
+
const observation = { command, directory: execution.directory, callID: key, sessionID, startedAt, nativeStartedAt: startedAt,
|
|
2675
|
+
...(completedAt ? { completedAt } : {}), ...(exit === undefined ? {} : { exit }), status,
|
|
2676
|
+
...(typeof metadata.shellID === "string" ? { shellID: metadata.shellID } : {}), ...(outcome ?? {}) };
|
|
2677
|
+
const prior = calls.get(key);
|
|
2678
|
+
if (prior && (prior.command !== command || prior.directory !== directory || prior.observation.startedAt !== startedAt ||
|
|
2679
|
+
prior.observation.completedAt && observation.completedAt && (prior.observation.status !== status ||
|
|
2680
|
+
prior.observation.completedAt !== observation.completedAt ||
|
|
2681
|
+
prior.observation.exit !== undefined && exit !== undefined && prior.observation.exit !== exit ||
|
|
2682
|
+
prior.observation.outcome !== undefined && observation.outcome !== undefined && JSON.stringify(prior.observation) !== JSON.stringify(observation)))) {
|
|
2683
|
+
conflicts.add(key);
|
|
2684
|
+
}
|
|
2685
|
+
else if (!prior || !conflicts.has(key) && (prior.observation.outcome === undefined || observation.outcome !== undefined)) {
|
|
2686
|
+
calls.set(key, { command, directory, observation });
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
for (const [callID, item] of calls)
|
|
2691
|
+
recovered.push({ ...item.observation, ...(conflicts.has(callID)
|
|
2692
|
+
? { conflicting: true, latestConflictingStartedAt: nativeStarts.get(callID) } : {}) });
|
|
2693
|
+
for (const callID of conflicts)
|
|
2694
|
+
conflictingCalls.push({ sessionID, callID, latestConflictingStartedAt: nativeStarts.get(callID) });
|
|
2695
|
+
}
|
|
2696
|
+
if (recovered.length === 0 && conflictingCalls.length === 0 && observedInputs.length === 0 && recoveryErrors.length === 0 && !execution.recovery)
|
|
2697
|
+
return mission;
|
|
2698
|
+
return missions.update(root, current => {
|
|
2699
|
+
if (current.runID !== mission.runID || current.kind !== "operation" || !current.execution ||
|
|
2700
|
+
["completed", "cancelled"].includes(current.phase) ||
|
|
2701
|
+
current.execution.directory !== execution.directory || JSON.stringify(current.execution.commands) !== JSON.stringify(execution.commands))
|
|
2702
|
+
return;
|
|
2703
|
+
if (recoveryErrors.length)
|
|
2704
|
+
current.execution.recovery = { status: "unavailable", errors: recoveryErrors };
|
|
2705
|
+
else
|
|
2706
|
+
delete current.execution.recovery;
|
|
2707
|
+
for (const nativeInput of observedInputs) {
|
|
2708
|
+
const prior = current.execution.observations.find(item => item.callID === nativeInput.callID && item.sessionID === nativeInput.sessionID);
|
|
2709
|
+
if (prior && (prior.command !== nativeInput.command || prior.directory !== nativeInput.directory)) {
|
|
2710
|
+
prior.conflicting = true;
|
|
2711
|
+
prior.latestConflictingStartedAt = laterStart(prior.latestConflictingStartedAt ?? prior.nativeStartedAt, nativeInput.startedAt);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
for (const observation of recovered) {
|
|
2715
|
+
const prior = current.execution.observations.find(item => item.callID === observation.callID && item.sessionID === observation.sessionID);
|
|
2716
|
+
if (!prior) {
|
|
2717
|
+
current.execution.observations.push(observation);
|
|
2718
|
+
continue;
|
|
2719
|
+
}
|
|
2720
|
+
if (prior.command !== observation.command || prior.directory !== observation.directory)
|
|
2721
|
+
continue;
|
|
2722
|
+
if (observation.conflicting || prior.nativeStartedAt !== undefined && prior.nativeStartedAt !== observation.nativeStartedAt ||
|
|
2723
|
+
prior.completedAt && observation.completedAt && (prior.status !== observation.status ||
|
|
2724
|
+
prior.exit !== undefined && observation.exit !== undefined && prior.exit !== observation.exit ||
|
|
2725
|
+
prior.outcome !== undefined && observation.outcome !== undefined && prior.outcome !== observation.outcome)) {
|
|
2726
|
+
prior.conflicting = true;
|
|
2727
|
+
}
|
|
2728
|
+
if (prior.conflicting)
|
|
2729
|
+
prior.latestConflictingStartedAt = laterStart(prior.latestConflictingStartedAt ?? prior.nativeStartedAt, observation.latestConflictingStartedAt ?? observation.nativeStartedAt);
|
|
2730
|
+
prior.nativeStartedAt ??= observation.nativeStartedAt;
|
|
2731
|
+
if (prior.outcome !== undefined)
|
|
2732
|
+
continue;
|
|
2733
|
+
const latestConflictingStartedAt = prior.latestConflictingStartedAt;
|
|
2734
|
+
const nativeStartedAt = prior.nativeStartedAt;
|
|
2735
|
+
delete prior.completedAt;
|
|
2736
|
+
delete prior.exit;
|
|
2737
|
+
delete prior.outcome;
|
|
2738
|
+
delete prior.result;
|
|
2739
|
+
Object.assign(prior, observation, { startedAt: prior.startedAt,
|
|
2740
|
+
...(nativeStartedAt ? { nativeStartedAt } : {}),
|
|
2741
|
+
...(latestConflictingStartedAt ? { latestConflictingStartedAt } : {}) });
|
|
2742
|
+
}
|
|
2743
|
+
for (const conflict of conflictingCalls) {
|
|
2744
|
+
const prior = current.execution.observations.find(item => item.callID === conflict.callID && item.sessionID === conflict.sessionID);
|
|
2745
|
+
if (prior) {
|
|
2746
|
+
prior.conflicting = true;
|
|
2747
|
+
prior.latestConflictingStartedAt = laterStart(prior.latestConflictingStartedAt ?? prior.nativeStartedAt, conflict.latestConflictingStartedAt);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2521
2752
|
async function declareMissionUnits(root, actor, mission, raw, reason, execution, planningCallID, handoffNotes, workQuantities) {
|
|
2522
2753
|
await control.currentBudget(root);
|
|
2523
2754
|
mission = await missions.required(root);
|
|
@@ -3186,13 +3417,14 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3186
3417
|
args: { status: { type: "string", enum: ["ready", "needs-decision", "blocked"] }, summary: stringSchema }, execute: async (args, context) => {
|
|
3187
3418
|
const { root } = await missionAuthority(context.sessionID);
|
|
3188
3419
|
return serializeDispatchTransition(root, async () => {
|
|
3189
|
-
|
|
3420
|
+
let mission = await missions.required(root);
|
|
3421
|
+
mission = await reconcileMissionExecution(root, mission, await operators.read(root));
|
|
3190
3422
|
if (args.status === "ready") {
|
|
3191
3423
|
if (mission.kind === "operation" && !missionExecutionComplete(mission)) {
|
|
3192
3424
|
return JSON.stringify({ ...missionPacket(mission, await operators.read(root)), status: "operation-incomplete",
|
|
3193
|
-
next_action: missionExecutionStatus(mission) === "running"
|
|
3425
|
+
next_action: missionExecutionRecoveryAction(mission) ?? (missionExecutionStatus(mission) === "running"
|
|
3194
3426
|
? "The declared operation is already running. Inspect its native shell/progress; do not start another Worker or run. Wait for a terminal result, or report the existing run as blocked if its completion cannot be observed."
|
|
3195
|
-
: "Continue the requested operation or submit its actual blocker. A successful setup/NO_START check does not authorize ready." });
|
|
3427
|
+
: "Continue the requested operation or submit its actual blocker. A successful setup/NO_START check does not authorize ready.") });
|
|
3196
3428
|
}
|
|
3197
3429
|
const run = await operators.required(root);
|
|
3198
3430
|
if (run.phase !== "awaiting-acceptance")
|
|
@@ -3215,7 +3447,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3215
3447
|
...compactMissionEvidence(missionPacket(updated, submittedRun), submittedRun, root),
|
|
3216
3448
|
next_action: args.status === "ready"
|
|
3217
3449
|
? "Return this result and acceptance_summary to Operator now. Compare the original requests with the candidate and actual evidence; inspect concrete gaps only, then complete_mission if satisfied. No routine archive search or full source reread. Completion still requires its final comparison and complete_mission receipt."
|
|
3218
|
-
: "Return the saved blocker or user-only decision to Operator now. This is incomplete delivery, not acceptance; retain the candidate, failed checks and cumulative spend. Do not repeat unchanged work or dispatch a replacement merely to finalize this result."
|
|
3450
|
+
: ["Return the saved blocker or user-only decision to Operator now. This is incomplete delivery, not acceptance; retain the candidate, failed checks and cumulative spend. Do not repeat unchanged work or dispatch a replacement merely to finalize this result.",
|
|
3451
|
+
missionExecutionRecoveryAction(updated)].filter(Boolean).join(" ") });
|
|
3219
3452
|
});
|
|
3220
3453
|
} };
|
|
3221
3454
|
tools[completeMission] = { description: "Operator only: after comparing the original request, source, actual checks and current review disposition (explicit native author self-recheck is not independent PASS), explicitly accept the whole mission. A collected terminal failure can satisfy a run-once/report request, not a request for successful execution. Returns operation outcome separately from the measured 🐾 Mission receipt; never treat Worker start, CORRECTION_READY or one passing check as completion.",
|
|
@@ -3872,17 +4105,16 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3872
4105
|
const operationArgs = record(mapped.args) ? mapped.args : {};
|
|
3873
4106
|
// A decorated launch would spend on the real operation but leave the native mission observation empty.
|
|
3874
4107
|
// Ask the same Worker to correct its shell input before that expensive side effect.
|
|
3875
|
-
if (writer &&
|
|
4108
|
+
if (writer && isShellTool(request.tool) && mission?.execution &&
|
|
3876
4109
|
typeof operationArgs.command === "string" &&
|
|
3877
|
-
resolve(input.directory, typeof operationArgs.workdir === "string" ? operationArgs.workdir : ".")
|
|
3878
|
-
const
|
|
3879
|
-
if (operationArgs.background === true &&
|
|
4110
|
+
missionOperationDirectoryMatch(resolve(input.directory, typeof operationArgs.workdir === "string" ? operationArgs.workdir : "."), mission.execution.directory)) {
|
|
4111
|
+
const declaredOperation = missionOperationCommandMatch(operationArgs.command, mission.execution.commands, mission.execution.directory);
|
|
4112
|
+
if (operationArgs.background === true && declaredOperation !== undefined) {
|
|
3880
4113
|
throw new Error("mission-operation-background: run the declared command in foreground with a suitable timeout. " +
|
|
3881
4114
|
"A background shell reports only launch, not process exit; no run was started by this refused call. " +
|
|
3882
4115
|
"Correct this same Worker command without a new plan or approval.");
|
|
3883
4116
|
}
|
|
3884
|
-
const decorated = mission.execution.commands
|
|
3885
|
-
/^\s*(?:\||\d?>|&&?|;)/u.test(actual.slice(declared.length)));
|
|
4117
|
+
const decorated = missionOperationCommandDecorated(operationArgs.command, mission.execution.commands, mission.execution.directory);
|
|
3886
4118
|
if (decorated)
|
|
3887
4119
|
throw new Error("mission-operation-command-not-observed: run the declared operation command exactly; " +
|
|
3888
4120
|
"do not append a pipe, tee, redirection or chained command. Capture its tool output or write a separate result file afterward. " +
|
|
@@ -3926,16 +4158,16 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3926
4158
|
}
|
|
3927
4159
|
else
|
|
3928
4160
|
Object.assign(output, outward);
|
|
3929
|
-
if (writer &&
|
|
3930
|
-
typeof args.command === "string" &&
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
4161
|
+
if (writer && isShellTool(request.tool) && mission?.execution &&
|
|
4162
|
+
typeof args.command === "string" && missionOperationDirectoryMatch(resolve(input.directory, typeof args.workdir === "string" ? args.workdir : "."), mission.execution.directory)) {
|
|
4163
|
+
const command = missionOperationCommandMatch(args.command, mission.execution.commands, mission.execution.directory);
|
|
4164
|
+
if (command !== undefined)
|
|
4165
|
+
await missions.update(root, item => {
|
|
4166
|
+
if (!item.execution.observations.some(observation => observation.callID === request.callID)) {
|
|
4167
|
+
item.execution.observations.push({ command, directory: item.execution.directory,
|
|
4168
|
+
callID: request.callID, sessionID: request.sessionID, startedAt: new Date().toISOString() });
|
|
4169
|
+
}
|
|
4170
|
+
});
|
|
3939
4171
|
}
|
|
3940
4172
|
}
|
|
3941
4173
|
catch (error) {
|
|
@@ -4192,7 +4424,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
4192
4424
|
await core["tool.execute.after"]?.({ ...translate(request, false),
|
|
4193
4425
|
...(ownership ? { sessionID: ownership.root } : {}) }, mapped);
|
|
4194
4426
|
Object.assign(output, translate(mapped, true));
|
|
4195
|
-
if (!ownership &&
|
|
4427
|
+
if (!ownership && isShellTool(request.tool)) {
|
|
4196
4428
|
const root = await rootFor(id);
|
|
4197
4429
|
const mission = root ? await missions.read(root) : undefined;
|
|
4198
4430
|
if (root && mission?.execution?.observations.some(item => item.callID === request.callID && item.sessionID === id)) {
|
|
@@ -4211,7 +4443,9 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
4211
4443
|
observation.status = output.status === "error" || metadata.status === "error" ? "error" : "completed";
|
|
4212
4444
|
if (Number.isSafeInteger(metadata.exit))
|
|
4213
4445
|
observation.exit = metadata.exit;
|
|
4214
|
-
|
|
4446
|
+
if (Number.isSafeInteger(metadata.exit)) {
|
|
4447
|
+
Object.assign(observation, missionCommandOutcome(returnedOutput, metadata.exit, observation.status));
|
|
4448
|
+
}
|
|
4215
4449
|
});
|
|
4216
4450
|
}
|
|
4217
4451
|
}
|
|
@@ -4489,42 +4723,48 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
4489
4723
|
: event.type.startsWith("session.") && typeof info?.id === "string" ? info.id : undefined;
|
|
4490
4724
|
if (!id)
|
|
4491
4725
|
return;
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
mappedPart
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
if (ownership.
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4726
|
+
try {
|
|
4727
|
+
if (event.type === "session.deleted" && canonicalAgent(profile, selected.get(id)) === "dog-coordinator") {
|
|
4728
|
+
await stop(id, "session-deleted");
|
|
4729
|
+
return;
|
|
4730
|
+
}
|
|
4731
|
+
if (!await rootFor(id))
|
|
4732
|
+
return;
|
|
4733
|
+
const mappedEvent = translate({ event }, false);
|
|
4734
|
+
const mappedPart = record(mappedEvent.event.properties?.part) ? mappedEvent.event.properties.part : undefined;
|
|
4735
|
+
if ((await identity(id)).role === "dog-coordinator" && mappedPart?.type === "text" && typeof mappedPart.text === "string") {
|
|
4736
|
+
mappedPart.text = forwardTerminalText(mappedPart.text);
|
|
4737
|
+
}
|
|
4738
|
+
await core.event?.(mappedEvent);
|
|
4739
|
+
if (event.type === "message.updated" && info)
|
|
4740
|
+
await renderCompletedReply(id, info);
|
|
4741
|
+
if (event.type === "message.part.updated" && part?.type === "tool" && part.tool === "task" &&
|
|
4742
|
+
typeof part.callID === "string" && record(part.state) && part.state.status === "error") {
|
|
4743
|
+
const ownership = taskOwners.get(part.callID);
|
|
4744
|
+
if (ownership !== undefined && ownership.actor === id) {
|
|
4745
|
+
if (ownership.mission) {
|
|
4746
|
+
await missions.update(ownership.root, state => { state.dispatchOpen = false; });
|
|
4747
|
+
taskOwners.delete(part.callID);
|
|
4748
|
+
return;
|
|
4749
|
+
}
|
|
4750
|
+
if (ownership.consultation) {
|
|
4751
|
+
taskOwners.delete(part.callID);
|
|
4752
|
+
return;
|
|
4753
|
+
}
|
|
4754
|
+
const settled = ownership.proposal
|
|
4755
|
+
? (await control.settleProposalBudget(ownership.root, (await proposals.required(ownership.root)).intent_id, part.callID, "failed"), true)
|
|
4756
|
+
: ownership.operator
|
|
4757
|
+
? (await operators.operatorRejected(ownership.root), true)
|
|
4758
|
+
: await control?.settleRejectedDispatch(ownership.root, part.callID) ?? false;
|
|
4759
|
+
if (settled)
|
|
4760
|
+
taskOwners.delete(part.callID);
|
|
4518
4761
|
}
|
|
4519
|
-
const settled = ownership.proposal
|
|
4520
|
-
? (await control.settleProposalBudget(ownership.root, (await proposals.required(ownership.root)).intent_id, part.callID, "failed"), true)
|
|
4521
|
-
: ownership.operator
|
|
4522
|
-
? (await operators.operatorRejected(ownership.root), true)
|
|
4523
|
-
: await control?.settleRejectedDispatch(ownership.root, part.callID) ?? false;
|
|
4524
|
-
if (settled)
|
|
4525
|
-
taskOwners.delete(part.callID);
|
|
4526
4762
|
}
|
|
4527
4763
|
}
|
|
4764
|
+
finally {
|
|
4765
|
+
if (event.type === "session.deleted")
|
|
4766
|
+
forgetWorkSession(input.directory, id);
|
|
4767
|
+
}
|
|
4528
4768
|
},
|
|
4529
4769
|
};
|
|
4530
4770
|
const before = hooks["tool.execute.before"];
|