cubefarm 0.3.4 → 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-CM2m1Ytr.js → ToyWorld-C1nzIgVK.js} +2 -2
- package/dist/assets/{index-BCFM-9fU.js → index-CvwxmawV.js} +2 -2
- package/dist/assets/{rapier-BLchqNuA.js → rapier-DS9bMu-p.js} +1 -1
- package/dist/index.html +1 -1
- package/dist-server/index.js +107 -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) {
|
|
@@ -3484,6 +3505,34 @@ function mergeStep(pr, rec, now2, opts) {
|
|
|
3484
3505
|
return { do: "merge", set };
|
|
3485
3506
|
}
|
|
3486
3507
|
|
|
3508
|
+
// server/qaOrphans.ts
|
|
3509
|
+
function orphanedQa(qa, agents, busy) {
|
|
3510
|
+
return qa.filter(
|
|
3511
|
+
(q) => q.status === "testing" && !agents.some((a) => a.id === q.qaAgentId && busy.includes(a.status) && a.task === "qa" && a.repoId === q.repoId && a.prNumber === q.prNumber)
|
|
3512
|
+
);
|
|
3513
|
+
}
|
|
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
|
+
|
|
3487
3536
|
// server/previews.ts
|
|
3488
3537
|
var PREVIEW_BASE_PORT = 6300;
|
|
3489
3538
|
var LOG_KEEP = 200;
|
|
@@ -4220,12 +4269,14 @@ var Swarm = class {
|
|
|
4220
4269
|
return [];
|
|
4221
4270
|
});
|
|
4222
4271
|
const carryOn = [];
|
|
4272
|
+
const preparing = /* @__PURE__ */ new Set();
|
|
4223
4273
|
for (const a of this.state.agents) {
|
|
4224
4274
|
if (!BUSY.includes(a.status)) continue;
|
|
4225
|
-
if (back.
|
|
4275
|
+
if (followKeptCli(a, back.find((c) => c.agentId === a.id))) {
|
|
4226
4276
|
carryOn.push(a);
|
|
4227
4277
|
continue;
|
|
4228
4278
|
}
|
|
4279
|
+
if (a.status === "preparing") preparing.add(a);
|
|
4229
4280
|
a.status = "stopped";
|
|
4230
4281
|
a.lastError = "The swarm server restarted while this agent was working.";
|
|
4231
4282
|
interrupted.push(a);
|
|
@@ -4261,7 +4312,8 @@ var Swarm = class {
|
|
|
4261
4312
|
this.previews.clearOrphans(this.state.repos)
|
|
4262
4313
|
]);
|
|
4263
4314
|
for (const r of this.state.repos) void this.previews.refreshDefault(r);
|
|
4264
|
-
this.recover(interrupted);
|
|
4315
|
+
this.recover(interrupted, preparing);
|
|
4316
|
+
for (const rec of orphanedQa(this.state.qa, this.state.agents, BUSY)) this.setQa(rec, { status: "queued", qaAgentId: null });
|
|
4265
4317
|
this.officeHead = await this.backend.office.head();
|
|
4266
4318
|
const updated = await this.backend.office.takeLastUpdate().catch(() => null);
|
|
4267
4319
|
if (updated) await this.reportUpdate(updated);
|
|
@@ -4275,22 +4327,23 @@ var Swarm = class {
|
|
|
4275
4327
|
this.save();
|
|
4276
4328
|
setTimeout(() => this.schedule(), 1e3);
|
|
4277
4329
|
}
|
|
4278
|
-
/**
|
|
4279
|
-
|
|
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) {
|
|
4280
4335
|
for (const a of agents) {
|
|
4281
|
-
|
|
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)) {
|
|
4282
4338
|
this.appendLog(a, [{ kind: "system", text: "\u21BA The office server restarted. Starting over from the queue." }]);
|
|
4283
4339
|
const rec = a.task === "qa" ? this.state.qa.find((q) => q.qaAgentId === a.id && q.status === "testing") : void 0;
|
|
4284
4340
|
if (rec) this.setQa(rec, { status: "queued" });
|
|
4285
|
-
const fix = a.task === "fix" ? this.state.qa.find((q) => q.devAgentId === a.id && q.status === "fixing") : void 0;
|
|
4286
4341
|
if (fix) this.setQa(fix, { status: "failed" });
|
|
4287
4342
|
this.clearTask(a);
|
|
4288
4343
|
continue;
|
|
4289
4344
|
}
|
|
4290
4345
|
if (this.slotsFull()) continue;
|
|
4291
|
-
void this.message(a.id,
|
|
4292
|
-
(err) => console.warn(`could not resume ${a.name}`, err)
|
|
4293
|
-
);
|
|
4346
|
+
void this.message(a.id, resumeNote(a, fix)).catch((err) => console.warn(`could not resume ${a.name}`, err));
|
|
4294
4347
|
}
|
|
4295
4348
|
}
|
|
4296
4349
|
// ---------- views ----------
|
|
@@ -5256,7 +5309,7 @@ Note from the manager: ${note}` : ""
|
|
|
5256
5309
|
void this.backend.releaseDesk(repo.fullName, this.agentSlug(a), this.port(a)).catch(() => void 0);
|
|
5257
5310
|
if (result.interrupted) this.interrupted(a);
|
|
5258
5311
|
if (a.task === "qa") await this.onQaFinished(a, repo, result);
|
|
5259
|
-
else if (a.task === "fix") this.onFixFinished(a, repo, result);
|
|
5312
|
+
else if (a.task === "fix") await this.onFixFinished(a, repo, result);
|
|
5260
5313
|
else await this.onIssueFinished(a, repo, result);
|
|
5261
5314
|
this.emitAgent(a);
|
|
5262
5315
|
this.save();
|
|
@@ -5366,6 +5419,7 @@ Note from the manager: ${note}` : ""
|
|
|
5366
5419
|
if (rec && (rec.status === "needs-human" || rec.status === "passed" || rec.status === "failed")) {
|
|
5367
5420
|
rec.round += 1;
|
|
5368
5421
|
rec.sessionFailures = 0;
|
|
5422
|
+
rec.mergeNote = null;
|
|
5369
5423
|
}
|
|
5370
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));
|
|
5371
5425
|
this.queueQa(repo, prNumber, dev ?? null, pr.closesIssues[0] ?? null);
|
|
@@ -5557,7 +5611,8 @@ ${issue2.body.trim() || "(no description)"}` : ""
|
|
|
5557
5611
|
this.setQa(rec, { status: "fixing", devAgentId: dev.id });
|
|
5558
5612
|
this.beginTask(
|
|
5559
5613
|
dev,
|
|
5560
|
-
|
|
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 },
|
|
5561
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}`,
|
|
5562
5617
|
`Checking out PR #${rec.prNumber}\u2026`
|
|
5563
5618
|
);
|
|
@@ -5602,8 +5657,9 @@ Full report with screenshots: ${rec.commentUrl}` : "",
|
|
|
5602
5657
|
const resume = original && rec.devSessionId ? rec.devSessionId : void 0;
|
|
5603
5658
|
this.startAgentSession(dev, repo, cwd, prompt, this.buildSystemAppend(dev, repo, cwd, headRef, { pr: rec.prNumber, headRef }), resume);
|
|
5604
5659
|
}
|
|
5605
|
-
onFixFinished(a, repo, result) {
|
|
5660
|
+
async onFixFinished(a, repo, result) {
|
|
5606
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;
|
|
5607
5663
|
if (a.status === "stopped") {
|
|
5608
5664
|
if (rec?.status === "fixing") this.setQa(rec, { status: "failed" });
|
|
5609
5665
|
return;
|
|
@@ -5614,17 +5670,40 @@ Full report with screenshots: ${rec.commentUrl}` : "",
|
|
|
5614
5670
|
if (rec) this.setQa(rec, { status: failures >= 2 ? "needs-human" : "failed", sessionFailures: failures });
|
|
5615
5671
|
return;
|
|
5616
5672
|
}
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
this.appendLog(a, [{ kind: "done", text: `\u2714 PR #${a.prNumber}
|
|
5620
|
-
this.setQa(rec, { status: "passed", devSessionId: a.sessionId ?? rec.devSessionId, mergeNote: "waiting for fresh checks" });
|
|
5621
|
-
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.` }]);
|
|
5622
5676
|
return;
|
|
5623
5677
|
}
|
|
5624
|
-
|
|
5625
|
-
if (
|
|
5626
|
-
|
|
5627
|
-
|
|
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;
|
|
5628
5707
|
}
|
|
5629
5708
|
}
|
|
5630
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. */
|
|
@@ -5945,13 +6024,14 @@ Full report with screenshots: ${rec.commentUrl}` : "",
|
|
|
5945
6024
|
const u = this.officeUpdate;
|
|
5946
6025
|
Object.assign(u, { sent: false, handedOver: false, requested: false, drainingSince: null });
|
|
5947
6026
|
const interrupted = this.state.agents.filter((a) => BUSY.includes(a.status));
|
|
6027
|
+
const preparing = new Set(interrupted.filter((a) => a.status === "preparing"));
|
|
5948
6028
|
for (const a of interrupted) {
|
|
5949
6029
|
Object.assign(a, { status: "stopped", lastError: "The office updated itself while this agent was working." });
|
|
5950
6030
|
const rt = this.agentRt.get(a.id);
|
|
5951
6031
|
if (rt) Object.assign(rt, { session: null, currentTool: null });
|
|
5952
6032
|
}
|
|
5953
6033
|
this.ensureCeo(interrupted);
|
|
5954
|
-
this.recover(interrupted);
|
|
6034
|
+
this.recover(interrupted, preparing);
|
|
5955
6035
|
interrupted.forEach((a) => this.emitAgent(a));
|
|
5956
6036
|
await this.reportUpdate(result);
|
|
5957
6037
|
if (!result.ok) u.failedBehind = u.behind;
|