pi-crew 0.9.66 → 0.9.67
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 +61 -0
- package/README.md +2 -3
- package/agents/executor.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/dist/index.mjs +4441 -4091
- package/package.json +7 -4
- package/scripts/analyze-run.mjs +1333 -0
- package/scripts/resource-sampler.mjs +482 -0
- package/skills/real-test-pi-crew/SKILL.md +10 -9
- package/src/config/role-tools.ts +2 -2
- package/src/extension/knowledge-injection.ts +17 -0
- package/src/extension/pi-api.ts +0 -16
- package/src/extension/team-tool/api/agent-control.ts +358 -0
- package/src/extension/team-tool/api/handler-context.ts +57 -0
- package/src/extension/team-tool/api/heartbeat.ts +75 -0
- package/src/extension/team-tool/api/mailbox.ts +242 -0
- package/src/extension/team-tool/api/plan-approval.ts +190 -0
- package/src/extension/team-tool/api/read.ts +443 -0
- package/src/extension/team-tool/api/task-claims.ts +207 -0
- package/src/extension/team-tool/api.ts +56 -1301
- package/src/extension/team-tool/cancel.ts +15 -4
- package/src/extension/team-tool/goal-wrap.ts +2 -2
- package/src/extension/team-tool/goal.ts +4 -4
- package/src/extension/team-tool/lifecycle-actions.ts +5 -5
- package/src/extension/team-tool/parallel-dispatch.ts +2 -2
- package/src/extension/team-tool/respond.ts +11 -3
- package/src/extension/team-tool/run-intent.ts +357 -0
- package/src/extension/team-tool/run.ts +8 -285
- package/src/extension/team-tool/status.ts +56 -21
- package/src/extension/team-tool.ts +10 -8
- package/src/prompt/scratchpad-lifecycle.ts +80 -5
- package/src/runtime/child-pi/child-pi-spawn.ts +13 -7
- package/src/runtime/child-pi/child-pi.ts +22 -144
- package/src/runtime/child-pi/mock-fixtures.ts +171 -0
- package/src/runtime/crew-agent-records.ts +18 -1
- package/src/runtime/scratchpad/README.md +6 -0
- package/src/runtime/scratchpad/guest.ts +54 -5
- package/src/runtime/scratchpad/snapshot-hmac.ts +7 -1
- package/src/runtime/supervisor-contact.ts +0 -16
- package/src/runtime/task-runner/child-executor.ts +1 -0
- package/src/runtime/task-runner/state-helpers.ts +9 -1
- package/src/runtime/team-runner.ts +39 -2
- package/src/state/contracts.ts +3 -0
- package/src/state/event-log/event-log.ts +19 -27
- package/src/state/gitignore-manager.ts +61 -7
- package/src/utils/glob-match.ts +29 -0
- package/types/dwf.d.ts +1 -1
- package/src/types/new-api-types.ts +0 -35
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracted `api` operation handlers — mailbox group (H3 phase 2).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `src/extension/team-tool/api.ts` on 2026-08-10. Behaviour is
|
|
5
|
+
* byte-identical to the inline `if` blocks they replace.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { withRunLockSync } from "../../../state/coordination/locks.ts";
|
|
9
|
+
import {
|
|
10
|
+
acknowledgeMailboxMessage,
|
|
11
|
+
appendMailboxMessage,
|
|
12
|
+
type MailboxDirection,
|
|
13
|
+
type MailboxMessageKind,
|
|
14
|
+
readDeliveryState,
|
|
15
|
+
readMailbox,
|
|
16
|
+
readMailboxMessage,
|
|
17
|
+
validateMailbox,
|
|
18
|
+
} from "../../../state/coordination/mailbox.ts";
|
|
19
|
+
import { appendEvent } from "../../../state/event-log/event-log.ts";
|
|
20
|
+
import type { ApiOperationHandler } from "./handler-context.ts";
|
|
21
|
+
|
|
22
|
+
export const handleReadMailbox: ApiOperationHandler = (hctx) => {
|
|
23
|
+
const { cfg, loaded, result } = hctx;
|
|
24
|
+
const direction = cfg.direction === "inbox" || cfg.direction === "outbox" ? (cfg.direction as MailboxDirection) : undefined;
|
|
25
|
+
const taskId = typeof cfg.taskId === "string" ? cfg.taskId : undefined;
|
|
26
|
+
const kind =
|
|
27
|
+
typeof cfg.kind === "string" && ["message", "steer", "follow-up", "response", "group_join"].includes(cfg.kind)
|
|
28
|
+
? (cfg.kind as MailboxMessageKind)
|
|
29
|
+
: undefined;
|
|
30
|
+
if (taskId && !loaded.tasks.some((task) => task.id === taskId))
|
|
31
|
+
return result(
|
|
32
|
+
`API read-mailbox taskId '${taskId}' does not match a run task.`,
|
|
33
|
+
{
|
|
34
|
+
action: "api",
|
|
35
|
+
status: "error",
|
|
36
|
+
runId: loaded.manifest.runId,
|
|
37
|
+
},
|
|
38
|
+
true,
|
|
39
|
+
);
|
|
40
|
+
try {
|
|
41
|
+
return result(JSON.stringify(readMailbox(loaded.manifest, direction, taskId, kind), null, 2), {
|
|
42
|
+
action: "api",
|
|
43
|
+
status: "ok",
|
|
44
|
+
runId: loaded.manifest.runId,
|
|
45
|
+
artifactsRoot: loaded.manifest.artifactsRoot,
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
return result(
|
|
50
|
+
message,
|
|
51
|
+
{
|
|
52
|
+
action: "api",
|
|
53
|
+
status: "error",
|
|
54
|
+
runId: loaded.manifest.runId,
|
|
55
|
+
},
|
|
56
|
+
true,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const handleValidateMailbox: ApiOperationHandler = (hctx) => {
|
|
62
|
+
const { cfg, loaded, result } = hctx;
|
|
63
|
+
const report = validateMailbox(loaded.manifest, {
|
|
64
|
+
repair: cfg.repair === true,
|
|
65
|
+
});
|
|
66
|
+
return result(
|
|
67
|
+
JSON.stringify(report, null, 2),
|
|
68
|
+
{
|
|
69
|
+
action: "api",
|
|
70
|
+
status: report.issues.some((issue) => issue.level === "error") && cfg.repair !== true ? "error" : "ok",
|
|
71
|
+
runId: loaded.manifest.runId,
|
|
72
|
+
artifactsRoot: loaded.manifest.artifactsRoot,
|
|
73
|
+
},
|
|
74
|
+
report.issues.some((issue) => issue.level === "error") && cfg.repair !== true,
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const handleReadDelivery: ApiOperationHandler = (hctx) => {
|
|
79
|
+
const { loaded, result } = hctx;
|
|
80
|
+
return result(JSON.stringify(readDeliveryState(loaded.manifest), null, 2), {
|
|
81
|
+
action: "api",
|
|
82
|
+
status: "ok",
|
|
83
|
+
runId: loaded.manifest.runId,
|
|
84
|
+
artifactsRoot: loaded.manifest.artifactsRoot,
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const handleSendMessage: ApiOperationHandler = (hctx) => {
|
|
89
|
+
const { cfg, loaded, result, paramRequired, ctx } = hctx;
|
|
90
|
+
const direction = cfg.direction === "outbox" ? "outbox" : "inbox";
|
|
91
|
+
const from = typeof cfg.from === "string" && cfg.from.trim() ? cfg.from.trim() : "api";
|
|
92
|
+
const to = typeof cfg.to === "string" && cfg.to.trim() ? cfg.to.trim() : "leader";
|
|
93
|
+
const body = typeof cfg.body === "string" && cfg.body.trim() ? cfg.body : undefined;
|
|
94
|
+
const taskId = typeof cfg.taskId === "string" && cfg.taskId.trim() ? cfg.taskId.trim() : undefined;
|
|
95
|
+
if (!body)
|
|
96
|
+
return result(
|
|
97
|
+
paramRequired(
|
|
98
|
+
"API send-message",
|
|
99
|
+
"config.body",
|
|
100
|
+
"{ action: 'api', runId: 'team_...', config: { operation: 'send-message', body: '<message>' } }",
|
|
101
|
+
),
|
|
102
|
+
{
|
|
103
|
+
action: "api",
|
|
104
|
+
status: "error",
|
|
105
|
+
runId: loaded.manifest.runId,
|
|
106
|
+
},
|
|
107
|
+
true,
|
|
108
|
+
);
|
|
109
|
+
if (taskId && !loaded.tasks.some((task) => task.id === taskId))
|
|
110
|
+
return result(
|
|
111
|
+
`API send-message taskId '${taskId}' does not match a run task.`,
|
|
112
|
+
{
|
|
113
|
+
action: "api",
|
|
114
|
+
status: "error",
|
|
115
|
+
runId: loaded.manifest.runId,
|
|
116
|
+
},
|
|
117
|
+
true,
|
|
118
|
+
);
|
|
119
|
+
try {
|
|
120
|
+
return withRunLockSync(loaded.manifest, () => {
|
|
121
|
+
const message = appendMailboxMessage(loaded.manifest, {
|
|
122
|
+
direction,
|
|
123
|
+
from,
|
|
124
|
+
to,
|
|
125
|
+
body,
|
|
126
|
+
taskId,
|
|
127
|
+
});
|
|
128
|
+
// H1 (2026-08-10): informational mailbox event inside a SYNC
|
|
129
|
+
// run-lock callback. Sync append (byte-identical to pre-extract
|
|
130
|
+
// api.ts) so consumers reading eventsPath immediately after the
|
|
131
|
+
// call see the event — async fire-and-forget would race.
|
|
132
|
+
appendEvent(loaded.manifest.eventsPath, {
|
|
133
|
+
type: "mailbox.message",
|
|
134
|
+
runId: loaded.manifest.runId,
|
|
135
|
+
data: { id: message.id, direction, from, to },
|
|
136
|
+
});
|
|
137
|
+
ctx.events?.emit?.("crew.mailbox.message", {
|
|
138
|
+
runId: loaded.manifest.runId,
|
|
139
|
+
id: message.id,
|
|
140
|
+
direction,
|
|
141
|
+
from,
|
|
142
|
+
to,
|
|
143
|
+
taskId,
|
|
144
|
+
source: "send-message",
|
|
145
|
+
});
|
|
146
|
+
return result(JSON.stringify(message, null, 2), {
|
|
147
|
+
action: "api",
|
|
148
|
+
status: "ok",
|
|
149
|
+
runId: loaded.manifest.runId,
|
|
150
|
+
artifactsRoot: loaded.manifest.artifactsRoot,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
155
|
+
return result(
|
|
156
|
+
message,
|
|
157
|
+
{
|
|
158
|
+
action: "api",
|
|
159
|
+
status: "error",
|
|
160
|
+
runId: loaded.manifest.runId,
|
|
161
|
+
},
|
|
162
|
+
true,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const handleAckMessage: ApiOperationHandler = (hctx) => {
|
|
168
|
+
const { cfg, loaded, result, paramRequired, ctx } = hctx;
|
|
169
|
+
const messageId = typeof cfg.messageId === "string" ? cfg.messageId : undefined;
|
|
170
|
+
if (!messageId)
|
|
171
|
+
return result(
|
|
172
|
+
paramRequired(
|
|
173
|
+
"API ack-message",
|
|
174
|
+
"config.messageId",
|
|
175
|
+
"{ action: 'api', runId: 'team_...', config: { operation: 'ack-message', messageId: 'msg-1' } }",
|
|
176
|
+
),
|
|
177
|
+
{
|
|
178
|
+
action: "api",
|
|
179
|
+
status: "error",
|
|
180
|
+
runId: loaded.manifest.runId,
|
|
181
|
+
},
|
|
182
|
+
true,
|
|
183
|
+
);
|
|
184
|
+
try {
|
|
185
|
+
return withRunLockSync(loaded.manifest, () => {
|
|
186
|
+
const message = readMailboxMessage(loaded.manifest, messageId);
|
|
187
|
+
const delivery = acknowledgeMailboxMessage(loaded.manifest, messageId);
|
|
188
|
+
appendEvent(loaded.manifest.eventsPath, {
|
|
189
|
+
type: "mailbox.acknowledged",
|
|
190
|
+
runId: loaded.manifest.runId,
|
|
191
|
+
data: { messageId },
|
|
192
|
+
});
|
|
193
|
+
if (message?.data?.kind === "group_join" && typeof message.data.requestId === "string") {
|
|
194
|
+
appendEvent(loaded.manifest.eventsPath, {
|
|
195
|
+
type: "agent.group_join.acknowledged",
|
|
196
|
+
runId: loaded.manifest.runId,
|
|
197
|
+
message: "Group join delivery acknowledged via mailbox ack.",
|
|
198
|
+
data: {
|
|
199
|
+
requestId: message.data.requestId,
|
|
200
|
+
messageId,
|
|
201
|
+
batchId: message.data.batchId,
|
|
202
|
+
partial: message.data.partial,
|
|
203
|
+
acknowledgedAt: delivery.updatedAt,
|
|
204
|
+
acknowledgedBy: "leader",
|
|
205
|
+
},
|
|
206
|
+
metadata: { provenance: "api" },
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
ctx.events?.emit?.("crew.mailbox.acknowledged", {
|
|
210
|
+
runId: loaded.manifest.runId,
|
|
211
|
+
messageId,
|
|
212
|
+
delivery,
|
|
213
|
+
});
|
|
214
|
+
return result(JSON.stringify(delivery, null, 2), {
|
|
215
|
+
action: "api",
|
|
216
|
+
status: "ok",
|
|
217
|
+
runId: loaded.manifest.runId,
|
|
218
|
+
artifactsRoot: loaded.manifest.artifactsRoot,
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
} catch (error) {
|
|
222
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
223
|
+
return result(
|
|
224
|
+
message,
|
|
225
|
+
{
|
|
226
|
+
action: "api",
|
|
227
|
+
status: "error",
|
|
228
|
+
runId: loaded.manifest.runId,
|
|
229
|
+
},
|
|
230
|
+
true,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/** Dispatcher map for the mailbox group. Consumed by handleApi. */
|
|
236
|
+
export const MAILBOX_OPERATIONS: Record<string, ApiOperationHandler> = {
|
|
237
|
+
"read-mailbox": handleReadMailbox,
|
|
238
|
+
"validate-mailbox": handleValidateMailbox,
|
|
239
|
+
"read-delivery": handleReadDelivery,
|
|
240
|
+
"send-message": handleSendMessage,
|
|
241
|
+
"ack-message": handleAckMessage,
|
|
242
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracted `api` operation handlers — plan approval group (H3 phase 2).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `src/extension/team-tool/api.ts` on 2026-08-10. Behaviour is
|
|
5
|
+
* byte-identical to the inline `if` blocks they replace.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { terminateLiveAgentsForRun } from "../../../runtime/live-session/live-agent-manager.ts";
|
|
9
|
+
import { currentCrewRole, permissionForRole } from "../../../runtime/role-permission.ts";
|
|
10
|
+
import { withRunLock } from "../../../state/coordination/locks.ts";
|
|
11
|
+
import { appendEvent } from "../../../state/event-log/event-log.ts";
|
|
12
|
+
import { loadRunManifestById, saveRunManifestAsync, saveRunTasks, updateRunStatus } from "../../../state/stores/state-store.ts";
|
|
13
|
+
import { logInternalError } from "../../../utils/internal-error.ts";
|
|
14
|
+
import type { ApiOperationHandler } from "./handler-context.ts";
|
|
15
|
+
|
|
16
|
+
function canApprovePlan(): { allowed: boolean; reason?: string } {
|
|
17
|
+
const role = currentCrewRole();
|
|
18
|
+
if (!role) return { allowed: true };
|
|
19
|
+
if (permissionForRole(role) === "read_only")
|
|
20
|
+
return {
|
|
21
|
+
allowed: false,
|
|
22
|
+
reason: `Role '${role}' is read-only and cannot approve or cancel plan gates.`,
|
|
23
|
+
};
|
|
24
|
+
return { allowed: true };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const handleApprovePlan: ApiOperationHandler = async (hctx) => {
|
|
28
|
+
const { loaded, result, ctx } = hctx;
|
|
29
|
+
const permission = canApprovePlan();
|
|
30
|
+
if (!permission.allowed)
|
|
31
|
+
return result(
|
|
32
|
+
permission.reason ?? "Plan approval is not allowed in this context.",
|
|
33
|
+
{
|
|
34
|
+
action: "api",
|
|
35
|
+
status: "error",
|
|
36
|
+
runId: loaded.manifest.runId,
|
|
37
|
+
},
|
|
38
|
+
true,
|
|
39
|
+
);
|
|
40
|
+
try {
|
|
41
|
+
return await withRunLock(loaded.manifest, async () => {
|
|
42
|
+
const current = loadRunManifestById(ctx.cwd, loaded.manifest.runId) ?? loaded; // NOTE: inside withRunLock - consistent read
|
|
43
|
+
const approval = current.manifest.planApproval;
|
|
44
|
+
if (!approval?.required || approval.status !== "pending")
|
|
45
|
+
return result(
|
|
46
|
+
"Run has no pending plan approval request.",
|
|
47
|
+
{
|
|
48
|
+
action: "api",
|
|
49
|
+
status: "error",
|
|
50
|
+
runId: loaded.manifest.runId,
|
|
51
|
+
},
|
|
52
|
+
true,
|
|
53
|
+
);
|
|
54
|
+
const now = new Date().toISOString();
|
|
55
|
+
const manifest = {
|
|
56
|
+
...current.manifest,
|
|
57
|
+
updatedAt: now,
|
|
58
|
+
planApproval: {
|
|
59
|
+
...approval,
|
|
60
|
+
status: "approved" as const,
|
|
61
|
+
approvedAt: now,
|
|
62
|
+
updatedAt: now,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
await saveRunManifestAsync(manifest);
|
|
66
|
+
appendEvent(manifest.eventsPath, {
|
|
67
|
+
type: "plan.approved",
|
|
68
|
+
runId: manifest.runId,
|
|
69
|
+
taskId: approval.planTaskId,
|
|
70
|
+
message: "Adaptive implementation plan approved; resume the run to execute mutating tasks.",
|
|
71
|
+
metadata: { provenance: "api" },
|
|
72
|
+
});
|
|
73
|
+
return result(JSON.stringify(manifest.planApproval, null, 2), {
|
|
74
|
+
action: "api",
|
|
75
|
+
status: "ok",
|
|
76
|
+
runId: manifest.runId,
|
|
77
|
+
artifactsRoot: manifest.artifactsRoot,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
return result(
|
|
83
|
+
message,
|
|
84
|
+
{
|
|
85
|
+
action: "api",
|
|
86
|
+
status: "error",
|
|
87
|
+
runId: loaded.manifest.runId,
|
|
88
|
+
},
|
|
89
|
+
true,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const handleCancelPlan: ApiOperationHandler = async (hctx) => {
|
|
95
|
+
const { loaded, result, ctx } = hctx;
|
|
96
|
+
const permission = canApprovePlan();
|
|
97
|
+
if (!permission.allowed)
|
|
98
|
+
return result(
|
|
99
|
+
permission.reason ?? "Plan approval cancellation is not allowed in this context.",
|
|
100
|
+
{
|
|
101
|
+
action: "api",
|
|
102
|
+
status: "error",
|
|
103
|
+
runId: loaded.manifest.runId,
|
|
104
|
+
},
|
|
105
|
+
true,
|
|
106
|
+
);
|
|
107
|
+
try {
|
|
108
|
+
return await withRunLock(loaded.manifest, async () => {
|
|
109
|
+
const current = loadRunManifestById(ctx.cwd, loaded.manifest.runId) ?? loaded; // NOTE: inside withRunLock - consistent read
|
|
110
|
+
const approval = current.manifest.planApproval;
|
|
111
|
+
if (!approval?.required || approval.status !== "pending")
|
|
112
|
+
return result(
|
|
113
|
+
"Run has no pending plan approval request.",
|
|
114
|
+
{
|
|
115
|
+
action: "api",
|
|
116
|
+
status: "error",
|
|
117
|
+
runId: loaded.manifest.runId,
|
|
118
|
+
},
|
|
119
|
+
true,
|
|
120
|
+
);
|
|
121
|
+
const now = new Date().toISOString();
|
|
122
|
+
const tasks = current.tasks.map((task) =>
|
|
123
|
+
task.status === "queued" || task.status === "running" || task.status === "waiting"
|
|
124
|
+
? {
|
|
125
|
+
...task,
|
|
126
|
+
status: "cancelled" as const,
|
|
127
|
+
finishedAt: now,
|
|
128
|
+
error: "Plan approval was cancelled.",
|
|
129
|
+
}
|
|
130
|
+
: task,
|
|
131
|
+
);
|
|
132
|
+
let manifest: typeof current.manifest = {
|
|
133
|
+
...current.manifest,
|
|
134
|
+
updatedAt: now,
|
|
135
|
+
planApproval: {
|
|
136
|
+
...approval,
|
|
137
|
+
status: "cancelled" as const,
|
|
138
|
+
cancelledAt: now,
|
|
139
|
+
updatedAt: now,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
await saveRunManifestAsync(manifest);
|
|
143
|
+
saveRunTasks(manifest, tasks);
|
|
144
|
+
appendEvent(manifest.eventsPath, {
|
|
145
|
+
type: "plan.cancelled",
|
|
146
|
+
runId: manifest.runId,
|
|
147
|
+
taskId: approval.planTaskId,
|
|
148
|
+
message: "Adaptive implementation plan was cancelled.",
|
|
149
|
+
metadata: { provenance: "api" },
|
|
150
|
+
});
|
|
151
|
+
manifest = updateRunStatus(manifest, "cancelled", "Plan approval was cancelled.");
|
|
152
|
+
void terminateLiveAgentsForRun(manifest.runId, "cancelled", appendEvent, manifest.eventsPath).catch((error) =>
|
|
153
|
+
logInternalError("team-tool.cancel-plan.terminate", error, `runId=${manifest.runId}`),
|
|
154
|
+
);
|
|
155
|
+
return result(
|
|
156
|
+
JSON.stringify(
|
|
157
|
+
{
|
|
158
|
+
planApproval: manifest.planApproval,
|
|
159
|
+
cancelledTasks: tasks.filter((task) => task.status === "cancelled").map((task) => task.id),
|
|
160
|
+
},
|
|
161
|
+
null,
|
|
162
|
+
2,
|
|
163
|
+
),
|
|
164
|
+
{
|
|
165
|
+
action: "api",
|
|
166
|
+
status: "ok",
|
|
167
|
+
runId: manifest.runId,
|
|
168
|
+
artifactsRoot: manifest.artifactsRoot,
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
174
|
+
return result(
|
|
175
|
+
message,
|
|
176
|
+
{
|
|
177
|
+
action: "api",
|
|
178
|
+
status: "error",
|
|
179
|
+
runId: loaded.manifest.runId,
|
|
180
|
+
},
|
|
181
|
+
true,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/** Dispatcher map for the plan-approval group. Consumed by handleApi. */
|
|
187
|
+
export const PLAN_APPROVAL_OPERATIONS: Record<string, ApiOperationHandler> = {
|
|
188
|
+
"approve-plan": handleApprovePlan,
|
|
189
|
+
"cancel-plan": handleCancelPlan,
|
|
190
|
+
};
|