cubefarm 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{ToyWorld-KCq1UWWM.js → ToyWorld-C1nzIgVK.js} +2 -2
- package/dist/assets/{index-CeoPkKws.js → index-CvwxmawV.js} +2 -2
- package/dist/assets/{rapier-JdQfFHwQ.js → rapier-DS9bMu-p.js} +1 -1
- package/dist/index.html +1 -1
- package/dist-server/index.js +99 -27
- package/dist-server/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-CvwxmawV.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-BU33ZUhQ.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
package/dist-server/index.js
CHANGED
|
@@ -2386,13 +2386,13 @@ function qaScript(cb, pr, title, round) {
|
|
|
2386
2386
|
[{ kind: "tool", tool: "mcp__playwright__browser_console_messages", text: "\u23FA \u{1F310} console_messages" }, { kind: "result", text: " \u23BF No errors" }]
|
|
2387
2387
|
];
|
|
2388
2388
|
}
|
|
2389
|
-
function fixScript(pr) {
|
|
2389
|
+
function fixScript(pr, pushes) {
|
|
2390
2390
|
return [
|
|
2391
2391
|
[{ kind: "text", text: `\u25CF Reading the QA report for PR #${pr}. The toolbar overflows on phones; I'll let it wrap.` }],
|
|
2392
2392
|
[{ kind: "tool", tool: "Read", text: "\u23FA Read src/styles.css" }, { kind: "result", text: " \u23BF Read 212 lines" }],
|
|
2393
2393
|
[{ kind: "tool", tool: "Edit", text: "\u23FA Edit src/styles.css" }, { kind: "result", text: " \u23BF Updated" }],
|
|
2394
2394
|
[{ kind: "tool", tool: "Bash", text: "\u23FA $ npm test -- --run" }, { kind: "result", text: " \u23BF Test Files 8 passed (8)" }],
|
|
2395
|
-
[{ kind: "tool", tool: "Bash", text: '\u23FA $ git commit -am "fix: wrap toolbar on narrow screens" && git push origin HEAD' }, { kind: "result", text: " \u23BF pushed" }]
|
|
2395
|
+
pushes ? [{ kind: "tool", tool: "Bash", text: '\u23FA $ git commit -am "fix: wrap toolbar on narrow screens" && git push origin HEAD' }, { kind: "result", text: " \u23BF pushed" }] : [{ kind: "tool", tool: "Bash", text: "\u23FA $ git status --short" }, { kind: "result", text: " \u23BF M src/styles.css" }]
|
|
2396
2396
|
];
|
|
2397
2397
|
}
|
|
2398
2398
|
function fakeSession(opts, cb, fullName) {
|
|
@@ -2404,11 +2404,12 @@ function fakeSession(opts, cb, fullName) {
|
|
|
2404
2404
|
const number = Number((kind === "issue" ? issueMatch?.[1] : prMatch?.[1]) ?? 0);
|
|
2405
2405
|
const title = (kind === "issue" ? issueMatch?.[2] : prMatch?.[2])?.trim() ?? "follow-up";
|
|
2406
2406
|
const round = Number(opts.prompt.match(/QA round (\d+)/)?.[1] ?? 1);
|
|
2407
|
+
const pushes = kind !== "fix" || !/FAILED|taking over pull request/.test(opts.prompt) || Math.random() > 0.25;
|
|
2407
2408
|
const header = [
|
|
2408
2409
|
{ kind: "system", text: `\u273B Claude Code (demo) \xB7 ${opts.model} \xB7 ${opts.effort} effort` },
|
|
2409
2410
|
{ kind: "system", text: ` cwd ${opts.cwd}` }
|
|
2410
2411
|
];
|
|
2411
|
-
const body = kind === "qa" ? qaScript(cb, number, title, round) : kind === "fix" ? fixScript(number) : devScript(opts, cb, number, title);
|
|
2412
|
+
const body = kind === "qa" ? qaScript(cb, number, title, round) : kind === "fix" ? fixScript(number, pushes) : devScript(opts, cb, number, title);
|
|
2412
2413
|
const script = [header, ...body];
|
|
2413
2414
|
const finish = () => {
|
|
2414
2415
|
const costUsd = 0.3 + Math.random();
|
|
@@ -2445,13 +2446,13 @@ function fakeSession(opts, cb, fullName) {
|
|
|
2445
2446
|
}
|
|
2446
2447
|
if (kind === "fix") {
|
|
2447
2448
|
const pr = repos.get(fullName)?.pulls.find((p) => p.number === number);
|
|
2448
|
-
if (pr) {
|
|
2449
|
+
if (pr && pushes) {
|
|
2449
2450
|
pr.headSha = fakeSha();
|
|
2450
2451
|
pr.mergeState = "CLEAN";
|
|
2451
2452
|
pr.mergeable = "MERGEABLE";
|
|
2452
2453
|
runChecks(pr, false);
|
|
2453
2454
|
}
|
|
2454
|
-
cb.log([{ kind: "text", text: `\u25CF Fixed PR #${number} and pushed. Ready for another QA round.` }]);
|
|
2455
|
+
cb.log([{ kind: "text", text: pushes ? `\u25CF Fixed PR #${number} and pushed. Ready for another QA round.` : `\u25CF The toolbar wraps on narrow screens now. Ready for another QA round.` }]);
|
|
2455
2456
|
cb.finished({ ok: true, text: "", costUsd, turns, errors: [] });
|
|
2456
2457
|
return;
|
|
2457
2458
|
}
|
|
@@ -3443,6 +3444,26 @@ function specialtySlug(s) {
|
|
|
3443
3444
|
}
|
|
3444
3445
|
var specialtyLabel = (slug) => `swarm:${slug}`;
|
|
3445
3446
|
|
|
3447
|
+
// server/fixOutcome.ts
|
|
3448
|
+
var MAX_FIX_FAILURES = 2;
|
|
3449
|
+
function fixOutcome(rec, head, minutes, counts = true) {
|
|
3450
|
+
const pr = rec.prNumber;
|
|
3451
|
+
const before = rec.fixReason === "conflict" ? rec.passedSha ?? rec.testedSha : rec.testedSha;
|
|
3452
|
+
if (rec.fixReason !== "checks" && head && before && head === before) {
|
|
3453
|
+
const sessionFailures = rec.sessionFailures + (counts ? 1 : 0);
|
|
3454
|
+
const needsHuman = sessionFailures >= MAX_FIX_FAILURES;
|
|
3455
|
+
return {
|
|
3456
|
+
set: needsHuman ? { status: "needs-human", sessionFailures, mergeNote: rec.fixReason === "conflict" ? "the conflict was never resolved" : "the fix was never pushed" } : { status: "failed", sessionFailures },
|
|
3457
|
+
log: { kind: "error", text: `\u2717 No new commits were pushed for PR #${pr}` },
|
|
3458
|
+
pushed: false
|
|
3459
|
+
};
|
|
3460
|
+
}
|
|
3461
|
+
if (rec.fixReason === "checks" || rec.fixReason === "conflict") {
|
|
3462
|
+
return { set: { status: "passed", sessionFailures: 0, mergeNote: "waiting for fresh checks" }, log: { kind: "done", text: `\u2714 PR #${pr} fixed in ${minutes}m. Back in line to merge.` }, pushed: true };
|
|
3463
|
+
}
|
|
3464
|
+
return { set: { status: "queued", round: rec.round + 1, sessionFailures: 0 }, log: { kind: "done", text: `\u2714 Fix pushed for PR #${pr} in ${minutes}m. Back to QA.` }, pushed: true };
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3446
3467
|
// server/httpError.ts
|
|
3447
3468
|
var HttpError = class extends Error {
|
|
3448
3469
|
constructor(status, message) {
|
|
@@ -3491,6 +3512,27 @@ function orphanedQa(qa, agents, busy) {
|
|
|
3491
3512
|
);
|
|
3492
3513
|
}
|
|
3493
3514
|
|
|
3515
|
+
// server/restartRecovery.ts
|
|
3516
|
+
function followKeptCli(a, cli) {
|
|
3517
|
+
if (!cli?.busy || a.status === "preparing") return false;
|
|
3518
|
+
return !(a.sessionId && cli.resumeId && cli.resumeId !== a.sessionId);
|
|
3519
|
+
}
|
|
3520
|
+
function resumesAfterRestart(a, preparing, demo) {
|
|
3521
|
+
return !demo && !preparing && a.task !== "qa" && !!a.sessionId && !!a.branch;
|
|
3522
|
+
}
|
|
3523
|
+
function resumeNote(a, fix) {
|
|
3524
|
+
const yours = "Uncommitted changes in your worktree are yours to finish.";
|
|
3525
|
+
if (a.task === "fix" && a.prNumber && a.branch) {
|
|
3526
|
+
const job = fix?.fixReason === "conflict" ? `resolving the merge conflicts on PR #${a.prNumber}` : fix?.fixReason === "checks" ? `fixing the failing checks on PR #${a.prNumber}` : `fixing PR #${a.prNumber}${fix ? ` after QA round ${fix.round}` : ""}`;
|
|
3527
|
+
return `The office server restarted while you were ${job}. Finish the fix, commit, and push with: git push origin HEAD:${a.branch}. ${yours}`;
|
|
3528
|
+
}
|
|
3529
|
+
if (a.task === "issue" && a.issueNumber && a.branch) {
|
|
3530
|
+
const pr = a.prNumber ? `push with: git push origin ${a.branch} to update PR #${a.prNumber}` : `push with: git push -u origin ${a.branch}, and open the pull request with "Closes #${a.issueNumber}" (or push to it if it's already open)`;
|
|
3531
|
+
return `The office server restarted while you were working on issue #${a.issueNumber}${a.issueTitle ? ` (${a.issueTitle})` : ""} on branch ${a.branch}. Finish it, commit, ${pr}. ${yours}`;
|
|
3532
|
+
}
|
|
3533
|
+
return "The office server restarted while you were working. Check the state of your worktree and continue where you left off.";
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3494
3536
|
// server/previews.ts
|
|
3495
3537
|
var PREVIEW_BASE_PORT = 6300;
|
|
3496
3538
|
var LOG_KEEP = 200;
|
|
@@ -4227,12 +4269,14 @@ var Swarm = class {
|
|
|
4227
4269
|
return [];
|
|
4228
4270
|
});
|
|
4229
4271
|
const carryOn = [];
|
|
4272
|
+
const preparing = /* @__PURE__ */ new Set();
|
|
4230
4273
|
for (const a of this.state.agents) {
|
|
4231
4274
|
if (!BUSY.includes(a.status)) continue;
|
|
4232
|
-
if (back.
|
|
4275
|
+
if (followKeptCli(a, back.find((c) => c.agentId === a.id))) {
|
|
4233
4276
|
carryOn.push(a);
|
|
4234
4277
|
continue;
|
|
4235
4278
|
}
|
|
4279
|
+
if (a.status === "preparing") preparing.add(a);
|
|
4236
4280
|
a.status = "stopped";
|
|
4237
4281
|
a.lastError = "The swarm server restarted while this agent was working.";
|
|
4238
4282
|
interrupted.push(a);
|
|
@@ -4268,7 +4312,7 @@ var Swarm = class {
|
|
|
4268
4312
|
this.previews.clearOrphans(this.state.repos)
|
|
4269
4313
|
]);
|
|
4270
4314
|
for (const r of this.state.repos) void this.previews.refreshDefault(r);
|
|
4271
|
-
this.recover(interrupted);
|
|
4315
|
+
this.recover(interrupted, preparing);
|
|
4272
4316
|
for (const rec of orphanedQa(this.state.qa, this.state.agents, BUSY)) this.setQa(rec, { status: "queued", qaAgentId: null });
|
|
4273
4317
|
this.officeHead = await this.backend.office.head();
|
|
4274
4318
|
const updated = await this.backend.office.takeLastUpdate().catch(() => null);
|
|
@@ -4283,22 +4327,23 @@ var Swarm = class {
|
|
|
4283
4327
|
this.save();
|
|
4284
4328
|
setTimeout(() => this.schedule(), 1e3);
|
|
4285
4329
|
}
|
|
4286
|
-
/**
|
|
4287
|
-
|
|
4330
|
+
/**
|
|
4331
|
+
* Agents cut off by a server restart pick their session back up, told what they were doing. QA, demo agents and
|
|
4332
|
+
* tasks still being prepared start over from the queue.
|
|
4333
|
+
*/
|
|
4334
|
+
recover(agents, preparing) {
|
|
4288
4335
|
for (const a of agents) {
|
|
4289
|
-
|
|
4336
|
+
const fix = a.task === "fix" ? this.state.qa.find((q) => q.devAgentId === a.id && q.status === "fixing") : void 0;
|
|
4337
|
+
if (!resumesAfterRestart(a, preparing.has(a), this.backend.demo)) {
|
|
4290
4338
|
this.appendLog(a, [{ kind: "system", text: "\u21BA The office server restarted. Starting over from the queue." }]);
|
|
4291
4339
|
const rec = a.task === "qa" ? this.state.qa.find((q) => q.qaAgentId === a.id && q.status === "testing") : void 0;
|
|
4292
4340
|
if (rec) this.setQa(rec, { status: "queued" });
|
|
4293
|
-
const fix = a.task === "fix" ? this.state.qa.find((q) => q.devAgentId === a.id && q.status === "fixing") : void 0;
|
|
4294
4341
|
if (fix) this.setQa(fix, { status: "failed" });
|
|
4295
4342
|
this.clearTask(a);
|
|
4296
4343
|
continue;
|
|
4297
4344
|
}
|
|
4298
4345
|
if (this.slotsFull()) continue;
|
|
4299
|
-
void this.message(a.id,
|
|
4300
|
-
(err) => console.warn(`could not resume ${a.name}`, err)
|
|
4301
|
-
);
|
|
4346
|
+
void this.message(a.id, resumeNote(a, fix)).catch((err) => console.warn(`could not resume ${a.name}`, err));
|
|
4302
4347
|
}
|
|
4303
4348
|
}
|
|
4304
4349
|
// ---------- views ----------
|
|
@@ -5264,7 +5309,7 @@ Note from the manager: ${note}` : ""
|
|
|
5264
5309
|
void this.backend.releaseDesk(repo.fullName, this.agentSlug(a), this.port(a)).catch(() => void 0);
|
|
5265
5310
|
if (result.interrupted) this.interrupted(a);
|
|
5266
5311
|
if (a.task === "qa") await this.onQaFinished(a, repo, result);
|
|
5267
|
-
else if (a.task === "fix") this.onFixFinished(a, repo, result);
|
|
5312
|
+
else if (a.task === "fix") await this.onFixFinished(a, repo, result);
|
|
5268
5313
|
else await this.onIssueFinished(a, repo, result);
|
|
5269
5314
|
this.emitAgent(a);
|
|
5270
5315
|
this.save();
|
|
@@ -5374,6 +5419,7 @@ Note from the manager: ${note}` : ""
|
|
|
5374
5419
|
if (rec && (rec.status === "needs-human" || rec.status === "passed" || rec.status === "failed")) {
|
|
5375
5420
|
rec.round += 1;
|
|
5376
5421
|
rec.sessionFailures = 0;
|
|
5422
|
+
rec.mergeNote = null;
|
|
5377
5423
|
}
|
|
5378
5424
|
const dev = this.state.agents.find((a) => a.repoId === repo.id && a.role === "dev" && a.task !== "qa" && (a.prNumber === prNumber || a.branch === pr.headRefName));
|
|
5379
5425
|
this.queueQa(repo, prNumber, dev ?? null, pr.closesIssues[0] ?? null);
|
|
@@ -5565,7 +5611,8 @@ ${issue2.body.trim() || "(no description)"}` : ""
|
|
|
5565
5611
|
this.setQa(rec, { status: "fixing", devAgentId: dev.id });
|
|
5566
5612
|
this.beginTask(
|
|
5567
5613
|
dev,
|
|
5568
|
-
|
|
5614
|
+
// The session they held was another task's (QA, say); the author's own is resumed below, from rec.devSessionId.
|
|
5615
|
+
{ task: "fix", issueNumber: rec.issueNumber, issueTitle: pull?.title ?? `PR #${rec.prNumber}`, branch: headRef, prNumber: rec.prNumber, prUrl: pull?.url ?? null, sessionId: null },
|
|
5569
5616
|
rec.fixReason === "conflict" ? `Resolving conflicts on PR #${rec.prNumber}` : rec.fixReason === "checks" ? `Fixing checks on PR #${rec.prNumber}` : `Fixing PR #${rec.prNumber} after QA round ${rec.round}`,
|
|
5570
5617
|
`Checking out PR #${rec.prNumber}\u2026`
|
|
5571
5618
|
);
|
|
@@ -5610,8 +5657,9 @@ Full report with screenshots: ${rec.commentUrl}` : "",
|
|
|
5610
5657
|
const resume = original && rec.devSessionId ? rec.devSessionId : void 0;
|
|
5611
5658
|
this.startAgentSession(dev, repo, cwd, prompt, this.buildSystemAppend(dev, repo, cwd, headRef, { pr: rec.prNumber, headRef }), resume);
|
|
5612
5659
|
}
|
|
5613
|
-
onFixFinished(a, repo, result) {
|
|
5660
|
+
async onFixFinished(a, repo, result) {
|
|
5614
5661
|
const rec = this.state.qa.find((q) => q.repoId === repo.id && q.prNumber === a.prNumber);
|
|
5662
|
+
const head = rec && result.ok && a.status !== "stopped" && rec.fixReason !== "checks" ? await this.prHead(a, repo, rec.prNumber) : null;
|
|
5615
5663
|
if (a.status === "stopped") {
|
|
5616
5664
|
if (rec?.status === "fixing") this.setQa(rec, { status: "failed" });
|
|
5617
5665
|
return;
|
|
@@ -5622,17 +5670,40 @@ Full report with screenshots: ${rec.commentUrl}` : "",
|
|
|
5622
5670
|
if (rec) this.setQa(rec, { status: failures >= 2 ? "needs-human" : "failed", sessionFailures: failures });
|
|
5623
5671
|
return;
|
|
5624
5672
|
}
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
this.appendLog(a, [{ kind: "done", text: `\u2714 PR #${a.prNumber}
|
|
5628
|
-
this.setQa(rec, { status: "passed", devSessionId: a.sessionId ?? rec.devSessionId, mergeNote: "waiting for fresh checks" });
|
|
5629
|
-
this.toast("info", `${a.name} fixed PR #${rec.prNumber}; it merges once it passes again`);
|
|
5673
|
+
if (!rec || !this.state.qa.includes(rec)) {
|
|
5674
|
+
a.status = "done";
|
|
5675
|
+
this.appendLog(a, [{ kind: "done", text: `\u2714 Fix pushed for PR #${a.prNumber} in ${this.minutes(a)}m. Back to QA.` }]);
|
|
5630
5676
|
return;
|
|
5631
5677
|
}
|
|
5632
|
-
|
|
5633
|
-
if (
|
|
5634
|
-
|
|
5635
|
-
|
|
5678
|
+
const step = fixOutcome(rec, head, this.minutes(a), !this.limited());
|
|
5679
|
+
if (!step.pushed) {
|
|
5680
|
+
const needsHuman = step.set.status === "needs-human";
|
|
5681
|
+
a.status = "error";
|
|
5682
|
+
a.lastError = `No new commits were pushed for PR #${rec.prNumber}`;
|
|
5683
|
+
this.appendLog(a, [step.log]);
|
|
5684
|
+
this.setQa(rec, step.set);
|
|
5685
|
+
this.toast("error", `${a.name} ended the fix for PR #${rec.prNumber} without pushing a commit${needsHuman ? "; it needs you" : "; it goes back for another try"}`);
|
|
5686
|
+
if (needsHuman) {
|
|
5687
|
+
this.postMessage("office", `\u26A0\uFE0F PR #${rec.prNumber} on ${repo.fullName} went back for fixes, but ${step.set.sessionFailures} fix sessions in a row ended without pushing a commit, so it needs you.`);
|
|
5688
|
+
}
|
|
5689
|
+
return;
|
|
5690
|
+
}
|
|
5691
|
+
a.status = "done";
|
|
5692
|
+
this.appendLog(a, [step.log]);
|
|
5693
|
+
this.setQa(rec, { ...step.set, devSessionId: a.sessionId ?? rec.devSessionId });
|
|
5694
|
+
this.toast(
|
|
5695
|
+
"info",
|
|
5696
|
+
step.set.status === "passed" ? `${a.name} fixed PR #${rec.prNumber}; it merges once it passes again` : `${a.name} pushed fixes for PR #${rec.prNumber}; QA round ${rec.round} is queued`
|
|
5697
|
+
);
|
|
5698
|
+
}
|
|
5699
|
+
/** The PR's head commit now; null (with a warning in the agent's log) when GitHub can't be asked. */
|
|
5700
|
+
async prHead(a, repo, prNumber) {
|
|
5701
|
+
try {
|
|
5702
|
+
return (await this.backend.prDetails(repo.fullName, prNumber)).headSha;
|
|
5703
|
+
} catch (err) {
|
|
5704
|
+
console.warn(`could not read the head of ${repo.fullName}#${prNumber}`, err);
|
|
5705
|
+
this.appendLog(a, [{ kind: "system", text: `\u26A0 Could not check PR #${prNumber} for new commits (${oneLine(err)}); taking the fix at its word.` }]);
|
|
5706
|
+
return null;
|
|
5636
5707
|
}
|
|
5637
5708
|
}
|
|
5638
5709
|
/** A message for an agent: sent into their running session, or a follow-up that resumes it. typed: the manager typed it at their CLI's prompt, where it's already running. */
|
|
@@ -5953,13 +6024,14 @@ Full report with screenshots: ${rec.commentUrl}` : "",
|
|
|
5953
6024
|
const u = this.officeUpdate;
|
|
5954
6025
|
Object.assign(u, { sent: false, handedOver: false, requested: false, drainingSince: null });
|
|
5955
6026
|
const interrupted = this.state.agents.filter((a) => BUSY.includes(a.status));
|
|
6027
|
+
const preparing = new Set(interrupted.filter((a) => a.status === "preparing"));
|
|
5956
6028
|
for (const a of interrupted) {
|
|
5957
6029
|
Object.assign(a, { status: "stopped", lastError: "The office updated itself while this agent was working." });
|
|
5958
6030
|
const rt = this.agentRt.get(a.id);
|
|
5959
6031
|
if (rt) Object.assign(rt, { session: null, currentTool: null });
|
|
5960
6032
|
}
|
|
5961
6033
|
this.ensureCeo(interrupted);
|
|
5962
|
-
this.recover(interrupted);
|
|
6034
|
+
this.recover(interrupted, preparing);
|
|
5963
6035
|
interrupted.forEach((a) => this.emitAgent(a));
|
|
5964
6036
|
await this.reportUpdate(result);
|
|
5965
6037
|
if (!result.ok) u.failedBehind = u.behind;
|