sortie-dogs 0.13.10 → 0.13.11
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 +5 -0
- package/dist/asset-version.d.ts +2 -2
- package/dist/asset-version.js +2 -2
- package/dist/codex/mission-session.d.ts +1 -0
- package/dist/codex/mission-session.js +26 -2
- package/dist/codex/mission-write.d.ts +5 -0
- package/dist/codex/mission-write.js +13 -0
- package/dist/core/operator-mission.d.ts +5 -0
- package/dist/core/operator-mission.js +20 -5
- package/dist/plugin/profiled.js +51 -26
- package/dist/runtime-mission-assets.js +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -194,6 +194,11 @@ host must reconcile any still-running process. Omission keeps native `command/ex
|
|
|
194
194
|
executor never falls back to native execution after rejection or failure. Existing Mission before/after
|
|
195
195
|
hooks, command identity and validation acceptance remain shared. CLI progress identifies the selected
|
|
196
196
|
executor; the CLI itself does not attach a parent executor.
|
|
197
|
+
File writes use a short argv referencing a temporary UTF-8 payload, so large files and literal quotes,
|
|
198
|
+
newlines or NUL characters do not overflow Windows command-line limits. Staging does not write the
|
|
199
|
+
project file; the same configured executor performs that write, and the payload is removed afterward.
|
|
200
|
+
Status observations (without `confirmed_conditions`) can return during an active child Task. Condition
|
|
201
|
+
registration and other modifying tools remain serialized; status does not imply Task completion.
|
|
197
202
|
The bash tool accepts `workdir` (absolute or relative to the project root). The shared hooks and both
|
|
198
203
|
executors use that directory; progress and receipts include the effective cwd. A successful command in
|
|
199
204
|
another directory does not satisfy a validation declared for the project root.
|
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.13.
|
|
7
|
-
export declare const CODEX_SKILL_ASSET_VERSION = "0.13.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.13.11-operation-result-v1";
|
|
7
|
+
export declare const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
|
|
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.13.
|
|
7
|
-
export const CODEX_SKILL_ASSET_VERSION = "0.13.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.13.11-operation-result-v1";
|
|
7
|
+
export const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
|
|
@@ -59,6 +59,7 @@ export declare class CodexMissionSession {
|
|
|
59
59
|
private observations;
|
|
60
60
|
private readonly ownedRoots;
|
|
61
61
|
private readonly toolQueues;
|
|
62
|
+
private readonly observationsInFlight;
|
|
62
63
|
readonly directory: string;
|
|
63
64
|
private constructor();
|
|
64
65
|
static create(options: CodexMissionSessionOptions): Promise<CodexMissionSession>;
|
|
@@ -9,6 +9,7 @@ import { runtimeAssets } from "../runtime-assets-v010.js";
|
|
|
9
9
|
import { OperatorRuntime } from "../core/operator-runtime.js";
|
|
10
10
|
import { OperatorMissionRuntime, codexProcessOwner, codexOwnerGone } from "../core/operator-mission.js";
|
|
11
11
|
import { resolveCodexMissionShell } from "./mission-shell.js";
|
|
12
|
+
import { prepareCodexWrite } from "./mission-write.js";
|
|
12
13
|
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
14
|
/** Native Codex transport for the existing Mission tools, roles and acceptance state. */
|
|
14
15
|
export class CodexMissionSession {
|
|
@@ -24,6 +25,7 @@ export class CodexMissionSession {
|
|
|
24
25
|
observations = [];
|
|
25
26
|
ownedRoots = new Set();
|
|
26
27
|
toolQueues = new Map();
|
|
28
|
+
observationsInFlight = new Set();
|
|
27
29
|
directory;
|
|
28
30
|
constructor(options, shell) {
|
|
29
31
|
this.options = options;
|
|
@@ -80,7 +82,7 @@ export class CodexMissionSession {
|
|
|
80
82
|
}
|
|
81
83
|
async close() {
|
|
82
84
|
await this.stopHosts();
|
|
83
|
-
await Promise.allSettled([...this.toolQueues.values()]);
|
|
85
|
+
await Promise.allSettled([...this.toolQueues.values(), ...this.observationsInFlight]);
|
|
84
86
|
const missions = new OperatorMissionRuntime(this.directory, profile);
|
|
85
87
|
for (const root of this.ownedRoots)
|
|
86
88
|
await missions.codexRecovery(root, async (state) => {
|
|
@@ -269,6 +271,15 @@ export class CodexMissionSession {
|
|
|
269
271
|
return await this.options.permissionsApproval?.(request) ?? { permissions: {}, scope: "turn" };
|
|
270
272
|
},
|
|
271
273
|
dynamicTool: call => {
|
|
274
|
+
// Observations must remain available while a Task awaits its child. Status
|
|
275
|
+
// with confirmed_conditions writes launch conditions and stays serialized.
|
|
276
|
+
if (call.tool === `${profile.toolPrefix}operator_status` && object(call.arguments) &&
|
|
277
|
+
legacyToolArgs(call.arguments, this.hooks.tool?.[call.tool]?.args ?? {}).confirmed_conditions === undefined) {
|
|
278
|
+
const observation = this.execute(call);
|
|
279
|
+
this.observationsInFlight.add(observation);
|
|
280
|
+
void observation.finally(() => this.observationsInFlight.delete(observation)).catch(() => undefined);
|
|
281
|
+
return observation;
|
|
282
|
+
}
|
|
272
283
|
const execution = (this.toolQueues.get(call.threadId) ?? Promise.resolve()).catch(() => undefined).then(() => this.execute(call));
|
|
273
284
|
this.toolQueues.set(call.threadId, execution);
|
|
274
285
|
return execution;
|
|
@@ -530,6 +541,7 @@ export class CodexMissionSession {
|
|
|
530
541
|
let dispatched = false;
|
|
531
542
|
let child;
|
|
532
543
|
let metadata;
|
|
544
|
+
let payload;
|
|
533
545
|
try {
|
|
534
546
|
if (call.tool === "task") {
|
|
535
547
|
child = typeof args.task_id === "string" && args.task_id ? this.required(args.task_id) : undefined;
|
|
@@ -547,9 +559,11 @@ export class CodexMissionSession {
|
|
|
547
559
|
metadata = { sessionId: child.id, status: result.status };
|
|
548
560
|
}
|
|
549
561
|
else {
|
|
562
|
+
if (call.tool === "write")
|
|
563
|
+
payload = await prepareCodexWrite(resolve(this.directory, String(args.filePath)), String(args.content));
|
|
550
564
|
const command = call.tool === "bash" ? this.shell.command(String(args.command))
|
|
551
565
|
: call.tool === "read" ? [process.execPath, "-e", "const fs=require('node:fs'),p=process.argv[1];process.stdout.write(fs.statSync(p).isDirectory()?fs.readdirSync(p,{withFileTypes:true}).map(e=>e.name+(e.isDirectory()?'/':'')).sort().join('\\n')+'\\n':fs.readFileSync(p,'utf8'))", resolve(this.directory, String(args.filePath))]
|
|
552
|
-
:
|
|
566
|
+
: payload.command;
|
|
553
567
|
const executor = this.options.executeCommand ? "host" : "native";
|
|
554
568
|
await this.options.onEvent?.({ method: "sortie/commandExecution", threadId: session.id,
|
|
555
569
|
params: { turnId: call.turnId, callId: call.callId, tool: call.tool, executor, cwd, status: "started" } });
|
|
@@ -610,6 +624,16 @@ export class CodexMissionSession {
|
|
|
610
624
|
await this.stopHosts().catch(() => undefined);
|
|
611
625
|
throw error;
|
|
612
626
|
}
|
|
627
|
+
finally {
|
|
628
|
+
if (payload)
|
|
629
|
+
await payload.dispose().catch(async (error) => {
|
|
630
|
+
try {
|
|
631
|
+
await this.options.onEvent?.({ method: "sortie/payloadCleanup", threadId: session.id,
|
|
632
|
+
params: { callId: call.callId, status: "failed", reason: String(error) } });
|
|
633
|
+
}
|
|
634
|
+
catch { /* Cleanup reporting cannot replace the authoritative command outcome. */ }
|
|
635
|
+
});
|
|
636
|
+
}
|
|
613
637
|
const time = { start, end: Date.now() };
|
|
614
638
|
metadata.sortie_execution = time;
|
|
615
639
|
session.history.push({ info: { id: call.callId, role: "assistant", sessionID: session.id, codexToolReceipt: true,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { writeFile, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
/** Stage transport data only. The configured executor still performs the project write. */
|
|
6
|
+
export async function prepareCodexWrite(filePath, content) {
|
|
7
|
+
const payload = join(tmpdir(), `sortie-codex-write-${randomUUID()}`);
|
|
8
|
+
await writeFile(payload, content, { encoding: "utf8", flag: "wx" });
|
|
9
|
+
return {
|
|
10
|
+
command: [process.execPath, "-e", "const fs=require('node:fs');fs.writeFileSync(process.argv[1],fs.readFileSync(process.argv[2]))", filePath, payload],
|
|
11
|
+
dispose: () => rm(payload, { force: true }),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -301,6 +301,11 @@ export declare function missionReviewAccepted(review: NonNullable<OperatorMissio
|
|
|
301
301
|
/** A short native report, not a tag/hash-based second-review policy or an approval checklist. */
|
|
302
302
|
export declare function missionSelfRecheckReport(text: string, source: string, hostBound?: boolean): Pick<MissionSelfRecheck, "unresolvedFindings" | "residualMajor"> | undefined;
|
|
303
303
|
export declare function missionExecutionStatus(mission: OperatorMission): "not-required" | "not-started" | "running" | "execution-failed" | "executed";
|
|
304
|
+
/** A terminal failure is a collected result, not a successful operation. Required success
|
|
305
|
+
* remains in the declared formal checks and Operator's original-request comparison. */
|
|
306
|
+
export declare function missionExecutionComplete(mission: OperatorMission): boolean;
|
|
307
|
+
/** Projection of existing observations only; never another check or inferred success. */
|
|
308
|
+
export declare function missionOperationSummary(mission: OperatorMission): Record<string, unknown>;
|
|
304
309
|
/** Observe the command's own terminal summary, never Worker/Reviewer prose. Scores are result data,
|
|
305
310
|
* not process success. Unstructured commands retain their native exit semantics. */
|
|
306
311
|
export declare function missionCommandOutcome(output: unknown, exit: unknown, status: unknown): {
|
|
@@ -116,14 +116,28 @@ export function missionExecutionStatus(mission) {
|
|
|
116
116
|
if (mission.kind !== "operation")
|
|
117
117
|
return "not-required";
|
|
118
118
|
const execution = mission.execution;
|
|
119
|
-
if (!execution || execution.observations.length === 0)
|
|
119
|
+
if (!execution || execution.commands.length === 0 || execution.observations.length === 0)
|
|
120
120
|
return "not-started";
|
|
121
121
|
const latest = execution.commands.map(command => [...execution.observations].reverse().find(item => item.command === command && item.directory === execution.directory));
|
|
122
122
|
if (latest.some(item => item && !item.completedAt))
|
|
123
123
|
return "running";
|
|
124
|
+
if (latest.some(item => !item || !["executed", "execution-failed"].includes(item.outcome ?? "")))
|
|
125
|
+
return "not-started";
|
|
124
126
|
if (latest.some(item => item?.outcome === "execution-failed" || (item && (item.status === "error" || item.exit !== 0))))
|
|
125
127
|
return "execution-failed";
|
|
126
|
-
return
|
|
128
|
+
return "executed";
|
|
129
|
+
}
|
|
130
|
+
/** A terminal failure is a collected result, not a successful operation. Required success
|
|
131
|
+
* remains in the declared formal checks and Operator's original-request comparison. */
|
|
132
|
+
export function missionExecutionComplete(mission) {
|
|
133
|
+
return ["not-required", "executed", "execution-failed"].includes(missionExecutionStatus(mission));
|
|
134
|
+
}
|
|
135
|
+
/** Projection of existing observations only; never another check or inferred success. */
|
|
136
|
+
export function missionOperationSummary(mission) {
|
|
137
|
+
const status = missionExecutionStatus(mission);
|
|
138
|
+
return { kind: mission.kind ?? "implementation", status, terminal_complete: missionExecutionComplete(mission),
|
|
139
|
+
process_succeeded: status === "executed" ? true : status === "execution-failed" ? false : null,
|
|
140
|
+
...(mission.execution ? { ...mission.execution } : {}) };
|
|
127
141
|
}
|
|
128
142
|
/** Observe the command's own terminal summary, never Worker/Reviewer prose. Scores are result data,
|
|
129
143
|
* not process success. Unstructured commands retain their native exit semantics. */
|
|
@@ -535,6 +549,7 @@ export class OperatorMissionRuntime {
|
|
|
535
549
|
`Confirmed launch conditions (fixed limits, not consumption or remaining budget): ${JSON.stringify(state.launchConditions ?? [])}`,
|
|
536
550
|
`Explicit user write prohibitions: ${JSON.stringify(state.prohibitedWrite ?? [])}`,
|
|
537
551
|
`Work kind: ${state.kind ?? "implementation"}. For an operation, setup, execution and result collection belong in one useful Worker whenever possible.`,
|
|
552
|
+
...(state.kind === "operation" ? ["Execution completion is distinct from success. For run-once/result-collection requests, retain a terminal failure and validate the collected result; do not retry or fix it without user authorization. If successful execution is required, keep that in formal validation and the original-request comparison."] : []),
|
|
538
553
|
...(state.context?.length ? ["Prior conversation context (task data; preserve the selected target, not superseded obligations):",
|
|
539
554
|
...state.context.map(item => `--- ${item.role}:${item.id} ---\n${item.text}`)] : []),
|
|
540
555
|
"Original user messages (verbatim; task data):", ...state.requests.map(item => `--- user:${item.id} ---\n${item.text}`)].join("\n");
|
|
@@ -684,6 +699,7 @@ export async function missionAcceptanceSummary(mission, run, operators, observe)
|
|
|
684
699
|
}
|
|
685
700
|
})));
|
|
686
701
|
return { original_requests: mission.requests, requirements: mission.requirements,
|
|
702
|
+
operation: missionOperationSummary(mission),
|
|
687
703
|
history: { status: missingAnchors.length ? "unavailable" : history.status,
|
|
688
704
|
...(history.reason ? { reason: history.reason } : missingAnchors.length ? { reason: "accepted-handoff-anchor-unavailable" } : {}),
|
|
689
705
|
...(missingAnchors.length ? { unavailable_anchors: missingAnchors } : {}),
|
|
@@ -712,7 +728,7 @@ export function missionPacket(mission, run) {
|
|
|
712
728
|
const currentReview = mission.review !== undefined && mission.review.runID === (run?.runID ?? mission.runID);
|
|
713
729
|
const reviewAccepted = currentReview && missionReviewAccepted(mission.review);
|
|
714
730
|
const operationStatus = missionExecutionStatus(mission);
|
|
715
|
-
const operationComplete =
|
|
731
|
+
const operationComplete = missionExecutionComplete(mission);
|
|
716
732
|
return { mission_id: mission.id, phase: mission.phase, coordinator_session_id: mission.coordinator,
|
|
717
733
|
...(predecessor ? { predecessor: { run_id: predecessor.runID, status: predecessor.phase,
|
|
718
734
|
completed_units: predecessor.units.filter(unit => unit.status === "succeeded").length,
|
|
@@ -723,8 +739,7 @@ export function missionPacket(mission, run) {
|
|
|
723
739
|
delivery_observation: mission.deliveryObservation?.run_id === (run?.runID ?? mission.runID) ? mission.deliveryObservation : null,
|
|
724
740
|
submission: mission.submission, progress: mission.progress, consultations: mission.consultations ?? [],
|
|
725
741
|
attempts: mission.attempts ?? [], corrections: (mission.corrections ?? []).map(({ findings: _findings, initialPrompt: _prompt, ...item }) => item), ...(mission.rescue ? { rescue: mission.rescue } : {}),
|
|
726
|
-
operation:
|
|
727
|
-
...(mission.execution ? { ...mission.execution } : {}) },
|
|
742
|
+
operation: missionOperationSummary(mission),
|
|
728
743
|
execution_summary: { completed_units: run?.units.filter(unit => unit.status === "succeeded").length ?? 0,
|
|
729
744
|
running_units: run?.units.filter(unit => unit.status === "running").map(unit => ({ id: unit.unit.id, title: unit.unit.title, child_session_id: unit.childSessionID })) ?? [],
|
|
730
745
|
pending_units: run?.units.filter(unit => unit.status === "pending").length ?? 0,
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -13,7 +13,7 @@ import { decoratePreviewHeadings, returnReportPanel } from "./receipt-presentati
|
|
|
13
13
|
import { sanitizeTerminalReport, terminalRunOutcome } from "./run-metrics.js";
|
|
14
14
|
import { normalizeCommand, canonicalDeclaredValidationMembers, extractWritePaths } 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, missionExecutionStatus, missionValidationCommand, missionReviewTraces, missionReviewVerdict, missionSelfRecheckReport } from "../core/operator-mission.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";
|
|
17
17
|
import { publishMissionProgress } from "./mission-progress.js";
|
|
18
18
|
import { completedMissionReviewPrompts, initialMissionReviewPrompt, missionDeliveryObservation, missionReviewBaseline, missionReviewSource, observedMissionValidation, observedMissionValidationSummary, missionReviewValidation, reviewerCorrectionValidation, reviewerCorrectionValidationFresh, validationDirectoryMatches } from "./mission-review.js";
|
|
19
19
|
import { missionLocations, missionLocationPacket } from "./mission-location.js";
|
|
@@ -983,6 +983,12 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
983
983
|
taskOwners.delete(mission.callID);
|
|
984
984
|
return reconciled;
|
|
985
985
|
}
|
|
986
|
+
function withMissionOperationOutcome(text, mission) {
|
|
987
|
+
if (!mission || missionExecutionStatus(mission) !== "execution-failed" || terminalRunOutcome(text) !== "DONE")
|
|
988
|
+
return text;
|
|
989
|
+
const detail = "Operation result: execution-failed (result accepted; process did not succeed).";
|
|
990
|
+
return text.includes(detail) ? text : text.replace(/^((?:[ \t]*\r?\n)*[^\r\n]+)/u, `$1 ${detail}`);
|
|
991
|
+
}
|
|
986
992
|
function missionDispatchPacket(mission, run) {
|
|
987
993
|
const packet = { ...missionPacket(mission, run), project_root: input.directory,
|
|
988
994
|
coordinator_dispatch: mission.dispatchOpen ? "active" : ["completed", "cancelled"].includes(mission.phase) ? "terminal" : "resumable" };
|
|
@@ -1006,7 +1012,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1006
1012
|
next_action: "Fast-lane: plan one useful Worker unit with the meaningful formal check known from user, project or task context and estimated scope, then dispatch its Task immediately. Investigation/edit/check/requested commit belong inside Worker before independent Review. Objective: target 2000 characters; original requests are supplied separately. Use Coordinator for an unknown check or real unit decomposition." };
|
|
1007
1013
|
}
|
|
1008
1014
|
if (mission.coordinator === null && mission.runID === run?.runID && !mission.dispatchOpen &&
|
|
1009
|
-
run?.phase === "awaiting-acceptance" && mission.kind === "operation" &&
|
|
1015
|
+
run?.phase === "awaiting-acceptance" && mission.kind === "operation" && !missionExecutionComplete(mission)) {
|
|
1010
1016
|
return { ...packet, task: missions.task(mission),
|
|
1011
1017
|
next_action: "Fast-lane operation is not executed. Dispatch this same mission's Coordinator Task to finish or report its actual blocker; a setup or validation success is not operation completion." };
|
|
1012
1018
|
}
|
|
@@ -1442,6 +1448,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1442
1448
|
budget_remaining_units: budget?.remaining_units ?? null,
|
|
1443
1449
|
next_action: typeof packet.next_action === "string" ? packet.next_action : null,
|
|
1444
1450
|
...(record(packet.acceptance_summary) ? { observations: {
|
|
1451
|
+
operation: packet.acceptance_summary.operation,
|
|
1445
1452
|
formal_validation: packet.acceptance_summary.formal_validation,
|
|
1446
1453
|
native_declared_validation: packet.acceptance_summary.native_declared_validation,
|
|
1447
1454
|
worker_terminals: (mission.attempts ?? []).map(attempt => ({ run_id: attempt.runID, status: attempt.status, terminal: attempt.terminal ?? null })),
|
|
@@ -1627,7 +1634,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1627
1634
|
await requireRoot(context.sessionID);
|
|
1628
1635
|
const state = await operators.required(context.sessionID);
|
|
1629
1636
|
let mission = await missions.read(context.sessionID);
|
|
1630
|
-
if (mission?.kind === "operation" &&
|
|
1637
|
+
if (mission?.kind === "operation" && !missionExecutionComplete(mission))
|
|
1631
1638
|
return JSON.stringify({
|
|
1632
1639
|
status: "not-ready", operation_status: missionExecutionStatus(mission),
|
|
1633
1640
|
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.",
|
|
@@ -1658,7 +1665,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1658
1665
|
const reviewGaps = missionReportReview(mission, state.runID) === "evidence-gaps"
|
|
1659
1666
|
? mission.review.result?.trim() || "独立Reviewの未解決証拠あり" : undefined;
|
|
1660
1667
|
const gapSummary = reviewGaps?.replace(/^EVIDENCE_GAPS\s*/u, "").replace(/\s+/gu, " ").slice(0, 500);
|
|
1661
|
-
const text = `✅ **DONE** \`${state.runID}\` — declared checks passed; Operator accepted the result
|
|
1668
|
+
const text = withMissionOperationOutcome(`✅ **DONE** \`${state.runID}\` — declared checks passed; Operator accepted the result.`, mission) + `\n\n` +
|
|
1662
1669
|
`**変更点:** ${state.units.map(unit => unit.unit.title).join("; ")}\n\n` +
|
|
1663
1670
|
`**確認結果:** 宣言検証合格 — ${[...new Set(state.units.flatMap(unit => unit.unit.validation))].join("; ")}` +
|
|
1664
1671
|
(mission?.review ? `\nレビュー: ${mission.review.verdict === "self-rechecked" ? "修正著者の自己再確認(独立PASSではない)" : mission.review.verdict === "evidence-gaps" ? "証拠不足を残して受入れ(レビューPASSではない)" : mission.review.verdict}.` : "") +
|
|
@@ -1670,6 +1677,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1670
1677
|
panel = returnReportPanel(rendered);
|
|
1671
1678
|
}
|
|
1672
1679
|
return JSON.stringify({ status: result.status, run_id: state.runID,
|
|
1680
|
+
...(mission?.kind === "operation" ? { operation: missionOperationSummary(mission) } : {}),
|
|
1673
1681
|
acceptance_fingerprint: state.acceptanceFingerprint, receipt: result.receipt ?? null,
|
|
1674
1682
|
...(result.receipt?.status === "succeeded" && missionReportReview(mission, state.runID) === "evidence-gaps"
|
|
1675
1683
|
? { review_evidence_gaps: mission.review.result ?? null } : {}),
|
|
@@ -2032,9 +2040,13 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2032
2040
|
next_action: "Read operator_status: if the Coordinator dispatch is active, continue it; otherwise dispatch the returned task to resume the same Coordinator. Retain requirements, spend and candidate; no new mission or plan approval." });
|
|
2033
2041
|
});
|
|
2034
2042
|
} };
|
|
2035
|
-
|
|
2043
|
+
const missionExecutionSchema = { type: "object", properties: { commands: stringList, directory: { type: "string" } },
|
|
2044
|
+
required: ["commands", "directory"], additionalProperties: false,
|
|
2045
|
+
description: "For an operation: the actual run/grade commands, not a preflight or NO_START check. Host observes their native shell completion separately from successful exits; no handwritten proof file is needed. For run-once/result collection, validate the collected result without rerunning the operation.", "x-sortie-optional": true };
|
|
2046
|
+
tools[startMission] = { description: `Operator: save current requirements; the host retains the original request verbatim. With a meaningful formal check known from user, project or task context and one useful unit, include unit NOW (and execution for an operation) and dispatch its Worker directly; plan_units remains available after start. A literal command in the original request is not required. Worker owns investigation/edit/check/requested commit before independent Review; no routine preparation or commit-only handoff. ${MISSION_GIT_SCOPE} Unknown checks or real unit decomposition use Coordinator. intent=replace follows an actual user requirement change, retaining unchanged constraints and cumulative spend; for mission-source-reconciliation-required use the saved requirements when they reflect that change. intent=new is separate work in another location.`,
|
|
2036
2047
|
args: { requirements: { ...stringList, minItems: 1, maxItems: 64 },
|
|
2037
2048
|
unit: { ...missionUnitSchema, description: "When the meaningful check and single-unit scope are already known, include this unit now. Returns its configured Worker directly, combining start_mission and plan_units without another model round trip.", "x-sortie-optional": true },
|
|
2049
|
+
execution: missionExecutionSchema,
|
|
2038
2050
|
confirmed_conditions: conditionsSchema,
|
|
2039
2051
|
prohibited_write: { ...stringList, description: "Only explicit path prohibitions from the user or applicable instructions. Never infer a parent glob from project/repository names, semantic 'do not modify the product', or the complement of estimated unit.write. Preserve the authorized clone and exact prohibited paths; semantic constraints stay in requirements.", "x-sortie-optional": true },
|
|
2040
2052
|
kind: { type: "string", enum: ["implementation", "operation"], description: "Use operation for running an existing benchmark, command or procedure. The host records its actual execution separately from setup and checks.", "x-sortie-optional": true },
|
|
@@ -2123,10 +2135,16 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2123
2135
|
if (baseline)
|
|
2124
2136
|
mission = await missions.update(context.sessionID, state => { state.reviewBaseline = baseline; });
|
|
2125
2137
|
}
|
|
2126
|
-
if (args.unit !== undefined && mission.runID === null && !mission.dispatchOpen &&
|
|
2127
|
-
|
|
2138
|
+
if (args.unit !== undefined && mission.runID === null && !mission.dispatchOpen &&
|
|
2139
|
+
(mission.kind !== "operation" || args.execution !== undefined || mission.execution)) {
|
|
2140
|
+
const planned = JSON.parse(await declareMissionUnits(context.sessionID, context.sessionID, mission, [args.unit], undefined, args.execution));
|
|
2128
2141
|
return JSON.stringify({ ...locationObservation(context.sessionID), mission_id: mission.id, requirements: mission.requirements, ...planned });
|
|
2129
2142
|
}
|
|
2143
|
+
if (args.execution !== undefined && mission.runID === null && !mission.dispatchOpen) {
|
|
2144
|
+
const operation = declaredMissionExecution(mission, args.execution);
|
|
2145
|
+
if (operation)
|
|
2146
|
+
mission = await missions.update(context.sessionID, state => { state.kind = "operation"; state.execution = operation; });
|
|
2147
|
+
}
|
|
2130
2148
|
return JSON.stringify({ ...locationObservation(context.sessionID), ...(mission.dispatchOpen
|
|
2131
2149
|
? missionDispatchPacket(mission, await operators.read(context.sessionID))
|
|
2132
2150
|
: { mission_id: mission.id, requirements: mission.requirements, task: missions.task(mission),
|
|
@@ -2267,6 +2285,19 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2267
2285
|
obligations: policy.proposal.obligations }, run_id: prepared.runID, unit_id: unitID,
|
|
2268
2286
|
task: record(next) ? next.task : undefined, budget: await control.currentBudget(root) });
|
|
2269
2287
|
} };
|
|
2288
|
+
function declaredMissionExecution(mission, execution) {
|
|
2289
|
+
const operation = mission.execution;
|
|
2290
|
+
// Optional empty operation fields must not turn a normal edit into an operation.
|
|
2291
|
+
if (execution === undefined || mission.kind === "implementation" && record(execution) &&
|
|
2292
|
+
Array.isArray(execution.commands) && execution.commands.length === 0)
|
|
2293
|
+
return operation;
|
|
2294
|
+
if (!record(execution) || typeof execution.directory !== "string" || !execution.directory.trim() ||
|
|
2295
|
+
!Array.isArray(execution.commands) || execution.commands.length === 0 || execution.commands.length > 16 ||
|
|
2296
|
+
!execution.commands.every(command => typeof command === "string" && command.trim()))
|
|
2297
|
+
throw new Error("mission-operation-input: supply the actual operation commands and their working directory");
|
|
2298
|
+
const commands = [...new Set(execution.commands.map(missionValidationCommand).map(normalizeCommand))];
|
|
2299
|
+
return { commands, directory: resolve(input.directory, execution.directory), observations: operation?.observations ?? [] };
|
|
2300
|
+
}
|
|
2270
2301
|
async function declareMissionUnits(root, actor, mission, raw, reason, execution, planningCallID) {
|
|
2271
2302
|
await control.currentBudget(root);
|
|
2272
2303
|
mission = await missions.required(root);
|
|
@@ -2281,21 +2312,11 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2281
2312
|
`requirements to Operator. Operator can relink this mission in place via ${startMission} intent=replace ` +
|
|
2282
2313
|
`with those exact requirements when they match the user's changed scope; no Worker can start before that decision.` });
|
|
2283
2314
|
mission = await retainCancelledMissionAcceptance(root, mission, previous);
|
|
2284
|
-
|
|
2315
|
+
const operation = declaredMissionExecution(mission, execution);
|
|
2285
2316
|
// Models can serialize an optional operation field as an empty object for a normal edit.
|
|
2286
2317
|
// Do not turn an implementation mission into an operation (or block its first Worker).
|
|
2287
2318
|
const emptyImplementationExecution = mission.kind === "implementation" && record(execution) &&
|
|
2288
2319
|
Array.isArray(execution.commands) && execution.commands.length === 0;
|
|
2289
|
-
if (execution !== undefined && !emptyImplementationExecution) {
|
|
2290
|
-
if (!record(execution) || typeof execution.directory !== "string" || !execution.directory.trim() ||
|
|
2291
|
-
!Array.isArray(execution.commands) || execution.commands.length === 0 || execution.commands.length > 16 ||
|
|
2292
|
-
!execution.commands.every(command => typeof command === "string" && command.trim()))
|
|
2293
|
-
throw new Error("mission-operation-input: supply the actual operation commands and their working directory");
|
|
2294
|
-
const commands = [...new Set(execution.commands.map(missionValidationCommand).map(normalizeCommand))];
|
|
2295
|
-
const directory = resolve(input.directory, execution.directory);
|
|
2296
|
-
// A corrected command is allowed on replan; retain prior observations for accounting.
|
|
2297
|
-
operation = { commands, directory, observations: operation?.observations ?? [] };
|
|
2298
|
-
}
|
|
2299
2320
|
const plan = missionPlan(mission, raw, input.directory);
|
|
2300
2321
|
assertMissionWritePaths(mission, plan.units.flatMap(unit => unit.write));
|
|
2301
2322
|
if (actor === root && (mission.coordinator !== null || plan.units.length !== 1))
|
|
@@ -2462,9 +2483,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2462
2483
|
}
|
|
2463
2484
|
tools[planUnits] = { description: `Coordinator or single-unit Fast-lane Operator: declare useful work with estimated read/write scope and meaningful formal checks. executor=self starts implementation/correction and formal validation HERE without a Worker or handoff read; otherwise dispatch the returned configured Worker promptly. Keep investigation/edit/check/requested commit with the same author before independent Review, not a commit-only handoff. ${MISSION_GIT_SCOPE} Native scope reconciliation/expand_unit retain host permissions and explicit path prohibitions. Objective is the target or corrective delta: aim for 2000 characters; the full original request/public reproduction is supplied separately, not copied here. Oversized unit instructions are retained verbatim in handoff Mission context, not rejected for another planning round. Keep every requirement covered. Final validation proves the unit; empty or dummy checks do not qualify. Diagnostics need no registration. write: [] is read-only; dir/** and native absolute paths support actual outputs. reason replans settled work, or ends your own quiescent direct unit without acceptance to correct its declaration, in the same requirements/budget; same-scope failed validation uses retry_mission_unit. Reviewer FINDINGS stay with the same Reviewer for correction, formal validation and self-recheck.`,
|
|
2464
2485
|
args: { units: { type: "array", minItems: 1, maxItems: 32, items: missionUnitSchema },
|
|
2465
|
-
execution:
|
|
2466
|
-
required: ["commands", "directory"], additionalProperties: false,
|
|
2467
|
-
description: "For an operation: the actual run/grade commands, not a preflight or NO_START check. Host observes their native shell completion; no handwritten proof file is needed.", "x-sortie-optional": true },
|
|
2486
|
+
execution: missionExecutionSchema,
|
|
2468
2487
|
reason: { type: "string", "x-sortie-optional": true },
|
|
2469
2488
|
executor: { type: "string", enum: ["worker", "self"], description: "Use self to implement/correct and formally validate here without a Worker handoff. Default worker retains configured Worker routing.", "x-sortie-optional": true } }, execute: async (args, context) => {
|
|
2470
2489
|
const { root, mission } = await missionAuthority(context.sessionID);
|
|
@@ -2870,7 +2889,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2870
2889
|
args: { status: { type: "string", enum: ["ready", "needs-decision", "blocked"] }, summary: stringSchema }, execute: async (args, context) => {
|
|
2871
2890
|
const { root, mission } = await missionAuthority(context.sessionID);
|
|
2872
2891
|
if (args.status === "ready") {
|
|
2873
|
-
if (mission.kind === "operation" &&
|
|
2892
|
+
if (mission.kind === "operation" && !missionExecutionComplete(mission)) {
|
|
2874
2893
|
return JSON.stringify({ ...missionPacket(mission, await operators.read(root)), status: "operation-incomplete",
|
|
2875
2894
|
next_action: missionExecutionStatus(mission) === "running"
|
|
2876
2895
|
? "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."
|
|
@@ -2890,7 +2909,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2890
2909
|
...missionPacket(updated, submittedRun),
|
|
2891
2910
|
next_action: "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." });
|
|
2892
2911
|
} };
|
|
2893
|
-
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. Returns the measured 🐾
|
|
2912
|
+
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.",
|
|
2894
2913
|
args: {}, execute: async (_args, context) => {
|
|
2895
2914
|
await requireRoot(context.sessionID);
|
|
2896
2915
|
await missions.required(context.sessionID);
|
|
@@ -2941,7 +2960,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2941
2960
|
return;
|
|
2942
2961
|
const run = await operators.read(sessionID);
|
|
2943
2962
|
const mission = await missions.read(sessionID);
|
|
2944
|
-
const text = await control.renderReturnReport(sessionID, decoratePreviewHeadings(part.text), goalFingerprint(receipt), run ? missionReportReview(mission, run.runID) : undefined, run ? missionReportReviewGaps(mission, run.runID) : undefined);
|
|
2963
|
+
const text = await control.renderReturnReport(sessionID, withMissionOperationOutcome(decoratePreviewHeadings(part.text), mission), goalFingerprint(receipt), run ? missionReportReview(mission, run.runID) : undefined, run ? missionReportReviewGaps(mission, run.runID) : undefined);
|
|
2945
2964
|
if (text === undefined)
|
|
2946
2965
|
return;
|
|
2947
2966
|
if (text === part.text) {
|
|
@@ -3606,7 +3625,9 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3606
3625
|
}
|
|
3607
3626
|
if (ownership?.mission) {
|
|
3608
3627
|
const current = await missions.required(ownership.root);
|
|
3609
|
-
|
|
3628
|
+
// Native completion can arrive after cancellation/acceptance or an agent switch.
|
|
3629
|
+
// Close its historical owner without reviving the Mission or requiring revoked root authority.
|
|
3630
|
+
if (current.callID !== request.callID || ["completed", "cancelled"].includes(current.phase)) {
|
|
3610
3631
|
taskOwners.delete(request.callID);
|
|
3611
3632
|
return;
|
|
3612
3633
|
}
|
|
@@ -3941,7 +3962,11 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3941
3962
|
const role = (await identity(request.sessionID)).role;
|
|
3942
3963
|
if (role === "dog-operator" || !await rootFor(request.sessionID))
|
|
3943
3964
|
return;
|
|
3944
|
-
|
|
3965
|
+
let forwardedText = role === "dog-coordinator" ? forwardTerminalText(output.text) : output.text;
|
|
3966
|
+
if (role === "dog-coordinator" && terminalRunOutcome(forwardedText) === "DONE" &&
|
|
3967
|
+
(await control.currentReceipt(request.sessionID))?.status === "succeeded") {
|
|
3968
|
+
forwardedText = withMissionOperationOutcome(forwardedText, await missions.read(request.sessionID));
|
|
3969
|
+
}
|
|
3945
3970
|
// Ordinary Operator chat is not a request for a historical Mission return panel.
|
|
3946
3971
|
if (role === "dog-coordinator" && terminalRunOutcome(forwardedText) === undefined)
|
|
3947
3972
|
return;
|
|
@@ -54,6 +54,11 @@ not its exit; it cannot complete the declared operation. This does not restrict
|
|
|
54
54
|
For a declared operation, do not append a tee pipeline, redirection or wrapper that was not declared. Save output separately
|
|
55
55
|
when needed. Use that same run's terminal state and result for completion. If a run already started,
|
|
56
56
|
inspect its progress and report a missing terminal observation instead of launching it again.
|
|
57
|
+
Execution completion is not successful execution. For a run-once/result-collection request, preserve
|
|
58
|
+
a terminal failure, validate the collected result and return it without a repair loop or another run.
|
|
59
|
+
Successful execution requirements stay in meaningful formal validation and Operator's comparison
|
|
60
|
+
with the original request. Mission acceptance must report the operation's exit/outcome separately;
|
|
61
|
+
a succeeded Mission receipt never changes a failed benchmark into a successful benchmark.
|
|
57
62
|
`;
|
|
58
63
|
function controls(profile, names) {
|
|
59
64
|
return names.map(name => ` ${profile.toolPrefix}${name}: true`).join("\n");
|
|
@@ -114,7 +119,8 @@ quality threshold and explicit model/budget choice. Follow AGENTS.md and use the
|
|
|
114
119
|
${profile.toolPrefix}start_mission with intent: "replace" and the saved requirements. The cancelled
|
|
115
120
|
Mission is archived; cumulative spend is retained. Dispatch only the returned Coordinator Task.
|
|
116
121
|
2. If a meaningful formal check is known from the user, project or task context and the work fits one unit,
|
|
117
|
-
include unit in start_mission to receive its Worker immediately in the same call.
|
|
122
|
+
include unit in start_mission to receive its Worker immediately in the same call. For a known operation,
|
|
123
|
+
include execution commands/directory alongside unit. If already started,
|
|
118
124
|
call plan_units and dispatch its Worker. Source investigation, shell/environment checks,
|
|
119
125
|
fix design and output inventory belong inside that Worker, not a routine Operator preflight.
|
|
120
126
|
Use estimated read/write paths; native scope reconciliation and expand_unit cover actual outputs.
|