@computesdk/loops 0.1.5 → 0.1.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/API.md +15 -1
- package/README.md +9 -0
- package/client.ts +2 -8
- package/dist/assets/{abnfDiagram-VCTEODGH-B9RuyvBD.js → abnfDiagram-VCTEODGH-BKE3wXte.js} +1 -1
- package/dist/assets/{arc-oSrRugMo.js → arc-C6ZC2rP7.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-CEzHv5eh.js → architectureDiagram-5GKGNRK7-BP4EuLVY.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-B3sDGRLX.js → blockDiagram-I7D4REHJ-BLw7XXPz.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-D00KlNWl.js → c4Diagram-7LVT6UL2-Bx80SfZM.js} +1 -1
- package/dist/assets/channel-D148DRm1.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BN48Ke2K.js → chunk-2Q5K7J3B-BPBebY_E.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-9sDw2Q8Q.js → chunk-5VM5RSS4-QL5btn2z.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-BhGYOTAs.js → chunk-F27PBJKO-DwZ1yutT.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-B_ltGfuo.js → chunk-IMKFNOWR-D_oIgde6.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BTV5s7zl.js → chunk-JWPE2WC7-B7803oA6.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-BBbmsZMU.js → chunk-POPQ4Y6H-BVGDPtWK.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-BBVuPqvz.js → chunk-SVP7TREG-LBP3GH9l.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-DGOhII0-.js → chunk-TICWLB2K-BI5m3CJb.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-O9n8jFl3.js → chunk-XXDRQBXY-kxt8MH_7.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-CncavLbb.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-CncavLbb.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-Bg-fKdMW.js → cose-bilkent-JH36ORCC-9ujaMun3.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-C0JTyL1D.js → cynefin-OW5HDTMX-D-nWdDZr.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-BKEcSvZC.js → cynefinDiagram-5FMLGOSQ-CI6Scxp6.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-CF35QAK7.js → dagre-GXQ25YYZ-B8xNwtVY.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-uYVv0n_8.js → diagram-S7CK7UJ4-CbG9exCu.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-gJyJsotV.js → diagram-UQ7AKVKN-DJEVVOdt.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-CIbzn0k5.js → diagram-VSXAHHWV-DXqMiZ59.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-B3NSrsYt.js → diagram-VX7I27RA-BgUsFlGi.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-Dtn85G8Z.js → diagram-Z3DM3KII-15W3Mbq6.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-Bk3kpRQ6.js → ebnfDiagram-PWID7BFC-QnWuy49w.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-CQdpI2Az.js → erDiagram-RLTQ6QDP-CtFAKfHK.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-DFEdxXsx.js → flowDiagram-HODETNUW-BXiSgIdj.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-3M3mh5KH.js → ganttDiagram-EL5Y4UJY-0X4MJoKa.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-D-VPIWMG.js → gitGraphDiagram-WWUBYQGX-D1RSU4p4.js} +1 -1
- package/dist/assets/{highlighted-body-JLMTFACH-CXpgazVU.js → highlighted-body-JLMTFACH-DJpqqoLg.js} +1 -1
- package/dist/assets/index-B3Ugq2o6.js +902 -0
- package/dist/assets/{infoDiagram-27XIBGKW-mtm2MQD4.js → infoDiagram-27XIBGKW-DHPDHcTh.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-CCnoKccg.js → ishikawaDiagram-5VMMS53U-BaYQe1ZZ.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-DGGM7lRo.js → journeyDiagram-3NMN7TZE-gQ5s_wv1.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-BGmjik6L.js → kanban-definition-UXKFOSKX-BxwSa57H.js} +1 -1
- package/dist/assets/{layout-DMtoi_oP.js → layout-thi9yMYl.js} +1 -1
- package/dist/assets/{linear-B9xGDkRB.js → linear-C3tNDt0i.js} +1 -1
- package/dist/assets/{mindmap-definition-YA3MSWOX-VS9bTKR0.js → mindmap-definition-YA3MSWOX-D4R4OXev.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-_umSwImn.js → pegDiagram-XKGWAZYB-BnVd3_9Z.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-DwYDfmi7.js → pieDiagram-E7YTZNPT-CwX8mvdJ.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-DKU_Wsvy.js → quadrantDiagram-AXDQQJYC-CoiKagpY.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-DXB5lt92.js → railroadDiagram-O6MQD6OU-DZCee713.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-Dvnijc9_.js → requirementDiagram-BXWQKSXE-BCksgH3P.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-B0GfeizJ.js → sankeyDiagram-P5KCCOFB-B0xOVx2_.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-UZPfweQ-.js → sequenceDiagram-WJ2MYXX4-8myX-w3k.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CZMGAWQe.js → sizeCapture-INFHLROL-DtAA6XEA.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-B3JpYCVK.js → stateDiagram-D77RDMKH-eNo1EA8t.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-BZn7zD-F.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-CZcyw1wj.js → swimlanes-42K2YHIH-CuZGWGh2.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-BG9s9tCY.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-va5MgQAQ.js → timeline-definition-24CTP7MA-lyUBY_Xm.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-DolghlVs.js → vennDiagram-4TSXK5OY-BaFKzCtR.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DjA12Q4V.js → wardleyDiagram-VM6X3IG4-DMcUcLi9.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-C0gKPjZI.js → xychartDiagram-S5SC5T6Z-BVotbEcG.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/server.mjs +165 -47
- 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,25 +2981,53 @@ 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);
|
|
2987
3000
|
const bestLabel = state?.best?.label ?? bestEntry?.best?.label ?? head.best ?? null;
|
|
2988
3001
|
const bestScore = state?.best?.score ?? (bestLabel ? bestEntry?.scores?.[bestLabel] : void 0) ?? void 0;
|
|
2989
3002
|
const scores = roundEntries.map((r) => (r.pick && r.pick !== "CURRENT" ? r.scores?.[r.pick] : r.scores?.CURRENT) ?? (r.scores?.CURRENT ?? null)).map((v) => typeof v === "number" ? v : null).filter((v) => v != null);
|
|
2990
|
-
const
|
|
3003
|
+
const originalBase = roundEntries[0]?.from ?? roundEntries[0]?.baseCommit ?? entries.find((e) => e.customType === STATE_ENTRY && (e.data?.run === id || e.data?.state?.id === id) && e.data?.state?.baseCommit)?.data?.state?.baseCommit ?? head.baseCommit;
|
|
3004
|
+
const measured = (v) => typeof v === "number" && Number.isFinite(v);
|
|
3005
|
+
const baseline = measured(roundEntries[0]?.scores?.CURRENT) ? roundEntries[0].scores.CURRENT : typeof originalBase === "string" && originalBase ? roundEntries.find((r) => r.from === originalBase && measured(r.scores?.CURRENT))?.scores.CURRENT : void 0;
|
|
2991
3006
|
const log = [];
|
|
2992
3007
|
const firstT = Number(roundEntries[0]?.startedAt ?? Date.parse(head.at ?? "") ?? Date.now());
|
|
2993
3008
|
log.push({ t: Number.isFinite(firstT) ? firstT : Date.now(), kind: "start", text: `Started ${head.goal ?? state?.goal ?? ""}`.trim() });
|
|
2994
3009
|
for (const r of roundEntries) {
|
|
2995
|
-
|
|
3010
|
+
const judged = (!r.status || r.status === "done") && !!r.pick && !r.inconclusive && !r.unseen?.length;
|
|
3011
|
+
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 });
|
|
3012
|
+
else if (r.status !== "running") log.push({
|
|
3013
|
+
t: Number(r.endedAt ?? r.at ?? Date.now()),
|
|
3014
|
+
kind: "round",
|
|
3015
|
+
round: r.n,
|
|
3016
|
+
text: `Round ${r.n}: ${r.status === "aborted" ? "stopped before comparison completed" : r.status === "error" ? "failed before comparison completed" : "no completed comparison"}.`
|
|
3017
|
+
});
|
|
2996
3018
|
}
|
|
2997
3019
|
const wrote = state?.status ?? head.status ?? "done";
|
|
2998
3020
|
const stalled = !alive && wrote === "running";
|
|
2999
3021
|
const status = alive ? "running" : stalled ? "stalled" : wrote;
|
|
3000
|
-
if (!alive &&
|
|
3022
|
+
if (!alive && ["done", "aborted", "error"].includes(wrote)) {
|
|
3023
|
+
const reason = state?.stopReason ?? state?.error;
|
|
3024
|
+
const compared = log.filter((e) => e.kind === "judged").length;
|
|
3025
|
+
log.push({
|
|
3026
|
+
t: Number(roundEntries.at(-1)?.endedAt ?? Date.now()),
|
|
3027
|
+
kind: "done",
|
|
3028
|
+
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."
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3001
3031
|
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
3032
|
return {
|
|
3003
3033
|
id,
|
|
@@ -3009,7 +3039,7 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
3009
3039
|
goal: state?.goal ?? head.goal ?? "",
|
|
3010
3040
|
status: status === "aborted" ? "done" : status,
|
|
3011
3041
|
alive,
|
|
3012
|
-
stopReason: stalled ? "no signal from the round \u2014 the process went away" : status === "aborted" ? "aborted" :
|
|
3042
|
+
stopReason: stalled ? "no signal from the round \u2014 the process went away" : status === "aborted" ? "aborted" : state?.stopReason ?? state?.error,
|
|
3013
3043
|
rules: rules.map((text) => ({ text, source: "config" })),
|
|
3014
3044
|
rounds,
|
|
3015
3045
|
best: {
|
|
@@ -3056,7 +3086,15 @@ function summarize(cwd, entries) {
|
|
|
3056
3086
|
lastT = t;
|
|
3057
3087
|
const bestRound = state?.best?.round ?? rounds.filter((r) => r.pick && r.pick !== "CURRENT").at(-1)?.n;
|
|
3058
3088
|
const bestEntry = rounds.find((r) => r.n === bestRound);
|
|
3059
|
-
|
|
3089
|
+
const attempts = rounds.flatMap((r) => r.attempts ?? []);
|
|
3090
|
+
const failures = /* @__PURE__ */ new Map();
|
|
3091
|
+
for (const a of attempts) {
|
|
3092
|
+
const why = a.status === "budget" ? "token budget" : a.status === "timeout" ? "timed out" : String(a.status).startsWith("error") ? "error" : a.status === "aborted" ? "stopped" : null;
|
|
3093
|
+
if (why) failures.set(why, (failures.get(why) ?? 0) + 1);
|
|
3094
|
+
}
|
|
3095
|
+
const details = [...failures].map(([why, n]) => `${n} ${why}`).join(", ");
|
|
3096
|
+
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;
|
|
3097
|
+
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
3098
|
}
|
|
3061
3099
|
}
|
|
3062
3100
|
return { loops: ids.length, running, open, question, lastT, latest };
|
|
@@ -3278,6 +3316,12 @@ var Chat = class {
|
|
|
3278
3316
|
}
|
|
3279
3317
|
onEvent(event) {
|
|
3280
3318
|
const type = event.type;
|
|
3319
|
+
if (type === "extension_error") {
|
|
3320
|
+
this.notices.push({ t: Date.now(), level: "error", message: String(event.error ?? event.message ?? "Extension command failed") });
|
|
3321
|
+
this.notices = this.notices.slice(-20);
|
|
3322
|
+
this.broadcast(true);
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3281
3325
|
if (type === "message_start" && event.message?.role === "assistant") {
|
|
3282
3326
|
this.live = { busy: true, chat: this.id, startedAt: Date.now(), user: "", text: "", tools: [] };
|
|
3283
3327
|
this.broadcast();
|
|
@@ -3330,11 +3374,18 @@ var Chat = class {
|
|
|
3330
3374
|
this.touch();
|
|
3331
3375
|
}
|
|
3332
3376
|
async prompt(text, behavior) {
|
|
3333
|
-
|
|
3377
|
+
const previous = this.live;
|
|
3378
|
+
if (!text.startsWith("/") && !this.live) this.live = { busy: true, chat: this.id, startedAt: Date.now(), user: text, text: "", tools: [] };
|
|
3334
3379
|
this.broadcast(true);
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3380
|
+
try {
|
|
3381
|
+
const r = await this.pi.prompt(text, behavior);
|
|
3382
|
+
setTimeout(() => void this.refresh(), 400);
|
|
3383
|
+
return r?.disposition ?? "started";
|
|
3384
|
+
} catch (error) {
|
|
3385
|
+
if (!previous && this.live?.user === text) this.live = null;
|
|
3386
|
+
this.broadcast(true);
|
|
3387
|
+
throw error;
|
|
3388
|
+
}
|
|
3338
3389
|
}
|
|
3339
3390
|
/** The child's own view of "am I busy": a turn that settled with no event reaching us must not strand the UI. */
|
|
3340
3391
|
async settleIfIdle() {
|
|
@@ -3378,11 +3429,16 @@ var Chat = class {
|
|
|
3378
3429
|
this.answeringUi.delete(id);
|
|
3379
3430
|
}
|
|
3380
3431
|
}
|
|
3432
|
+
aborting = false;
|
|
3381
3433
|
async abort() {
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3434
|
+
if (this.aborting) throw new Error("Stop agent is already in flight.");
|
|
3435
|
+
this.aborting = true;
|
|
3436
|
+
try {
|
|
3437
|
+
await this.pi.send({ type: "abort" }, 15e3);
|
|
3438
|
+
await this.settleIfIdle();
|
|
3439
|
+
} finally {
|
|
3440
|
+
this.aborting = false;
|
|
3441
|
+
}
|
|
3386
3442
|
}
|
|
3387
3443
|
/** The newest session entry of a kind (`loops-doctor`, `loops-run`, …), for read-only views. */
|
|
3388
3444
|
custom(type) {
|
|
@@ -4740,7 +4796,7 @@ var withLive = (state) => {
|
|
|
4740
4796
|
return state;
|
|
4741
4797
|
};
|
|
4742
4798
|
var streams = /* @__PURE__ */ new Set();
|
|
4743
|
-
var snapshotOf = (chat, wsId) => withLive({ ...chat.snapshot(), chats: chatsOf(wsId), workspaces: workspaces.list(), ws: wsId, proposals });
|
|
4799
|
+
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
4800
|
manager.onChange((chat) => {
|
|
4745
4801
|
const space = workspaces.ofChat(chat.id);
|
|
4746
4802
|
const state = snapshotOf(chat, space.id);
|
|
@@ -4748,6 +4804,7 @@ manager.onChange((chat) => {
|
|
|
4748
4804
|
});
|
|
4749
4805
|
var watches = /* @__PURE__ */ new Map();
|
|
4750
4806
|
var proposals = [];
|
|
4807
|
+
var configErrors = /* @__PURE__ */ new Map();
|
|
4751
4808
|
var watchOf = (wsId) => {
|
|
4752
4809
|
const ws = workspaces.get(wsId);
|
|
4753
4810
|
let w = watches.get(ws.id);
|
|
@@ -4765,7 +4822,14 @@ var watchOf = (wsId) => {
|
|
|
4765
4822
|
fs9.watch(path9.join(ws.path, ".pi"), (_e, name) => {
|
|
4766
4823
|
if (name && name !== "loops.json") return;
|
|
4767
4824
|
clearTimeout(watchTimers.get(ws.id));
|
|
4768
|
-
watchTimers.set(ws.id, setTimeout(() =>
|
|
4825
|
+
watchTimers.set(ws.id, setTimeout(() => {
|
|
4826
|
+
try {
|
|
4827
|
+
checkConfig(ws.id);
|
|
4828
|
+
} catch (e) {
|
|
4829
|
+
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.` });
|
|
4830
|
+
notifyProposals();
|
|
4831
|
+
}
|
|
4832
|
+
}, 120));
|
|
4769
4833
|
});
|
|
4770
4834
|
} catch {
|
|
4771
4835
|
}
|
|
@@ -4774,12 +4838,18 @@ var watchOf = (wsId) => {
|
|
|
4774
4838
|
};
|
|
4775
4839
|
var watchTimers = /* @__PURE__ */ new Map();
|
|
4776
4840
|
function notifyProposals() {
|
|
4777
|
-
const payload = JSON.stringify({ type: "proposals", proposals });
|
|
4778
4841
|
for (const s of streams) {
|
|
4779
4842
|
try {
|
|
4780
4843
|
s.send("proposals", proposals);
|
|
4781
4844
|
} catch {
|
|
4782
4845
|
}
|
|
4846
|
+
const chat = manager.openChats().find((chat2) => chat2.id === s.chat);
|
|
4847
|
+
if (chat) {
|
|
4848
|
+
try {
|
|
4849
|
+
s.send("state", snapshotOf(chat, workspaces.ofChat(chat.id).id), chat.entryCursor);
|
|
4850
|
+
} catch {
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
4783
4853
|
}
|
|
4784
4854
|
}
|
|
4785
4855
|
function checkConfig(wsId) {
|
|
@@ -4792,14 +4862,33 @@ function checkConfig(wsId) {
|
|
|
4792
4862
|
return "";
|
|
4793
4863
|
}
|
|
4794
4864
|
})();
|
|
4865
|
+
let next;
|
|
4866
|
+
try {
|
|
4867
|
+
next = raw2.trim() ? JSON.parse(raw2) : {};
|
|
4868
|
+
if (!next || typeof next !== "object" || Array.isArray(next)) throw new Error("expected a JSON object");
|
|
4869
|
+
} catch (e) {
|
|
4870
|
+
let recovery = "";
|
|
4871
|
+
try {
|
|
4872
|
+
const backup = `${CONFIG}.invalid-${Date.now()}`;
|
|
4873
|
+
fs9.writeFileSync(backup, raw2);
|
|
4874
|
+
restoreConfig(w.cwd, w.accepted);
|
|
4875
|
+
recovery = ` Last accepted settings restored. Rejected text saved in ${backup}.`;
|
|
4876
|
+
} catch (writeError) {
|
|
4877
|
+
recovery = ` Could not restore settings: ${writeError.message}. Fix ${CONFIG} before running another loop.`;
|
|
4878
|
+
}
|
|
4879
|
+
configErrors.set(wsId, { ws: wsId, file: CONFIG, error: `Invalid config JSON: ${e.message}.${recovery}` });
|
|
4880
|
+
notifyProposals();
|
|
4881
|
+
return;
|
|
4882
|
+
}
|
|
4795
4883
|
if (isSelfWriting() || isSelfWrite(raw2)) {
|
|
4796
|
-
w.accepted =
|
|
4884
|
+
w.accepted = next;
|
|
4797
4885
|
return;
|
|
4798
4886
|
}
|
|
4799
|
-
const
|
|
4887
|
+
const hadError = configErrors.delete(wsId);
|
|
4800
4888
|
const changes = diffConfig(w.accepted, next);
|
|
4801
4889
|
if (!changes.length) {
|
|
4802
4890
|
w.accepted = next;
|
|
4891
|
+
if (hadError) notifyProposals();
|
|
4803
4892
|
return;
|
|
4804
4893
|
}
|
|
4805
4894
|
restoreConfig(w.cwd, w.accepted);
|
|
@@ -4912,7 +5001,7 @@ api.get("/state", async (c) => {
|
|
|
4912
5001
|
if (!id) return c.json({ error: "chat required" }, 400);
|
|
4913
5002
|
const chat = await manager.open(id);
|
|
4914
5003
|
const ws = workspaces.ofChat(id);
|
|
4915
|
-
return c.json(
|
|
5004
|
+
return c.json(snapshotOf(chat, ws.id));
|
|
4916
5005
|
});
|
|
4917
5006
|
api.post("/chat", async (c) => {
|
|
4918
5007
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -4988,10 +5077,15 @@ api.post("/rename", async (c) => {
|
|
|
4988
5077
|
return c.json({ ok: true, title: chat.title });
|
|
4989
5078
|
});
|
|
4990
5079
|
api.post("/abort", async (c) => {
|
|
4991
|
-
const { chat: id } = await c.req.json();
|
|
4992
|
-
|
|
4993
|
-
await
|
|
4994
|
-
|
|
5080
|
+
const { chat: id } = await c.req.json().catch(() => ({})) ?? {};
|
|
5081
|
+
if (typeof id !== "string" || !id.trim()) return c.json({ error: "chat required" }, 400);
|
|
5082
|
+
const chat = await manager.open(id);
|
|
5083
|
+
try {
|
|
5084
|
+
await chat.abort();
|
|
5085
|
+
return c.json({ ok: true, requested: true });
|
|
5086
|
+
} catch (e) {
|
|
5087
|
+
return c.json({ error: e.message }, 409);
|
|
5088
|
+
}
|
|
4995
5089
|
});
|
|
4996
5090
|
api.post("/chat/retry", async (c) => {
|
|
4997
5091
|
const { chat: id } = await c.req.json();
|
|
@@ -5019,29 +5113,51 @@ api.post("/answer", async (c) => {
|
|
|
5019
5113
|
await chat.prompt(`/loop answer ${String(question)} ${String(choice)}`);
|
|
5020
5114
|
return c.json({ ok: true });
|
|
5021
5115
|
});
|
|
5116
|
+
var steering = /* @__PURE__ */ new Set();
|
|
5022
5117
|
api.post("/steer", async (c) => {
|
|
5023
|
-
const { chat: id, run: run3, action, rule, attempt, text } = await c.req.json();
|
|
5024
|
-
const
|
|
5118
|
+
const { chat: id, run: run3, action, rule, attempt, text } = await c.req.json().catch(() => ({})) ?? {};
|
|
5119
|
+
const token = (v) => typeof v === "string" && /^[\w.-]+$/.test(v);
|
|
5120
|
+
if (!token(id) || !token(run3)) return c.json({ error: "chat and exact run ID required" }, 400);
|
|
5121
|
+
if (!["pick", "take", "apply", "stop", "clean", "rule", "talk", "more"].includes(action)) return c.json({ error: "valid action required" }, 400);
|
|
5122
|
+
if (["pick", "take", "talk"].includes(action) && !token(attempt)) return c.json({ error: "attempt required (durable name, e.g. R1-safe)" }, 400);
|
|
5123
|
+
if (action === "rule" && (typeof rule !== "string" || !rule.trim())) return c.json({ error: "rule required" }, 400);
|
|
5124
|
+
if (action === "talk" && (typeof text !== "string" || !text.trim())) return c.json({ error: "text required" }, 400);
|
|
5125
|
+
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);
|
|
5126
|
+
const chat = await manager.open(id);
|
|
5127
|
+
if (!chat.hasRun(run3)) return c.json({ error: "run does not belong to this chat" }, 404);
|
|
5128
|
+
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);
|
|
5129
|
+
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);
|
|
5130
|
+
const key = `${id}:${run3}:${action}`;
|
|
5131
|
+
if (steering.has(key)) return c.json({ error: "This action is already in flight." }, 409);
|
|
5132
|
+
steering.add(key);
|
|
5025
5133
|
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
|
-
|
|
5134
|
+
try {
|
|
5135
|
+
switch (String(action)) {
|
|
5136
|
+
case "pick":
|
|
5137
|
+
case "take":
|
|
5138
|
+
return c.json({ ok: true, sent: await slash(`/loop take ${attempt} ${run3}`) });
|
|
5139
|
+
case "apply":
|
|
5140
|
+
return c.json({ ok: true, sent: await slash(`/loop apply ${run3}`) });
|
|
5141
|
+
// Pi executes extension commands before its streaming queue. Do not send this as a queued follow-up:
|
|
5142
|
+
// a background round must remain independently stoppable while the parent is in a long-running tool.
|
|
5143
|
+
case "stop":
|
|
5144
|
+
return c.json({ ok: true, requested: true, sent: await slash(`/loop stop ${run3}`) });
|
|
5145
|
+
case "clean":
|
|
5146
|
+
return c.json({ ok: true, sent: await slash(`/loop clean ${run3}`) });
|
|
5147
|
+
case "rule":
|
|
5148
|
+
return c.json({ ok: true, sent: await slash(`/loop rule ${run3} ${rule}`) });
|
|
5149
|
+
case "talk":
|
|
5150
|
+
return c.json({ ok: true, sent: await slash(`/loop talk ${attempt} ${text}`) });
|
|
5151
|
+
// everything else is a decision only the agent can make (another round, a different direction)
|
|
5152
|
+
case "more":
|
|
5153
|
+
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} }).`) });
|
|
5154
|
+
default:
|
|
5155
|
+
return c.json({ error: `unknown action ${action}` }, 400);
|
|
5156
|
+
}
|
|
5157
|
+
} catch (e) {
|
|
5158
|
+
return c.json({ error: e.message }, 409);
|
|
5159
|
+
} finally {
|
|
5160
|
+
steering.delete(key);
|
|
5045
5161
|
}
|
|
5046
5162
|
});
|
|
5047
5163
|
api.get("/ui", async (c) => {
|
|
@@ -5144,18 +5260,20 @@ api.post("/proposals/:id/keep", (c) => {
|
|
|
5144
5260
|
api.get("/config", (c) => {
|
|
5145
5261
|
const ws = wsOf(c.req.query("chat"));
|
|
5146
5262
|
watchOf(ws.id);
|
|
5147
|
-
return c.json({ cwd: ws.path, ws: ws.id, keys: effectiveConfig(ws.path) });
|
|
5263
|
+
return c.json({ cwd: ws.path, ws: ws.id, keys: effectiveConfig(ws.path), error: configErrors.get(ws.id)?.error });
|
|
5148
5264
|
});
|
|
5149
5265
|
api.post("/config", async (c) => {
|
|
5150
5266
|
const { key, value, chat } = await c.req.json();
|
|
5151
5267
|
const ws = wsOf(chat);
|
|
5152
5268
|
try {
|
|
5153
5269
|
writeSetting(ws.path, String(key), value);
|
|
5270
|
+
configErrors.delete(ws.id);
|
|
5154
5271
|
const w = watchOf(ws.id);
|
|
5155
5272
|
try {
|
|
5156
5273
|
w.accepted = JSON.parse(fs9.readFileSync(path9.join(ws.path, ".pi", "loops.json"), "utf8"));
|
|
5157
5274
|
} catch {
|
|
5158
5275
|
}
|
|
5276
|
+
notifyProposals();
|
|
5159
5277
|
return c.json({ ok: true });
|
|
5160
5278
|
} catch (e) {
|
|
5161
5279
|
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.7",
|
|
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};
|