@computesdk/loops 0.1.5 → 0.1.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/API.md +15 -1
- package/README.md +9 -0
- package/client.ts +2 -8
- package/dist/assets/{abnfDiagram-VCTEODGH-B9RuyvBD.js → abnfDiagram-VCTEODGH-BmAHMdfH.js} +1 -1
- package/dist/assets/{arc-oSrRugMo.js → arc-D_2zELzk.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-CEzHv5eh.js → architectureDiagram-5GKGNRK7-DbtMlGE1.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-B3sDGRLX.js → blockDiagram-I7D4REHJ-oeouAQDw.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-D00KlNWl.js → c4Diagram-7LVT6UL2-BRo4V2Z7.js} +1 -1
- package/dist/assets/channel-GK1ExE_g.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BN48Ke2K.js → chunk-2Q5K7J3B-1yTKgMZ4.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-9sDw2Q8Q.js → chunk-5VM5RSS4-BsMua1fA.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-BhGYOTAs.js → chunk-F27PBJKO-BFx0TMpb.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-B_ltGfuo.js → chunk-IMKFNOWR-D8UfXi3T.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BTV5s7zl.js → chunk-JWPE2WC7-bBfJHxEJ.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-BBbmsZMU.js → chunk-POPQ4Y6H-CLnUbfSj.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-BBVuPqvz.js → chunk-SVP7TREG-uWuiRTo9.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-DGOhII0-.js → chunk-TICWLB2K-DV5DzCt1.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-O9n8jFl3.js → chunk-XXDRQBXY-DflGukDx.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-veE8mBSk.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-veE8mBSk.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-Bg-fKdMW.js → cose-bilkent-JH36ORCC-CXrAPLcM.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-C0JTyL1D.js → cynefin-OW5HDTMX-BZAor1Fd.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-BKEcSvZC.js → cynefinDiagram-5FMLGOSQ-2mtwTNrl.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-CF35QAK7.js → dagre-GXQ25YYZ-BpwBh8sN.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-uYVv0n_8.js → diagram-S7CK7UJ4-BtWd6QOl.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-gJyJsotV.js → diagram-UQ7AKVKN-918gle9R.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-CIbzn0k5.js → diagram-VSXAHHWV-BrjIR_Ik.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-B3NSrsYt.js → diagram-VX7I27RA-C28OpNTD.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-Dtn85G8Z.js → diagram-Z3DM3KII-m__eZu-M.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-Bk3kpRQ6.js → ebnfDiagram-PWID7BFC-DX1w-kGZ.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-CQdpI2Az.js → erDiagram-RLTQ6QDP-B7Ixmqav.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-DFEdxXsx.js → flowDiagram-HODETNUW-Bom5HiIz.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-3M3mh5KH.js → ganttDiagram-EL5Y4UJY-oGHhHPme.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-D-VPIWMG.js → gitGraphDiagram-WWUBYQGX-BpdyfMmU.js} +1 -1
- package/dist/assets/{highlighted-body-JLMTFACH-CXpgazVU.js → highlighted-body-JLMTFACH-DqYsmdT7.js} +1 -1
- package/dist/assets/index-XP9FiM_q.js +902 -0
- package/dist/assets/{infoDiagram-27XIBGKW-mtm2MQD4.js → infoDiagram-27XIBGKW-CwHXRfO4.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-CCnoKccg.js → ishikawaDiagram-5VMMS53U-CDMEI2BQ.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-DGGM7lRo.js → journeyDiagram-3NMN7TZE-Ba4NonhC.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-BGmjik6L.js → kanban-definition-UXKFOSKX-B7X_m14V.js} +1 -1
- package/dist/assets/{layout-DMtoi_oP.js → layout-CYu8n8qm.js} +1 -1
- package/dist/assets/{linear-B9xGDkRB.js → linear-BBLkaaSf.js} +1 -1
- package/dist/assets/{mindmap-definition-YA3MSWOX-VS9bTKR0.js → mindmap-definition-YA3MSWOX-BEACV2JJ.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-_umSwImn.js → pegDiagram-XKGWAZYB-BpkdZ-6G.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-DwYDfmi7.js → pieDiagram-E7YTZNPT-_DiRoL0F.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-DKU_Wsvy.js → quadrantDiagram-AXDQQJYC-CG5zvdga.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-DXB5lt92.js → railroadDiagram-O6MQD6OU-G4hMBXmt.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-Dvnijc9_.js → requirementDiagram-BXWQKSXE-BkZ5guig.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-B0GfeizJ.js → sankeyDiagram-P5KCCOFB-C5PzZw7u.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-UZPfweQ-.js → sequenceDiagram-WJ2MYXX4-UAlhcqTK.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CZMGAWQe.js → sizeCapture-INFHLROL-DNRbEYss.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-B3JpYCVK.js → stateDiagram-D77RDMKH-DPCeQvnD.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-CtZUrpem.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-CZcyw1wj.js → swimlanes-42K2YHIH-Dr76qlly.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-CWvCwFtT.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-va5MgQAQ.js → timeline-definition-24CTP7MA-jelGqxUz.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-DolghlVs.js → vennDiagram-4TSXK5OY-CfIVRv2v.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DjA12Q4V.js → wardleyDiagram-VM6X3IG4-BM5Zu9IN.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-C0gKPjZI.js → xychartDiagram-S5SC5T6Z-DYG0fGby.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/server.mjs +162 -46
- package/package.json +3 -3
- package/src/shared/types.ts +13 -1
- package/dist/assets/channel-d6swnjWD.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-BIQ2Cra4.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-BIQ2Cra4.js +0 -1
- package/dist/assets/index-rRV0z5I8.js +0 -902
- package/dist/assets/stateDiagram-v2-MP3YSRHH-DcAN0ZgF.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-ColO3r6O.js +0 -8
package/dist/server.mjs
CHANGED
|
@@ -2923,6 +2923,7 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
2923
2923
|
model: pretty(a.model),
|
|
2924
2924
|
style: a.style,
|
|
2925
2925
|
status: a.status ?? "done",
|
|
2926
|
+
cut: a.cut,
|
|
2926
2927
|
score,
|
|
2927
2928
|
kept,
|
|
2928
2929
|
humanPick: !!(kept && (r.human || chosen.round === r.n && !!chosen.label && chosen.label !== r.pick)),
|
|
@@ -2956,6 +2957,7 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
2956
2957
|
judgeCost: r.judgeCost ?? void 0,
|
|
2957
2958
|
startedAt: r.startedAt ?? void 0,
|
|
2958
2959
|
endedAt: r.endedAt ?? void 0,
|
|
2960
|
+
status: r.status,
|
|
2959
2961
|
inconclusive: !!r.inconclusive,
|
|
2960
2962
|
unseen: r.unseen ?? [],
|
|
2961
2963
|
...r.escaped ? { escaped: r.escaped } : {}
|
|
@@ -2979,8 +2981,19 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
2979
2981
|
}));
|
|
2980
2982
|
const round = { n, from: { round: n - 1, label: "CURRENT" }, attempts, inProgress: true, judging: /judg/.test(live.phase ?? ""), why: void 0 };
|
|
2981
2983
|
const at = rounds.findIndex((r) => r.n === n);
|
|
2982
|
-
if (at >= 0)
|
|
2983
|
-
|
|
2984
|
+
if (at >= 0) {
|
|
2985
|
+
const saved = rounds[at].attempts;
|
|
2986
|
+
const incoming = new Map(attempts.map((a) => [a.name, a]));
|
|
2987
|
+
rounds[at] = {
|
|
2988
|
+
...rounds[at],
|
|
2989
|
+
inProgress: true,
|
|
2990
|
+
judging: round.judging,
|
|
2991
|
+
attempts: [
|
|
2992
|
+
...saved.map((a) => incoming.has(a.name) ? { ...a, ...incoming.get(a.name), hasSession: a.hasSession || incoming.get(a.name).hasSession } : a),
|
|
2993
|
+
...attempts.filter((a) => !saved.some((old) => old.name === a.name))
|
|
2994
|
+
]
|
|
2995
|
+
};
|
|
2996
|
+
} else rounds.push(round);
|
|
2984
2997
|
}
|
|
2985
2998
|
const bestRound = state?.best?.round ?? roundEntries.filter((r) => r.pick && r.pick !== "CURRENT").at(-1)?.n ?? 0;
|
|
2986
2999
|
const bestEntry = roundEntries.filter((r) => r.n === bestRound).at(-1);
|
|
@@ -2992,12 +3005,27 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
2992
3005
|
const firstT = Number(roundEntries[0]?.startedAt ?? Date.parse(head.at ?? "") ?? Date.now());
|
|
2993
3006
|
log.push({ t: Number.isFinite(firstT) ? firstT : Date.now(), kind: "start", text: `Started ${head.goal ?? state?.goal ?? ""}`.trim() });
|
|
2994
3007
|
for (const r of roundEntries) {
|
|
2995
|
-
|
|
3008
|
+
const judged = (!r.status || r.status === "done") && !!r.pick && !r.inconclusive && !r.unseen?.length;
|
|
3009
|
+
if (judged) log.push({ t: Number(r.endedAt ?? r.at ?? Date.now()), kind: "judged", text: "", round: r.n, pick: r.pick, why: r.why ?? void 0, scores: r.scores ?? void 0 });
|
|
3010
|
+
else if (r.status !== "running") log.push({
|
|
3011
|
+
t: Number(r.endedAt ?? r.at ?? Date.now()),
|
|
3012
|
+
kind: "round",
|
|
3013
|
+
round: r.n,
|
|
3014
|
+
text: `Round ${r.n}: ${r.status === "aborted" ? "stopped before comparison completed" : r.status === "error" ? "failed before comparison completed" : "no completed comparison"}.`
|
|
3015
|
+
});
|
|
2996
3016
|
}
|
|
2997
3017
|
const wrote = state?.status ?? head.status ?? "done";
|
|
2998
3018
|
const stalled = !alive && wrote === "running";
|
|
2999
3019
|
const status = alive ? "running" : stalled ? "stalled" : wrote;
|
|
3000
|
-
if (!alive &&
|
|
3020
|
+
if (!alive && ["done", "aborted", "error"].includes(wrote)) {
|
|
3021
|
+
const reason = state?.stopReason ?? state?.error;
|
|
3022
|
+
const compared = log.filter((e) => e.kind === "judged").length;
|
|
3023
|
+
log.push({
|
|
3024
|
+
t: Number(roundEntries.at(-1)?.endedAt ?? Date.now()),
|
|
3025
|
+
kind: "done",
|
|
3026
|
+
text: wrote === "aborted" ? `Stopped; worktrees kept.${reason ? ` ${reason}` : ""}` : wrote === "error" ? `Failed: ${reason || "run failed before a usable result"}.` : compared ? `finished after ${compared} compared round(s)` : "Ended with no completed comparison."
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3001
3029
|
const tokens = roundEntries.reduce((sum, r) => sum + (r.attempts ?? []).reduce((s, a) => s + (a.tokens ?? 0), 0) + (r.judgeTokens ?? 0), 0) + (live ? (live.attempts ?? []).reduce((s, a) => s + (a.tokens ?? 0), 0) : 0);
|
|
3002
3030
|
return {
|
|
3003
3031
|
id,
|
|
@@ -3009,7 +3037,7 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
3009
3037
|
goal: state?.goal ?? head.goal ?? "",
|
|
3010
3038
|
status: status === "aborted" ? "done" : status,
|
|
3011
3039
|
alive,
|
|
3012
|
-
stopReason: stalled ? "no signal from the round \u2014 the process went away" : status === "aborted" ? "aborted" :
|
|
3040
|
+
stopReason: stalled ? "no signal from the round \u2014 the process went away" : status === "aborted" ? "aborted" : state?.stopReason ?? state?.error,
|
|
3013
3041
|
rules: rules.map((text) => ({ text, source: "config" })),
|
|
3014
3042
|
rounds,
|
|
3015
3043
|
best: {
|
|
@@ -3056,7 +3084,15 @@ function summarize(cwd, entries) {
|
|
|
3056
3084
|
lastT = t;
|
|
3057
3085
|
const bestRound = state?.best?.round ?? rounds.filter((r) => r.pick && r.pick !== "CURRENT").at(-1)?.n;
|
|
3058
3086
|
const bestEntry = rounds.find((r) => r.n === bestRound);
|
|
3059
|
-
|
|
3087
|
+
const attempts = rounds.flatMap((r) => r.attempts ?? []);
|
|
3088
|
+
const failures = /* @__PURE__ */ new Map();
|
|
3089
|
+
for (const a of attempts) {
|
|
3090
|
+
const why = a.status === "budget" ? "token budget" : a.status === "timeout" ? "timed out" : String(a.status).startsWith("error") ? "error" : a.status === "aborted" ? "stopped" : null;
|
|
3091
|
+
if (why) failures.set(why, (failures.get(why) ?? 0) + 1);
|
|
3092
|
+
}
|
|
3093
|
+
const details = [...failures].map(([why, n]) => `${n} ${why}`).join(", ");
|
|
3094
|
+
const outcome = alive ? void 0 : status === "aborted" ? "Stopped \u2014 worktrees kept" : rounds.at(-1)?.inconclusive || rounds.at(-1)?.unseen?.length ? "Inconclusive \u2014 judging incomplete" : attempts.length && attempts.every((a) => !["done", "unchanged", "no-change"].includes(a.status)) ? `No candidates completed${details ? ` \xB7 ${details}` : ""}` : status === "error" ? `Failed: ${state?.stopReason ?? state?.error ?? "run failed"}` : !rounds.length ? "No usable result \u2014 no rounds completed" : void 0;
|
|
3095
|
+
latest = { name: goalName(state?.goal ?? head.goal ?? id), status: stalled ? "stalled" : status, kind: head.kind ?? state?.kind ?? "visual", score: state?.best?.score ?? (bestEntry?.pick ? bestEntry.scores?.[bestEntry.pick] : void 0), unit: state?.metric?.unit ?? rounds.at(-1)?.metric?.unit ?? "", rounds: rounds.length, ...outcome ? { outcome } : {} };
|
|
3060
3096
|
}
|
|
3061
3097
|
}
|
|
3062
3098
|
return { loops: ids.length, running, open, question, lastT, latest };
|
|
@@ -3278,6 +3314,12 @@ var Chat = class {
|
|
|
3278
3314
|
}
|
|
3279
3315
|
onEvent(event) {
|
|
3280
3316
|
const type = event.type;
|
|
3317
|
+
if (type === "extension_error") {
|
|
3318
|
+
this.notices.push({ t: Date.now(), level: "error", message: String(event.error ?? event.message ?? "Extension command failed") });
|
|
3319
|
+
this.notices = this.notices.slice(-20);
|
|
3320
|
+
this.broadcast(true);
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3281
3323
|
if (type === "message_start" && event.message?.role === "assistant") {
|
|
3282
3324
|
this.live = { busy: true, chat: this.id, startedAt: Date.now(), user: "", text: "", tools: [] };
|
|
3283
3325
|
this.broadcast();
|
|
@@ -3330,11 +3372,18 @@ var Chat = class {
|
|
|
3330
3372
|
this.touch();
|
|
3331
3373
|
}
|
|
3332
3374
|
async prompt(text, behavior) {
|
|
3333
|
-
|
|
3375
|
+
const previous = this.live;
|
|
3376
|
+
if (!text.startsWith("/") && !this.live) this.live = { busy: true, chat: this.id, startedAt: Date.now(), user: text, text: "", tools: [] };
|
|
3334
3377
|
this.broadcast(true);
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3378
|
+
try {
|
|
3379
|
+
const r = await this.pi.prompt(text, behavior);
|
|
3380
|
+
setTimeout(() => void this.refresh(), 400);
|
|
3381
|
+
return r?.disposition ?? "started";
|
|
3382
|
+
} catch (error) {
|
|
3383
|
+
if (!previous && this.live?.user === text) this.live = null;
|
|
3384
|
+
this.broadcast(true);
|
|
3385
|
+
throw error;
|
|
3386
|
+
}
|
|
3338
3387
|
}
|
|
3339
3388
|
/** The child's own view of "am I busy": a turn that settled with no event reaching us must not strand the UI. */
|
|
3340
3389
|
async settleIfIdle() {
|
|
@@ -3378,11 +3427,16 @@ var Chat = class {
|
|
|
3378
3427
|
this.answeringUi.delete(id);
|
|
3379
3428
|
}
|
|
3380
3429
|
}
|
|
3430
|
+
aborting = false;
|
|
3381
3431
|
async abort() {
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3432
|
+
if (this.aborting) throw new Error("Stop agent is already in flight.");
|
|
3433
|
+
this.aborting = true;
|
|
3434
|
+
try {
|
|
3435
|
+
await this.pi.send({ type: "abort" }, 15e3);
|
|
3436
|
+
await this.settleIfIdle();
|
|
3437
|
+
} finally {
|
|
3438
|
+
this.aborting = false;
|
|
3439
|
+
}
|
|
3386
3440
|
}
|
|
3387
3441
|
/** The newest session entry of a kind (`loops-doctor`, `loops-run`, …), for read-only views. */
|
|
3388
3442
|
custom(type) {
|
|
@@ -4740,7 +4794,7 @@ var withLive = (state) => {
|
|
|
4740
4794
|
return state;
|
|
4741
4795
|
};
|
|
4742
4796
|
var streams = /* @__PURE__ */ new Set();
|
|
4743
|
-
var snapshotOf = (chat, wsId) => withLive({ ...chat.snapshot(), chats: chatsOf(wsId), workspaces: workspaces.list(), ws: wsId, proposals });
|
|
4797
|
+
var snapshotOf = (chat, wsId) => withLive({ ...chat.snapshot(), chats: chatsOf(wsId), workspaces: workspaces.list(), ws: wsId, proposals, configErrors: [...configErrors.values()].filter((e) => e.ws === wsId) });
|
|
4744
4798
|
manager.onChange((chat) => {
|
|
4745
4799
|
const space = workspaces.ofChat(chat.id);
|
|
4746
4800
|
const state = snapshotOf(chat, space.id);
|
|
@@ -4748,6 +4802,7 @@ manager.onChange((chat) => {
|
|
|
4748
4802
|
});
|
|
4749
4803
|
var watches = /* @__PURE__ */ new Map();
|
|
4750
4804
|
var proposals = [];
|
|
4805
|
+
var configErrors = /* @__PURE__ */ new Map();
|
|
4751
4806
|
var watchOf = (wsId) => {
|
|
4752
4807
|
const ws = workspaces.get(wsId);
|
|
4753
4808
|
let w = watches.get(ws.id);
|
|
@@ -4765,7 +4820,14 @@ var watchOf = (wsId) => {
|
|
|
4765
4820
|
fs9.watch(path9.join(ws.path, ".pi"), (_e, name) => {
|
|
4766
4821
|
if (name && name !== "loops.json") return;
|
|
4767
4822
|
clearTimeout(watchTimers.get(ws.id));
|
|
4768
|
-
watchTimers.set(ws.id, setTimeout(() =>
|
|
4823
|
+
watchTimers.set(ws.id, setTimeout(() => {
|
|
4824
|
+
try {
|
|
4825
|
+
checkConfig(ws.id);
|
|
4826
|
+
} catch (e) {
|
|
4827
|
+
configErrors.set(ws.id, { ws: ws.id, file: path9.join(ws.path, ".pi/loops.json"), error: `Could not validate settings: ${e.message}. Fix the file or save settings again.` });
|
|
4828
|
+
notifyProposals();
|
|
4829
|
+
}
|
|
4830
|
+
}, 120));
|
|
4769
4831
|
});
|
|
4770
4832
|
} catch {
|
|
4771
4833
|
}
|
|
@@ -4774,12 +4836,18 @@ var watchOf = (wsId) => {
|
|
|
4774
4836
|
};
|
|
4775
4837
|
var watchTimers = /* @__PURE__ */ new Map();
|
|
4776
4838
|
function notifyProposals() {
|
|
4777
|
-
const payload = JSON.stringify({ type: "proposals", proposals });
|
|
4778
4839
|
for (const s of streams) {
|
|
4779
4840
|
try {
|
|
4780
4841
|
s.send("proposals", proposals);
|
|
4781
4842
|
} catch {
|
|
4782
4843
|
}
|
|
4844
|
+
const chat = manager.openChats().find((chat2) => chat2.id === s.chat);
|
|
4845
|
+
if (chat) {
|
|
4846
|
+
try {
|
|
4847
|
+
s.send("state", snapshotOf(chat, workspaces.ofChat(chat.id).id), chat.entryCursor);
|
|
4848
|
+
} catch {
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4783
4851
|
}
|
|
4784
4852
|
}
|
|
4785
4853
|
function checkConfig(wsId) {
|
|
@@ -4792,14 +4860,33 @@ function checkConfig(wsId) {
|
|
|
4792
4860
|
return "";
|
|
4793
4861
|
}
|
|
4794
4862
|
})();
|
|
4863
|
+
let next;
|
|
4864
|
+
try {
|
|
4865
|
+
next = raw2.trim() ? JSON.parse(raw2) : {};
|
|
4866
|
+
if (!next || typeof next !== "object" || Array.isArray(next)) throw new Error("expected a JSON object");
|
|
4867
|
+
} catch (e) {
|
|
4868
|
+
let recovery = "";
|
|
4869
|
+
try {
|
|
4870
|
+
const backup = `${CONFIG}.invalid-${Date.now()}`;
|
|
4871
|
+
fs9.writeFileSync(backup, raw2);
|
|
4872
|
+
restoreConfig(w.cwd, w.accepted);
|
|
4873
|
+
recovery = ` Last accepted settings restored. Rejected text saved in ${backup}.`;
|
|
4874
|
+
} catch (writeError) {
|
|
4875
|
+
recovery = ` Could not restore settings: ${writeError.message}. Fix ${CONFIG} before running another loop.`;
|
|
4876
|
+
}
|
|
4877
|
+
configErrors.set(wsId, { ws: wsId, file: CONFIG, error: `Invalid config JSON: ${e.message}.${recovery}` });
|
|
4878
|
+
notifyProposals();
|
|
4879
|
+
return;
|
|
4880
|
+
}
|
|
4795
4881
|
if (isSelfWriting() || isSelfWrite(raw2)) {
|
|
4796
|
-
w.accepted =
|
|
4882
|
+
w.accepted = next;
|
|
4797
4883
|
return;
|
|
4798
4884
|
}
|
|
4799
|
-
const
|
|
4885
|
+
const hadError = configErrors.delete(wsId);
|
|
4800
4886
|
const changes = diffConfig(w.accepted, next);
|
|
4801
4887
|
if (!changes.length) {
|
|
4802
4888
|
w.accepted = next;
|
|
4889
|
+
if (hadError) notifyProposals();
|
|
4803
4890
|
return;
|
|
4804
4891
|
}
|
|
4805
4892
|
restoreConfig(w.cwd, w.accepted);
|
|
@@ -4912,7 +4999,7 @@ api.get("/state", async (c) => {
|
|
|
4912
4999
|
if (!id) return c.json({ error: "chat required" }, 400);
|
|
4913
5000
|
const chat = await manager.open(id);
|
|
4914
5001
|
const ws = workspaces.ofChat(id);
|
|
4915
|
-
return c.json(
|
|
5002
|
+
return c.json(snapshotOf(chat, ws.id));
|
|
4916
5003
|
});
|
|
4917
5004
|
api.post("/chat", async (c) => {
|
|
4918
5005
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -4988,10 +5075,15 @@ api.post("/rename", async (c) => {
|
|
|
4988
5075
|
return c.json({ ok: true, title: chat.title });
|
|
4989
5076
|
});
|
|
4990
5077
|
api.post("/abort", async (c) => {
|
|
4991
|
-
const { chat: id } = await c.req.json();
|
|
4992
|
-
|
|
4993
|
-
await
|
|
4994
|
-
|
|
5078
|
+
const { chat: id } = await c.req.json().catch(() => ({})) ?? {};
|
|
5079
|
+
if (typeof id !== "string" || !id.trim()) return c.json({ error: "chat required" }, 400);
|
|
5080
|
+
const chat = await manager.open(id);
|
|
5081
|
+
try {
|
|
5082
|
+
await chat.abort();
|
|
5083
|
+
return c.json({ ok: true, requested: true });
|
|
5084
|
+
} catch (e) {
|
|
5085
|
+
return c.json({ error: e.message }, 409);
|
|
5086
|
+
}
|
|
4995
5087
|
});
|
|
4996
5088
|
api.post("/chat/retry", async (c) => {
|
|
4997
5089
|
const { chat: id } = await c.req.json();
|
|
@@ -5019,29 +5111,51 @@ api.post("/answer", async (c) => {
|
|
|
5019
5111
|
await chat.prompt(`/loop answer ${String(question)} ${String(choice)}`);
|
|
5020
5112
|
return c.json({ ok: true });
|
|
5021
5113
|
});
|
|
5114
|
+
var steering = /* @__PURE__ */ new Set();
|
|
5022
5115
|
api.post("/steer", async (c) => {
|
|
5023
|
-
const { chat: id, run: run3, action, rule, attempt, text } = await c.req.json();
|
|
5024
|
-
const
|
|
5116
|
+
const { chat: id, run: run3, action, rule, attempt, text } = await c.req.json().catch(() => ({})) ?? {};
|
|
5117
|
+
const token = (v) => typeof v === "string" && /^[\w.-]+$/.test(v);
|
|
5118
|
+
if (!token(id) || !token(run3)) return c.json({ error: "chat and exact run ID required" }, 400);
|
|
5119
|
+
if (!["pick", "take", "apply", "stop", "clean", "rule", "talk", "more"].includes(action)) return c.json({ error: "valid action required" }, 400);
|
|
5120
|
+
if (["pick", "take", "talk"].includes(action) && !token(attempt)) return c.json({ error: "attempt required (durable name, e.g. R1-safe)" }, 400);
|
|
5121
|
+
if (action === "rule" && (typeof rule !== "string" || !rule.trim())) return c.json({ error: "rule required" }, 400);
|
|
5122
|
+
if (action === "talk" && (typeof text !== "string" || !text.trim())) return c.json({ error: "text required" }, 400);
|
|
5123
|
+
if (action === "more" && text !== void 0 && (!Number.isInteger(text) || text < 1 || text > 100)) return c.json({ error: "more rounds must be an integer from 1 to 100" }, 400);
|
|
5124
|
+
const chat = await manager.open(id);
|
|
5125
|
+
if (!chat.hasRun(run3)) return c.json({ error: "run does not belong to this chat" }, 404);
|
|
5126
|
+
if (action === "stop" && !chat.snapshot().runs.find((r) => r.id === run3)?.alive) return c.json({ error: "Selected run is not active. Read its latest session state." }, 409);
|
|
5127
|
+
if (["pick", "take", "talk"].includes(action) && !chat.snapshot().runs.find((r) => r.id === run3)?.rounds.some((r) => r.attempts.some((a) => a.name === attempt))) return c.json({ error: "unknown attempt for selected run" }, 400);
|
|
5128
|
+
const key = `${id}:${run3}:${action}`;
|
|
5129
|
+
if (steering.has(key)) return c.json({ error: "This action is already in flight." }, 409);
|
|
5130
|
+
steering.add(key);
|
|
5025
5131
|
const slash = (cmd) => chat.prompt(cmd);
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5132
|
+
try {
|
|
5133
|
+
switch (String(action)) {
|
|
5134
|
+
case "pick":
|
|
5135
|
+
case "take":
|
|
5136
|
+
return c.json({ ok: true, sent: await slash(`/loop take ${attempt} ${run3}`) });
|
|
5137
|
+
case "apply":
|
|
5138
|
+
return c.json({ ok: true, sent: await slash(`/loop apply ${run3}`) });
|
|
5139
|
+
// Pi executes extension commands before its streaming queue. Do not send this as a queued follow-up:
|
|
5140
|
+
// a background round must remain independently stoppable while the parent is in a long-running tool.
|
|
5141
|
+
case "stop":
|
|
5142
|
+
return c.json({ ok: true, requested: true, sent: await slash(`/loop stop ${run3}`) });
|
|
5143
|
+
case "clean":
|
|
5144
|
+
return c.json({ ok: true, sent: await slash(`/loop clean ${run3}`) });
|
|
5145
|
+
case "rule":
|
|
5146
|
+
return c.json({ ok: true, sent: await slash(`/loop rule ${run3} ${rule}`) });
|
|
5147
|
+
case "talk":
|
|
5148
|
+
return c.json({ ok: true, sent: await slash(`/loop talk ${attempt} ${text}`) });
|
|
5149
|
+
// everything else is a decision only the agent can make (another round, a different direction)
|
|
5150
|
+
case "more":
|
|
5151
|
+
return c.json({ ok: true, sent: await slash(`Continue loop ${run3} with ${Number(text) || 2} more rounds. Check loop_status first, then call loop({ from: "${run3}", rounds: ${Number(text) || 2} }).`) });
|
|
5152
|
+
default:
|
|
5153
|
+
return c.json({ error: `unknown action ${action}` }, 400);
|
|
5154
|
+
}
|
|
5155
|
+
} catch (e) {
|
|
5156
|
+
return c.json({ error: e.message }, 409);
|
|
5157
|
+
} finally {
|
|
5158
|
+
steering.delete(key);
|
|
5045
5159
|
}
|
|
5046
5160
|
});
|
|
5047
5161
|
api.get("/ui", async (c) => {
|
|
@@ -5144,18 +5258,20 @@ api.post("/proposals/:id/keep", (c) => {
|
|
|
5144
5258
|
api.get("/config", (c) => {
|
|
5145
5259
|
const ws = wsOf(c.req.query("chat"));
|
|
5146
5260
|
watchOf(ws.id);
|
|
5147
|
-
return c.json({ cwd: ws.path, ws: ws.id, keys: effectiveConfig(ws.path) });
|
|
5261
|
+
return c.json({ cwd: ws.path, ws: ws.id, keys: effectiveConfig(ws.path), error: configErrors.get(ws.id)?.error });
|
|
5148
5262
|
});
|
|
5149
5263
|
api.post("/config", async (c) => {
|
|
5150
5264
|
const { key, value, chat } = await c.req.json();
|
|
5151
5265
|
const ws = wsOf(chat);
|
|
5152
5266
|
try {
|
|
5153
5267
|
writeSetting(ws.path, String(key), value);
|
|
5268
|
+
configErrors.delete(ws.id);
|
|
5154
5269
|
const w = watchOf(ws.id);
|
|
5155
5270
|
try {
|
|
5156
5271
|
w.accepted = JSON.parse(fs9.readFileSync(path9.join(ws.path, ".pi", "loops.json"), "utf8"));
|
|
5157
5272
|
} catch {
|
|
5158
5273
|
}
|
|
5274
|
+
notifyProposals();
|
|
5159
5275
|
return c.json({ ok: true });
|
|
5160
5276
|
} catch (e) {
|
|
5161
5277
|
return c.json({ ok: false, error: String(e.message ?? e) }, 400);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@computesdk/loops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Loops for people who do not live in a terminal: one command starts a localhost UI that runs, watches and steers hill-climbing agents in your repos.",
|
|
6
6
|
"keywords": [
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"start": "node --experimental-strip-types server/index.ts",
|
|
38
38
|
"check": "tsc --noEmit && tsc --noEmit -p tsconfig.client.json",
|
|
39
39
|
"shots": "node shots.mjs",
|
|
40
|
-
"test": "esbuild test/compare.ts --bundle --platform=node --format=cjs --outfile=node_modules/.tmp/compare.cjs --loader:.tsx=tsx --jsx=automatic --log-level=error && node node_modules/.tmp/compare.cjs && node --experimental-strip-types test/runs.ts && node --experimental-strip-types test/workspaces.ts && node --experimental-strip-types test/chat-lifecycle.ts && node --experimental-strip-types test/workspace-api.ts && node --experimental-strip-types test/api.ts && node --experimental-strip-types test/cli.ts && node --experimental-strip-types test/pi-startup.ts",
|
|
41
|
-
"test:browser": "node test/workspace-browser.mjs && node test/pi-dialog-browser.mjs"
|
|
40
|
+
"test": "esbuild test/compare.ts --bundle --platform=node --format=cjs --outfile=node_modules/.tmp/compare.cjs --loader:.tsx=tsx --jsx=automatic --log-level=error && node node_modules/.tmp/compare.cjs && node --experimental-strip-types test/runs.ts && node --experimental-strip-types test/workspaces.ts && node --experimental-strip-types test/chat-lifecycle.ts && node --experimental-strip-types test/workspace-api.ts && node --experimental-strip-types test/api.ts && node --experimental-strip-types test/stop-api.ts && node --experimental-strip-types test/cli.ts && node --experimental-strip-types test/pi-startup.ts",
|
|
41
|
+
"test:browser": "node test/workspace-browser.mjs && node test/pi-dialog-browser.mjs && node test/stop-browser.mjs"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@computesdk/pi-loops": "^1.0.7",
|
package/src/shared/types.ts
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
// Pi's session entries (`loops-run`, `loops-round`, `loops-question`, tool results), the run's state file and
|
|
3
3
|
// the worktrees it owns. Nothing polls: a live round arrives as `tool_execution_update` from the RPC child.
|
|
4
4
|
|
|
5
|
+
/** Engine actions. Attempt is the durable name (R1-safe), never the display letter (A). */
|
|
6
|
+
export type SteerAction =
|
|
7
|
+
| { action: "pick" | "take"; attempt: string }
|
|
8
|
+
| { action: "apply" | "stop" | "clean" }
|
|
9
|
+
| { action: "rule"; rule: string }
|
|
10
|
+
| { action: "talk"; attempt: string; text: string }
|
|
11
|
+
| { action: "more"; text?: number };
|
|
12
|
+
|
|
5
13
|
export type RunAttempt = {
|
|
6
14
|
/** `run:rN-L` */
|
|
7
15
|
id: string;
|
|
@@ -12,6 +20,7 @@ export type RunAttempt = {
|
|
|
12
20
|
style: string;
|
|
13
21
|
/** running | done | error | unchanged | aborted | timeout | budget */
|
|
14
22
|
status: string;
|
|
23
|
+
cut?: string;
|
|
15
24
|
score?: number;
|
|
16
25
|
kept?: boolean;
|
|
17
26
|
humanPick?: boolean;
|
|
@@ -58,6 +67,7 @@ export type RunRound = {
|
|
|
58
67
|
shots?: Record<string, string[]>;
|
|
59
68
|
/** the judge never saw the images: nothing in this round was compared */
|
|
60
69
|
inconclusive?: boolean;
|
|
70
|
+
status?: string;
|
|
61
71
|
/** an attempt committed into your checkout; `restored` says whether it was put back */
|
|
62
72
|
escaped?: { from: string; to: string; restored?: boolean } | null;
|
|
63
73
|
/** candidates whose stamped code the judge could not read back */
|
|
@@ -137,7 +147,7 @@ export type ChatSummary = {
|
|
|
137
147
|
/** the newest open question, if any */
|
|
138
148
|
question?: { kind: string; round?: number; title: string };
|
|
139
149
|
lastT: number;
|
|
140
|
-
latest?: { name: string; status: string; kind: "visual" | "bench"; score?: number; unit?: string; rounds: number };
|
|
150
|
+
latest?: { name: string; status: string; kind: "visual" | "bench"; score?: number; unit?: string; rounds: number; outcome?: string };
|
|
141
151
|
/** a run whose process went away: it says running, nothing is working */
|
|
142
152
|
};
|
|
143
153
|
|
|
@@ -176,10 +186,12 @@ export type ChatState = {
|
|
|
176
186
|
items: ParentItem[];
|
|
177
187
|
live: ParentLive | null;
|
|
178
188
|
error?: string;
|
|
189
|
+
notices?: { t: number; level: string; message: string }[];
|
|
179
190
|
runs: Run[];
|
|
180
191
|
model?: string | null;
|
|
181
192
|
/** open questions for chats that are not loaded, so the sidebar can count them */
|
|
182
193
|
busyElsewhere?: boolean;
|
|
183
194
|
/** config writes waiting for a yes/no; the file is unchanged until one */
|
|
184
195
|
proposals?: ConfigProposal[];
|
|
196
|
+
configErrors?: { ws: string; file: string; error: string }[];
|
|
185
197
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./index-rRV0z5I8.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-TICWLB2K-DGOhII0-.js";import{_ as i}from"./index-rRV0z5I8.js";import"./chunk-5VM5RSS4-9sDw2Q8Q.js";import"./chunk-XXDRQBXY-O9n8jFl3.js";import"./chunk-POPQ4Y6H-BBbmsZMU.js";import"./chunk-F27PBJKO-BhGYOTAs.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-TICWLB2K-DGOhII0-.js";import{_ as i}from"./index-rRV0z5I8.js";import"./chunk-5VM5RSS4-9sDw2Q8Q.js";import"./chunk-XXDRQBXY-O9n8jFl3.js";import"./chunk-POPQ4Y6H-BBbmsZMU.js";import"./chunk-F27PBJKO-BhGYOTAs.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
|