@ordewell/web 0.10.5 → 0.10.7
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 +72 -105
- package/dist/server/main.js.map +1 -1
- package/package.json +2 -2
package/dist/server/main.js
CHANGED
|
@@ -922,27 +922,29 @@ function sessionsRoute(pool2) {
|
|
|
922
922
|
const id = c.req.param("id");
|
|
923
923
|
const saved = core.loadSessionPlanState(id, ws);
|
|
924
924
|
if (!saved) return refuse(c, 404, "Session not found", "session_not_found");
|
|
925
|
-
|
|
925
|
+
const run2 = pool2.runSnapshot(id);
|
|
926
|
+
return c.json({ meta: saved.meta, plan: core.surfacePlanState(pool2.getPlanState(id) ?? saved.plan), ...run2 ? { run: run2 } : {} });
|
|
926
927
|
});
|
|
927
928
|
router.post("/:id/load", (c) => {
|
|
928
929
|
const ws = c.req.query("workspace") || process.cwd();
|
|
929
930
|
try {
|
|
930
931
|
const id = c.req.param("id");
|
|
931
932
|
const plan = pool2.adoptSavedSession(id, ws);
|
|
932
|
-
|
|
933
|
+
const run2 = pool2.runSnapshot(id);
|
|
934
|
+
return c.json({ ok: true, plan: core.surfacePlan(plan), goal: pool2.getGoal(id), ...run2 ? { run: run2 } : {} });
|
|
933
935
|
} catch (err) {
|
|
934
936
|
return failure(c, err, "load session", { message: "Failed to load session" });
|
|
935
937
|
}
|
|
936
938
|
});
|
|
937
939
|
router.get("/:id/tasks/:taskId/log", (c) => {
|
|
938
940
|
const ws = c.req.query("workspace") || process.cwd();
|
|
939
|
-
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")) });
|
|
940
942
|
});
|
|
941
943
|
router.get("/:id/tasks/:taskId/log/:attempt", (c) => {
|
|
942
944
|
const ws = c.req.query("workspace") || process.cwd();
|
|
943
945
|
const attempt = Number(c.req.param("attempt"));
|
|
944
946
|
if (!Number.isInteger(attempt) || attempt < 1) return refuse(c, 400, "attempt must be a positive integer");
|
|
945
|
-
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")) });
|
|
946
948
|
});
|
|
947
949
|
router.delete("/:id", (c) => {
|
|
948
950
|
const ws = c.req.query("workspace") || process.cwd();
|
|
@@ -958,6 +960,9 @@ function sessionsRoute(pool2) {
|
|
|
958
960
|
return router;
|
|
959
961
|
}
|
|
960
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
|
+
}
|
|
961
966
|
async function runUserCommand(session, command) {
|
|
962
967
|
const outcome = await session.execute(command, { kind: "user" });
|
|
963
968
|
if (!outcome.ok) throw new core.TaskControlError(outcome.message);
|
|
@@ -984,8 +989,8 @@ function plansRoute(pool2) {
|
|
|
984
989
|
try {
|
|
985
990
|
const session = pool2.session(c.req.param("sessionId"));
|
|
986
991
|
if (!session.planState || session.planTasks.length === 0) throw new core.NoPlanError("execute");
|
|
987
|
-
await runUserCommand(session, { command: "run_plan" });
|
|
988
|
-
return c.json({ status: "running" });
|
|
992
|
+
const { message } = await runUserCommand(session, { command: "run_plan" });
|
|
993
|
+
return c.json({ status: "running", message });
|
|
989
994
|
} catch (err) {
|
|
990
995
|
return failure(c, err, "execute");
|
|
991
996
|
}
|
|
@@ -996,6 +1001,7 @@ function plansRoute(pool2) {
|
|
|
996
1001
|
const session = pool2.session(c.req.param("sessionId"));
|
|
997
1002
|
const outcome = await session.execute({ command: "stop_run" }, { kind: "user" });
|
|
998
1003
|
if (!outcome.ok && (session.isExecuting || session.startsPending)) throw new core.TaskControlError(outcome.message);
|
|
1004
|
+
return c.json({ status: "stopped", message: outcome.message });
|
|
999
1005
|
}
|
|
1000
1006
|
return c.json({ status: "stopped" });
|
|
1001
1007
|
} catch (err) {
|
|
@@ -1006,47 +1012,28 @@ function plansRoute(pool2) {
|
|
|
1006
1012
|
const cancelled = pool2.cancelPlanning(c.req.param("sessionId"));
|
|
1007
1013
|
return c.json({ cancelled });
|
|
1008
1014
|
});
|
|
1009
|
-
router.post("/:sessionId/tasks/:taskId/
|
|
1010
|
-
try {
|
|
1011
|
-
await pool2.session(c.req.param("sessionId")).markTaskComplete(c.req.param("taskId"));
|
|
1012
|
-
return c.json({ ok: true });
|
|
1013
|
-
} catch (err) {
|
|
1014
|
-
return failure(c, err, "mark complete", { fallback: 404, message: "Not found" });
|
|
1015
|
-
}
|
|
1016
|
-
});
|
|
1017
|
-
router.post("/:sessionId/tasks/:taskId/uncomplete", async (c) => {
|
|
1018
|
-
try {
|
|
1019
|
-
await pool2.session(c.req.param("sessionId")).markTaskIncomplete(c.req.param("taskId"));
|
|
1020
|
-
return c.json({ ok: true });
|
|
1021
|
-
} catch (err) {
|
|
1022
|
-
return failure(c, err, "mark incomplete", { fallback: 404, message: "Not found" });
|
|
1023
|
-
}
|
|
1024
|
-
});
|
|
1025
|
-
router.post("/:sessionId/tasks/:taskId/resume/cancel", (c) => {
|
|
1015
|
+
router.post("/:sessionId/tasks/:taskId/resume/cancel", async (c) => {
|
|
1026
1016
|
try {
|
|
1027
|
-
const
|
|
1028
|
-
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 });
|
|
1029
1019
|
} catch (err) {
|
|
1030
|
-
return failure(c, err, "cancel resume"
|
|
1031
|
-
}
|
|
1032
|
-
});
|
|
1033
|
-
router.post("/:sessionId/tasks/:taskId/resume/now", async (c) => {
|
|
1034
|
-
try {
|
|
1035
|
-
await pool2.session(c.req.param("sessionId")).resumeNow(c.req.param("taskId"));
|
|
1036
|
-
return c.json({ ok: true });
|
|
1037
|
-
} catch (err) {
|
|
1038
|
-
return failure(c, err, "resume now", { fallback: 404, message: "Not found" });
|
|
1020
|
+
return failure(c, err, "cancel resume");
|
|
1039
1021
|
}
|
|
1040
1022
|
});
|
|
1041
1023
|
for (const [segment, command] of [
|
|
1042
1024
|
["run", (id) => ({ command: "run_task", task: id })],
|
|
1043
1025
|
["retry", (id) => ({ command: "retry_task", task: id, from: "fresh" })],
|
|
1044
|
-
["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 })]
|
|
1045
1032
|
]) {
|
|
1046
1033
|
router.post(`/:sessionId/tasks/:taskId/${segment}`, async (c) => {
|
|
1047
1034
|
try {
|
|
1048
|
-
await runUserCommand(pool2.session(c.req.param("sessionId")), command(c.req.param("taskId")));
|
|
1049
|
-
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 });
|
|
1050
1037
|
} catch (err) {
|
|
1051
1038
|
return failure(c, err, segment);
|
|
1052
1039
|
}
|
|
@@ -1056,16 +1043,16 @@ function plansRoute(pool2) {
|
|
|
1056
1043
|
try {
|
|
1057
1044
|
const body = await c.req.json().catch(() => ({}));
|
|
1058
1045
|
const reason = typeof body === "object" && body !== null && "reason" in body && typeof body.reason === "string" ? body.reason : void 0;
|
|
1059
|
-
await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "skip_task", task: c.req.param("taskId"), ...reason ? { reason } : {} });
|
|
1060
|
-
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 });
|
|
1061
1048
|
} catch (err) {
|
|
1062
1049
|
return failure(c, err, "skip");
|
|
1063
1050
|
}
|
|
1064
1051
|
});
|
|
1065
1052
|
router.post("/:sessionId/tasks/:taskId/force-start", async (c) => {
|
|
1066
1053
|
try {
|
|
1067
|
-
const
|
|
1068
|
-
return c.json({ ok: true, ...queued ? { queued } : {} });
|
|
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 });
|
|
1069
1056
|
} catch (err) {
|
|
1070
1057
|
return failure(c, err, "force-start");
|
|
1071
1058
|
}
|
|
@@ -1081,8 +1068,12 @@ function plansRoute(pool2) {
|
|
|
1081
1068
|
return failure(c, err, "task edit");
|
|
1082
1069
|
}
|
|
1083
1070
|
};
|
|
1084
|
-
|
|
1085
|
-
|
|
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));
|
|
1086
1077
|
router.post("/:sessionId/tasks/:taskId/commands", async (c) => {
|
|
1087
1078
|
try {
|
|
1088
1079
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -1095,10 +1086,10 @@ function plansRoute(pool2) {
|
|
|
1095
1086
|
return failure(c, err, "task edit");
|
|
1096
1087
|
}
|
|
1097
1088
|
});
|
|
1098
|
-
router.post("/:sessionId/tasks/:taskId/messages/:messageId/now", (c) => {
|
|
1089
|
+
router.post("/:sessionId/tasks/:taskId/messages/:messageId/now", async (c) => {
|
|
1099
1090
|
try {
|
|
1100
|
-
const
|
|
1101
|
-
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 });
|
|
1102
1093
|
} catch (err) {
|
|
1103
1094
|
return failure(c, err, "task edit");
|
|
1104
1095
|
}
|
|
@@ -1108,57 +1099,40 @@ function plansRoute(pool2) {
|
|
|
1108
1099
|
const body = await c.req.json().catch(() => ({}));
|
|
1109
1100
|
const text = typeof body === "object" && body !== null && "text" in body ? body.text : void 0;
|
|
1110
1101
|
if (typeof text !== "string" || !text.trim()) return c.json({ error: "text is required" }, 400);
|
|
1111
|
-
await runUserCommand(pool2.session(c.req.param("sessionId")), { command: "retry_task", task: c.req.param("taskId"), from: "session", message: text });
|
|
1112
|
-
return c.json({ ok: true });
|
|
1113
|
-
} catch (err) {
|
|
1114
|
-
return failure(c, err, "task edit");
|
|
1115
|
-
}
|
|
1116
|
-
});
|
|
1117
|
-
router.delete("/:sessionId/tasks/:taskId/messages/:messageId", (c) => {
|
|
1118
|
-
try {
|
|
1119
|
-
const removed = pool2.session(c.req.param("sessionId")).removeQueuedTaskMessage(c.req.param("taskId"), c.req.param("messageId"));
|
|
1120
|
-
return c.json({ removed });
|
|
1121
|
-
} catch (err) {
|
|
1122
|
-
return failure(c, err, "task edit");
|
|
1123
|
-
}
|
|
1124
|
-
});
|
|
1125
|
-
router.post("/:sessionId/tasks/:taskId/interrupt", async (c) => {
|
|
1126
|
-
try {
|
|
1127
|
-
await pool2.session(c.req.param("sessionId")).interruptTask(c.req.param("taskId"));
|
|
1128
|
-
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 });
|
|
1129
1104
|
} catch (err) {
|
|
1130
1105
|
return failure(c, err, "task edit");
|
|
1131
1106
|
}
|
|
1132
1107
|
});
|
|
1133
|
-
router.
|
|
1108
|
+
router.delete("/:sessionId/tasks/:taskId/messages/:messageId", async (c) => {
|
|
1134
1109
|
try {
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
if (!session.awaitsCheckpoint(taskId)) return refuse(c, 409, NO_CHECKPOINT, "checkpoint_not_waiting");
|
|
1138
|
-
session.approveCheckpoint(taskId);
|
|
1139
|
-
return c.json({ ok: true });
|
|
1140
|
-
} catch (err) {
|
|
1141
|
-
return failure(c, err, "task edit");
|
|
1142
|
-
}
|
|
1143
|
-
});
|
|
1144
|
-
router.post("/:sessionId/tasks/:taskId/checkpoint/reject", async (c) => {
|
|
1145
|
-
try {
|
|
1146
|
-
const body = await c.req.json().catch(() => ({}));
|
|
1147
|
-
const reason = typeof body === "object" && body !== null && "reason" in body && typeof body.reason === "string" ? body.reason.trim() : "";
|
|
1148
|
-
const session = pool2.session(c.req.param("sessionId"));
|
|
1149
|
-
const taskId = c.req.param("taskId");
|
|
1150
|
-
if (!session.awaitsCheckpoint(taskId)) return refuse(c, 409, NO_CHECKPOINT, "checkpoint_not_waiting");
|
|
1151
|
-
session.rejectCheckpoint(taskId, reason || void 0);
|
|
1152
|
-
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 });
|
|
1153
1112
|
} catch (err) {
|
|
1154
1113
|
return failure(c, err, "task edit");
|
|
1155
1114
|
}
|
|
1156
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
|
+
}
|
|
1157
1131
|
router.get("/:sessionId/tasks/:taskId/merge-gate", (c) => {
|
|
1158
1132
|
try {
|
|
1159
1133
|
const session = pool2.session(c.req.param("sessionId"));
|
|
1160
1134
|
const tasks = core.flattenTasks(session.planState?.tasks ?? []);
|
|
1161
|
-
const mergeGate = session.mergeGate(c.req.param("taskId")).map((id) => {
|
|
1135
|
+
const mergeGate = session.mergeGate(idOfRef(session, c.req.param("taskId"))).map((id) => {
|
|
1162
1136
|
const dep = tasks.find((t) => t.id === id);
|
|
1163
1137
|
return { id, order: dep?.order ?? 0, title: dep?.title ?? id };
|
|
1164
1138
|
});
|
|
@@ -1176,7 +1150,10 @@ function plansRoute(pool2) {
|
|
|
1176
1150
|
});
|
|
1177
1151
|
router.post("/:sessionId/isolation/merge", async (c) => {
|
|
1178
1152
|
try {
|
|
1179
|
-
|
|
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.");
|
|
1180
1157
|
} catch (err) {
|
|
1181
1158
|
return failure(c, err, "task edit");
|
|
1182
1159
|
}
|
|
@@ -1206,22 +1183,9 @@ function plansRoute(pool2) {
|
|
|
1206
1183
|
});
|
|
1207
1184
|
router.put("/:sessionId/tasks/:taskId", async (c) => {
|
|
1208
1185
|
try {
|
|
1209
|
-
const
|
|
1210
|
-
if (
|
|
1211
|
-
const
|
|
1212
|
-
const taskId = c.req.param("taskId");
|
|
1213
|
-
const hasRunner = typeof assignedRunner === "string";
|
|
1214
|
-
const hasDeps = Array.isArray(dependencies);
|
|
1215
|
-
let result = null;
|
|
1216
|
-
if (hasRunner) result = await session.setTaskRunner(taskId, assignedRunner);
|
|
1217
|
-
if (hasDeps) {
|
|
1218
|
-
try {
|
|
1219
|
-
result = await session.setTaskDependencies(taskId, dependencies.map(String));
|
|
1220
|
-
} catch (err) {
|
|
1221
|
-
return failure(c, err, "task dependencies", { fallback: 400 });
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
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);
|
|
1225
1189
|
if (!result) return refuse(c, 404, "Task not found", "task_not_found");
|
|
1226
1190
|
return c.json({ ok: true });
|
|
1227
1191
|
} catch (err) {
|
|
@@ -1249,8 +1213,11 @@ function plansRoute(pool2) {
|
|
|
1249
1213
|
});
|
|
1250
1214
|
router.post("/:sessionId/review/approve", async (c) => {
|
|
1251
1215
|
try {
|
|
1252
|
-
const
|
|
1253
|
-
|
|
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 });
|
|
1254
1221
|
} catch (err) {
|
|
1255
1222
|
return failure(c, err, "review approve");
|
|
1256
1223
|
}
|
|
@@ -1415,9 +1382,9 @@ function approvalsRoute(pool2) {
|
|
|
1415
1382
|
const answer = answerOf(await c.req.json().catch(() => null));
|
|
1416
1383
|
const decided = core.toApprovalDecision(answer);
|
|
1417
1384
|
const request = pool2.outstandingApprovals(sessionId).find((pending) => pending.id === approvalId)?.request;
|
|
1418
|
-
if (
|
|
1385
|
+
if (request?.taskId) {
|
|
1419
1386
|
const outcome = await pool2.session(sessionId).execute(
|
|
1420
|
-
{ 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 } : {} },
|
|
1421
1388
|
{ kind: "user" }
|
|
1422
1389
|
);
|
|
1423
1390
|
if (!outcome.ok) return refuse(c, 400, outcome.message, "refused");
|