pi-subagents 0.83.0 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -2
- package/bundle/agents.js +4 -4
- package/bundle/capability-ceiling.js +1 -1
- package/bundle/child-tool-plan.js +3 -3
- package/bundle/{chunk-HJJAVHFB.js → chunk-235AWJUQ.js} +35 -32
- package/bundle/{chunk-P2NCCTZP.js → chunk-2D52JFLF.js} +1 -1
- package/bundle/{chunk-WG2OTTAK.js → chunk-2DHZTCOL.js} +2 -2
- package/bundle/{chunk-MBDOYZ2V.js → chunk-2S4K53MV.js} +1 -1
- package/bundle/{chunk-527E3D2J.js → chunk-4E7BKDTX.js} +5 -5
- package/bundle/{chunk-CZTMUBUI.js → chunk-7BWGNDNK.js} +4 -4
- package/bundle/{chunk-HXVKGS33.js → chunk-AKOIVLA5.js} +4 -4
- package/bundle/{chunk-MHX22USZ.js → chunk-C5P5CHYK.js} +1 -1
- package/bundle/{chunk-5ESXK62X.js → chunk-CGN2TVE7.js} +417 -389
- package/bundle/{chunk-VTMNBPCQ.js → chunk-D3JAFGMO.js} +1 -1
- package/bundle/{chunk-CDI3GMNV.js → chunk-DZDETGRJ.js} +18 -15
- package/bundle/{chunk-BKFWQ642.js → chunk-F4ASL6OV.js} +1 -1
- package/bundle/{chunk-JHZ6DZ5M.js → chunk-G2Z6ZH26.js} +1 -1
- package/bundle/{chunk-XXKG3P2W.js → chunk-J5YL7LYR.js} +4 -4
- package/bundle/{chunk-LDMXJIA6.js → chunk-JG4QKI5M.js} +58 -67
- package/bundle/{chunk-OOFW75PV.js → chunk-L3WFKBEI.js} +15 -11
- package/bundle/{chunk-Y3I6H6DV.js → chunk-LFDBD4PW.js} +2 -2
- package/bundle/{chunk-7F5SK2U3.js → chunk-LNRJCP2Y.js} +17 -14
- package/bundle/{chunk-4BW55BTL.js → chunk-NENDSQUY.js} +4 -4
- package/bundle/{chunk-6B4ITO2W.js → chunk-NKQ6JNAS.js} +1 -1
- package/bundle/{chunk-RZJN63GU.js → chunk-PALJGGYC.js} +2 -2
- package/bundle/{chunk-TJQJ3MEA.js → chunk-PV63UFUR.js} +10 -10
- package/bundle/{chunk-Z7KVHQHL.js → chunk-QXASAHIA.js} +2 -2
- package/bundle/{chunk-HLE22FSH.js → chunk-SSG7IEP5.js} +2 -2
- package/bundle/{chunk-CSSZRTKY.js → chunk-V2BZYLEP.js} +2 -2
- package/bundle/{chunk-CNLDYEH4.js → chunk-VQVVHSY6.js} +11 -11
- package/bundle/{chunk-CGXWSNPA.js → chunk-XDQORLZ5.js} +4 -4
- package/bundle/control-channel.js +3 -3
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +3 -3
- package/bundle/preflight.js +9 -9
- package/bundle/project-panes.js +4 -4
- package/docs/configuration.md +12 -1
- package/docs/extension-api.md +2 -0
- package/docs/missions.md +2 -0
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agent-management.d.ts +1 -1
- package/src/agents/agent-management.js +2 -2
- package/src/agents/agents.js +1 -2
- package/src/agents/frontmatter.js +22 -18
- package/src/extension/index.js +2 -2
- package/src/missions/actions.js +7 -2
- package/src/missions/lifecycle.d.ts +11 -0
- package/src/missions/lifecycle.js +37 -6
- package/src/missions/store.js +1 -0
- package/src/missions/types.d.ts +2 -0
- package/src/runs/background/async-execution.d.ts +4 -1
- package/src/runs/background/async-execution.js +18 -3
- package/src/runs/background/async-resume.js +3 -2
- package/src/runs/background/async-retention.d.ts +1 -0
- package/src/runs/background/async-retention.js +16 -10
- package/src/runs/background/retained-children.js +1 -1
- package/src/runs/background/run-child-session.d.ts +2 -0
- package/src/runs/background/run-child-session.js +9 -1
- package/src/runs/background/subagent-runner.js +1 -0
- package/src/runs/foreground/subagent-executor.js +29 -51
- package/src/runs/shared/capability-ceiling.d.ts +2 -1
- package/src/runs/shared/capability-ceiling.js +3 -2
- package/src/runs/shared/child-session.d.ts +2 -0
- package/src/runs/shared/child-session.js +1 -0
- package/src/runs/shared/model-resolution-diagnostic.d.ts +11 -0
- package/src/runs/shared/model-resolution-diagnostic.js +25 -0
- package/src/runs/shared/parallel-handoff.d.ts +1 -1
- package/src/runs/shared/parallel-handoff.js +10 -6
- package/src/runs/shared/parallel-utils.d.ts +2 -0
- package/src/shared/types.d.ts +1 -1
- package/src/workflows/scripted-workflow.js +6 -1
- package/workflow-script-worker.cjs +450 -175
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROJECT_SUBAGENTS_RELATIVE_DIR
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-D3JAFGMO.js";
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS,
|
|
6
6
|
DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
withFileLease,
|
|
23
23
|
writeAtomicJson,
|
|
24
24
|
writePrivateAtomicJson
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-LNRJCP2Y.js";
|
|
26
26
|
import {
|
|
27
27
|
DIRS,
|
|
28
28
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
@@ -2511,16 +2511,19 @@ ${script}
|
|
|
2511
2511
|
}
|
|
2512
2512
|
const wrapper = astNode(root.body) ? void 0 : Array.isArray(root.body) && astNode(root.body[0]) && astNode(root.body[0].expression) && astNode(root.body[0].expression.callee) ? root.body[0].expression.callee : void 0;
|
|
2513
2513
|
const workflowBody = wrapper && astNode(wrapper.body) ? wrapper.body : root;
|
|
2514
|
-
const
|
|
2514
|
+
const runsRebound = invalidatesRunsBinding(workflowBody);
|
|
2515
|
+
const agentNameError = options.agentNameError && !runsRebound ? options.agentNameError : void 0;
|
|
2515
2516
|
walkAst(workflowBody, (node) => {
|
|
2516
2517
|
if (node !== wrapper && node.async === true && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression")) {
|
|
2517
2518
|
errors.push({ message: "workflowScript does not support nested async functions. Use top-level await, plain helper functions that return runs.run(...), or explicit Promise chains.", ...nodeLocation(node) });
|
|
2518
2519
|
}
|
|
2520
|
+
if (node.type === "ImportExpression") errors.push({ message: "workflowScript cannot load modules with import().", ...nodeLocation(node) });
|
|
2519
2521
|
if (directRunsCall(node, "run")) {
|
|
2520
2522
|
const args = Array.isArray(node.arguments) ? node.arguments : [];
|
|
2521
2523
|
const keyNode = astNode(args[0]) ? args[0] : void 0;
|
|
2522
2524
|
const key = literalString(keyNode);
|
|
2523
2525
|
if (keyNode && key !== void 0 && !KEY_PATTERN.test(key)) errors.push({ message: "runs.run key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.", ...nodeLocation(keyNode) });
|
|
2526
|
+
if (keyNode?.type === "ObjectExpression" && !runsRebound) errors.push({ message: `runs.run expects (key, params); got an object as the first argument. Use runs.run("plan", { agent, task }) or runs.all([{ key, agent, task }]).`, ...nodeLocation(keyNode) });
|
|
2524
2527
|
if (astNode(args[1])) {
|
|
2525
2528
|
errors.push(...validateStaticBaseRef(args[1], "runs.run"));
|
|
2526
2529
|
const message = definitelyNonJson(args[1]);
|
|
@@ -3365,6 +3368,7 @@ function parseWorkflowChild(value, label) {
|
|
|
3365
3368
|
...optionalString(input.phase, `${label}.phase`) ? { phase: input.phase } : {},
|
|
3366
3369
|
...input.completedAt !== void 0 ? { completedAt: timestamp(input.completedAt, `${label}.completedAt`) } : {},
|
|
3367
3370
|
...optionalString(input.sessionPath, `${label}.sessionPath`) ? { sessionPath: input.sessionPath } : {},
|
|
3371
|
+
...optionalString(input.acceptanceStatus, `${label}.acceptanceStatus`) ? { acceptanceStatus: input.acceptanceStatus } : {},
|
|
3368
3372
|
...heartbeat ? { heartbeat: {
|
|
3369
3373
|
updatedAt: timestamp(heartbeat.updatedAt, `${label}.heartbeat.updatedAt`),
|
|
3370
3374
|
...optionalString(heartbeat.status, `${label}.heartbeat.status`) ? { status: heartbeat.status } : {},
|
|
@@ -4005,103 +4009,425 @@ function createMissionWorkflowState(location, missionId, options = {}) {
|
|
|
4005
4009
|
}
|
|
4006
4010
|
|
|
4007
4011
|
// src/missions/actions.ts
|
|
4012
|
+
import * as fs11 from "node:fs";
|
|
4013
|
+
import * as path12 from "node:path";
|
|
4014
|
+
|
|
4015
|
+
// src/missions/lifecycle.ts
|
|
4008
4016
|
import * as fs10 from "node:fs";
|
|
4009
4017
|
import * as path11 from "node:path";
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
function textResult(text, details) {
|
|
4020
|
-
return { content: [{ type: "text", text }], details };
|
|
4021
|
-
}
|
|
4022
|
-
function requireMissionId(params) {
|
|
4023
|
-
return validateMissionId(params.missionId, "missionId");
|
|
4024
|
-
}
|
|
4025
|
-
function validateStatus(value, label) {
|
|
4026
|
-
if (typeof value !== "string" || !MISSION_STATUSES.includes(value)) {
|
|
4027
|
-
throw new Error(`${label} must be one of ${MISSION_STATUSES.join(", ")}`);
|
|
4018
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
4019
|
+
var MISSION_BINDING_FILE = "mission.json";
|
|
4020
|
+
function workflowObjective(params) {
|
|
4021
|
+
const objective = params.task?.trim() || params.tasks?.find((task) => task.task?.trim())?.task?.trim() || params.chain?.find((step) => step.task?.trim())?.task?.trim();
|
|
4022
|
+
if (objective) return objective;
|
|
4023
|
+
for (const step of params.chain ?? []) {
|
|
4024
|
+
const parallel = Array.isArray(step.parallel) ? step.parallel : step.parallel ? [step.parallel] : [];
|
|
4025
|
+
const task = parallel.find((child) => child.task?.trim())?.task?.trim();
|
|
4026
|
+
if (task) return task;
|
|
4028
4027
|
}
|
|
4029
|
-
return
|
|
4028
|
+
return void 0;
|
|
4030
4029
|
}
|
|
4031
|
-
function
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4030
|
+
function prepareMissionLaunch(input) {
|
|
4031
|
+
const hasMissionId = input.params.missionId !== void 0;
|
|
4032
|
+
if (hasMissionId && input.params.mission !== void 0) throw new Error("Use missionId or mission, not both");
|
|
4033
|
+
if (input.params.mission === false) return void 0;
|
|
4034
|
+
const objective = workflowObjective(input.params);
|
|
4035
|
+
const missionsEnabled = input.config?.enabled !== false;
|
|
4036
|
+
const shouldCreate = input.params.mission !== void 0 || missionsEnabled && objective !== void 0;
|
|
4037
|
+
if (!hasMissionId && !shouldCreate) return void 0;
|
|
4038
|
+
const location = resolveMissionStoreLocation({ projectRoot: input.projectRoot, ...input.config ? { config: input.config } : {} });
|
|
4039
|
+
if (hasMissionId) {
|
|
4040
|
+
const missionId = validateMissionId(input.params.missionId);
|
|
4041
|
+
readMission(location, missionId);
|
|
4042
|
+
updateMission(location, missionId, { status: "active" });
|
|
4043
|
+
return { missionId, location, autoCreated: false, announceInContent: true };
|
|
4036
4044
|
}
|
|
4037
|
-
|
|
4038
|
-
const
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
+
const mission = input.params.mission !== void 0 ? validateMissionLaunch(input.params.mission) : void 0;
|
|
4046
|
+
const promptDerivedObjective = mission?.objective ?? (mission ? mission.title : objective ? PROMPT_REDACTED : void 0);
|
|
4047
|
+
const title = mission?.title || PROMPT_REDACTED;
|
|
4048
|
+
const record = createMission(location, {
|
|
4049
|
+
title,
|
|
4050
|
+
objective: promptDerivedObjective || title,
|
|
4051
|
+
...mission?.goal === true ? { goal: true } : {},
|
|
4052
|
+
...mission?.budget ? { budget: mission.budget } : {},
|
|
4053
|
+
status: "active",
|
|
4054
|
+
...mission?.labels ? { labels: mission.labels } : {},
|
|
4055
|
+
...input.ownerSessionId ? { ownerSessionId: input.ownerSessionId } : {}
|
|
4056
|
+
}, /* @__PURE__ */ new Date(), input.config?.retainTerminal);
|
|
4057
|
+
return { missionId: record.id, location, autoCreated: input.params.mission === void 0, announceInContent: true };
|
|
4058
|
+
}
|
|
4059
|
+
function toolResultIsError(result) {
|
|
4060
|
+
return "isError" in result && result.isError === true;
|
|
4061
|
+
}
|
|
4062
|
+
function missionRunModeForResult(mode) {
|
|
4063
|
+
return mode === "management" ? "external" : mode;
|
|
4064
|
+
}
|
|
4065
|
+
function runStatusForResult(result) {
|
|
4066
|
+
if (result.details.asyncDir) return "active";
|
|
4067
|
+
if (result.details.results.some((child) => child.interrupted || child.detached)) return "paused";
|
|
4068
|
+
if (toolResultIsError(result) || result.details.results.some((child) => child.exitCode !== 0)) return "failed";
|
|
4069
|
+
return "completed";
|
|
4070
|
+
}
|
|
4071
|
+
var UNACCEPTED_ACCEPTANCE_STATUSES = /* @__PURE__ */ new Set(["pending", "claimed", "review-required", "rejected"]);
|
|
4072
|
+
function missionAcceptanceStatus(results) {
|
|
4073
|
+
const statuses = (Array.isArray(results) ? results : []).map((result) => result?.acceptance?.status).filter((status) => typeof status === "string" && status.trim() !== "");
|
|
4074
|
+
return statuses.find((status) => UNACCEPTED_ACCEPTANCE_STATUSES.has(status)) ?? statuses[0];
|
|
4075
|
+
}
|
|
4076
|
+
function finishedMissionStatus(record, autoCreated, runAcceptance) {
|
|
4077
|
+
if (autoCreated) return "completed";
|
|
4078
|
+
if (runAcceptance.some((run) => UNACCEPTED_ACCEPTANCE_STATUSES.has(run.status))) return "waiting";
|
|
4079
|
+
const reported = new Set(runAcceptance.map((run) => run.runId));
|
|
4080
|
+
return record.workflowChildren.some((child) => !reported.has(child.runId) && UNACCEPTED_ACCEPTANCE_STATUSES.has(child.acceptanceStatus)) ? "waiting" : "completed";
|
|
4081
|
+
}
|
|
4082
|
+
function missionStatusForRun(record, runId, runStatus, acceptanceStatus, autoCreated) {
|
|
4083
|
+
if (record.status === "completed" || record.status === "failed" || record.status === "cancelled") return record.status;
|
|
4084
|
+
if (runStatus === "active" || runStatus === "queued" || runStatus === "running") return "active";
|
|
4085
|
+
if (record.goal) return "active";
|
|
4086
|
+
if (runStatus === "paused") return "waiting";
|
|
4087
|
+
const otherActive = record.runs.some((run) => run.runId !== runId && (run.status === "active" || run.status === "queued" || run.status === "running"));
|
|
4088
|
+
if (otherActive) return "active";
|
|
4089
|
+
if (runStatus === "completed" || runStatus === "complete") return finishedMissionStatus(record, autoCreated, [{ runId, status: acceptanceStatus }]);
|
|
4090
|
+
if (runStatus === "stopped" || runStatus === "rejected" || runStatus === "cancelled") return "cancelled";
|
|
4091
|
+
return "failed";
|
|
4092
|
+
}
|
|
4093
|
+
function usageForResult(result) {
|
|
4094
|
+
const tokens = result.details.results.reduce((total, child) => total + child.usage.input + child.usage.output, 0);
|
|
4095
|
+
return tokens > 0 ? { tokens } : void 0;
|
|
4096
|
+
}
|
|
4097
|
+
function usageFromUnknown(value) {
|
|
4098
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4099
|
+
const total = value.total;
|
|
4100
|
+
return Number.isSafeInteger(total) && total >= 0 ? { tokens: total } : void 0;
|
|
4101
|
+
}
|
|
4102
|
+
function artifactsForResult(result) {
|
|
4103
|
+
const artifacts = [];
|
|
4104
|
+
if (result.details.asyncDir) {
|
|
4105
|
+
artifacts.push(
|
|
4106
|
+
{ kind: "status", path: path11.join(result.details.asyncDir, "status.json") },
|
|
4107
|
+
{ kind: "other", path: path11.join(result.details.asyncDir, "events.jsonl"), description: "Lifecycle events" }
|
|
4108
|
+
);
|
|
4045
4109
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4110
|
+
for (const child of result.details.results) {
|
|
4111
|
+
if (child.artifactPaths?.outputPath) artifacts.push({ kind: "output", path: child.artifactPaths.outputPath });
|
|
4112
|
+
if (child.savedOutputPath) artifacts.push({ kind: "output", path: child.savedOutputPath });
|
|
4113
|
+
if (child.transcriptPath) artifacts.push({ kind: "other", path: child.transcriptPath, description: "Child transcript" });
|
|
4114
|
+
if (child.structuredOutputPath) artifacts.push({ kind: "output", path: child.structuredOutputPath, description: "Structured output" });
|
|
4049
4115
|
}
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
...typeof input.objective === "string" ? { objective: input.objective.trim() } : {},
|
|
4053
|
-
...input.goal === true ? { goal: true } : {},
|
|
4054
|
-
...budget !== void 0 ? { budget: { tokens: budget.tokens } } : {},
|
|
4055
|
-
...input.labels !== void 0 ? { labels: input.labels.map((label) => label.trim()) } : {}
|
|
4056
|
-
};
|
|
4116
|
+
if (result.details.parallelHandoff?.path) artifacts.push({ kind: "manifest", path: result.details.parallelHandoff.path });
|
|
4117
|
+
return artifacts;
|
|
4057
4118
|
}
|
|
4058
|
-
function
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
if (input.description !== void 0 && (typeof input.description !== "string" || !input.description.trim())) throw new Error(`missionUpdate.artifacts[${index}].description must be a non-empty string`);
|
|
4119
|
+
function firstText(result) {
|
|
4120
|
+
const text = result.content.find((item) => item.type === "text")?.text.trim();
|
|
4121
|
+
if (!text) return void 0;
|
|
4122
|
+
return text.length > 2e3 ? `${text.slice(0, 1997)}...` : text;
|
|
4123
|
+
}
|
|
4124
|
+
function persistedBinding(binding) {
|
|
4065
4125
|
return {
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4126
|
+
schemaVersion: 1,
|
|
4127
|
+
missionId: binding.missionId,
|
|
4128
|
+
projectRoot: binding.location.projectRoot,
|
|
4129
|
+
missionDir: binding.location.missionDir,
|
|
4130
|
+
globalIndexDir: binding.location.globalIndexDir,
|
|
4131
|
+
writeGlobalIndex: binding.location.writeGlobalIndex,
|
|
4132
|
+
...binding.location.retainTerminal !== void 0 ? { retainTerminal: binding.location.retainTerminal } : {},
|
|
4133
|
+
autoCreated: binding.autoCreated
|
|
4069
4134
|
};
|
|
4070
4135
|
}
|
|
4071
|
-
function
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4136
|
+
function writeMissionAsyncBinding(asyncDir, binding) {
|
|
4137
|
+
writePrivateAtomicJson(path11.join(asyncDir, MISSION_BINDING_FILE), persistedBinding(binding));
|
|
4138
|
+
}
|
|
4139
|
+
function attachMissionToLaunchResult(input) {
|
|
4140
|
+
const runId = input.result.details.runId ?? input.result.details.asyncId;
|
|
4141
|
+
if (!runId) {
|
|
4142
|
+
const current2 = readMission(input.binding.location, input.binding.missionId);
|
|
4143
|
+
const activeRunExists = current2.runs.some((run2) => run2.status === "active" || run2.status === "queued" || run2.status === "running");
|
|
4144
|
+
const mission2 = toolResultIsError(input.result) ? updateMission(input.binding.location, input.binding.missionId, {
|
|
4145
|
+
status: activeRunExists ? "active" : "failed",
|
|
4146
|
+
...firstText(input.result) ? { summary: firstText(input.result) } : {}
|
|
4147
|
+
}) : current2;
|
|
4148
|
+
return {
|
|
4149
|
+
...input.result,
|
|
4150
|
+
details: {
|
|
4151
|
+
...input.result.details,
|
|
4152
|
+
missionId: input.binding.missionId,
|
|
4153
|
+
missionPath: missionRecordPath(input.binding.location, input.binding.missionId),
|
|
4154
|
+
mission: mission2
|
|
4155
|
+
}
|
|
4156
|
+
};
|
|
4083
4157
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4158
|
+
const runStatus = runStatusForResult(input.result);
|
|
4159
|
+
const current = readMission(input.binding.location, input.binding.missionId);
|
|
4160
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4161
|
+
const usage = usageForResult(input.result);
|
|
4162
|
+
const run = {
|
|
4163
|
+
runId,
|
|
4164
|
+
mode: missionRunModeForResult(input.result.details.mode),
|
|
4165
|
+
status: runStatus,
|
|
4166
|
+
startedAt,
|
|
4167
|
+
...input.result.details.asyncDir ? { asyncDir: input.result.details.asyncDir } : {},
|
|
4168
|
+
...input.result.details.results.length === 1 && input.result.details.results[0]?.agent ? { agent: input.result.details.results[0].agent } : {},
|
|
4169
|
+
...runStatus !== "active" ? { completedAt: startedAt } : {},
|
|
4170
|
+
...usage ? { usage } : {}
|
|
4171
|
+
};
|
|
4172
|
+
let mission = updateMission(input.binding.location, input.binding.missionId, {
|
|
4173
|
+
status: missionStatusForRun(current, runId, runStatus, missionAcceptanceStatus(input.result.details.results), input.binding.autoCreated),
|
|
4174
|
+
addRuns: [run],
|
|
4175
|
+
addArtifacts: artifactsForResult(input.result),
|
|
4176
|
+
...firstText(input.result) && runStatus !== "active" ? { summary: firstText(input.result) } : {},
|
|
4177
|
+
...input.result.details.results.length === 1 && input.result.details.results[0]?.acceptance ? { acceptance: input.result.details.results[0].acceptance } : {}
|
|
4178
|
+
});
|
|
4179
|
+
if (input.result.details.asyncDir) {
|
|
4180
|
+
writeMissionAsyncBinding(input.result.details.asyncDir, input.binding);
|
|
4181
|
+
const statusPath = path11.join(input.result.details.asyncDir, "status.json");
|
|
4182
|
+
if (fs10.existsSync(statusPath)) {
|
|
4183
|
+
try {
|
|
4184
|
+
const status = JSON.parse(fs10.readFileSync(statusPath, "utf-8"));
|
|
4185
|
+
if (typeof status.state === "string" && !["queued", "running"].includes(status.state)) {
|
|
4186
|
+
mission = syncMissionFromAsyncCompletion({
|
|
4187
|
+
runId,
|
|
4188
|
+
asyncDir: input.result.details.asyncDir,
|
|
4189
|
+
mode: input.result.details.mode,
|
|
4190
|
+
state: status.state,
|
|
4191
|
+
// A run that finished before this launch result was attached keeps its acceptance in the status steps.
|
|
4192
|
+
...Array.isArray(status.steps) ? { results: status.steps.map((step) => ({ acceptance: step?.acceptance })) } : {},
|
|
4193
|
+
...firstText(input.result) ? { summary: firstText(input.result) } : {}
|
|
4194
|
+
}) ?? mission;
|
|
4195
|
+
}
|
|
4196
|
+
} catch (error) {
|
|
4197
|
+
throw new Error(`Failed to reconcile mission from terminal async status '${statusPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4088
4200
|
}
|
|
4089
|
-
|
|
4201
|
+
const lastTextIndex = input.result.content.findLastIndex((item) => item.type === "text");
|
|
4202
|
+
const hasStructuredOutput = input.result.details.results.some((child) => child.structuredOutputPath !== void 0);
|
|
4203
|
+
const textIsJson = lastTextIndex >= 0 && (() => {
|
|
4204
|
+
try {
|
|
4205
|
+
JSON.parse(input.result.content[lastTextIndex].text);
|
|
4206
|
+
return true;
|
|
4207
|
+
} catch {
|
|
4208
|
+
return false;
|
|
4209
|
+
}
|
|
4210
|
+
})();
|
|
4090
4211
|
return {
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4212
|
+
...input.result,
|
|
4213
|
+
content: input.binding.announceInContent === true && lastTextIndex >= 0 && !hasStructuredOutput && !textIsJson ? input.result.content.map((item, index) => index === lastTextIndex && item.type === "text" ? { ...item, text: `${item.text}
|
|
4214
|
+
Mission: ${mission.id} (${mission.status})` } : item) : input.result.content,
|
|
4215
|
+
details: {
|
|
4216
|
+
...input.result.details,
|
|
4217
|
+
missionId: mission.id,
|
|
4218
|
+
missionPath: missionRecordPath(input.binding.location, mission.id),
|
|
4219
|
+
mission
|
|
4220
|
+
}
|
|
4096
4221
|
};
|
|
4097
4222
|
}
|
|
4098
|
-
function
|
|
4099
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(
|
|
4223
|
+
function parsePersistedBinding(value, source) {
|
|
4224
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${source} must be an object`);
|
|
4100
4225
|
const input = value;
|
|
4101
|
-
|
|
4102
|
-
|
|
4226
|
+
if (input.schemaVersion !== 1) throw new Error(`${source}.schemaVersion must be 1`);
|
|
4227
|
+
for (const field of ["projectRoot", "missionDir", "globalIndexDir"]) {
|
|
4228
|
+
if (typeof input[field] !== "string" || !input[field].trim()) throw new Error(`${source}.${field} must be a non-empty string`);
|
|
4103
4229
|
}
|
|
4104
|
-
|
|
4230
|
+
if (typeof input.writeGlobalIndex !== "boolean") throw new Error(`${source}.writeGlobalIndex must be boolean`);
|
|
4231
|
+
if (input.retainTerminal !== void 0 && (!Number.isInteger(input.retainTerminal) || input.retainTerminal < 1)) throw new Error(`${source}.retainTerminal must be a positive integer`);
|
|
4232
|
+
if (input.autoCreated !== void 0 && typeof input.autoCreated !== "boolean") throw new Error(`${source}.autoCreated must be boolean`);
|
|
4233
|
+
return {
|
|
4234
|
+
missionId: validateMissionId(input.missionId, `${source}.missionId`),
|
|
4235
|
+
// Bindings saved before this field existed belong to runs that completed their mission without an acceptance check.
|
|
4236
|
+
autoCreated: input.autoCreated !== false,
|
|
4237
|
+
location: {
|
|
4238
|
+
projectRoot: input.projectRoot,
|
|
4239
|
+
missionDir: input.missionDir,
|
|
4240
|
+
globalIndexDir: input.globalIndexDir,
|
|
4241
|
+
writeGlobalIndex: input.writeGlobalIndex,
|
|
4242
|
+
...input.retainTerminal !== void 0 ? { retainTerminal: input.retainTerminal } : {}
|
|
4243
|
+
}
|
|
4244
|
+
};
|
|
4245
|
+
}
|
|
4246
|
+
function readMissionBinding(asyncDir) {
|
|
4247
|
+
const bindingPath = path11.join(asyncDir, MISSION_BINDING_FILE);
|
|
4248
|
+
if (!fs10.existsSync(bindingPath)) return void 0;
|
|
4249
|
+
const raw = fs10.readFileSync(bindingPath, "utf-8");
|
|
4250
|
+
try {
|
|
4251
|
+
return parsePersistedBinding(JSON.parse(raw), bindingPath);
|
|
4252
|
+
} catch (error) {
|
|
4253
|
+
throw new Error(`Invalid mission binding file '${bindingPath}': ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
function syncMissionFromAsyncCompletion(value) {
|
|
4257
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4258
|
+
const event = value;
|
|
4259
|
+
if (typeof event.asyncDir !== "string" || !event.asyncDir.trim()) return void 0;
|
|
4260
|
+
const binding = readMissionBinding(event.asyncDir);
|
|
4261
|
+
if (!binding) return void 0;
|
|
4262
|
+
const runId = typeof event.runId === "string" ? event.runId : typeof event.id === "string" ? event.id : void 0;
|
|
4263
|
+
if (!runId) throw new Error("Async mission completion is missing runId");
|
|
4264
|
+
const runStatus = typeof event.state === "string" ? event.state : event.success === true ? "completed" : "failed";
|
|
4265
|
+
let current;
|
|
4266
|
+
try {
|
|
4267
|
+
current = readMission(binding.location, binding.missionId);
|
|
4268
|
+
} catch (error) {
|
|
4269
|
+
if (!(error instanceof MissionNotFoundError)) throw error;
|
|
4270
|
+
const reason = "mission-record-missing";
|
|
4271
|
+
const markerId = createHash4("sha256").update(JSON.stringify([runId, binding.missionId, reason])).digest("hex");
|
|
4272
|
+
const markerPath3 = path11.join(event.asyncDir, `.mission-sync-skipped-${markerId}.json`);
|
|
4273
|
+
try {
|
|
4274
|
+
fs10.writeFileSync(markerPath3, `${JSON.stringify({ runId, missionId: binding.missionId, reason })}
|
|
4275
|
+
`, { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
4276
|
+
fs10.appendFileSync(path11.join(event.asyncDir, "events.jsonl"), `${JSON.stringify({
|
|
4277
|
+
type: "subagent.mission.sync.skipped",
|
|
4278
|
+
ts: Date.now(),
|
|
4279
|
+
runId,
|
|
4280
|
+
missionId: binding.missionId,
|
|
4281
|
+
reason,
|
|
4282
|
+
missionPath: missionRecordPath(binding.location, binding.missionId)
|
|
4283
|
+
})}
|
|
4284
|
+
`, "utf-8");
|
|
4285
|
+
} catch {
|
|
4286
|
+
}
|
|
4287
|
+
return void 0;
|
|
4288
|
+
}
|
|
4289
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4290
|
+
const artifacts = [
|
|
4291
|
+
{ kind: "status", path: path11.join(event.asyncDir, "status.json") },
|
|
4292
|
+
{ kind: "other", path: path11.join(event.asyncDir, "events.jsonl"), description: "Lifecycle events" }
|
|
4293
|
+
];
|
|
4294
|
+
if (event.parallelHandoff && typeof event.parallelHandoff === "object" && typeof event.parallelHandoff.path === "string") {
|
|
4295
|
+
artifacts.push({ kind: "manifest", path: event.parallelHandoff.path });
|
|
4296
|
+
}
|
|
4297
|
+
if (Array.isArray(event.results)) {
|
|
4298
|
+
for (const result of event.results) {
|
|
4299
|
+
if (!result || typeof result !== "object") continue;
|
|
4300
|
+
const child = result;
|
|
4301
|
+
if (typeof child.artifactPath === "string") artifacts.push({ kind: "output", path: child.artifactPath });
|
|
4302
|
+
if (child.artifactPaths && typeof child.artifactPaths === "object" && typeof child.artifactPaths.outputPath === "string") {
|
|
4303
|
+
artifacts.push({ kind: "output", path: child.artifactPaths.outputPath });
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
const summary = typeof event.summary === "string" && event.summary.trim() ? event.summary.slice(0, 2e3) : void 0;
|
|
4308
|
+
const usage = usageFromUnknown(event.totalTokens) ?? (Array.isArray(event.results) ? { tokens: event.results.reduce((total, result) => {
|
|
4309
|
+
if (!result || typeof result !== "object") return total;
|
|
4310
|
+
return total + (usageFromUnknown(result.tokens)?.tokens ?? 0);
|
|
4311
|
+
}, 0) } : void 0);
|
|
4312
|
+
const workflowRunId = typeof event.parentWorkflowRunId === "string" && event.parentWorkflowRunId.trim() ? event.parentWorkflowRunId.trim() : void 0;
|
|
4313
|
+
const workflowKey = typeof event.workflowKey === "string" && event.workflowKey.trim() ? event.workflowKey.trim() : void 0;
|
|
4314
|
+
const workflowChildStatus = runStatus === "complete" || runStatus === "completed" || event.success === true ? "completed" : runStatus === "paused" ? "paused" : runStatus === "stopped" ? "stopped" : "failed";
|
|
4315
|
+
const workflowChildTerminal = !["running", "queued", "active", "paused"].includes(workflowChildStatus);
|
|
4316
|
+
const acceptanceStatus = missionAcceptanceStatus(event.results);
|
|
4317
|
+
return updateMission(binding.location, binding.missionId, {
|
|
4318
|
+
status: missionStatusForRun(current, runId, runStatus, acceptanceStatus, binding.autoCreated),
|
|
4319
|
+
addRuns: [{ runId, mode: typeof event.mode === "string" && ["single", "parallel", "chain", "workflow"].includes(event.mode) ? event.mode : "external", asyncDir: event.asyncDir, status: runStatus, completedAt, ...usage && usage.tokens > 0 ? { usage } : {} }],
|
|
4320
|
+
addArtifacts: artifacts,
|
|
4321
|
+
...workflowRunId && workflowKey ? { upsertWorkflowChildren: [{
|
|
4322
|
+
workflowRunId,
|
|
4323
|
+
key: workflowKey,
|
|
4324
|
+
runId,
|
|
4325
|
+
status: workflowChildStatus,
|
|
4326
|
+
artifactPaths: artifacts.map((artifact) => artifact.path),
|
|
4327
|
+
...workflowChildTerminal ? { completedAt } : {},
|
|
4328
|
+
...acceptanceStatus ? { acceptanceStatus } : {},
|
|
4329
|
+
heartbeat: { status: workflowChildStatus, ...summary ? { message: summary } : {} }
|
|
4330
|
+
}] } : {},
|
|
4331
|
+
...summary ? { summary } : {}
|
|
4332
|
+
});
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4335
|
+
// src/missions/actions.ts
|
|
4336
|
+
var MISSION_ACTIONS = [
|
|
4337
|
+
"mission.create",
|
|
4338
|
+
"mission.list",
|
|
4339
|
+
"mission.show",
|
|
4340
|
+
"mission.update",
|
|
4341
|
+
"mission.resolve-decision",
|
|
4342
|
+
"mission.attach-run",
|
|
4343
|
+
"mission.close"
|
|
4344
|
+
];
|
|
4345
|
+
function textResult(text, details) {
|
|
4346
|
+
return { content: [{ type: "text", text }], details };
|
|
4347
|
+
}
|
|
4348
|
+
function requireMissionId(params) {
|
|
4349
|
+
return validateMissionId(params.missionId, "missionId");
|
|
4350
|
+
}
|
|
4351
|
+
function validateStatus(value, label) {
|
|
4352
|
+
if (typeof value !== "string" || !MISSION_STATUSES.includes(value)) {
|
|
4353
|
+
throw new Error(`${label} must be one of ${MISSION_STATUSES.join(", ")}`);
|
|
4354
|
+
}
|
|
4355
|
+
return value;
|
|
4356
|
+
}
|
|
4357
|
+
function validateMissionLaunch(value) {
|
|
4358
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("mission must be an object");
|
|
4359
|
+
const input = value;
|
|
4360
|
+
for (const key of Object.keys(input)) {
|
|
4361
|
+
if (key !== "title" && key !== "summary" && key !== "objective" && key !== "goal" && key !== "budget" && key !== "labels") throw new Error(`mission.${key} is unknown`);
|
|
4362
|
+
}
|
|
4363
|
+
if (input.title !== void 0 && input.summary !== void 0) throw new Error("mission.title and mission.summary cannot both be set");
|
|
4364
|
+
const title = input.title ?? input.summary;
|
|
4365
|
+
if (typeof title !== "string" || !title.trim()) throw new Error("mission.title or mission.summary must be a non-empty string");
|
|
4366
|
+
if (input.objective !== void 0 && (typeof input.objective !== "string" || !input.objective.trim())) throw new Error("mission.objective must be a non-empty string");
|
|
4367
|
+
if (input.goal !== void 0 && input.goal !== true) throw new Error("mission.goal must be true when supplied");
|
|
4368
|
+
const budget = input.budget;
|
|
4369
|
+
if (budget !== void 0 && (!budget || typeof budget !== "object" || Array.isArray(budget) || !Number.isSafeInteger(budget.tokens) || budget.tokens < 1)) {
|
|
4370
|
+
throw new Error("mission.budget.tokens must be a positive integer");
|
|
4371
|
+
}
|
|
4372
|
+
if (input.goal === true && budget === void 0) throw new Error("mission.budget is required when mission.goal is true");
|
|
4373
|
+
if (input.labels !== void 0 && (!Array.isArray(input.labels) || input.labels.some((label) => typeof label !== "string" || !label.trim()))) {
|
|
4374
|
+
throw new Error("mission.labels must contain only non-empty strings");
|
|
4375
|
+
}
|
|
4376
|
+
return {
|
|
4377
|
+
title: title.trim(),
|
|
4378
|
+
...typeof input.objective === "string" ? { objective: input.objective.trim() } : {},
|
|
4379
|
+
...input.goal === true ? { goal: true } : {},
|
|
4380
|
+
...budget !== void 0 ? { budget: { tokens: budget.tokens } } : {},
|
|
4381
|
+
...input.labels !== void 0 ? { labels: input.labels.map((label) => label.trim()) } : {}
|
|
4382
|
+
};
|
|
4383
|
+
}
|
|
4384
|
+
function validateArtifact(value, index) {
|
|
4385
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`missionUpdate.artifacts[${index}] must be an object`);
|
|
4386
|
+
const input = value;
|
|
4387
|
+
const kinds = ["status", "output", "patch", "manifest", "review", "note", "other"];
|
|
4388
|
+
if (typeof input.kind !== "string" || !kinds.includes(input.kind)) throw new Error(`missionUpdate.artifacts[${index}].kind is invalid`);
|
|
4389
|
+
if (typeof input.path !== "string" || !input.path.trim()) throw new Error(`missionUpdate.artifacts[${index}].path must be a non-empty string`);
|
|
4390
|
+
if (input.description !== void 0 && (typeof input.description !== "string" || !input.description.trim())) throw new Error(`missionUpdate.artifacts[${index}].description must be a non-empty string`);
|
|
4391
|
+
return {
|
|
4392
|
+
kind: input.kind,
|
|
4393
|
+
path: input.path,
|
|
4394
|
+
...typeof input.description === "string" ? { description: input.description } : {}
|
|
4395
|
+
};
|
|
4396
|
+
}
|
|
4397
|
+
function validateReceipt(value, index) {
|
|
4398
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`missionUpdate.receipts[${index}] must be an object`);
|
|
4399
|
+
const input = value;
|
|
4400
|
+
for (const key of Object.keys(input)) {
|
|
4401
|
+
if (!["kind", "status", "title", "url", "description"].includes(key)) throw new Error(`missionUpdate.receipts[${index}].${key} is unknown`);
|
|
4402
|
+
}
|
|
4403
|
+
const kinds = ["pull_request", "ci", "deployment", "release"];
|
|
4404
|
+
const statuses = ["pending", "ready", "succeeded", "failed"];
|
|
4405
|
+
if (typeof input.kind !== "string" || !kinds.includes(input.kind)) throw new Error(`missionUpdate.receipts[${index}].kind is invalid`);
|
|
4406
|
+
if (typeof input.status !== "string" || !statuses.includes(input.status)) throw new Error(`missionUpdate.receipts[${index}].status is invalid`);
|
|
4407
|
+
for (const field of ["title", "url"]) {
|
|
4408
|
+
if (typeof input[field] !== "string" || !input[field].trim()) throw new Error(`missionUpdate.receipts[${index}].${field} must be a non-empty string`);
|
|
4409
|
+
}
|
|
4410
|
+
try {
|
|
4411
|
+
new URL(input.url);
|
|
4412
|
+
} catch {
|
|
4413
|
+
throw new Error(`missionUpdate.receipts[${index}].url must be an absolute URL`);
|
|
4414
|
+
}
|
|
4415
|
+
if (input.description !== void 0 && (typeof input.description !== "string" || !input.description.trim())) throw new Error(`missionUpdate.receipts[${index}].description must be a non-empty string`);
|
|
4416
|
+
return {
|
|
4417
|
+
kind: input.kind,
|
|
4418
|
+
status: input.status,
|
|
4419
|
+
title: input.title.trim(),
|
|
4420
|
+
url: input.url.trim(),
|
|
4421
|
+
...typeof input.description === "string" ? { description: input.description.trim() } : {}
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
function validateMissionUpdate(value) {
|
|
4425
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("missionUpdate must be an object");
|
|
4426
|
+
const input = value;
|
|
4427
|
+
for (const key of Object.keys(input)) {
|
|
4428
|
+
if (!["title", "objective", "goal", "budget", "status", "summary", "labels", "artifacts", "receipts", "decisions"].includes(key)) throw new Error(`missionUpdate.${key} is unknown`);
|
|
4429
|
+
}
|
|
4430
|
+
const update = {};
|
|
4105
4431
|
for (const field of ["title", "objective", "summary"]) {
|
|
4106
4432
|
const candidate = input[field];
|
|
4107
4433
|
if (candidate === void 0) continue;
|
|
@@ -4156,13 +4482,14 @@ function validateMissionUpdate(value) {
|
|
|
4156
4482
|
}
|
|
4157
4483
|
function refreshLinkedRunStatus(location, record) {
|
|
4158
4484
|
const warnings = [];
|
|
4485
|
+
const runAcceptance = [];
|
|
4159
4486
|
const updates = record.runs.flatMap((run) => {
|
|
4160
4487
|
if (!run.asyncDir) return [];
|
|
4161
|
-
const statusPath =
|
|
4162
|
-
if (!
|
|
4488
|
+
const statusPath = path12.join(run.asyncDir, "status.json");
|
|
4489
|
+
if (!fs11.existsSync(statusPath)) return [];
|
|
4163
4490
|
let status2;
|
|
4164
4491
|
try {
|
|
4165
|
-
status2 = JSON.parse(
|
|
4492
|
+
status2 = JSON.parse(fs11.readFileSync(statusPath, "utf-8"));
|
|
4166
4493
|
} catch (error) {
|
|
4167
4494
|
warnings.push(`Failed to read linked run status '${statusPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4168
4495
|
return [];
|
|
@@ -4176,6 +4503,7 @@ function refreshLinkedRunStatus(location, record) {
|
|
|
4176
4503
|
warnings.push(`Linked run status '${statusPath}' is missing state`);
|
|
4177
4504
|
return [];
|
|
4178
4505
|
}
|
|
4506
|
+
runAcceptance.push({ runId: run.runId, status: missionAcceptanceStatus(status2.steps) });
|
|
4179
4507
|
if (state === run.status) return [];
|
|
4180
4508
|
return [{
|
|
4181
4509
|
...run,
|
|
@@ -4186,7 +4514,7 @@ function refreshLinkedRunStatus(location, record) {
|
|
|
4186
4514
|
if (updates.length === 0) return { record, warnings };
|
|
4187
4515
|
const merged = record.runs.map((run) => updates.find((update) => update.runId === run.runId && update.childIndex === run.childIndex) ?? run);
|
|
4188
4516
|
const states = merged.map((run) => run.status);
|
|
4189
|
-
const status = record.status === "completed" || record.status === "failed" || record.status === "cancelled" ? record.status : record.goal && !states.some((state) => state === "queued" || state === "running" || state === "active") ? "active" : states.some((state) => state === "queued" || state === "running" || state === "active") ? "active" : states.some((state) => state === "paused") ? "waiting" : states.some((state) => state === "failed") ? "failed" : states.some((state) => state === "stopped" || state === "rejected" || state === "cancelled") ? "cancelled" : states.length > 0 && states.every((state) => state === "complete" || state === "completed") ?
|
|
4517
|
+
const status = record.status === "completed" || record.status === "failed" || record.status === "cancelled" ? record.status : record.goal && !states.some((state) => state === "queued" || state === "running" || state === "active") ? "active" : states.some((state) => state === "queued" || state === "running" || state === "active") ? "active" : states.some((state) => state === "paused") ? "waiting" : states.some((state) => state === "failed") ? "failed" : states.some((state) => state === "stopped" || state === "rejected" || state === "cancelled") ? "cancelled" : states.length > 0 && states.every((state) => state === "complete" || state === "completed") ? finishedMissionStatus(record, !merged.some((run) => run.asyncDir && readMissionBinding(run.asyncDir)?.autoCreated === false), runAcceptance) : record.status;
|
|
4190
4518
|
return { record: updateMission(location, record.id, { status, addRuns: updates }), warnings };
|
|
4191
4519
|
}
|
|
4192
4520
|
function formatMission(record) {
|
|
@@ -4210,7 +4538,8 @@ function formatMission(record) {
|
|
|
4210
4538
|
const identity = child.runId ? `${child.key} (${child.runId})` : child.key;
|
|
4211
4539
|
const heartbeat = child.heartbeat ? `; heartbeat ${child.heartbeat.status ?? child.status}${child.heartbeat.phase ? `/${child.heartbeat.phase}` : ""} at ${child.heartbeat.updatedAt}` : "";
|
|
4212
4540
|
const recovery = child.sessionPath ? `; session ${child.sessionPath}` : child.artifactPaths.length ? `; artifacts ${child.artifactPaths.length}` : "";
|
|
4213
|
-
|
|
4541
|
+
const acceptance = child.acceptanceStatus ? `; acceptance ${child.acceptanceStatus}` : "";
|
|
4542
|
+
lines.push(` ${identity}: ${child.status}${child.agent ? ` \u2014 ${child.agent}` : ""}${child.phase ? ` [${child.phase}]` : ""}${acceptance}; updated ${child.updatedAt}${heartbeat}${recovery}`);
|
|
4214
4543
|
}
|
|
4215
4544
|
}
|
|
4216
4545
|
if (record.decisions.length) {
|
|
@@ -4331,308 +4660,6 @@ ${formatMission(record2)}`, { mode: "management", results: [], missionId: record
|
|
|
4331
4660
|
}
|
|
4332
4661
|
}
|
|
4333
4662
|
|
|
4334
|
-
// src/missions/lifecycle.ts
|
|
4335
|
-
import * as fs11 from "node:fs";
|
|
4336
|
-
import * as path12 from "node:path";
|
|
4337
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
4338
|
-
var MISSION_BINDING_FILE = "mission.json";
|
|
4339
|
-
function workflowObjective(params) {
|
|
4340
|
-
const objective = params.task?.trim() || params.tasks?.find((task) => task.task?.trim())?.task?.trim() || params.chain?.find((step) => step.task?.trim())?.task?.trim();
|
|
4341
|
-
if (objective) return objective;
|
|
4342
|
-
for (const step of params.chain ?? []) {
|
|
4343
|
-
const parallel = Array.isArray(step.parallel) ? step.parallel : step.parallel ? [step.parallel] : [];
|
|
4344
|
-
const task = parallel.find((child) => child.task?.trim())?.task?.trim();
|
|
4345
|
-
if (task) return task;
|
|
4346
|
-
}
|
|
4347
|
-
return void 0;
|
|
4348
|
-
}
|
|
4349
|
-
function prepareMissionLaunch(input) {
|
|
4350
|
-
const hasMissionId = input.params.missionId !== void 0;
|
|
4351
|
-
if (hasMissionId && input.params.mission !== void 0) throw new Error("Use missionId or mission, not both");
|
|
4352
|
-
if (input.params.mission === false) return void 0;
|
|
4353
|
-
const objective = workflowObjective(input.params);
|
|
4354
|
-
const missionsEnabled = input.config?.enabled !== false;
|
|
4355
|
-
const shouldCreate = input.params.mission !== void 0 || missionsEnabled && objective !== void 0;
|
|
4356
|
-
if (!hasMissionId && !shouldCreate) return void 0;
|
|
4357
|
-
const location = resolveMissionStoreLocation({ projectRoot: input.projectRoot, ...input.config ? { config: input.config } : {} });
|
|
4358
|
-
if (hasMissionId) {
|
|
4359
|
-
const missionId = validateMissionId(input.params.missionId);
|
|
4360
|
-
readMission(location, missionId);
|
|
4361
|
-
updateMission(location, missionId, { status: "active" });
|
|
4362
|
-
return { missionId, location, autoCreated: false, announceInContent: true };
|
|
4363
|
-
}
|
|
4364
|
-
const mission = input.params.mission !== void 0 ? validateMissionLaunch(input.params.mission) : void 0;
|
|
4365
|
-
const promptDerivedObjective = mission?.objective ?? (mission ? mission.title : objective ? PROMPT_REDACTED : void 0);
|
|
4366
|
-
const title = mission?.title || PROMPT_REDACTED;
|
|
4367
|
-
const record = createMission(location, {
|
|
4368
|
-
title,
|
|
4369
|
-
objective: promptDerivedObjective || title,
|
|
4370
|
-
...mission?.goal === true ? { goal: true } : {},
|
|
4371
|
-
...mission?.budget ? { budget: mission.budget } : {},
|
|
4372
|
-
status: "active",
|
|
4373
|
-
...mission?.labels ? { labels: mission.labels } : {},
|
|
4374
|
-
...input.ownerSessionId ? { ownerSessionId: input.ownerSessionId } : {}
|
|
4375
|
-
}, /* @__PURE__ */ new Date(), input.config?.retainTerminal);
|
|
4376
|
-
return { missionId: record.id, location, autoCreated: true, announceInContent: true };
|
|
4377
|
-
}
|
|
4378
|
-
function toolResultIsError(result) {
|
|
4379
|
-
return "isError" in result && result.isError === true;
|
|
4380
|
-
}
|
|
4381
|
-
function missionRunModeForResult(mode) {
|
|
4382
|
-
return mode === "management" ? "external" : mode;
|
|
4383
|
-
}
|
|
4384
|
-
function runStatusForResult(result) {
|
|
4385
|
-
if (result.details.asyncDir) return "active";
|
|
4386
|
-
if (result.details.results.some((child) => child.interrupted || child.detached)) return "paused";
|
|
4387
|
-
if (toolResultIsError(result) || result.details.results.some((child) => child.exitCode !== 0)) return "failed";
|
|
4388
|
-
return "completed";
|
|
4389
|
-
}
|
|
4390
|
-
function missionStatusForRun(record, runId, runStatus) {
|
|
4391
|
-
if (record.status === "completed" || record.status === "failed" || record.status === "cancelled") return record.status;
|
|
4392
|
-
if (runStatus === "active" || runStatus === "queued" || runStatus === "running") return "active";
|
|
4393
|
-
if (record.goal) return "active";
|
|
4394
|
-
if (runStatus === "paused") return "waiting";
|
|
4395
|
-
const otherActive = record.runs.some((run) => run.runId !== runId && (run.status === "active" || run.status === "queued" || run.status === "running"));
|
|
4396
|
-
if (otherActive) return "active";
|
|
4397
|
-
if (runStatus === "completed" || runStatus === "complete") return "completed";
|
|
4398
|
-
if (runStatus === "stopped" || runStatus === "rejected" || runStatus === "cancelled") return "cancelled";
|
|
4399
|
-
return "failed";
|
|
4400
|
-
}
|
|
4401
|
-
function usageForResult(result) {
|
|
4402
|
-
const tokens = result.details.results.reduce((total, child) => total + child.usage.input + child.usage.output, 0);
|
|
4403
|
-
return tokens > 0 ? { tokens } : void 0;
|
|
4404
|
-
}
|
|
4405
|
-
function usageFromUnknown(value) {
|
|
4406
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4407
|
-
const total = value.total;
|
|
4408
|
-
return Number.isSafeInteger(total) && total >= 0 ? { tokens: total } : void 0;
|
|
4409
|
-
}
|
|
4410
|
-
function artifactsForResult(result) {
|
|
4411
|
-
const artifacts = [];
|
|
4412
|
-
if (result.details.asyncDir) {
|
|
4413
|
-
artifacts.push(
|
|
4414
|
-
{ kind: "status", path: path12.join(result.details.asyncDir, "status.json") },
|
|
4415
|
-
{ kind: "other", path: path12.join(result.details.asyncDir, "events.jsonl"), description: "Lifecycle events" }
|
|
4416
|
-
);
|
|
4417
|
-
}
|
|
4418
|
-
for (const child of result.details.results) {
|
|
4419
|
-
if (child.artifactPaths?.outputPath) artifacts.push({ kind: "output", path: child.artifactPaths.outputPath });
|
|
4420
|
-
if (child.savedOutputPath) artifacts.push({ kind: "output", path: child.savedOutputPath });
|
|
4421
|
-
if (child.transcriptPath) artifacts.push({ kind: "other", path: child.transcriptPath, description: "Child transcript" });
|
|
4422
|
-
if (child.structuredOutputPath) artifacts.push({ kind: "output", path: child.structuredOutputPath, description: "Structured output" });
|
|
4423
|
-
}
|
|
4424
|
-
if (result.details.parallelHandoff?.path) artifacts.push({ kind: "manifest", path: result.details.parallelHandoff.path });
|
|
4425
|
-
return artifacts;
|
|
4426
|
-
}
|
|
4427
|
-
function firstText(result) {
|
|
4428
|
-
const text = result.content.find((item) => item.type === "text")?.text.trim();
|
|
4429
|
-
if (!text) return void 0;
|
|
4430
|
-
return text.length > 2e3 ? `${text.slice(0, 1997)}...` : text;
|
|
4431
|
-
}
|
|
4432
|
-
function persistedBinding(binding) {
|
|
4433
|
-
return {
|
|
4434
|
-
schemaVersion: 1,
|
|
4435
|
-
missionId: binding.missionId,
|
|
4436
|
-
projectRoot: binding.location.projectRoot,
|
|
4437
|
-
missionDir: binding.location.missionDir,
|
|
4438
|
-
globalIndexDir: binding.location.globalIndexDir,
|
|
4439
|
-
writeGlobalIndex: binding.location.writeGlobalIndex,
|
|
4440
|
-
...binding.location.retainTerminal !== void 0 ? { retainTerminal: binding.location.retainTerminal } : {}
|
|
4441
|
-
};
|
|
4442
|
-
}
|
|
4443
|
-
function writeMissionAsyncBinding(asyncDir, binding) {
|
|
4444
|
-
writePrivateAtomicJson(path12.join(asyncDir, MISSION_BINDING_FILE), persistedBinding(binding));
|
|
4445
|
-
}
|
|
4446
|
-
function attachMissionToLaunchResult(input) {
|
|
4447
|
-
const runId = input.result.details.runId ?? input.result.details.asyncId;
|
|
4448
|
-
if (!runId) {
|
|
4449
|
-
const current2 = readMission(input.binding.location, input.binding.missionId);
|
|
4450
|
-
const activeRunExists = current2.runs.some((run2) => run2.status === "active" || run2.status === "queued" || run2.status === "running");
|
|
4451
|
-
const mission2 = toolResultIsError(input.result) ? updateMission(input.binding.location, input.binding.missionId, {
|
|
4452
|
-
status: activeRunExists ? "active" : "failed",
|
|
4453
|
-
...firstText(input.result) ? { summary: firstText(input.result) } : {}
|
|
4454
|
-
}) : current2;
|
|
4455
|
-
return {
|
|
4456
|
-
...input.result,
|
|
4457
|
-
details: {
|
|
4458
|
-
...input.result.details,
|
|
4459
|
-
missionId: input.binding.missionId,
|
|
4460
|
-
missionPath: missionRecordPath(input.binding.location, input.binding.missionId),
|
|
4461
|
-
mission: mission2
|
|
4462
|
-
}
|
|
4463
|
-
};
|
|
4464
|
-
}
|
|
4465
|
-
const runStatus = runStatusForResult(input.result);
|
|
4466
|
-
const current = readMission(input.binding.location, input.binding.missionId);
|
|
4467
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4468
|
-
const usage = usageForResult(input.result);
|
|
4469
|
-
const run = {
|
|
4470
|
-
runId,
|
|
4471
|
-
mode: missionRunModeForResult(input.result.details.mode),
|
|
4472
|
-
status: runStatus,
|
|
4473
|
-
startedAt,
|
|
4474
|
-
...input.result.details.asyncDir ? { asyncDir: input.result.details.asyncDir } : {},
|
|
4475
|
-
...input.result.details.results.length === 1 && input.result.details.results[0]?.agent ? { agent: input.result.details.results[0].agent } : {},
|
|
4476
|
-
...runStatus !== "active" ? { completedAt: startedAt } : {},
|
|
4477
|
-
...usage ? { usage } : {}
|
|
4478
|
-
};
|
|
4479
|
-
let mission = updateMission(input.binding.location, input.binding.missionId, {
|
|
4480
|
-
status: missionStatusForRun(current, runId, runStatus),
|
|
4481
|
-
addRuns: [run],
|
|
4482
|
-
addArtifacts: artifactsForResult(input.result),
|
|
4483
|
-
...firstText(input.result) && runStatus !== "active" ? { summary: firstText(input.result) } : {},
|
|
4484
|
-
...input.result.details.results.length === 1 && input.result.details.results[0]?.acceptance ? { acceptance: input.result.details.results[0].acceptance } : {}
|
|
4485
|
-
});
|
|
4486
|
-
if (input.result.details.asyncDir) {
|
|
4487
|
-
writeMissionAsyncBinding(input.result.details.asyncDir, input.binding);
|
|
4488
|
-
const statusPath = path12.join(input.result.details.asyncDir, "status.json");
|
|
4489
|
-
if (fs11.existsSync(statusPath)) {
|
|
4490
|
-
try {
|
|
4491
|
-
const status = JSON.parse(fs11.readFileSync(statusPath, "utf-8"));
|
|
4492
|
-
if (typeof status.state === "string" && !["queued", "running"].includes(status.state)) {
|
|
4493
|
-
mission = syncMissionFromAsyncCompletion({
|
|
4494
|
-
runId,
|
|
4495
|
-
asyncDir: input.result.details.asyncDir,
|
|
4496
|
-
mode: input.result.details.mode,
|
|
4497
|
-
state: status.state,
|
|
4498
|
-
...firstText(input.result) ? { summary: firstText(input.result) } : {}
|
|
4499
|
-
}) ?? mission;
|
|
4500
|
-
}
|
|
4501
|
-
} catch (error) {
|
|
4502
|
-
throw new Error(`Failed to reconcile mission from terminal async status '${statusPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4503
|
-
}
|
|
4504
|
-
}
|
|
4505
|
-
}
|
|
4506
|
-
const lastTextIndex = input.result.content.findLastIndex((item) => item.type === "text");
|
|
4507
|
-
const hasStructuredOutput = input.result.details.results.some((child) => child.structuredOutputPath !== void 0);
|
|
4508
|
-
const textIsJson = lastTextIndex >= 0 && (() => {
|
|
4509
|
-
try {
|
|
4510
|
-
JSON.parse(input.result.content[lastTextIndex].text);
|
|
4511
|
-
return true;
|
|
4512
|
-
} catch {
|
|
4513
|
-
return false;
|
|
4514
|
-
}
|
|
4515
|
-
})();
|
|
4516
|
-
return {
|
|
4517
|
-
...input.result,
|
|
4518
|
-
content: input.binding.announceInContent === true && lastTextIndex >= 0 && !hasStructuredOutput && !textIsJson ? input.result.content.map((item, index) => index === lastTextIndex && item.type === "text" ? { ...item, text: `${item.text}
|
|
4519
|
-
Mission: ${mission.id} (${mission.status})` } : item) : input.result.content,
|
|
4520
|
-
details: {
|
|
4521
|
-
...input.result.details,
|
|
4522
|
-
missionId: mission.id,
|
|
4523
|
-
missionPath: missionRecordPath(input.binding.location, mission.id),
|
|
4524
|
-
mission
|
|
4525
|
-
}
|
|
4526
|
-
};
|
|
4527
|
-
}
|
|
4528
|
-
function parsePersistedBinding(value, source) {
|
|
4529
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${source} must be an object`);
|
|
4530
|
-
const input = value;
|
|
4531
|
-
if (input.schemaVersion !== 1) throw new Error(`${source}.schemaVersion must be 1`);
|
|
4532
|
-
for (const field of ["projectRoot", "missionDir", "globalIndexDir"]) {
|
|
4533
|
-
if (typeof input[field] !== "string" || !input[field].trim()) throw new Error(`${source}.${field} must be a non-empty string`);
|
|
4534
|
-
}
|
|
4535
|
-
if (typeof input.writeGlobalIndex !== "boolean") throw new Error(`${source}.writeGlobalIndex must be boolean`);
|
|
4536
|
-
if (input.retainTerminal !== void 0 && (!Number.isInteger(input.retainTerminal) || input.retainTerminal < 1)) throw new Error(`${source}.retainTerminal must be a positive integer`);
|
|
4537
|
-
return {
|
|
4538
|
-
missionId: validateMissionId(input.missionId, `${source}.missionId`),
|
|
4539
|
-
autoCreated: false,
|
|
4540
|
-
location: {
|
|
4541
|
-
projectRoot: input.projectRoot,
|
|
4542
|
-
missionDir: input.missionDir,
|
|
4543
|
-
globalIndexDir: input.globalIndexDir,
|
|
4544
|
-
writeGlobalIndex: input.writeGlobalIndex,
|
|
4545
|
-
...input.retainTerminal !== void 0 ? { retainTerminal: input.retainTerminal } : {}
|
|
4546
|
-
}
|
|
4547
|
-
};
|
|
4548
|
-
}
|
|
4549
|
-
function readMissionBinding(asyncDir) {
|
|
4550
|
-
const bindingPath = path12.join(asyncDir, MISSION_BINDING_FILE);
|
|
4551
|
-
if (!fs11.existsSync(bindingPath)) return void 0;
|
|
4552
|
-
const raw = fs11.readFileSync(bindingPath, "utf-8");
|
|
4553
|
-
try {
|
|
4554
|
-
return parsePersistedBinding(JSON.parse(raw), bindingPath);
|
|
4555
|
-
} catch (error) {
|
|
4556
|
-
throw new Error(`Invalid mission binding file '${bindingPath}': ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
4557
|
-
}
|
|
4558
|
-
}
|
|
4559
|
-
function syncMissionFromAsyncCompletion(value) {
|
|
4560
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4561
|
-
const event = value;
|
|
4562
|
-
if (typeof event.asyncDir !== "string" || !event.asyncDir.trim()) return void 0;
|
|
4563
|
-
const binding = readMissionBinding(event.asyncDir);
|
|
4564
|
-
if (!binding) return void 0;
|
|
4565
|
-
const runId = typeof event.runId === "string" ? event.runId : typeof event.id === "string" ? event.id : void 0;
|
|
4566
|
-
if (!runId) throw new Error("Async mission completion is missing runId");
|
|
4567
|
-
const runStatus = typeof event.state === "string" ? event.state : event.success === true ? "completed" : "failed";
|
|
4568
|
-
let current;
|
|
4569
|
-
try {
|
|
4570
|
-
current = readMission(binding.location, binding.missionId);
|
|
4571
|
-
} catch (error) {
|
|
4572
|
-
if (!(error instanceof MissionNotFoundError)) throw error;
|
|
4573
|
-
const reason = "mission-record-missing";
|
|
4574
|
-
const markerId = createHash4("sha256").update(JSON.stringify([runId, binding.missionId, reason])).digest("hex");
|
|
4575
|
-
const markerPath3 = path12.join(event.asyncDir, `.mission-sync-skipped-${markerId}.json`);
|
|
4576
|
-
try {
|
|
4577
|
-
fs11.writeFileSync(markerPath3, `${JSON.stringify({ runId, missionId: binding.missionId, reason })}
|
|
4578
|
-
`, { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
4579
|
-
fs11.appendFileSync(path12.join(event.asyncDir, "events.jsonl"), `${JSON.stringify({
|
|
4580
|
-
type: "subagent.mission.sync.skipped",
|
|
4581
|
-
ts: Date.now(),
|
|
4582
|
-
runId,
|
|
4583
|
-
missionId: binding.missionId,
|
|
4584
|
-
reason,
|
|
4585
|
-
missionPath: missionRecordPath(binding.location, binding.missionId)
|
|
4586
|
-
})}
|
|
4587
|
-
`, "utf-8");
|
|
4588
|
-
} catch {
|
|
4589
|
-
}
|
|
4590
|
-
return void 0;
|
|
4591
|
-
}
|
|
4592
|
-
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4593
|
-
const artifacts = [
|
|
4594
|
-
{ kind: "status", path: path12.join(event.asyncDir, "status.json") },
|
|
4595
|
-
{ kind: "other", path: path12.join(event.asyncDir, "events.jsonl"), description: "Lifecycle events" }
|
|
4596
|
-
];
|
|
4597
|
-
if (event.parallelHandoff && typeof event.parallelHandoff === "object" && typeof event.parallelHandoff.path === "string") {
|
|
4598
|
-
artifacts.push({ kind: "manifest", path: event.parallelHandoff.path });
|
|
4599
|
-
}
|
|
4600
|
-
if (Array.isArray(event.results)) {
|
|
4601
|
-
for (const result of event.results) {
|
|
4602
|
-
if (!result || typeof result !== "object") continue;
|
|
4603
|
-
const child = result;
|
|
4604
|
-
if (typeof child.artifactPath === "string") artifacts.push({ kind: "output", path: child.artifactPath });
|
|
4605
|
-
if (child.artifactPaths && typeof child.artifactPaths === "object" && typeof child.artifactPaths.outputPath === "string") {
|
|
4606
|
-
artifacts.push({ kind: "output", path: child.artifactPaths.outputPath });
|
|
4607
|
-
}
|
|
4608
|
-
}
|
|
4609
|
-
}
|
|
4610
|
-
const summary = typeof event.summary === "string" && event.summary.trim() ? event.summary.slice(0, 2e3) : void 0;
|
|
4611
|
-
const usage = usageFromUnknown(event.totalTokens) ?? (Array.isArray(event.results) ? { tokens: event.results.reduce((total, result) => {
|
|
4612
|
-
if (!result || typeof result !== "object") return total;
|
|
4613
|
-
return total + (usageFromUnknown(result.tokens)?.tokens ?? 0);
|
|
4614
|
-
}, 0) } : void 0);
|
|
4615
|
-
const workflowRunId = typeof event.parentWorkflowRunId === "string" && event.parentWorkflowRunId.trim() ? event.parentWorkflowRunId.trim() : void 0;
|
|
4616
|
-
const workflowKey = typeof event.workflowKey === "string" && event.workflowKey.trim() ? event.workflowKey.trim() : void 0;
|
|
4617
|
-
const workflowChildStatus = runStatus === "complete" || runStatus === "completed" || event.success === true ? "completed" : runStatus === "paused" ? "paused" : runStatus === "stopped" ? "stopped" : "failed";
|
|
4618
|
-
const workflowChildTerminal = !["running", "queued", "active", "paused"].includes(workflowChildStatus);
|
|
4619
|
-
return updateMission(binding.location, binding.missionId, {
|
|
4620
|
-
status: missionStatusForRun(current, runId, runStatus),
|
|
4621
|
-
addRuns: [{ runId, mode: typeof event.mode === "string" && ["single", "parallel", "chain", "workflow"].includes(event.mode) ? event.mode : "external", asyncDir: event.asyncDir, status: runStatus, completedAt, ...usage && usage.tokens > 0 ? { usage } : {} }],
|
|
4622
|
-
addArtifacts: artifacts,
|
|
4623
|
-
...workflowRunId && workflowKey ? { upsertWorkflowChildren: [{
|
|
4624
|
-
workflowRunId,
|
|
4625
|
-
key: workflowKey,
|
|
4626
|
-
runId,
|
|
4627
|
-
status: workflowChildStatus,
|
|
4628
|
-
artifactPaths: artifacts.map((artifact) => artifact.path),
|
|
4629
|
-
...workflowChildTerminal ? { completedAt } : {},
|
|
4630
|
-
heartbeat: { status: workflowChildStatus, ...summary ? { message: summary } : {} }
|
|
4631
|
-
}] } : {},
|
|
4632
|
-
...summary ? { summary } : {}
|
|
4633
|
-
});
|
|
4634
|
-
}
|
|
4635
|
-
|
|
4636
4663
|
// src/runs/background/result-files.ts
|
|
4637
4664
|
import { createHash as createHash5 } from "node:crypto";
|
|
4638
4665
|
import * as fs12 from "node:fs";
|
|
@@ -7229,6 +7256,7 @@ export {
|
|
|
7229
7256
|
MISSION_ACTIONS,
|
|
7230
7257
|
handleMissionAction,
|
|
7231
7258
|
prepareMissionLaunch,
|
|
7259
|
+
missionAcceptanceStatus,
|
|
7232
7260
|
writeMissionAsyncBinding,
|
|
7233
7261
|
attachMissionToLaunchResult,
|
|
7234
7262
|
readMissionBinding,
|