@ordewell/web 0.10.4 → 0.10.6
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/dist/server/main.js +105 -120
- package/dist/server/main.js.map +1 -1
- package/package.json +2 -2
package/dist/server/main.js
CHANGED
|
@@ -244,8 +244,8 @@ var PoolFileSystem = class extends core.BaseFileSystem {
|
|
|
244
244
|
}
|
|
245
245
|
async globImpl(pattern, absRoot, headLimit) {
|
|
246
246
|
if (await this.hasRg()) {
|
|
247
|
-
const anchor = await this.
|
|
248
|
-
const result2 = await run("rg", core.buildGlobArgs(pattern,
|
|
247
|
+
const { root, anchor } = await this.searchTarget(absRoot);
|
|
248
|
+
const result2 = await run("rg", core.buildGlobArgs(pattern, root), { timeout: SEARCH_TIMEOUT_MS, cwd: anchor, env: this.searchEnv() });
|
|
249
249
|
if (result2.code !== 0 && result2.code !== 1 && !result2.stdout) {
|
|
250
250
|
return { success: false, output: `Glob failed: ${result2.stderr.trim() || "ripgrep error"}`, truncated: false };
|
|
251
251
|
}
|
|
@@ -258,41 +258,50 @@ var PoolFileSystem = class extends core.BaseFileSystem {
|
|
|
258
258
|
}
|
|
259
259
|
const basename = pattern.split("/").pop() || pattern;
|
|
260
260
|
const excludeArgs = core.SEARCH_EXCLUSIONS.flatMap((dir) => ["-not", "-path", `*/${dir}/*`]);
|
|
261
|
-
const
|
|
261
|
+
const { root: findRoot } = await this.searchTarget(absRoot);
|
|
262
|
+
const result = await run("find", [findRoot, "-type", "f", "-name", basename, ...excludeArgs], { timeout: SEARCH_TIMEOUT_MS, env: this.searchEnv() });
|
|
262
263
|
if (result.code !== 0 && !result.stdout) {
|
|
263
264
|
return { success: false, output: `Glob failed: ${result.stderr.trim() || "find error"}`, truncated: false };
|
|
264
265
|
}
|
|
265
266
|
const capped = core.applyHeadLimit(result.stdout, headLimit);
|
|
266
267
|
return {
|
|
267
268
|
success: true,
|
|
268
|
-
output: core.formatSearchOutput(capped,
|
|
269
|
+
output: core.formatSearchOutput(capped, findRoot, { emptyMessage: "No files matched." }),
|
|
269
270
|
truncated: capped.truncated
|
|
270
271
|
};
|
|
271
272
|
}
|
|
272
273
|
/**
|
|
273
|
-
*
|
|
274
|
-
* `--glob`/`--include` patterns resolve against the search root
|
|
275
|
-
*
|
|
274
|
+
* The search path and a directory to anchor it in. `cwd` exists only so
|
|
275
|
+
* anchored `--glob`/`--include` patterns resolve against the search root, and
|
|
276
|
+
* spawn(2) rejects a `cwd` that is a file with ENOTDIR.
|
|
276
277
|
*
|
|
277
278
|
* `grep`'s `path` is documented as a directory, but the tool description
|
|
278
279
|
* tells the model to survey with output_mode="files" and then re-run "on a
|
|
279
280
|
* narrower path" — and what that survey hands back are files. rg and grep
|
|
280
281
|
* both search a single file happily, so anchor at the parent rather than
|
|
281
282
|
* failing a legitimately file-scoped search.
|
|
283
|
+
*
|
|
284
|
+
* The root is resolved through symlinks because the OS reports a child's cwd
|
|
285
|
+
* as the resolved path (macOS tmpdir is `/var/...` -> `/private/var/...`),
|
|
286
|
+
* and ripgrep matches an anchored glob against the search path as written: a
|
|
287
|
+
* root that goes through a link never matches and yields a false empty
|
|
288
|
+
* result. Result rows then carry the resolved prefix, which is why callers
|
|
289
|
+
* strip `anchor` rather than the path they were handed.
|
|
282
290
|
*/
|
|
283
|
-
async
|
|
291
|
+
async searchTarget(absRoot) {
|
|
284
292
|
try {
|
|
285
|
-
|
|
293
|
+
const root = await fs.promises.realpath(absRoot);
|
|
294
|
+
return { root, anchor: (await fs.promises.stat(root)).isDirectory() ? root : path__namespace.dirname(root) };
|
|
286
295
|
} catch {
|
|
287
|
-
return absRoot;
|
|
296
|
+
return { root: absRoot, anchor: absRoot };
|
|
288
297
|
}
|
|
289
298
|
}
|
|
290
299
|
async grepImpl(pattern, absRoot, opts) {
|
|
291
300
|
const headLimit = opts.headLimit ?? 100;
|
|
292
301
|
const useRg = await this.hasRg();
|
|
293
|
-
const anchor = await this.
|
|
302
|
+
const { root, anchor } = await this.searchTarget(absRoot);
|
|
294
303
|
const anchoredInclude = !useRg && opts.include?.includes("/") ? opts.include : void 0;
|
|
295
|
-
const result = useRg ? await run("rg", core.buildGrepArgs(pattern, opts,
|
|
304
|
+
const result = useRg ? await run("rg", core.buildGrepArgs(pattern, opts, root).args, { timeout: SEARCH_TIMEOUT_MS, cwd: anchor, env: this.searchEnv() }) : await run("grep", core.buildFallbackGrepArgs(pattern, anchoredInclude ? { ...opts, include: void 0 } : opts, root, await this.fallbackGrepRegex()), { timeout: SEARCH_TIMEOUT_MS, cwd: anchor, env: this.searchEnv() });
|
|
296
305
|
if (result.code !== 0 && result.code !== 1 && !result.stdout) {
|
|
297
306
|
return { success: false, output: `Search failed: ${result.stderr.trim() || "search error"}`, truncated: false };
|
|
298
307
|
}
|
|
@@ -561,9 +570,10 @@ var OrchestratorPool = class {
|
|
|
561
570
|
* environment's defaults over what the user had chosen.
|
|
562
571
|
*/
|
|
563
572
|
setRunnerEnabled(id, enabled) {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
573
|
+
this.settingsService.updateEnabledRunners((saved) => {
|
|
574
|
+
const current = saved ?? new WebConfig().enabledRunners;
|
|
575
|
+
return enabled ? current.includes(id) ? current : [...current, id] : current.filter((r) => r !== id);
|
|
576
|
+
});
|
|
567
577
|
}
|
|
568
578
|
getSettings() {
|
|
569
579
|
const config = this.plannerConfig();
|
|
@@ -912,27 +922,29 @@ function sessionsRoute(pool2) {
|
|
|
912
922
|
const id = c.req.param("id");
|
|
913
923
|
const saved = core.loadSessionPlanState(id, ws);
|
|
914
924
|
if (!saved) return refuse(c, 404, "Session not found", "session_not_found");
|
|
915
|
-
|
|
925
|
+
const run2 = pool2.runSnapshot(id);
|
|
926
|
+
return c.json({ meta: saved.meta, plan: core.surfacePlanState(pool2.getPlanState(id) ?? saved.plan), ...run2 ? { run: run2 } : {} });
|
|
916
927
|
});
|
|
917
928
|
router.post("/:id/load", (c) => {
|
|
918
929
|
const ws = c.req.query("workspace") || process.cwd();
|
|
919
930
|
try {
|
|
920
931
|
const id = c.req.param("id");
|
|
921
932
|
const plan = pool2.adoptSavedSession(id, ws);
|
|
922
|
-
|
|
933
|
+
const run2 = pool2.runSnapshot(id);
|
|
934
|
+
return c.json({ ok: true, plan: core.surfacePlan(plan), goal: pool2.getGoal(id), ...run2 ? { run: run2 } : {} });
|
|
923
935
|
} catch (err) {
|
|
924
936
|
return failure(c, err, "load session", { message: "Failed to load session" });
|
|
925
937
|
}
|
|
926
938
|
});
|
|
927
939
|
router.get("/:id/tasks/:taskId/log", (c) => {
|
|
928
940
|
const ws = c.req.query("workspace") || process.cwd();
|
|
929
|
-
return c.json({ attempts: core.listTaskLogAttempts({ baseDir: ws, sessionId: c.req.param("id") }, c.req.param("taskId")) });
|
|
941
|
+
return c.json({ attempts: core.listTaskLogAttempts({ baseDir: ws, sessionId: c.req.param("id") }, c.req.param("taskId")), position: pool2.streamPosition(c.req.param("id")) });
|
|
930
942
|
});
|
|
931
943
|
router.get("/:id/tasks/:taskId/log/:attempt", (c) => {
|
|
932
944
|
const ws = c.req.query("workspace") || process.cwd();
|
|
933
945
|
const attempt = Number(c.req.param("attempt"));
|
|
934
946
|
if (!Number.isInteger(attempt) || attempt < 1) return refuse(c, 400, "attempt must be a positive integer");
|
|
935
|
-
return c.json({ attempt, events: core.taskLogForSurface(core.readTaskLog({ baseDir: ws, sessionId: c.req.param("id") }, c.req.param("taskId"), attempt)) });
|
|
947
|
+
return c.json({ attempt, events: core.taskLogForSurface(core.readTaskLog({ baseDir: ws, sessionId: c.req.param("id") }, c.req.param("taskId"), attempt)), position: pool2.streamPosition(c.req.param("id")) });
|
|
936
948
|
});
|
|
937
949
|
router.delete("/:id", (c) => {
|
|
938
950
|
const ws = c.req.query("workspace") || process.cwd();
|
|
@@ -948,6 +960,9 @@ function sessionsRoute(pool2) {
|
|
|
948
960
|
return router;
|
|
949
961
|
}
|
|
950
962
|
var NO_CHECKPOINT = "The task is not waiting at a checkpoint: it was answered already, withdrawn, or has no runner left to hear it.";
|
|
963
|
+
function idOfRef(session, ref) {
|
|
964
|
+
return core.resolveUserTaskRef(session.planState?.tasks ?? [], ref)?.id ?? ref;
|
|
965
|
+
}
|
|
951
966
|
async function runUserCommand(session, command) {
|
|
952
967
|
const outcome = await session.execute(command, { kind: "user" });
|
|
953
968
|
if (!outcome.ok) throw new core.TaskControlError(outcome.message);
|
|
@@ -974,9 +989,8 @@ function plansRoute(pool2) {
|
|
|
974
989
|
try {
|
|
975
990
|
const session = pool2.session(c.req.param("sessionId"));
|
|
976
991
|
if (!session.planState || session.planTasks.length === 0) throw new core.NoPlanError("execute");
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
return c.json({ status: "running" });
|
|
992
|
+
const { message } = await runUserCommand(session, { command: "run_plan" });
|
|
993
|
+
return c.json({ status: "running", message });
|
|
980
994
|
} catch (err) {
|
|
981
995
|
return failure(c, err, "execute");
|
|
982
996
|
}
|
|
@@ -987,6 +1001,7 @@ function plansRoute(pool2) {
|
|
|
987
1001
|
const session = pool2.session(c.req.param("sessionId"));
|
|
988
1002
|
const outcome = await session.execute({ command: "stop_run" }, { kind: "user" });
|
|
989
1003
|
if (!outcome.ok && (session.isExecuting || session.startsPending)) throw new core.TaskControlError(outcome.message);
|
|
1004
|
+
return c.json({ status: "stopped", message: outcome.message });
|
|
990
1005
|
}
|
|
991
1006
|
return c.json({ status: "stopped" });
|
|
992
1007
|
} catch (err) {
|
|
@@ -997,47 +1012,28 @@ function plansRoute(pool2) {
|
|
|
997
1012
|
const cancelled = pool2.cancelPlanning(c.req.param("sessionId"));
|
|
998
1013
|
return c.json({ cancelled });
|
|
999
1014
|
});
|
|
1000
|
-
router.post("/:sessionId/tasks/:taskId/
|
|
1001
|
-
try {
|
|
1002
|
-
await pool2.session(c.req.param("sessionId")).markTaskComplete(c.req.param("taskId"));
|
|
1003
|
-
return c.json({ ok: true });
|
|
1004
|
-
} catch (err) {
|
|
1005
|
-
return failure(c, err, "mark complete", { fallback: 404, message: "Not found" });
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1008
|
-
router.post("/:sessionId/tasks/:taskId/uncomplete", async (c) => {
|
|
1015
|
+
router.post("/:sessionId/tasks/:taskId/resume/cancel", async (c) => {
|
|
1009
1016
|
try {
|
|
1010
|
-
await pool2.session(c.req.param("sessionId"))
|
|
1011
|
-
return c.json({
|
|
1012
|
-
} catch (err) {
|
|
1013
|
-
return failure(c, err, "mark incomplete", { fallback: 404, message: "Not found" });
|
|
1014
|
-
}
|
|
1015
|
-
});
|
|
1016
|
-
router.post("/:sessionId/tasks/:taskId/resume/cancel", (c) => {
|
|
1017
|
-
try {
|
|
1018
|
-
const cancelled = pool2.session(c.req.param("sessionId")).cancelScheduledResume(c.req.param("taskId"));
|
|
1019
|
-
return c.json({ cancelled });
|
|
1017
|
+
const { message } = await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "cancel_resume", task: c.req.param("taskId") });
|
|
1018
|
+
return c.json({ cancelled: true, message });
|
|
1020
1019
|
} catch (err) {
|
|
1021
|
-
return failure(c, err, "cancel resume"
|
|
1022
|
-
}
|
|
1023
|
-
});
|
|
1024
|
-
router.post("/:sessionId/tasks/:taskId/resume/now", async (c) => {
|
|
1025
|
-
try {
|
|
1026
|
-
await pool2.session(c.req.param("sessionId")).resumeNow(c.req.param("taskId"));
|
|
1027
|
-
return c.json({ ok: true });
|
|
1028
|
-
} catch (err) {
|
|
1029
|
-
return failure(c, err, "resume now", { fallback: 404, message: "Not found" });
|
|
1020
|
+
return failure(c, err, "cancel resume");
|
|
1030
1021
|
}
|
|
1031
1022
|
});
|
|
1032
1023
|
for (const [segment, command] of [
|
|
1033
1024
|
["run", (id) => ({ command: "run_task", task: id })],
|
|
1034
1025
|
["retry", (id) => ({ command: "retry_task", task: id, from: "fresh" })],
|
|
1035
|
-
["cancel", (id) => ({ command: "stop_task", task: id })]
|
|
1026
|
+
["cancel", (id) => ({ command: "stop_task", task: id })],
|
|
1027
|
+
["complete", (id) => ({ command: "mark_complete", task: id })],
|
|
1028
|
+
["uncomplete", (id) => ({ command: "mark_not_done", task: id })],
|
|
1029
|
+
// The user says the limit has reset early: the task, and every other one waiting on its runner's reset, resumes in its saved session now.
|
|
1030
|
+
["resume/now", (id) => ({ command: "retry_task", task: id, from: "session" })],
|
|
1031
|
+
["interrupt", (id) => ({ command: "interrupt_task", task: id })]
|
|
1036
1032
|
]) {
|
|
1037
1033
|
router.post(`/:sessionId/tasks/:taskId/${segment}`, async (c) => {
|
|
1038
1034
|
try {
|
|
1039
|
-
await runUserCommand(pool2.session(c.req.param("sessionId")), command(c.req.param("taskId")));
|
|
1040
|
-
return c.json({ ok: true });
|
|
1035
|
+
const { message } = await runUserCommand(pool2.session(c.req.param("sessionId")), command(c.req.param("taskId")));
|
|
1036
|
+
return c.json({ ok: true, message });
|
|
1041
1037
|
} catch (err) {
|
|
1042
1038
|
return failure(c, err, segment);
|
|
1043
1039
|
}
|
|
@@ -1047,16 +1043,16 @@ function plansRoute(pool2) {
|
|
|
1047
1043
|
try {
|
|
1048
1044
|
const body = await c.req.json().catch(() => ({}));
|
|
1049
1045
|
const reason = typeof body === "object" && body !== null && "reason" in body && typeof body.reason === "string" ? body.reason : void 0;
|
|
1050
|
-
await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "skip_task", task: c.req.param("taskId"), ...reason ? { reason } : {} });
|
|
1051
|
-
return c.json({ ok: true });
|
|
1046
|
+
const { message } = await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "skip_task", task: c.req.param("taskId"), ...reason ? { reason } : {} });
|
|
1047
|
+
return c.json({ ok: true, message });
|
|
1052
1048
|
} catch (err) {
|
|
1053
1049
|
return failure(c, err, "skip");
|
|
1054
1050
|
}
|
|
1055
1051
|
});
|
|
1056
1052
|
router.post("/:sessionId/tasks/:taskId/force-start", async (c) => {
|
|
1057
1053
|
try {
|
|
1058
|
-
await pool2.session(c.req.param("sessionId"))
|
|
1059
|
-
return c.json({ ok: true });
|
|
1054
|
+
const { message, payload } = await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "force_start", task: c.req.param("taskId") });
|
|
1055
|
+
return c.json({ ok: true, ...payload?.kind === "queued" ? { queued: payload.line } : {}, message });
|
|
1060
1056
|
} catch (err) {
|
|
1061
1057
|
return failure(c, err, "force-start");
|
|
1062
1058
|
}
|
|
@@ -1072,8 +1068,12 @@ function plansRoute(pool2) {
|
|
|
1072
1068
|
return failure(c, err, "task edit");
|
|
1073
1069
|
}
|
|
1074
1070
|
};
|
|
1075
|
-
|
|
1076
|
-
|
|
1071
|
+
const sendMessage = (force) => messageHandler(async (session, taskId, text) => {
|
|
1072
|
+
const { payload } = await runUserCommand(session, { command: "message_task", task: taskId, text, ...force ? { force } : {} });
|
|
1073
|
+
return payload?.kind === "message" ? payload.id : "";
|
|
1074
|
+
});
|
|
1075
|
+
router.post("/:sessionId/tasks/:taskId/messages", sendMessage(false));
|
|
1076
|
+
router.post("/:sessionId/tasks/:taskId/messages/now", sendMessage(true));
|
|
1077
1077
|
router.post("/:sessionId/tasks/:taskId/commands", async (c) => {
|
|
1078
1078
|
try {
|
|
1079
1079
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -1086,10 +1086,10 @@ function plansRoute(pool2) {
|
|
|
1086
1086
|
return failure(c, err, "task edit");
|
|
1087
1087
|
}
|
|
1088
1088
|
});
|
|
1089
|
-
router.post("/:sessionId/tasks/:taskId/messages/:messageId/now", (c) => {
|
|
1089
|
+
router.post("/:sessionId/tasks/:taskId/messages/:messageId/now", async (c) => {
|
|
1090
1090
|
try {
|
|
1091
|
-
const
|
|
1092
|
-
return c.json({ sent });
|
|
1091
|
+
const { message } = await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "force_send_queued", task: c.req.param("taskId"), id: c.req.param("messageId") });
|
|
1092
|
+
return c.json({ sent: true, message });
|
|
1093
1093
|
} catch (err) {
|
|
1094
1094
|
return failure(c, err, "task edit");
|
|
1095
1095
|
}
|
|
@@ -1099,57 +1099,40 @@ function plansRoute(pool2) {
|
|
|
1099
1099
|
const body = await c.req.json().catch(() => ({}));
|
|
1100
1100
|
const text = typeof body === "object" && body !== null && "text" in body ? body.text : void 0;
|
|
1101
1101
|
if (typeof text !== "string" || !text.trim()) return c.json({ error: "text is required" }, 400);
|
|
1102
|
-
await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "retry_task", task: c.req.param("taskId"), from: "session", message: text });
|
|
1103
|
-
return c.json({ ok: true });
|
|
1104
|
-
} catch (err) {
|
|
1105
|
-
return failure(c, err, "task edit");
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
|
-
router.delete("/:sessionId/tasks/:taskId/messages/:messageId", (c) => {
|
|
1109
|
-
try {
|
|
1110
|
-
const removed = pool2.session(c.req.param("sessionId")).removeQueuedTaskMessage(c.req.param("taskId"), c.req.param("messageId"));
|
|
1111
|
-
return c.json({ removed });
|
|
1112
|
-
} catch (err) {
|
|
1113
|
-
return failure(c, err, "task edit");
|
|
1114
|
-
}
|
|
1115
|
-
});
|
|
1116
|
-
router.post("/:sessionId/tasks/:taskId/interrupt", async (c) => {
|
|
1117
|
-
try {
|
|
1118
|
-
await pool2.session(c.req.param("sessionId")).interruptTask(c.req.param("taskId"));
|
|
1119
|
-
return c.json({ ok: true });
|
|
1120
|
-
} catch (err) {
|
|
1121
|
-
return failure(c, err, "task edit");
|
|
1122
|
-
}
|
|
1123
|
-
});
|
|
1124
|
-
router.post("/:sessionId/tasks/:taskId/checkpoint/approve", (c) => {
|
|
1125
|
-
try {
|
|
1126
|
-
const session = pool2.session(c.req.param("sessionId"));
|
|
1127
|
-
const taskId = c.req.param("taskId");
|
|
1128
|
-
if (!session.awaitsCheckpoint(taskId)) return refuse(c, 409, NO_CHECKPOINT, "checkpoint_not_waiting");
|
|
1129
|
-
session.approveCheckpoint(taskId);
|
|
1130
|
-
return c.json({ ok: true });
|
|
1102
|
+
const { message } = await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "retry_task", task: c.req.param("taskId"), from: "session", message: text });
|
|
1103
|
+
return c.json({ ok: true, message });
|
|
1131
1104
|
} catch (err) {
|
|
1132
1105
|
return failure(c, err, "task edit");
|
|
1133
1106
|
}
|
|
1134
1107
|
});
|
|
1135
|
-
router.
|
|
1108
|
+
router.delete("/:sessionId/tasks/:taskId/messages/:messageId", async (c) => {
|
|
1136
1109
|
try {
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
const session = pool2.session(c.req.param("sessionId"));
|
|
1140
|
-
const taskId = c.req.param("taskId");
|
|
1141
|
-
if (!session.awaitsCheckpoint(taskId)) return refuse(c, 409, NO_CHECKPOINT, "checkpoint_not_waiting");
|
|
1142
|
-
session.rejectCheckpoint(taskId, reason || void 0);
|
|
1143
|
-
return c.json({ ok: true });
|
|
1110
|
+
const { message } = await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "remove_queued", task: c.req.param("taskId"), id: c.req.param("messageId") });
|
|
1111
|
+
return c.json({ removed: true, message });
|
|
1144
1112
|
} catch (err) {
|
|
1145
1113
|
return failure(c, err, "task edit");
|
|
1146
1114
|
}
|
|
1147
1115
|
});
|
|
1116
|
+
for (const answer of ["approve", "reject"]) {
|
|
1117
|
+
router.post(`/:sessionId/tasks/:taskId/checkpoint/${answer}`, async (c) => {
|
|
1118
|
+
try {
|
|
1119
|
+
const body = answer === "reject" ? await c.req.json().catch(() => ({})) : {};
|
|
1120
|
+
const reason = typeof body === "object" && body !== null && "reason" in body && typeof body.reason === "string" ? body.reason.trim() : "";
|
|
1121
|
+
const session = pool2.session(c.req.param("sessionId"));
|
|
1122
|
+
const taskId = idOfRef(session, c.req.param("taskId"));
|
|
1123
|
+
if (!session.awaitsCheckpoint(taskId)) return refuse(c, 409, NO_CHECKPOINT, "checkpoint_not_waiting");
|
|
1124
|
+
const { message } = await runUserCommand(session, { command: "answer_checkpoint", task: taskId, answer, ...reason ? { reason } : {} });
|
|
1125
|
+
return c.json({ ok: true, message });
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
return failure(c, err, "task edit");
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1148
1131
|
router.get("/:sessionId/tasks/:taskId/merge-gate", (c) => {
|
|
1149
1132
|
try {
|
|
1150
1133
|
const session = pool2.session(c.req.param("sessionId"));
|
|
1151
1134
|
const tasks = core.flattenTasks(session.planState?.tasks ?? []);
|
|
1152
|
-
const mergeGate = session.mergeGate(c.req.param("taskId")).map((id) => {
|
|
1135
|
+
const mergeGate = session.mergeGate(idOfRef(session, c.req.param("taskId"))).map((id) => {
|
|
1153
1136
|
const dep = tasks.find((t) => t.id === id);
|
|
1154
1137
|
return { id, order: dep?.order ?? 0, title: dep?.title ?? id };
|
|
1155
1138
|
});
|
|
@@ -1167,7 +1150,10 @@ function plansRoute(pool2) {
|
|
|
1167
1150
|
});
|
|
1168
1151
|
router.post("/:sessionId/isolation/merge", async (c) => {
|
|
1169
1152
|
try {
|
|
1170
|
-
|
|
1153
|
+
const outcome = await pool2.session(c.req.param("sessionId")).execute({ command: "merge_all" }, { kind: "user" });
|
|
1154
|
+
if (outcome.payload?.kind === "merge") return c.json(outcome.payload.result);
|
|
1155
|
+
if (!outcome.ok) throw new core.TaskControlError(outcome.message);
|
|
1156
|
+
throw new Error("Merge all answered without its result.");
|
|
1171
1157
|
} catch (err) {
|
|
1172
1158
|
return failure(c, err, "task edit");
|
|
1173
1159
|
}
|
|
@@ -1197,21 +1183,9 @@ function plansRoute(pool2) {
|
|
|
1197
1183
|
});
|
|
1198
1184
|
router.put("/:sessionId/tasks/:taskId", async (c) => {
|
|
1199
1185
|
try {
|
|
1200
|
-
const
|
|
1201
|
-
|
|
1202
|
-
const
|
|
1203
|
-
const hasRunner = typeof assignedRunner === "string";
|
|
1204
|
-
const hasDeps = Array.isArray(dependencies);
|
|
1205
|
-
let result = null;
|
|
1206
|
-
if (hasRunner) result = await session.setTaskRunner(taskId, assignedRunner);
|
|
1207
|
-
if (hasDeps) {
|
|
1208
|
-
try {
|
|
1209
|
-
result = await session.setTaskDependencies(taskId, dependencies.map(String));
|
|
1210
|
-
} catch (err) {
|
|
1211
|
-
return failure(c, err, "task dependencies", { fallback: 400 });
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
if (Object.keys(rest).length > 0 || !hasRunner && !hasDeps) result = await session.updateTask(taskId, rest);
|
|
1186
|
+
const changes = await c.req.json();
|
|
1187
|
+
if (changes.assignedModel === null) changes.assignedModel = void 0;
|
|
1188
|
+
const result = await pool2.session(c.req.param("sessionId")).updateTask(c.req.param("taskId"), changes);
|
|
1215
1189
|
if (!result) return refuse(c, 404, "Task not found", "task_not_found");
|
|
1216
1190
|
return c.json({ ok: true });
|
|
1217
1191
|
} catch (err) {
|
|
@@ -1239,8 +1213,11 @@ function plansRoute(pool2) {
|
|
|
1239
1213
|
});
|
|
1240
1214
|
router.post("/:sessionId/review/approve", async (c) => {
|
|
1241
1215
|
try {
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1216
|
+
const session = pool2.session(c.req.param("sessionId"));
|
|
1217
|
+
const plan = session.planState;
|
|
1218
|
+
if (!plan) throw new core.NoPlanError("review");
|
|
1219
|
+
const { message } = await runUserCommand(session, { command: "approve_review" });
|
|
1220
|
+
return c.json({ plan: core.surfacePlan(plan), message });
|
|
1244
1221
|
} catch (err) {
|
|
1245
1222
|
return failure(c, err, "review approve");
|
|
1246
1223
|
}
|
|
@@ -1297,6 +1274,14 @@ function plansRoute(pool2) {
|
|
|
1297
1274
|
return failure(c, err, "conversation message");
|
|
1298
1275
|
}
|
|
1299
1276
|
});
|
|
1277
|
+
router.post("/:sessionId/converse/resend/cancel", (c) => {
|
|
1278
|
+
try {
|
|
1279
|
+
const cancelled = pool2.session(c.req.param("sessionId")).cancelPlannerResend();
|
|
1280
|
+
return c.json({ cancelled });
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
return failure(c, err, "cancel resend", { fallback: 404, message: "Not found" });
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1300
1285
|
router.post("/:sessionId/conversation/fork", (c) => {
|
|
1301
1286
|
try {
|
|
1302
1287
|
const fork = pool2.forkConversation(c.req.param("sessionId"));
|
|
@@ -1397,9 +1382,9 @@ function approvalsRoute(pool2) {
|
|
|
1397
1382
|
const answer = answerOf(await c.req.json().catch(() => null));
|
|
1398
1383
|
const decided = core.toApprovalDecision(answer);
|
|
1399
1384
|
const request = pool2.outstandingApprovals(sessionId).find((pending) => pending.id === approvalId)?.request;
|
|
1400
|
-
if (
|
|
1385
|
+
if (request?.taskId) {
|
|
1401
1386
|
const outcome = await pool2.session(sessionId).execute(
|
|
1402
|
-
{ command: "answer_approval", task: request.taskId, approval: approvalId, answer: "deny"
|
|
1387
|
+
{ command: "answer_approval", task: request.taskId, approval: approvalId, answer: decided.decision, ...decided.decision === "deny" && decided.note ? { note: decided.note } : {} },
|
|
1403
1388
|
{ kind: "user" }
|
|
1404
1389
|
);
|
|
1405
1390
|
if (!outcome.ok) return refuse(c, 400, outcome.message, "refused");
|