@computesdk/loops 0.1.4 → 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 +14 -0
- package/client.ts +2 -8
- package/dist/assets/{abnfDiagram-VCTEODGH-rmJwkbxW.js → abnfDiagram-VCTEODGH-BmAHMdfH.js} +1 -1
- package/dist/assets/{arc-aSBi73PQ.js → arc-D_2zELzk.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-Cq32yybi.js → architectureDiagram-5GKGNRK7-DbtMlGE1.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-C-PFhPqQ.js → blockDiagram-I7D4REHJ-oeouAQDw.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-B4jKtTT9.js → c4Diagram-7LVT6UL2-BRo4V2Z7.js} +1 -1
- package/dist/assets/channel-GK1ExE_g.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-CeU3QCre.js → chunk-2Q5K7J3B-1yTKgMZ4.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BThX-upu.js → chunk-5VM5RSS4-BsMua1fA.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-BkZOip-K.js → chunk-F27PBJKO-BFx0TMpb.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-D1qNuGK_.js → chunk-IMKFNOWR-D8UfXi3T.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BEeC9bHr.js → chunk-JWPE2WC7-bBfJHxEJ.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-eA1aBSf8.js → chunk-POPQ4Y6H-CLnUbfSj.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-CzlTp8tj.js → chunk-SVP7TREG-uWuiRTo9.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-iCYUhmUO.js → chunk-TICWLB2K-DV5DzCt1.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-j5Ron3J8.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-w0OitGtv.js → cose-bilkent-JH36ORCC-CXrAPLcM.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-OLlhwFhm.js → cynefin-OW5HDTMX-BZAor1Fd.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-DAIF0opu.js → cynefinDiagram-5FMLGOSQ-2mtwTNrl.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-H_tRA8C3.js → dagre-GXQ25YYZ-BpwBh8sN.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-BO3eHMoV.js → diagram-S7CK7UJ4-BtWd6QOl.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-CH7FayQf.js → diagram-UQ7AKVKN-918gle9R.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-Chh0kFtU.js → diagram-VSXAHHWV-BrjIR_Ik.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-CXgNEbN7.js → diagram-VX7I27RA-C28OpNTD.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-COvioQLg.js → diagram-Z3DM3KII-m__eZu-M.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-IM5LRIiK.js → ebnfDiagram-PWID7BFC-DX1w-kGZ.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-DwLblHb1.js → erDiagram-RLTQ6QDP-B7Ixmqav.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-BqT9Pj2j.js → flowDiagram-HODETNUW-Bom5HiIz.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-RjLtOHIE.js → ganttDiagram-EL5Y4UJY-oGHhHPme.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-Dr80A4cy.js → gitGraphDiagram-WWUBYQGX-BpdyfMmU.js} +1 -1
- package/dist/assets/{highlighted-body-JLMTFACH-BXGUXFR6.js → highlighted-body-JLMTFACH-DqYsmdT7.js} +1 -1
- package/dist/assets/index-D3WPco0Q.css +1 -0
- package/dist/assets/index-XP9FiM_q.js +902 -0
- package/dist/assets/{infoDiagram-27XIBGKW-DkRLZyNK.js → infoDiagram-27XIBGKW-CwHXRfO4.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-DprIz3OL.js → ishikawaDiagram-5VMMS53U-CDMEI2BQ.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-Cyuw80dG.js → journeyDiagram-3NMN7TZE-Ba4NonhC.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-Bg_5N9_d.js → kanban-definition-UXKFOSKX-B7X_m14V.js} +1 -1
- package/dist/assets/{layout-r28aTTlm.js → layout-CYu8n8qm.js} +1 -1
- package/dist/assets/{linear-BKwkVe0c.js → linear-BBLkaaSf.js} +1 -1
- package/dist/assets/{mindmap-definition-YA3MSWOX-D-tP2Z-N.js → mindmap-definition-YA3MSWOX-BEACV2JJ.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-CFlfyIN4.js → pegDiagram-XKGWAZYB-BpkdZ-6G.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-CNKQ5goz.js → pieDiagram-E7YTZNPT-_DiRoL0F.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-0BQiaJkL.js → quadrantDiagram-AXDQQJYC-CG5zvdga.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-D-abldoP.js → railroadDiagram-O6MQD6OU-G4hMBXmt.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-CVFpCeZa.js → requirementDiagram-BXWQKSXE-BkZ5guig.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-B-ShpZg7.js → sankeyDiagram-P5KCCOFB-C5PzZw7u.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-Dds0Csl1.js → sequenceDiagram-WJ2MYXX4-UAlhcqTK.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-DnmQEpM8.js → sizeCapture-INFHLROL-DNRbEYss.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-DxjhM7Uf.js → stateDiagram-D77RDMKH-DPCeQvnD.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-CtZUrpem.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-CYyjkOVO.js → swimlanes-42K2YHIH-Dr76qlly.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-CWvCwFtT.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-DxbZ-ELG.js → timeline-definition-24CTP7MA-jelGqxUz.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-CriTGGlx.js → vennDiagram-4TSXK5OY-CfIVRv2v.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-BfrfqQx4.js → wardleyDiagram-VM6X3IG4-BM5Zu9IN.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-Bcg1QYkE.js → xychartDiagram-S5SC5T6Z-DYG0fGby.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/server.mjs +175 -47
- package/package.json +3 -3
- package/src/shared/types.ts +19 -2
- package/dist/assets/channel-BWhkGVIf.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-C0ZfnZDP.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-C0ZfnZDP.js +0 -1
- package/dist/assets/index-Bejffjbf.js +0 -902
- package/dist/assets/index-CO9BshXI.css +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-B3k-YTyy.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-GJ1L3IlY.js +0 -8
package/dist/server.mjs
CHANGED
|
@@ -2833,6 +2833,11 @@ function stateFromEntries(entries, id) {
|
|
|
2833
2833
|
const newer = custom.filter((e) => e.customType === "loops-round" && e.data?.run === id && Number(e.data?.at ?? 0) > snapAt);
|
|
2834
2834
|
const latest = newer.at(-1)?.data?.best;
|
|
2835
2835
|
const winner = latest?.label && latest.round != null ? { ...base.best ?? {}, ...Object.fromEntries(Object.entries(latest).filter(([, v]) => v != null)) } : null;
|
|
2836
|
+
if (winner) {
|
|
2837
|
+
const sameWinner = latest.label === base.best?.label && latest.round === base.best?.round && latest.commit === base.best?.commit;
|
|
2838
|
+
if (Object.hasOwn(latest, "applied")) winner.applied = latest.applied;
|
|
2839
|
+
else if (!sameWinner) delete winner.applied;
|
|
2840
|
+
}
|
|
2836
2841
|
return {
|
|
2837
2842
|
...base,
|
|
2838
2843
|
...winner ? { best: winner } : {},
|
|
@@ -2918,6 +2923,7 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
2918
2923
|
model: pretty(a.model),
|
|
2919
2924
|
style: a.style,
|
|
2920
2925
|
status: a.status ?? "done",
|
|
2926
|
+
cut: a.cut,
|
|
2921
2927
|
score,
|
|
2922
2928
|
kept,
|
|
2923
2929
|
humanPick: !!(kept && (r.human || chosen.round === r.n && !!chosen.label && chosen.label !== r.pick)),
|
|
@@ -2951,6 +2957,7 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
2951
2957
|
judgeCost: r.judgeCost ?? void 0,
|
|
2952
2958
|
startedAt: r.startedAt ?? void 0,
|
|
2953
2959
|
endedAt: r.endedAt ?? void 0,
|
|
2960
|
+
status: r.status,
|
|
2954
2961
|
inconclusive: !!r.inconclusive,
|
|
2955
2962
|
unseen: r.unseen ?? [],
|
|
2956
2963
|
...r.escaped ? { escaped: r.escaped } : {}
|
|
@@ -2974,8 +2981,19 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
2974
2981
|
}));
|
|
2975
2982
|
const round = { n, from: { round: n - 1, label: "CURRENT" }, attempts, inProgress: true, judging: /judg/.test(live.phase ?? ""), why: void 0 };
|
|
2976
2983
|
const at = rounds.findIndex((r) => r.n === n);
|
|
2977
|
-
if (at >= 0)
|
|
2978
|
-
|
|
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);
|
|
2979
2997
|
}
|
|
2980
2998
|
const bestRound = state?.best?.round ?? roundEntries.filter((r) => r.pick && r.pick !== "CURRENT").at(-1)?.n ?? 0;
|
|
2981
2999
|
const bestEntry = roundEntries.filter((r) => r.n === bestRound).at(-1);
|
|
@@ -2987,12 +3005,27 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
2987
3005
|
const firstT = Number(roundEntries[0]?.startedAt ?? Date.parse(head.at ?? "") ?? Date.now());
|
|
2988
3006
|
log.push({ t: Number.isFinite(firstT) ? firstT : Date.now(), kind: "start", text: `Started ${head.goal ?? state?.goal ?? ""}`.trim() });
|
|
2989
3007
|
for (const r of roundEntries) {
|
|
2990
|
-
|
|
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
|
+
});
|
|
2991
3016
|
}
|
|
2992
3017
|
const wrote = state?.status ?? head.status ?? "done";
|
|
2993
3018
|
const stalled = !alive && wrote === "running";
|
|
2994
3019
|
const status = alive ? "running" : stalled ? "stalled" : wrote;
|
|
2995
|
-
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
|
+
}
|
|
2996
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);
|
|
2997
3030
|
return {
|
|
2998
3031
|
id,
|
|
@@ -3004,10 +3037,17 @@ function buildRun({ cwd, id, entries, live }) {
|
|
|
3004
3037
|
goal: state?.goal ?? head.goal ?? "",
|
|
3005
3038
|
status: status === "aborted" ? "done" : status,
|
|
3006
3039
|
alive,
|
|
3007
|
-
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,
|
|
3008
3041
|
rules: rules.map((text) => ({ text, source: "config" })),
|
|
3009
3042
|
rounds,
|
|
3010
|
-
best: {
|
|
3043
|
+
best: {
|
|
3044
|
+
round: bestRound,
|
|
3045
|
+
label: bestLabel ?? "base",
|
|
3046
|
+
score: bestScore,
|
|
3047
|
+
shot: bestLabel && shots.length ? shotUrl(id, bestLabel, shots[0]) : void 0,
|
|
3048
|
+
byHuman: !!(bestEntry && bestLabel && bestEntry.pick && bestEntry.pick !== bestLabel) || !!state?.best?.human,
|
|
3049
|
+
...typeof state?.best?.applied === "string" && state.best.applied.trim() ? { applied: state.best.applied } : {}
|
|
3050
|
+
},
|
|
3011
3051
|
scores,
|
|
3012
3052
|
maxRounds: Math.max(rounds.length, 1),
|
|
3013
3053
|
tokens,
|
|
@@ -3044,7 +3084,15 @@ function summarize(cwd, entries) {
|
|
|
3044
3084
|
lastT = t;
|
|
3045
3085
|
const bestRound = state?.best?.round ?? rounds.filter((r) => r.pick && r.pick !== "CURRENT").at(-1)?.n;
|
|
3046
3086
|
const bestEntry = rounds.find((r) => r.n === bestRound);
|
|
3047
|
-
|
|
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 } : {} };
|
|
3048
3096
|
}
|
|
3049
3097
|
}
|
|
3050
3098
|
return { loops: ids.length, running, open, question, lastT, latest };
|
|
@@ -3266,6 +3314,12 @@ var Chat = class {
|
|
|
3266
3314
|
}
|
|
3267
3315
|
onEvent(event) {
|
|
3268
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
|
+
}
|
|
3269
3323
|
if (type === "message_start" && event.message?.role === "assistant") {
|
|
3270
3324
|
this.live = { busy: true, chat: this.id, startedAt: Date.now(), user: "", text: "", tools: [] };
|
|
3271
3325
|
this.broadcast();
|
|
@@ -3318,11 +3372,18 @@ var Chat = class {
|
|
|
3318
3372
|
this.touch();
|
|
3319
3373
|
}
|
|
3320
3374
|
async prompt(text, behavior) {
|
|
3321
|
-
|
|
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: [] };
|
|
3322
3377
|
this.broadcast(true);
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
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
|
+
}
|
|
3326
3387
|
}
|
|
3327
3388
|
/** The child's own view of "am I busy": a turn that settled with no event reaching us must not strand the UI. */
|
|
3328
3389
|
async settleIfIdle() {
|
|
@@ -3366,11 +3427,16 @@ var Chat = class {
|
|
|
3366
3427
|
this.answeringUi.delete(id);
|
|
3367
3428
|
}
|
|
3368
3429
|
}
|
|
3430
|
+
aborting = false;
|
|
3369
3431
|
async abort() {
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
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
|
+
}
|
|
3374
3440
|
}
|
|
3375
3441
|
/** The newest session entry of a kind (`loops-doctor`, `loops-run`, …), for read-only views. */
|
|
3376
3442
|
custom(type) {
|
|
@@ -4728,7 +4794,7 @@ var withLive = (state) => {
|
|
|
4728
4794
|
return state;
|
|
4729
4795
|
};
|
|
4730
4796
|
var streams = /* @__PURE__ */ new Set();
|
|
4731
|
-
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) });
|
|
4732
4798
|
manager.onChange((chat) => {
|
|
4733
4799
|
const space = workspaces.ofChat(chat.id);
|
|
4734
4800
|
const state = snapshotOf(chat, space.id);
|
|
@@ -4736,6 +4802,7 @@ manager.onChange((chat) => {
|
|
|
4736
4802
|
});
|
|
4737
4803
|
var watches = /* @__PURE__ */ new Map();
|
|
4738
4804
|
var proposals = [];
|
|
4805
|
+
var configErrors = /* @__PURE__ */ new Map();
|
|
4739
4806
|
var watchOf = (wsId) => {
|
|
4740
4807
|
const ws = workspaces.get(wsId);
|
|
4741
4808
|
let w = watches.get(ws.id);
|
|
@@ -4753,7 +4820,14 @@ var watchOf = (wsId) => {
|
|
|
4753
4820
|
fs9.watch(path9.join(ws.path, ".pi"), (_e, name) => {
|
|
4754
4821
|
if (name && name !== "loops.json") return;
|
|
4755
4822
|
clearTimeout(watchTimers.get(ws.id));
|
|
4756
|
-
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));
|
|
4757
4831
|
});
|
|
4758
4832
|
} catch {
|
|
4759
4833
|
}
|
|
@@ -4762,12 +4836,18 @@ var watchOf = (wsId) => {
|
|
|
4762
4836
|
};
|
|
4763
4837
|
var watchTimers = /* @__PURE__ */ new Map();
|
|
4764
4838
|
function notifyProposals() {
|
|
4765
|
-
const payload = JSON.stringify({ type: "proposals", proposals });
|
|
4766
4839
|
for (const s of streams) {
|
|
4767
4840
|
try {
|
|
4768
4841
|
s.send("proposals", proposals);
|
|
4769
4842
|
} catch {
|
|
4770
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
|
+
}
|
|
4771
4851
|
}
|
|
4772
4852
|
}
|
|
4773
4853
|
function checkConfig(wsId) {
|
|
@@ -4780,14 +4860,33 @@ function checkConfig(wsId) {
|
|
|
4780
4860
|
return "";
|
|
4781
4861
|
}
|
|
4782
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
|
+
}
|
|
4783
4881
|
if (isSelfWriting() || isSelfWrite(raw2)) {
|
|
4784
|
-
w.accepted =
|
|
4882
|
+
w.accepted = next;
|
|
4785
4883
|
return;
|
|
4786
4884
|
}
|
|
4787
|
-
const
|
|
4885
|
+
const hadError = configErrors.delete(wsId);
|
|
4788
4886
|
const changes = diffConfig(w.accepted, next);
|
|
4789
4887
|
if (!changes.length) {
|
|
4790
4888
|
w.accepted = next;
|
|
4889
|
+
if (hadError) notifyProposals();
|
|
4791
4890
|
return;
|
|
4792
4891
|
}
|
|
4793
4892
|
restoreConfig(w.cwd, w.accepted);
|
|
@@ -4900,7 +4999,7 @@ api.get("/state", async (c) => {
|
|
|
4900
4999
|
if (!id) return c.json({ error: "chat required" }, 400);
|
|
4901
5000
|
const chat = await manager.open(id);
|
|
4902
5001
|
const ws = workspaces.ofChat(id);
|
|
4903
|
-
return c.json(
|
|
5002
|
+
return c.json(snapshotOf(chat, ws.id));
|
|
4904
5003
|
});
|
|
4905
5004
|
api.post("/chat", async (c) => {
|
|
4906
5005
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -4976,10 +5075,15 @@ api.post("/rename", async (c) => {
|
|
|
4976
5075
|
return c.json({ ok: true, title: chat.title });
|
|
4977
5076
|
});
|
|
4978
5077
|
api.post("/abort", async (c) => {
|
|
4979
|
-
const { chat: id } = await c.req.json();
|
|
4980
|
-
|
|
4981
|
-
await
|
|
4982
|
-
|
|
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
|
+
}
|
|
4983
5087
|
});
|
|
4984
5088
|
api.post("/chat/retry", async (c) => {
|
|
4985
5089
|
const { chat: id } = await c.req.json();
|
|
@@ -5007,29 +5111,51 @@ api.post("/answer", async (c) => {
|
|
|
5007
5111
|
await chat.prompt(`/loop answer ${String(question)} ${String(choice)}`);
|
|
5008
5112
|
return c.json({ ok: true });
|
|
5009
5113
|
});
|
|
5114
|
+
var steering = /* @__PURE__ */ new Set();
|
|
5010
5115
|
api.post("/steer", async (c) => {
|
|
5011
|
-
const { chat: id, run: run3, action, rule, attempt, text } = await c.req.json();
|
|
5012
|
-
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);
|
|
5013
5131
|
const slash = (cmd) => chat.prompt(cmd);
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
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);
|
|
5033
5159
|
}
|
|
5034
5160
|
});
|
|
5035
5161
|
api.get("/ui", async (c) => {
|
|
@@ -5132,18 +5258,20 @@ api.post("/proposals/:id/keep", (c) => {
|
|
|
5132
5258
|
api.get("/config", (c) => {
|
|
5133
5259
|
const ws = wsOf(c.req.query("chat"));
|
|
5134
5260
|
watchOf(ws.id);
|
|
5135
|
-
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 });
|
|
5136
5262
|
});
|
|
5137
5263
|
api.post("/config", async (c) => {
|
|
5138
5264
|
const { key, value, chat } = await c.req.json();
|
|
5139
5265
|
const ws = wsOf(chat);
|
|
5140
5266
|
try {
|
|
5141
5267
|
writeSetting(ws.path, String(key), value);
|
|
5268
|
+
configErrors.delete(ws.id);
|
|
5142
5269
|
const w = watchOf(ws.id);
|
|
5143
5270
|
try {
|
|
5144
5271
|
w.accepted = JSON.parse(fs9.readFileSync(path9.join(ws.path, ".pi", "loops.json"), "utf8"));
|
|
5145
5272
|
} catch {
|
|
5146
5273
|
}
|
|
5274
|
+
notifyProposals();
|
|
5147
5275
|
return c.json({ ok: true });
|
|
5148
5276
|
} catch (e) {
|
|
5149
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 */
|
|
@@ -81,6 +91,8 @@ export type Question = {
|
|
|
81
91
|
label?: string;
|
|
82
92
|
options: { label: string; steer?: Record<string, unknown> }[];
|
|
83
93
|
answered?: { i?: number; auto?: string; t: number };
|
|
94
|
+
/** The decision deadline passed without an answer. */
|
|
95
|
+
expired?: boolean;
|
|
84
96
|
};
|
|
85
97
|
|
|
86
98
|
export type Run = {
|
|
@@ -97,7 +109,10 @@ export type Run = {
|
|
|
97
109
|
stopReason?: string;
|
|
98
110
|
rules: { text: string; source: string }[];
|
|
99
111
|
rounds: RunRound[];
|
|
100
|
-
best: { round: number; label: string; score?: number; shot?: string; live?: string; byHuman?: boolean
|
|
112
|
+
best: { round: number; label: string; score?: number; shot?: string; live?: string; byHuman?: boolean;
|
|
113
|
+
/** The engine's successful apply commit; absent means unknown, not proof of an unapplied winner. */
|
|
114
|
+
applied?: string;
|
|
115
|
+
};
|
|
101
116
|
scores: number[];
|
|
102
117
|
maxRounds: number;
|
|
103
118
|
tokens: number;
|
|
@@ -132,7 +147,7 @@ export type ChatSummary = {
|
|
|
132
147
|
/** the newest open question, if any */
|
|
133
148
|
question?: { kind: string; round?: number; title: string };
|
|
134
149
|
lastT: number;
|
|
135
|
-
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 };
|
|
136
151
|
/** a run whose process went away: it says running, nothing is working */
|
|
137
152
|
};
|
|
138
153
|
|
|
@@ -171,10 +186,12 @@ export type ChatState = {
|
|
|
171
186
|
items: ParentItem[];
|
|
172
187
|
live: ParentLive | null;
|
|
173
188
|
error?: string;
|
|
189
|
+
notices?: { t: number; level: string; message: string }[];
|
|
174
190
|
runs: Run[];
|
|
175
191
|
model?: string | null;
|
|
176
192
|
/** open questions for chats that are not loaded, so the sidebar can count them */
|
|
177
193
|
busyElsewhere?: boolean;
|
|
178
194
|
/** config writes waiting for a yes/no; the file is unchanged until one */
|
|
179
195
|
proposals?: ConfigProposal[];
|
|
196
|
+
configErrors?: { ws: string; file: string; error: string }[];
|
|
180
197
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./index-Bejffjbf.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-iCYUhmUO.js";import{_ as i}from"./index-Bejffjbf.js";import"./chunk-5VM5RSS4-BThX-upu.js";import"./chunk-XXDRQBXY-j5Ron3J8.js";import"./chunk-POPQ4Y6H-eA1aBSf8.js";import"./chunk-F27PBJKO-BkZOip-K.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-iCYUhmUO.js";import{_ as i}from"./index-Bejffjbf.js";import"./chunk-5VM5RSS4-BThX-upu.js";import"./chunk-XXDRQBXY-j5Ron3J8.js";import"./chunk-POPQ4Y6H-eA1aBSf8.js";import"./chunk-F27PBJKO-BkZOip-K.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};
|