pi-subagents 0.83.0 → 0.83.1
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 +22 -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-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-HJJAVHFB.js → chunk-5YIITCPD.js} +23 -23
- package/bundle/{chunk-CNLDYEH4.js → chunk-AHIFYGR3.js} +11 -11
- package/bundle/{chunk-MHX22USZ.js → chunk-C5P5CHYK.js} +1 -1
- package/bundle/{chunk-XXKG3P2W.js → chunk-C7JMPU5F.js} +4 -4
- package/bundle/{chunk-VTMNBPCQ.js → chunk-D3JAFGMO.js} +1 -1
- package/bundle/{chunk-BKFWQ642.js → chunk-F4ASL6OV.js} +1 -1
- package/bundle/{chunk-CDI3GMNV.js → chunk-F5DBXJ2G.js} +15 -15
- package/bundle/{chunk-JHZ6DZ5M.js → chunk-G2Z6ZH26.js} +1 -1
- package/bundle/{chunk-TJQJ3MEA.js → chunk-IPGZMQXV.js} +10 -10
- package/bundle/{chunk-Y3I6H6DV.js → chunk-LFDBD4PW.js} +2 -2
- package/bundle/{chunk-7F5SK2U3.js → chunk-LNRJCP2Y.js} +17 -14
- package/bundle/{chunk-6B4ITO2W.js → chunk-MABL3FUM.js} +1 -1
- package/bundle/{chunk-CGXWSNPA.js → chunk-NPIYWVOD.js} +4 -4
- package/bundle/{chunk-RZJN63GU.js → chunk-QP5ZWNIS.js} +2 -2
- package/bundle/{chunk-Z7KVHQHL.js → chunk-QXASAHIA.js} +2 -2
- package/bundle/{chunk-5ESXK62X.js → chunk-RNIZLSEA.js} +416 -389
- package/bundle/{chunk-HLE22FSH.js → chunk-SSG7IEP5.js} +2 -2
- package/bundle/{chunk-CZTMUBUI.js → chunk-UFL7IYCW.js} +4 -4
- package/bundle/{chunk-LDMXJIA6.js → chunk-UUP372DR.js} +44 -67
- package/bundle/{chunk-OOFW75PV.js → chunk-UYK5MZWE.js} +15 -11
- package/bundle/{chunk-CSSZRTKY.js → chunk-V2BZYLEP.js} +2 -2
- package/bundle/{chunk-4BW55BTL.js → chunk-VLFZIN6D.js} +4 -4
- package/bundle/{chunk-527E3D2J.js → chunk-YXFJVCOL.js} +5 -5
- package/bundle/{chunk-HXVKGS33.js → chunk-ZVOAMTUQ.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 +1 -1
- package/src/runs/background/async-execution.js +3 -3
- package/src/runs/background/async-resume.js +3 -2
- package/src/runs/background/retained-children.js +1 -1
- package/src/runs/foreground/subagent-executor.js +24 -51
- package/src/runs/shared/capability-ceiling.d.ts +2 -1
- package/src/runs/shared/capability-ceiling.js +3 -2
- package/src/runs/shared/parallel-handoff.d.ts +1 -1
- package/src/runs/shared/parallel-handoff.js +10 -6
- package/src/shared/types.d.ts +1 -1
- package/src/workflows/scripted-workflow.js +4 -1
- package/workflow-script-worker.cjs +2 -1
|
@@ -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,7 +2511,8 @@ ${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) });
|
|
@@ -2521,6 +2522,7 @@ ${script}
|
|
|
2521
2522
|
const keyNode = astNode(args[0]) ? args[0] : void 0;
|
|
2522
2523
|
const key = literalString(keyNode);
|
|
2523
2524
|
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) });
|
|
2525
|
+
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
2526
|
if (astNode(args[1])) {
|
|
2525
2527
|
errors.push(...validateStaticBaseRef(args[1], "runs.run"));
|
|
2526
2528
|
const message = definitelyNonJson(args[1]);
|
|
@@ -3365,6 +3367,7 @@ function parseWorkflowChild(value, label) {
|
|
|
3365
3367
|
...optionalString(input.phase, `${label}.phase`) ? { phase: input.phase } : {},
|
|
3366
3368
|
...input.completedAt !== void 0 ? { completedAt: timestamp(input.completedAt, `${label}.completedAt`) } : {},
|
|
3367
3369
|
...optionalString(input.sessionPath, `${label}.sessionPath`) ? { sessionPath: input.sessionPath } : {},
|
|
3370
|
+
...optionalString(input.acceptanceStatus, `${label}.acceptanceStatus`) ? { acceptanceStatus: input.acceptanceStatus } : {},
|
|
3368
3371
|
...heartbeat ? { heartbeat: {
|
|
3369
3372
|
updatedAt: timestamp(heartbeat.updatedAt, `${label}.heartbeat.updatedAt`),
|
|
3370
3373
|
...optionalString(heartbeat.status, `${label}.heartbeat.status`) ? { status: heartbeat.status } : {},
|
|
@@ -4005,103 +4008,425 @@ function createMissionWorkflowState(location, missionId, options = {}) {
|
|
|
4005
4008
|
}
|
|
4006
4009
|
|
|
4007
4010
|
// src/missions/actions.ts
|
|
4011
|
+
import * as fs11 from "node:fs";
|
|
4012
|
+
import * as path12 from "node:path";
|
|
4013
|
+
|
|
4014
|
+
// src/missions/lifecycle.ts
|
|
4008
4015
|
import * as fs10 from "node:fs";
|
|
4009
4016
|
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(", ")}`);
|
|
4017
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
4018
|
+
var MISSION_BINDING_FILE = "mission.json";
|
|
4019
|
+
function workflowObjective(params) {
|
|
4020
|
+
const objective = params.task?.trim() || params.tasks?.find((task) => task.task?.trim())?.task?.trim() || params.chain?.find((step) => step.task?.trim())?.task?.trim();
|
|
4021
|
+
if (objective) return objective;
|
|
4022
|
+
for (const step of params.chain ?? []) {
|
|
4023
|
+
const parallel = Array.isArray(step.parallel) ? step.parallel : step.parallel ? [step.parallel] : [];
|
|
4024
|
+
const task = parallel.find((child) => child.task?.trim())?.task?.trim();
|
|
4025
|
+
if (task) return task;
|
|
4028
4026
|
}
|
|
4029
|
-
return
|
|
4027
|
+
return void 0;
|
|
4030
4028
|
}
|
|
4031
|
-
function
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4029
|
+
function prepareMissionLaunch(input) {
|
|
4030
|
+
const hasMissionId = input.params.missionId !== void 0;
|
|
4031
|
+
if (hasMissionId && input.params.mission !== void 0) throw new Error("Use missionId or mission, not both");
|
|
4032
|
+
if (input.params.mission === false) return void 0;
|
|
4033
|
+
const objective = workflowObjective(input.params);
|
|
4034
|
+
const missionsEnabled = input.config?.enabled !== false;
|
|
4035
|
+
const shouldCreate = input.params.mission !== void 0 || missionsEnabled && objective !== void 0;
|
|
4036
|
+
if (!hasMissionId && !shouldCreate) return void 0;
|
|
4037
|
+
const location = resolveMissionStoreLocation({ projectRoot: input.projectRoot, ...input.config ? { config: input.config } : {} });
|
|
4038
|
+
if (hasMissionId) {
|
|
4039
|
+
const missionId = validateMissionId(input.params.missionId);
|
|
4040
|
+
readMission(location, missionId);
|
|
4041
|
+
updateMission(location, missionId, { status: "active" });
|
|
4042
|
+
return { missionId, location, autoCreated: false, announceInContent: true };
|
|
4036
4043
|
}
|
|
4037
|
-
|
|
4038
|
-
const
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4044
|
+
const mission = input.params.mission !== void 0 ? validateMissionLaunch(input.params.mission) : void 0;
|
|
4045
|
+
const promptDerivedObjective = mission?.objective ?? (mission ? mission.title : objective ? PROMPT_REDACTED : void 0);
|
|
4046
|
+
const title = mission?.title || PROMPT_REDACTED;
|
|
4047
|
+
const record = createMission(location, {
|
|
4048
|
+
title,
|
|
4049
|
+
objective: promptDerivedObjective || title,
|
|
4050
|
+
...mission?.goal === true ? { goal: true } : {},
|
|
4051
|
+
...mission?.budget ? { budget: mission.budget } : {},
|
|
4052
|
+
status: "active",
|
|
4053
|
+
...mission?.labels ? { labels: mission.labels } : {},
|
|
4054
|
+
...input.ownerSessionId ? { ownerSessionId: input.ownerSessionId } : {}
|
|
4055
|
+
}, /* @__PURE__ */ new Date(), input.config?.retainTerminal);
|
|
4056
|
+
return { missionId: record.id, location, autoCreated: input.params.mission === void 0, announceInContent: true };
|
|
4057
|
+
}
|
|
4058
|
+
function toolResultIsError(result) {
|
|
4059
|
+
return "isError" in result && result.isError === true;
|
|
4060
|
+
}
|
|
4061
|
+
function missionRunModeForResult(mode) {
|
|
4062
|
+
return mode === "management" ? "external" : mode;
|
|
4063
|
+
}
|
|
4064
|
+
function runStatusForResult(result) {
|
|
4065
|
+
if (result.details.asyncDir) return "active";
|
|
4066
|
+
if (result.details.results.some((child) => child.interrupted || child.detached)) return "paused";
|
|
4067
|
+
if (toolResultIsError(result) || result.details.results.some((child) => child.exitCode !== 0)) return "failed";
|
|
4068
|
+
return "completed";
|
|
4069
|
+
}
|
|
4070
|
+
var UNACCEPTED_ACCEPTANCE_STATUSES = /* @__PURE__ */ new Set(["pending", "claimed", "review-required", "rejected"]);
|
|
4071
|
+
function missionAcceptanceStatus(results) {
|
|
4072
|
+
const statuses = (Array.isArray(results) ? results : []).map((result) => result?.acceptance?.status).filter((status) => typeof status === "string" && status.trim() !== "");
|
|
4073
|
+
return statuses.find((status) => UNACCEPTED_ACCEPTANCE_STATUSES.has(status)) ?? statuses[0];
|
|
4074
|
+
}
|
|
4075
|
+
function finishedMissionStatus(record, autoCreated, runAcceptance) {
|
|
4076
|
+
if (autoCreated) return "completed";
|
|
4077
|
+
if (runAcceptance.some((run) => UNACCEPTED_ACCEPTANCE_STATUSES.has(run.status))) return "waiting";
|
|
4078
|
+
const reported = new Set(runAcceptance.map((run) => run.runId));
|
|
4079
|
+
return record.workflowChildren.some((child) => !reported.has(child.runId) && UNACCEPTED_ACCEPTANCE_STATUSES.has(child.acceptanceStatus)) ? "waiting" : "completed";
|
|
4080
|
+
}
|
|
4081
|
+
function missionStatusForRun(record, runId, runStatus, acceptanceStatus, autoCreated) {
|
|
4082
|
+
if (record.status === "completed" || record.status === "failed" || record.status === "cancelled") return record.status;
|
|
4083
|
+
if (runStatus === "active" || runStatus === "queued" || runStatus === "running") return "active";
|
|
4084
|
+
if (record.goal) return "active";
|
|
4085
|
+
if (runStatus === "paused") return "waiting";
|
|
4086
|
+
const otherActive = record.runs.some((run) => run.runId !== runId && (run.status === "active" || run.status === "queued" || run.status === "running"));
|
|
4087
|
+
if (otherActive) return "active";
|
|
4088
|
+
if (runStatus === "completed" || runStatus === "complete") return finishedMissionStatus(record, autoCreated, [{ runId, status: acceptanceStatus }]);
|
|
4089
|
+
if (runStatus === "stopped" || runStatus === "rejected" || runStatus === "cancelled") return "cancelled";
|
|
4090
|
+
return "failed";
|
|
4091
|
+
}
|
|
4092
|
+
function usageForResult(result) {
|
|
4093
|
+
const tokens = result.details.results.reduce((total, child) => total + child.usage.input + child.usage.output, 0);
|
|
4094
|
+
return tokens > 0 ? { tokens } : void 0;
|
|
4095
|
+
}
|
|
4096
|
+
function usageFromUnknown(value) {
|
|
4097
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4098
|
+
const total = value.total;
|
|
4099
|
+
return Number.isSafeInteger(total) && total >= 0 ? { tokens: total } : void 0;
|
|
4100
|
+
}
|
|
4101
|
+
function artifactsForResult(result) {
|
|
4102
|
+
const artifacts = [];
|
|
4103
|
+
if (result.details.asyncDir) {
|
|
4104
|
+
artifacts.push(
|
|
4105
|
+
{ kind: "status", path: path11.join(result.details.asyncDir, "status.json") },
|
|
4106
|
+
{ kind: "other", path: path11.join(result.details.asyncDir, "events.jsonl"), description: "Lifecycle events" }
|
|
4107
|
+
);
|
|
4045
4108
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4109
|
+
for (const child of result.details.results) {
|
|
4110
|
+
if (child.artifactPaths?.outputPath) artifacts.push({ kind: "output", path: child.artifactPaths.outputPath });
|
|
4111
|
+
if (child.savedOutputPath) artifacts.push({ kind: "output", path: child.savedOutputPath });
|
|
4112
|
+
if (child.transcriptPath) artifacts.push({ kind: "other", path: child.transcriptPath, description: "Child transcript" });
|
|
4113
|
+
if (child.structuredOutputPath) artifacts.push({ kind: "output", path: child.structuredOutputPath, description: "Structured output" });
|
|
4049
4114
|
}
|
|
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
|
-
};
|
|
4115
|
+
if (result.details.parallelHandoff?.path) artifacts.push({ kind: "manifest", path: result.details.parallelHandoff.path });
|
|
4116
|
+
return artifacts;
|
|
4057
4117
|
}
|
|
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`);
|
|
4118
|
+
function firstText(result) {
|
|
4119
|
+
const text = result.content.find((item) => item.type === "text")?.text.trim();
|
|
4120
|
+
if (!text) return void 0;
|
|
4121
|
+
return text.length > 2e3 ? `${text.slice(0, 1997)}...` : text;
|
|
4122
|
+
}
|
|
4123
|
+
function persistedBinding(binding) {
|
|
4065
4124
|
return {
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4125
|
+
schemaVersion: 1,
|
|
4126
|
+
missionId: binding.missionId,
|
|
4127
|
+
projectRoot: binding.location.projectRoot,
|
|
4128
|
+
missionDir: binding.location.missionDir,
|
|
4129
|
+
globalIndexDir: binding.location.globalIndexDir,
|
|
4130
|
+
writeGlobalIndex: binding.location.writeGlobalIndex,
|
|
4131
|
+
...binding.location.retainTerminal !== void 0 ? { retainTerminal: binding.location.retainTerminal } : {},
|
|
4132
|
+
autoCreated: binding.autoCreated
|
|
4069
4133
|
};
|
|
4070
4134
|
}
|
|
4071
|
-
function
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4135
|
+
function writeMissionAsyncBinding(asyncDir, binding) {
|
|
4136
|
+
writePrivateAtomicJson(path11.join(asyncDir, MISSION_BINDING_FILE), persistedBinding(binding));
|
|
4137
|
+
}
|
|
4138
|
+
function attachMissionToLaunchResult(input) {
|
|
4139
|
+
const runId = input.result.details.runId ?? input.result.details.asyncId;
|
|
4140
|
+
if (!runId) {
|
|
4141
|
+
const current2 = readMission(input.binding.location, input.binding.missionId);
|
|
4142
|
+
const activeRunExists = current2.runs.some((run2) => run2.status === "active" || run2.status === "queued" || run2.status === "running");
|
|
4143
|
+
const mission2 = toolResultIsError(input.result) ? updateMission(input.binding.location, input.binding.missionId, {
|
|
4144
|
+
status: activeRunExists ? "active" : "failed",
|
|
4145
|
+
...firstText(input.result) ? { summary: firstText(input.result) } : {}
|
|
4146
|
+
}) : current2;
|
|
4147
|
+
return {
|
|
4148
|
+
...input.result,
|
|
4149
|
+
details: {
|
|
4150
|
+
...input.result.details,
|
|
4151
|
+
missionId: input.binding.missionId,
|
|
4152
|
+
missionPath: missionRecordPath(input.binding.location, input.binding.missionId),
|
|
4153
|
+
mission: mission2
|
|
4154
|
+
}
|
|
4155
|
+
};
|
|
4083
4156
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4157
|
+
const runStatus = runStatusForResult(input.result);
|
|
4158
|
+
const current = readMission(input.binding.location, input.binding.missionId);
|
|
4159
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4160
|
+
const usage = usageForResult(input.result);
|
|
4161
|
+
const run = {
|
|
4162
|
+
runId,
|
|
4163
|
+
mode: missionRunModeForResult(input.result.details.mode),
|
|
4164
|
+
status: runStatus,
|
|
4165
|
+
startedAt,
|
|
4166
|
+
...input.result.details.asyncDir ? { asyncDir: input.result.details.asyncDir } : {},
|
|
4167
|
+
...input.result.details.results.length === 1 && input.result.details.results[0]?.agent ? { agent: input.result.details.results[0].agent } : {},
|
|
4168
|
+
...runStatus !== "active" ? { completedAt: startedAt } : {},
|
|
4169
|
+
...usage ? { usage } : {}
|
|
4170
|
+
};
|
|
4171
|
+
let mission = updateMission(input.binding.location, input.binding.missionId, {
|
|
4172
|
+
status: missionStatusForRun(current, runId, runStatus, missionAcceptanceStatus(input.result.details.results), input.binding.autoCreated),
|
|
4173
|
+
addRuns: [run],
|
|
4174
|
+
addArtifacts: artifactsForResult(input.result),
|
|
4175
|
+
...firstText(input.result) && runStatus !== "active" ? { summary: firstText(input.result) } : {},
|
|
4176
|
+
...input.result.details.results.length === 1 && input.result.details.results[0]?.acceptance ? { acceptance: input.result.details.results[0].acceptance } : {}
|
|
4177
|
+
});
|
|
4178
|
+
if (input.result.details.asyncDir) {
|
|
4179
|
+
writeMissionAsyncBinding(input.result.details.asyncDir, input.binding);
|
|
4180
|
+
const statusPath = path11.join(input.result.details.asyncDir, "status.json");
|
|
4181
|
+
if (fs10.existsSync(statusPath)) {
|
|
4182
|
+
try {
|
|
4183
|
+
const status = JSON.parse(fs10.readFileSync(statusPath, "utf-8"));
|
|
4184
|
+
if (typeof status.state === "string" && !["queued", "running"].includes(status.state)) {
|
|
4185
|
+
mission = syncMissionFromAsyncCompletion({
|
|
4186
|
+
runId,
|
|
4187
|
+
asyncDir: input.result.details.asyncDir,
|
|
4188
|
+
mode: input.result.details.mode,
|
|
4189
|
+
state: status.state,
|
|
4190
|
+
// A run that finished before this launch result was attached keeps its acceptance in the status steps.
|
|
4191
|
+
...Array.isArray(status.steps) ? { results: status.steps.map((step) => ({ acceptance: step?.acceptance })) } : {},
|
|
4192
|
+
...firstText(input.result) ? { summary: firstText(input.result) } : {}
|
|
4193
|
+
}) ?? mission;
|
|
4194
|
+
}
|
|
4195
|
+
} catch (error) {
|
|
4196
|
+
throw new Error(`Failed to reconcile mission from terminal async status '${statusPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4088
4199
|
}
|
|
4089
|
-
|
|
4200
|
+
const lastTextIndex = input.result.content.findLastIndex((item) => item.type === "text");
|
|
4201
|
+
const hasStructuredOutput = input.result.details.results.some((child) => child.structuredOutputPath !== void 0);
|
|
4202
|
+
const textIsJson = lastTextIndex >= 0 && (() => {
|
|
4203
|
+
try {
|
|
4204
|
+
JSON.parse(input.result.content[lastTextIndex].text);
|
|
4205
|
+
return true;
|
|
4206
|
+
} catch {
|
|
4207
|
+
return false;
|
|
4208
|
+
}
|
|
4209
|
+
})();
|
|
4090
4210
|
return {
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4211
|
+
...input.result,
|
|
4212
|
+
content: input.binding.announceInContent === true && lastTextIndex >= 0 && !hasStructuredOutput && !textIsJson ? input.result.content.map((item, index) => index === lastTextIndex && item.type === "text" ? { ...item, text: `${item.text}
|
|
4213
|
+
Mission: ${mission.id} (${mission.status})` } : item) : input.result.content,
|
|
4214
|
+
details: {
|
|
4215
|
+
...input.result.details,
|
|
4216
|
+
missionId: mission.id,
|
|
4217
|
+
missionPath: missionRecordPath(input.binding.location, mission.id),
|
|
4218
|
+
mission
|
|
4219
|
+
}
|
|
4096
4220
|
};
|
|
4097
4221
|
}
|
|
4098
|
-
function
|
|
4099
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(
|
|
4222
|
+
function parsePersistedBinding(value, source) {
|
|
4223
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${source} must be an object`);
|
|
4100
4224
|
const input = value;
|
|
4101
|
-
|
|
4102
|
-
|
|
4225
|
+
if (input.schemaVersion !== 1) throw new Error(`${source}.schemaVersion must be 1`);
|
|
4226
|
+
for (const field of ["projectRoot", "missionDir", "globalIndexDir"]) {
|
|
4227
|
+
if (typeof input[field] !== "string" || !input[field].trim()) throw new Error(`${source}.${field} must be a non-empty string`);
|
|
4103
4228
|
}
|
|
4104
|
-
|
|
4229
|
+
if (typeof input.writeGlobalIndex !== "boolean") throw new Error(`${source}.writeGlobalIndex must be boolean`);
|
|
4230
|
+
if (input.retainTerminal !== void 0 && (!Number.isInteger(input.retainTerminal) || input.retainTerminal < 1)) throw new Error(`${source}.retainTerminal must be a positive integer`);
|
|
4231
|
+
if (input.autoCreated !== void 0 && typeof input.autoCreated !== "boolean") throw new Error(`${source}.autoCreated must be boolean`);
|
|
4232
|
+
return {
|
|
4233
|
+
missionId: validateMissionId(input.missionId, `${source}.missionId`),
|
|
4234
|
+
// Bindings saved before this field existed belong to runs that completed their mission without an acceptance check.
|
|
4235
|
+
autoCreated: input.autoCreated !== false,
|
|
4236
|
+
location: {
|
|
4237
|
+
projectRoot: input.projectRoot,
|
|
4238
|
+
missionDir: input.missionDir,
|
|
4239
|
+
globalIndexDir: input.globalIndexDir,
|
|
4240
|
+
writeGlobalIndex: input.writeGlobalIndex,
|
|
4241
|
+
...input.retainTerminal !== void 0 ? { retainTerminal: input.retainTerminal } : {}
|
|
4242
|
+
}
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
function readMissionBinding(asyncDir) {
|
|
4246
|
+
const bindingPath = path11.join(asyncDir, MISSION_BINDING_FILE);
|
|
4247
|
+
if (!fs10.existsSync(bindingPath)) return void 0;
|
|
4248
|
+
const raw = fs10.readFileSync(bindingPath, "utf-8");
|
|
4249
|
+
try {
|
|
4250
|
+
return parsePersistedBinding(JSON.parse(raw), bindingPath);
|
|
4251
|
+
} catch (error) {
|
|
4252
|
+
throw new Error(`Invalid mission binding file '${bindingPath}': ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
function syncMissionFromAsyncCompletion(value) {
|
|
4256
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4257
|
+
const event = value;
|
|
4258
|
+
if (typeof event.asyncDir !== "string" || !event.asyncDir.trim()) return void 0;
|
|
4259
|
+
const binding = readMissionBinding(event.asyncDir);
|
|
4260
|
+
if (!binding) return void 0;
|
|
4261
|
+
const runId = typeof event.runId === "string" ? event.runId : typeof event.id === "string" ? event.id : void 0;
|
|
4262
|
+
if (!runId) throw new Error("Async mission completion is missing runId");
|
|
4263
|
+
const runStatus = typeof event.state === "string" ? event.state : event.success === true ? "completed" : "failed";
|
|
4264
|
+
let current;
|
|
4265
|
+
try {
|
|
4266
|
+
current = readMission(binding.location, binding.missionId);
|
|
4267
|
+
} catch (error) {
|
|
4268
|
+
if (!(error instanceof MissionNotFoundError)) throw error;
|
|
4269
|
+
const reason = "mission-record-missing";
|
|
4270
|
+
const markerId = createHash4("sha256").update(JSON.stringify([runId, binding.missionId, reason])).digest("hex");
|
|
4271
|
+
const markerPath3 = path11.join(event.asyncDir, `.mission-sync-skipped-${markerId}.json`);
|
|
4272
|
+
try {
|
|
4273
|
+
fs10.writeFileSync(markerPath3, `${JSON.stringify({ runId, missionId: binding.missionId, reason })}
|
|
4274
|
+
`, { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
4275
|
+
fs10.appendFileSync(path11.join(event.asyncDir, "events.jsonl"), `${JSON.stringify({
|
|
4276
|
+
type: "subagent.mission.sync.skipped",
|
|
4277
|
+
ts: Date.now(),
|
|
4278
|
+
runId,
|
|
4279
|
+
missionId: binding.missionId,
|
|
4280
|
+
reason,
|
|
4281
|
+
missionPath: missionRecordPath(binding.location, binding.missionId)
|
|
4282
|
+
})}
|
|
4283
|
+
`, "utf-8");
|
|
4284
|
+
} catch {
|
|
4285
|
+
}
|
|
4286
|
+
return void 0;
|
|
4287
|
+
}
|
|
4288
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4289
|
+
const artifacts = [
|
|
4290
|
+
{ kind: "status", path: path11.join(event.asyncDir, "status.json") },
|
|
4291
|
+
{ kind: "other", path: path11.join(event.asyncDir, "events.jsonl"), description: "Lifecycle events" }
|
|
4292
|
+
];
|
|
4293
|
+
if (event.parallelHandoff && typeof event.parallelHandoff === "object" && typeof event.parallelHandoff.path === "string") {
|
|
4294
|
+
artifacts.push({ kind: "manifest", path: event.parallelHandoff.path });
|
|
4295
|
+
}
|
|
4296
|
+
if (Array.isArray(event.results)) {
|
|
4297
|
+
for (const result of event.results) {
|
|
4298
|
+
if (!result || typeof result !== "object") continue;
|
|
4299
|
+
const child = result;
|
|
4300
|
+
if (typeof child.artifactPath === "string") artifacts.push({ kind: "output", path: child.artifactPath });
|
|
4301
|
+
if (child.artifactPaths && typeof child.artifactPaths === "object" && typeof child.artifactPaths.outputPath === "string") {
|
|
4302
|
+
artifacts.push({ kind: "output", path: child.artifactPaths.outputPath });
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
const summary = typeof event.summary === "string" && event.summary.trim() ? event.summary.slice(0, 2e3) : void 0;
|
|
4307
|
+
const usage = usageFromUnknown(event.totalTokens) ?? (Array.isArray(event.results) ? { tokens: event.results.reduce((total, result) => {
|
|
4308
|
+
if (!result || typeof result !== "object") return total;
|
|
4309
|
+
return total + (usageFromUnknown(result.tokens)?.tokens ?? 0);
|
|
4310
|
+
}, 0) } : void 0);
|
|
4311
|
+
const workflowRunId = typeof event.parentWorkflowRunId === "string" && event.parentWorkflowRunId.trim() ? event.parentWorkflowRunId.trim() : void 0;
|
|
4312
|
+
const workflowKey = typeof event.workflowKey === "string" && event.workflowKey.trim() ? event.workflowKey.trim() : void 0;
|
|
4313
|
+
const workflowChildStatus = runStatus === "complete" || runStatus === "completed" || event.success === true ? "completed" : runStatus === "paused" ? "paused" : runStatus === "stopped" ? "stopped" : "failed";
|
|
4314
|
+
const workflowChildTerminal = !["running", "queued", "active", "paused"].includes(workflowChildStatus);
|
|
4315
|
+
const acceptanceStatus = missionAcceptanceStatus(event.results);
|
|
4316
|
+
return updateMission(binding.location, binding.missionId, {
|
|
4317
|
+
status: missionStatusForRun(current, runId, runStatus, acceptanceStatus, binding.autoCreated),
|
|
4318
|
+
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 } : {} }],
|
|
4319
|
+
addArtifacts: artifacts,
|
|
4320
|
+
...workflowRunId && workflowKey ? { upsertWorkflowChildren: [{
|
|
4321
|
+
workflowRunId,
|
|
4322
|
+
key: workflowKey,
|
|
4323
|
+
runId,
|
|
4324
|
+
status: workflowChildStatus,
|
|
4325
|
+
artifactPaths: artifacts.map((artifact) => artifact.path),
|
|
4326
|
+
...workflowChildTerminal ? { completedAt } : {},
|
|
4327
|
+
...acceptanceStatus ? { acceptanceStatus } : {},
|
|
4328
|
+
heartbeat: { status: workflowChildStatus, ...summary ? { message: summary } : {} }
|
|
4329
|
+
}] } : {},
|
|
4330
|
+
...summary ? { summary } : {}
|
|
4331
|
+
});
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
// src/missions/actions.ts
|
|
4335
|
+
var MISSION_ACTIONS = [
|
|
4336
|
+
"mission.create",
|
|
4337
|
+
"mission.list",
|
|
4338
|
+
"mission.show",
|
|
4339
|
+
"mission.update",
|
|
4340
|
+
"mission.resolve-decision",
|
|
4341
|
+
"mission.attach-run",
|
|
4342
|
+
"mission.close"
|
|
4343
|
+
];
|
|
4344
|
+
function textResult(text, details) {
|
|
4345
|
+
return { content: [{ type: "text", text }], details };
|
|
4346
|
+
}
|
|
4347
|
+
function requireMissionId(params) {
|
|
4348
|
+
return validateMissionId(params.missionId, "missionId");
|
|
4349
|
+
}
|
|
4350
|
+
function validateStatus(value, label) {
|
|
4351
|
+
if (typeof value !== "string" || !MISSION_STATUSES.includes(value)) {
|
|
4352
|
+
throw new Error(`${label} must be one of ${MISSION_STATUSES.join(", ")}`);
|
|
4353
|
+
}
|
|
4354
|
+
return value;
|
|
4355
|
+
}
|
|
4356
|
+
function validateMissionLaunch(value) {
|
|
4357
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("mission must be an object");
|
|
4358
|
+
const input = value;
|
|
4359
|
+
for (const key of Object.keys(input)) {
|
|
4360
|
+
if (key !== "title" && key !== "summary" && key !== "objective" && key !== "goal" && key !== "budget" && key !== "labels") throw new Error(`mission.${key} is unknown`);
|
|
4361
|
+
}
|
|
4362
|
+
if (input.title !== void 0 && input.summary !== void 0) throw new Error("mission.title and mission.summary cannot both be set");
|
|
4363
|
+
const title = input.title ?? input.summary;
|
|
4364
|
+
if (typeof title !== "string" || !title.trim()) throw new Error("mission.title or mission.summary must be a non-empty string");
|
|
4365
|
+
if (input.objective !== void 0 && (typeof input.objective !== "string" || !input.objective.trim())) throw new Error("mission.objective must be a non-empty string");
|
|
4366
|
+
if (input.goal !== void 0 && input.goal !== true) throw new Error("mission.goal must be true when supplied");
|
|
4367
|
+
const budget = input.budget;
|
|
4368
|
+
if (budget !== void 0 && (!budget || typeof budget !== "object" || Array.isArray(budget) || !Number.isSafeInteger(budget.tokens) || budget.tokens < 1)) {
|
|
4369
|
+
throw new Error("mission.budget.tokens must be a positive integer");
|
|
4370
|
+
}
|
|
4371
|
+
if (input.goal === true && budget === void 0) throw new Error("mission.budget is required when mission.goal is true");
|
|
4372
|
+
if (input.labels !== void 0 && (!Array.isArray(input.labels) || input.labels.some((label) => typeof label !== "string" || !label.trim()))) {
|
|
4373
|
+
throw new Error("mission.labels must contain only non-empty strings");
|
|
4374
|
+
}
|
|
4375
|
+
return {
|
|
4376
|
+
title: title.trim(),
|
|
4377
|
+
...typeof input.objective === "string" ? { objective: input.objective.trim() } : {},
|
|
4378
|
+
...input.goal === true ? { goal: true } : {},
|
|
4379
|
+
...budget !== void 0 ? { budget: { tokens: budget.tokens } } : {},
|
|
4380
|
+
...input.labels !== void 0 ? { labels: input.labels.map((label) => label.trim()) } : {}
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
function validateArtifact(value, index) {
|
|
4384
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`missionUpdate.artifacts[${index}] must be an object`);
|
|
4385
|
+
const input = value;
|
|
4386
|
+
const kinds = ["status", "output", "patch", "manifest", "review", "note", "other"];
|
|
4387
|
+
if (typeof input.kind !== "string" || !kinds.includes(input.kind)) throw new Error(`missionUpdate.artifacts[${index}].kind is invalid`);
|
|
4388
|
+
if (typeof input.path !== "string" || !input.path.trim()) throw new Error(`missionUpdate.artifacts[${index}].path must be a non-empty string`);
|
|
4389
|
+
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`);
|
|
4390
|
+
return {
|
|
4391
|
+
kind: input.kind,
|
|
4392
|
+
path: input.path,
|
|
4393
|
+
...typeof input.description === "string" ? { description: input.description } : {}
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
function validateReceipt(value, index) {
|
|
4397
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`missionUpdate.receipts[${index}] must be an object`);
|
|
4398
|
+
const input = value;
|
|
4399
|
+
for (const key of Object.keys(input)) {
|
|
4400
|
+
if (!["kind", "status", "title", "url", "description"].includes(key)) throw new Error(`missionUpdate.receipts[${index}].${key} is unknown`);
|
|
4401
|
+
}
|
|
4402
|
+
const kinds = ["pull_request", "ci", "deployment", "release"];
|
|
4403
|
+
const statuses = ["pending", "ready", "succeeded", "failed"];
|
|
4404
|
+
if (typeof input.kind !== "string" || !kinds.includes(input.kind)) throw new Error(`missionUpdate.receipts[${index}].kind is invalid`);
|
|
4405
|
+
if (typeof input.status !== "string" || !statuses.includes(input.status)) throw new Error(`missionUpdate.receipts[${index}].status is invalid`);
|
|
4406
|
+
for (const field of ["title", "url"]) {
|
|
4407
|
+
if (typeof input[field] !== "string" || !input[field].trim()) throw new Error(`missionUpdate.receipts[${index}].${field} must be a non-empty string`);
|
|
4408
|
+
}
|
|
4409
|
+
try {
|
|
4410
|
+
new URL(input.url);
|
|
4411
|
+
} catch {
|
|
4412
|
+
throw new Error(`missionUpdate.receipts[${index}].url must be an absolute URL`);
|
|
4413
|
+
}
|
|
4414
|
+
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`);
|
|
4415
|
+
return {
|
|
4416
|
+
kind: input.kind,
|
|
4417
|
+
status: input.status,
|
|
4418
|
+
title: input.title.trim(),
|
|
4419
|
+
url: input.url.trim(),
|
|
4420
|
+
...typeof input.description === "string" ? { description: input.description.trim() } : {}
|
|
4421
|
+
};
|
|
4422
|
+
}
|
|
4423
|
+
function validateMissionUpdate(value) {
|
|
4424
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("missionUpdate must be an object");
|
|
4425
|
+
const input = value;
|
|
4426
|
+
for (const key of Object.keys(input)) {
|
|
4427
|
+
if (!["title", "objective", "goal", "budget", "status", "summary", "labels", "artifacts", "receipts", "decisions"].includes(key)) throw new Error(`missionUpdate.${key} is unknown`);
|
|
4428
|
+
}
|
|
4429
|
+
const update = {};
|
|
4105
4430
|
for (const field of ["title", "objective", "summary"]) {
|
|
4106
4431
|
const candidate = input[field];
|
|
4107
4432
|
if (candidate === void 0) continue;
|
|
@@ -4156,13 +4481,14 @@ function validateMissionUpdate(value) {
|
|
|
4156
4481
|
}
|
|
4157
4482
|
function refreshLinkedRunStatus(location, record) {
|
|
4158
4483
|
const warnings = [];
|
|
4484
|
+
const runAcceptance = [];
|
|
4159
4485
|
const updates = record.runs.flatMap((run) => {
|
|
4160
4486
|
if (!run.asyncDir) return [];
|
|
4161
|
-
const statusPath =
|
|
4162
|
-
if (!
|
|
4487
|
+
const statusPath = path12.join(run.asyncDir, "status.json");
|
|
4488
|
+
if (!fs11.existsSync(statusPath)) return [];
|
|
4163
4489
|
let status2;
|
|
4164
4490
|
try {
|
|
4165
|
-
status2 = JSON.parse(
|
|
4491
|
+
status2 = JSON.parse(fs11.readFileSync(statusPath, "utf-8"));
|
|
4166
4492
|
} catch (error) {
|
|
4167
4493
|
warnings.push(`Failed to read linked run status '${statusPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4168
4494
|
return [];
|
|
@@ -4176,6 +4502,7 @@ function refreshLinkedRunStatus(location, record) {
|
|
|
4176
4502
|
warnings.push(`Linked run status '${statusPath}' is missing state`);
|
|
4177
4503
|
return [];
|
|
4178
4504
|
}
|
|
4505
|
+
runAcceptance.push({ runId: run.runId, status: missionAcceptanceStatus(status2.steps) });
|
|
4179
4506
|
if (state === run.status) return [];
|
|
4180
4507
|
return [{
|
|
4181
4508
|
...run,
|
|
@@ -4186,7 +4513,7 @@ function refreshLinkedRunStatus(location, record) {
|
|
|
4186
4513
|
if (updates.length === 0) return { record, warnings };
|
|
4187
4514
|
const merged = record.runs.map((run) => updates.find((update) => update.runId === run.runId && update.childIndex === run.childIndex) ?? run);
|
|
4188
4515
|
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") ?
|
|
4516
|
+
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
4517
|
return { record: updateMission(location, record.id, { status, addRuns: updates }), warnings };
|
|
4191
4518
|
}
|
|
4192
4519
|
function formatMission(record) {
|
|
@@ -4210,7 +4537,8 @@ function formatMission(record) {
|
|
|
4210
4537
|
const identity = child.runId ? `${child.key} (${child.runId})` : child.key;
|
|
4211
4538
|
const heartbeat = child.heartbeat ? `; heartbeat ${child.heartbeat.status ?? child.status}${child.heartbeat.phase ? `/${child.heartbeat.phase}` : ""} at ${child.heartbeat.updatedAt}` : "";
|
|
4212
4539
|
const recovery = child.sessionPath ? `; session ${child.sessionPath}` : child.artifactPaths.length ? `; artifacts ${child.artifactPaths.length}` : "";
|
|
4213
|
-
|
|
4540
|
+
const acceptance = child.acceptanceStatus ? `; acceptance ${child.acceptanceStatus}` : "";
|
|
4541
|
+
lines.push(` ${identity}: ${child.status}${child.agent ? ` \u2014 ${child.agent}` : ""}${child.phase ? ` [${child.phase}]` : ""}${acceptance}; updated ${child.updatedAt}${heartbeat}${recovery}`);
|
|
4214
4542
|
}
|
|
4215
4543
|
}
|
|
4216
4544
|
if (record.decisions.length) {
|
|
@@ -4331,308 +4659,6 @@ ${formatMission(record2)}`, { mode: "management", results: [], missionId: record
|
|
|
4331
4659
|
}
|
|
4332
4660
|
}
|
|
4333
4661
|
|
|
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
4662
|
// src/runs/background/result-files.ts
|
|
4637
4663
|
import { createHash as createHash5 } from "node:crypto";
|
|
4638
4664
|
import * as fs12 from "node:fs";
|
|
@@ -7229,6 +7255,7 @@ export {
|
|
|
7229
7255
|
MISSION_ACTIONS,
|
|
7230
7256
|
handleMissionAction,
|
|
7231
7257
|
prepareMissionLaunch,
|
|
7258
|
+
missionAcceptanceStatus,
|
|
7232
7259
|
writeMissionAsyncBinding,
|
|
7233
7260
|
attachMissionToLaunchResult,
|
|
7234
7261
|
readMissionBinding,
|