@sideboard-ai/core 0.1.157 → 0.1.158
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/{agents-LCDXN2B2.js → agents-E3ZPWZRS.js} +1 -1
- package/dist/{agents-C55LAEUR.js → agents-RGE7PSUY.js} +4 -4
- package/dist/{chunk-4I4VKAPZ.js → chunk-3SUZMPGL.js} +2 -2
- package/dist/{chunk-NVXVEGGM.js → chunk-5U4NZLBH.js} +4 -0
- package/dist/{chunk-CJQLPPEJ.js → chunk-A7QXLYBN.js} +1 -1
- package/dist/{chunk-TIJ6QVX5.js → chunk-BLRHO7XQ.js} +7 -2
- package/dist/{chunk-KQ4JOGUH.js → chunk-EPQIOAAY.js} +8 -4
- package/dist/{chunk-SUCJAWV4.js → chunk-FBFIOVDH.js} +375 -105
- package/dist/{chunk-2SDMENAL.js → chunk-JV4VNFMS.js} +1 -1
- package/dist/{chunk-GWEEGE6L.js → chunk-KYKNFJK5.js} +369 -99
- package/dist/{coordinator-prompt-AM47SATF.js → coordinator-prompt-GZESIQNH.js} +3 -3
- package/dist/{global-workspace-QV7CC7SK.js → global-workspace-REI55FZ6.js} +3 -3
- package/dist/index.cjs +456 -183
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +21 -24
- package/dist/mcp/run-stdio.cjs +456 -182
- package/dist/mcp/run-stdio.js +27 -30
- package/dist/{orchestrator-KQO4MXBI.js → orchestrator-J727WIJY.js} +6 -6
- package/dist/{orchestrator-BUH3LJLL.js → orchestrator-ZVW2AC6B.js} +2 -2
- package/dist/{plan-file-OZEBFSGQ.js → plan-file-I24VJVP2.js} +2 -2
- package/dist/{workspaces-Q2VCHIAS.js → workspaces-OCEPZXFD.js} +3 -3
- package/dist/{worktree-PEWDAX5O.js → worktree-VTMBZKY6.js} +2 -2
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
stripBrightsyNdjsonNoise,
|
|
25
25
|
sumUsageList,
|
|
26
26
|
toolDescription
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-BLRHO7XQ.js";
|
|
28
28
|
import {
|
|
29
29
|
SLACK_REPLY_FORMATTING,
|
|
30
30
|
addWorkspace,
|
|
@@ -101,7 +101,9 @@ import {
|
|
|
101
101
|
} from "./chunk-LQMFCS54.js";
|
|
102
102
|
import {
|
|
103
103
|
ATTACHMENTS_DIR,
|
|
104
|
-
|
|
104
|
+
DETACHED_JOBS_DIR,
|
|
105
|
+
LEGACY_ATTACHMENTS_DIR,
|
|
106
|
+
LEGACY_DETACHED_JOBS_DIR
|
|
105
107
|
} from "./chunk-JRH62XC4.js";
|
|
106
108
|
import {
|
|
107
109
|
httpFetch
|
|
@@ -459,7 +461,7 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
459
461
|
await continueOnReply(threadId, prompt);
|
|
460
462
|
return;
|
|
461
463
|
}
|
|
462
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
464
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-ZVW2AC6B.js");
|
|
463
465
|
await getOrchestrator2().send(threadId, prompt);
|
|
464
466
|
} catch {
|
|
465
467
|
}
|
|
@@ -719,7 +721,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
719
721
|
}
|
|
720
722
|
|
|
721
723
|
// src/orchestrator/orchestrator.ts
|
|
722
|
-
import { existsSync as
|
|
724
|
+
import { existsSync as existsSync18 } from "fs";
|
|
723
725
|
|
|
724
726
|
// src/agents/spawn.ts
|
|
725
727
|
import { createInterface } from "readline";
|
|
@@ -2564,8 +2566,232 @@ function notifyParentOfChildHalt(child, status, send) {
|
|
|
2564
2566
|
return true;
|
|
2565
2567
|
}
|
|
2566
2568
|
|
|
2569
|
+
// src/mcp/wait-for-job.ts
|
|
2570
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2571
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
2572
|
+
|
|
2573
|
+
// src/mcp/wait-for-turn.ts
|
|
2574
|
+
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
2575
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
2576
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
2577
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
2578
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
2579
|
+
}
|
|
2580
|
+
var MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send a check-in prompt or assume a hang while progress is updating.";
|
|
2581
|
+
var MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send a check-in prompt, force_stop, or assume it failed to start.";
|
|
2582
|
+
function mcpWaitStillRunningHint(status) {
|
|
2583
|
+
return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
|
|
2584
|
+
}
|
|
2585
|
+
var MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_thread to resume, or tell the user.";
|
|
2586
|
+
var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
2587
|
+
var MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
|
|
2588
|
+
function mcpWaitFinishedHint(status) {
|
|
2589
|
+
if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
|
|
2590
|
+
if (status === "broken") return MCP_WAIT_BROKEN_HINT;
|
|
2591
|
+
if (status === "error") return MCP_WAIT_ERROR_HINT;
|
|
2592
|
+
return void 0;
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
// src/mcp/wait-for-job.ts
|
|
2596
|
+
var MAX_JOB_CONTINUES = 8;
|
|
2597
|
+
var MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running. present_artifact type=log with the same artifact_id and content=delta (new lines only). Then call wait_for_job again. Do not end the turn or tell the user you will let them know later.";
|
|
2598
|
+
var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
2599
|
+
function mcpWaitForJobTimeoutMs(requested) {
|
|
2600
|
+
return mcpWaitForTurnTimeoutMs(requested);
|
|
2601
|
+
}
|
|
2602
|
+
function sanitizeDetachedJobId(id) {
|
|
2603
|
+
const s = id.trim();
|
|
2604
|
+
if (!JOB_ID_RE.test(s)) {
|
|
2605
|
+
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
2606
|
+
}
|
|
2607
|
+
return s;
|
|
2608
|
+
}
|
|
2609
|
+
function jobAlive(pid) {
|
|
2610
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
2611
|
+
try {
|
|
2612
|
+
process.kill(pid, 0);
|
|
2613
|
+
return true;
|
|
2614
|
+
} catch {
|
|
2615
|
+
return false;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
function readIntFile(file) {
|
|
2619
|
+
if (!existsSync8(file)) return null;
|
|
2620
|
+
const n = Number.parseInt(readFileSync7(file, "utf8").trim(), 10);
|
|
2621
|
+
return Number.isInteger(n) ? n : null;
|
|
2622
|
+
}
|
|
2623
|
+
function jobDir(root, id, legacy = false) {
|
|
2624
|
+
return join10(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
2625
|
+
}
|
|
2626
|
+
function resolveJobDir(root, id) {
|
|
2627
|
+
const modern = jobDir(root, id, false);
|
|
2628
|
+
if (existsSync8(modern)) return modern;
|
|
2629
|
+
const legacy = jobDir(root, id, true);
|
|
2630
|
+
if (existsSync8(legacy)) return legacy;
|
|
2631
|
+
return modern;
|
|
2632
|
+
}
|
|
2633
|
+
function listJobIdsIn(root, rel) {
|
|
2634
|
+
const dir = join10(root, rel);
|
|
2635
|
+
if (!existsSync8(dir)) return [];
|
|
2636
|
+
return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
2637
|
+
}
|
|
2638
|
+
function listRunningDetachedJobs(worktreePath) {
|
|
2639
|
+
const root = worktreePath.trim();
|
|
2640
|
+
if (!root) return [];
|
|
2641
|
+
const ids = /* @__PURE__ */ new Set([
|
|
2642
|
+
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
2643
|
+
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
2644
|
+
]);
|
|
2645
|
+
const running = [];
|
|
2646
|
+
for (const id of ids) {
|
|
2647
|
+
const dir = resolveJobDir(root, id);
|
|
2648
|
+
const pid = readIntFile(join10(dir, "pid"));
|
|
2649
|
+
if (pid != null && jobAlive(pid)) running.push(id);
|
|
2650
|
+
}
|
|
2651
|
+
return running.sort();
|
|
2652
|
+
}
|
|
2653
|
+
function looksLikeDeferredDonePromise(text) {
|
|
2654
|
+
const t = (text ?? "").trim();
|
|
2655
|
+
if (!t) return false;
|
|
2656
|
+
return /\b(i['’]?ll|i will)\s+let you know\b/i.test(t) || /\blet you know when\b/i.test(t) || /\b(check back|ping me)\s+when\b/i.test(t) || /\bi(?:['’]ll| will)\s+(report|update you)\s+when\b/i.test(t);
|
|
2657
|
+
}
|
|
2658
|
+
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
2659
|
+
const ids = jobIds.join(", ");
|
|
2660
|
+
return [
|
|
2661
|
+
`Detached job still running: ${ids}.`,
|
|
2662
|
+
"Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
|
|
2663
|
+
"Then report the result. Do not tell the user you will let them know later."
|
|
2664
|
+
].join(" ");
|
|
2665
|
+
}
|
|
2666
|
+
function formatDeferredDoneContinuePrompt() {
|
|
2667
|
+
return [
|
|
2668
|
+
"You ended the turn after promising to report later, but no detached job is running.",
|
|
2669
|
+
"If tests/pack/deploy still need to run: start once with detached-job.js, present_artifact type=log, then loop wait_for_job until stillRunning is false.",
|
|
2670
|
+
"Do not say you will let the user know later."
|
|
2671
|
+
].join(" ");
|
|
2672
|
+
}
|
|
2673
|
+
function turnWatchedDetachedJob(parts) {
|
|
2674
|
+
return parts.some((p) => {
|
|
2675
|
+
if (p.type !== "tool") return false;
|
|
2676
|
+
if (/wait_for_job$/i.test(p.name ?? "")) return true;
|
|
2677
|
+
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
2678
|
+
return /detached-job\.js\b/i.test(blob);
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
function planJobContinue(opts) {
|
|
2682
|
+
if (opts.isOrchestrator) return { action: "none" };
|
|
2683
|
+
if (opts.agent === "brightsy") return { action: "none" };
|
|
2684
|
+
if (opts.queueLength > 0) return { action: "none" };
|
|
2685
|
+
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
2686
|
+
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
2687
|
+
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
2688
|
+
return {
|
|
2689
|
+
action: "wait",
|
|
2690
|
+
jobIds: opts.runningJobIds,
|
|
2691
|
+
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
2695
|
+
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
2696
|
+
}
|
|
2697
|
+
return { action: "none" };
|
|
2698
|
+
}
|
|
2699
|
+
function tailProgress(logFile, maxLines = 12) {
|
|
2700
|
+
if (!existsSync8(logFile)) return "(no log yet)";
|
|
2701
|
+
const lines = readFileSync7(logFile, "utf8").split("\n");
|
|
2702
|
+
return lines.slice(-maxLines).join("\n");
|
|
2703
|
+
}
|
|
2704
|
+
function readLogLines(file) {
|
|
2705
|
+
if (!existsSync8(file)) return [];
|
|
2706
|
+
const lines = readFileSync7(file, "utf8").split("\n");
|
|
2707
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
2708
|
+
return lines;
|
|
2709
|
+
}
|
|
2710
|
+
function takeDelta(logFile, cursorFile) {
|
|
2711
|
+
const lines = readLogLines(logFile);
|
|
2712
|
+
const cursor = readIntFile(cursorFile) ?? 0;
|
|
2713
|
+
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
2714
|
+
return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
|
|
2715
|
+
}
|
|
2716
|
+
function snapshotJob(dir) {
|
|
2717
|
+
const pid = readIntFile(join10(dir, "pid"));
|
|
2718
|
+
const running = pid != null && jobAlive(pid);
|
|
2719
|
+
return {
|
|
2720
|
+
pid,
|
|
2721
|
+
running,
|
|
2722
|
+
exitCode: readIntFile(join10(dir, "exit")),
|
|
2723
|
+
log: join10(dir, "log"),
|
|
2724
|
+
cursor: join10(dir, "present.cursor"),
|
|
2725
|
+
progress: tailProgress(join10(dir, "log"))
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
function toResult(id, snap, extra) {
|
|
2729
|
+
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
2730
|
+
const ok = !snap.running && snap.exitCode === 0;
|
|
2731
|
+
const stillRunning = snap.running && !ok;
|
|
2732
|
+
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
2733
|
+
try {
|
|
2734
|
+
mkdirSync3(dirname3(snap.cursor), { recursive: true });
|
|
2735
|
+
writeFileSync4(snap.cursor, `${nextCursor}
|
|
2736
|
+
`);
|
|
2737
|
+
} catch {
|
|
2738
|
+
}
|
|
2739
|
+
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
2740
|
+
return {
|
|
2741
|
+
stillRunning,
|
|
2742
|
+
ok,
|
|
2743
|
+
failed: Boolean(failed && !stillRunning && !ok),
|
|
2744
|
+
status,
|
|
2745
|
+
id,
|
|
2746
|
+
pid: snap.pid,
|
|
2747
|
+
exitCode: snap.exitCode ?? void 0,
|
|
2748
|
+
delta,
|
|
2749
|
+
progress: extra?.progress ?? snap.progress,
|
|
2750
|
+
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
async function sleepMs(ms) {
|
|
2754
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2755
|
+
}
|
|
2756
|
+
async function waitForDetachedJob(cwd, id, opts) {
|
|
2757
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
2758
|
+
const root = cwd.trim() || process.cwd();
|
|
2759
|
+
const dir = resolveJobDir(root, jobId);
|
|
2760
|
+
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
2761
|
+
const sleep = opts?.sleep ?? sleepMs;
|
|
2762
|
+
if (!existsSync8(dir)) {
|
|
2763
|
+
return {
|
|
2764
|
+
stillRunning: false,
|
|
2765
|
+
ok: false,
|
|
2766
|
+
failed: true,
|
|
2767
|
+
status: "failed",
|
|
2768
|
+
id: jobId,
|
|
2769
|
+
delta: "",
|
|
2770
|
+
progress: "No detached job. Start one first.",
|
|
2771
|
+
hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
const deadline = Date.now() + timeoutMs;
|
|
2775
|
+
let snap = snapshotJob(dir);
|
|
2776
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
2777
|
+
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
2778
|
+
return toResult(jobId, snap, {
|
|
2779
|
+
failed: true,
|
|
2780
|
+
progress: "No detached job. Start one first."
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
while (Date.now() < deadline) {
|
|
2784
|
+
snap = snapshotJob(dir);
|
|
2785
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
2786
|
+
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
2787
|
+
await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
2788
|
+
}
|
|
2789
|
+
snap = snapshotJob(dir);
|
|
2790
|
+
return toResult(jobId, snap);
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2567
2793
|
// src/threads/create.ts
|
|
2568
|
-
import { existsSync as
|
|
2794
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2569
2795
|
|
|
2570
2796
|
// src/detect/detect.ts
|
|
2571
2797
|
async function detectAgents() {
|
|
@@ -3127,7 +3353,7 @@ function reuseLiveThread(input, repoPath, match) {
|
|
|
3127
3353
|
}
|
|
3128
3354
|
async function createThread(input, _onSetupLine) {
|
|
3129
3355
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
3130
|
-
if (!
|
|
3356
|
+
if (!existsSync9(repoPath)) {
|
|
3131
3357
|
throw new Error(`Repo not found: ${repoPath}`);
|
|
3132
3358
|
}
|
|
3133
3359
|
const reused = reuseLiveThread(input, repoPath, {
|
|
@@ -3299,7 +3525,7 @@ async function createThread(input, _onSetupLine) {
|
|
|
3299
3525
|
return readThread(thread.id) ?? thread;
|
|
3300
3526
|
}
|
|
3301
3527
|
async function listLinearIssues(agent, repoPath) {
|
|
3302
|
-
const { getAdapter: getAdapter2 } = await import("./agents-
|
|
3528
|
+
const { getAdapter: getAdapter2 } = await import("./agents-E3ZPWZRS.js");
|
|
3303
3529
|
await requireAgent(agent, { requireLinear: true });
|
|
3304
3530
|
const adapter = getAdapter2(agent);
|
|
3305
3531
|
if (!adapter.listLinearIssues) {
|
|
@@ -3322,11 +3548,11 @@ function shouldRemoveWorktreeOnTeardown(thread) {
|
|
|
3322
3548
|
|
|
3323
3549
|
// src/store/turn-live.ts
|
|
3324
3550
|
import {
|
|
3325
|
-
existsSync as
|
|
3551
|
+
existsSync as existsSync10,
|
|
3326
3552
|
renameSync,
|
|
3327
3553
|
unlinkSync as unlinkSync2,
|
|
3328
|
-
writeFileSync as
|
|
3329
|
-
readFileSync as
|
|
3554
|
+
writeFileSync as writeFileSync5,
|
|
3555
|
+
readFileSync as readFileSync8
|
|
3330
3556
|
} from "fs";
|
|
3331
3557
|
var buffers = /* @__PURE__ */ new Map();
|
|
3332
3558
|
var FLUSH_MS = 800;
|
|
@@ -3403,7 +3629,7 @@ function writeTurnLive(threadId, progress) {
|
|
|
3403
3629
|
const path = threadLivePath(threadId);
|
|
3404
3630
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
3405
3631
|
try {
|
|
3406
|
-
|
|
3632
|
+
writeFileSync5(tmp, JSON.stringify(progress), "utf8");
|
|
3407
3633
|
renameSync(tmp, path);
|
|
3408
3634
|
} catch {
|
|
3409
3635
|
try {
|
|
@@ -3414,9 +3640,9 @@ function writeTurnLive(threadId, progress) {
|
|
|
3414
3640
|
}
|
|
3415
3641
|
function readTurnLive(threadId) {
|
|
3416
3642
|
const path = threadLivePath(threadId);
|
|
3417
|
-
if (!
|
|
3643
|
+
if (!existsSync10(path)) return null;
|
|
3418
3644
|
try {
|
|
3419
|
-
const raw = JSON.parse(
|
|
3645
|
+
const raw = JSON.parse(readFileSync8(path, "utf8"));
|
|
3420
3646
|
if (!raw || typeof raw.summary !== "string") return null;
|
|
3421
3647
|
return raw;
|
|
3422
3648
|
} catch {
|
|
@@ -3428,7 +3654,7 @@ function clearTurnLive(threadId) {
|
|
|
3428
3654
|
if (buf?.timer) clearTimeout(buf.timer);
|
|
3429
3655
|
buffers.delete(threadId);
|
|
3430
3656
|
const path = threadLivePath(threadId);
|
|
3431
|
-
if (!
|
|
3657
|
+
if (!existsSync10(path)) return;
|
|
3432
3658
|
try {
|
|
3433
3659
|
unlinkSync2(path);
|
|
3434
3660
|
} catch {
|
|
@@ -3616,26 +3842,26 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
3616
3842
|
import { execFileSync } from "child_process";
|
|
3617
3843
|
import {
|
|
3618
3844
|
copyFileSync as copyFileSync2,
|
|
3619
|
-
existsSync as
|
|
3845
|
+
existsSync as existsSync11,
|
|
3620
3846
|
mkdtempSync,
|
|
3621
|
-
readdirSync as
|
|
3622
|
-
readFileSync as
|
|
3847
|
+
readdirSync as readdirSync4,
|
|
3848
|
+
readFileSync as readFileSync9,
|
|
3623
3849
|
rmSync
|
|
3624
3850
|
} from "fs";
|
|
3625
3851
|
import { tmpdir } from "os";
|
|
3626
|
-
import { join as
|
|
3852
|
+
import { join as join11 } from "path";
|
|
3627
3853
|
import { createRequire } from "module";
|
|
3628
|
-
var CONDUCTOR_APP_SUPPORT =
|
|
3854
|
+
var CONDUCTOR_APP_SUPPORT = join11(
|
|
3629
3855
|
process.env.HOME ?? "",
|
|
3630
3856
|
"Library",
|
|
3631
3857
|
"Application Support",
|
|
3632
3858
|
"com.conductor.app"
|
|
3633
3859
|
);
|
|
3634
|
-
var CONDUCTOR_DB =
|
|
3635
|
-
var CURSOR_SDK_STORE =
|
|
3860
|
+
var CONDUCTOR_DB = join11(CONDUCTOR_APP_SUPPORT, "conductor.db");
|
|
3861
|
+
var CURSOR_SDK_STORE = join11(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
|
|
3636
3862
|
function thisModuleFile() {
|
|
3637
3863
|
const cjsFile = typeof __filename !== "undefined" ? __filename : "";
|
|
3638
|
-
return cjsFile || process.argv[1] ||
|
|
3864
|
+
return cjsFile || process.argv[1] || join11(process.cwd(), "package.json");
|
|
3639
3865
|
}
|
|
3640
3866
|
function openReadonlySqlite(file) {
|
|
3641
3867
|
const req = createRequire(thisModuleFile());
|
|
@@ -3653,21 +3879,21 @@ function mapAgentType(raw) {
|
|
|
3653
3879
|
return null;
|
|
3654
3880
|
}
|
|
3655
3881
|
function resolveConductorCursorAgentId(workspacePath) {
|
|
3656
|
-
if (!workspacePath || !
|
|
3882
|
+
if (!workspacePath || !existsSync11(CURSOR_SDK_STORE)) return null;
|
|
3657
3883
|
const normalized = workspacePath.replace(/\/$/, "");
|
|
3658
3884
|
let best = null;
|
|
3659
3885
|
let hashes;
|
|
3660
3886
|
try {
|
|
3661
|
-
hashes =
|
|
3887
|
+
hashes = readdirSync4(CURSOR_SDK_STORE);
|
|
3662
3888
|
} catch {
|
|
3663
3889
|
return null;
|
|
3664
3890
|
}
|
|
3665
3891
|
for (const hash of hashes) {
|
|
3666
|
-
const agentsFile =
|
|
3667
|
-
if (!
|
|
3892
|
+
const agentsFile = join11(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
3893
|
+
if (!existsSync11(agentsFile)) continue;
|
|
3668
3894
|
let text;
|
|
3669
3895
|
try {
|
|
3670
|
-
text =
|
|
3896
|
+
text = readFileSync9(agentsFile, "utf8");
|
|
3671
3897
|
} catch {
|
|
3672
3898
|
continue;
|
|
3673
3899
|
}
|
|
@@ -3691,7 +3917,7 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
3691
3917
|
return best?.agentId ?? null;
|
|
3692
3918
|
}
|
|
3693
3919
|
async function adoptThread(input) {
|
|
3694
|
-
if (!
|
|
3920
|
+
if (!existsSync11(input.worktreePath)) {
|
|
3695
3921
|
throw new Error(`Worktree not found: ${input.worktreePath}`);
|
|
3696
3922
|
}
|
|
3697
3923
|
const repoPath = await resolveRepoRoot(input.worktreePath);
|
|
@@ -3720,16 +3946,16 @@ function conductorDbPath() {
|
|
|
3720
3946
|
return CONDUCTOR_DB;
|
|
3721
3947
|
}
|
|
3722
3948
|
function listConductorWorkspaces() {
|
|
3723
|
-
if (!
|
|
3949
|
+
if (!existsSync11(CONDUCTOR_DB)) {
|
|
3724
3950
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
3725
3951
|
}
|
|
3726
|
-
const tmp = mkdtempSync(
|
|
3727
|
-
const snapshot =
|
|
3952
|
+
const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
|
|
3953
|
+
const snapshot = join11(tmp, "conductor.db");
|
|
3728
3954
|
try {
|
|
3729
3955
|
copyFileSync2(CONDUCTOR_DB, snapshot);
|
|
3730
3956
|
for (const suffix of ["-wal", "-shm"]) {
|
|
3731
3957
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
3732
|
-
if (
|
|
3958
|
+
if (existsSync11(src)) {
|
|
3733
3959
|
try {
|
|
3734
3960
|
copyFileSync2(src, `${snapshot}${suffix}`);
|
|
3735
3961
|
} catch {
|
|
@@ -3811,16 +4037,16 @@ function listConductorWorkspaces() {
|
|
|
3811
4037
|
}
|
|
3812
4038
|
}
|
|
3813
4039
|
function importConductorWorkspace(workspaceId) {
|
|
3814
|
-
if (!
|
|
4040
|
+
if (!existsSync11(CONDUCTOR_DB)) {
|
|
3815
4041
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
3816
4042
|
}
|
|
3817
|
-
const tmp = mkdtempSync(
|
|
3818
|
-
const snapshot =
|
|
4043
|
+
const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
|
|
4044
|
+
const snapshot = join11(tmp, "conductor.db");
|
|
3819
4045
|
try {
|
|
3820
4046
|
copyFileSync2(CONDUCTOR_DB, snapshot);
|
|
3821
4047
|
for (const suffix of ["-wal", "-shm"]) {
|
|
3822
4048
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
3823
|
-
if (
|
|
4049
|
+
if (existsSync11(src)) {
|
|
3824
4050
|
try {
|
|
3825
4051
|
copyFileSync2(src, `${snapshot}${suffix}`);
|
|
3826
4052
|
} catch {
|
|
@@ -3840,7 +4066,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
3840
4066
|
).get(workspaceId);
|
|
3841
4067
|
if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
|
|
3842
4068
|
const worktreePath = String(row.workspacePath);
|
|
3843
|
-
if (!
|
|
4069
|
+
if (!existsSync11(worktreePath)) {
|
|
3844
4070
|
throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
|
|
3845
4071
|
}
|
|
3846
4072
|
let sessionId = null;
|
|
@@ -3911,7 +4137,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
|
|
|
3911
4137
|
}
|
|
3912
4138
|
|
|
3913
4139
|
// src/threads/stack-layers.ts
|
|
3914
|
-
import { existsSync as
|
|
4140
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3915
4141
|
function stackIdFrom(stack) {
|
|
3916
4142
|
if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
|
|
3917
4143
|
const key = stack.layers.map((l) => l.branchName).join("|");
|
|
@@ -3974,7 +4200,7 @@ async function openStackLayer(input, _onSetupLine) {
|
|
|
3974
4200
|
let createdWorktree = false;
|
|
3975
4201
|
const trees = await listWorktrees(repoPath);
|
|
3976
4202
|
const checkedOut = trees.find((w) => w.branch === branchName);
|
|
3977
|
-
if (checkedOut?.path &&
|
|
4203
|
+
if (checkedOut?.path && existsSync12(checkedOut.path)) {
|
|
3978
4204
|
if (input.reuseExistingWorktree !== false) {
|
|
3979
4205
|
worktreePath = checkedOut.path;
|
|
3980
4206
|
} else {
|
|
@@ -4116,7 +4342,7 @@ async function initStackFromThread(input, onSetupLine) {
|
|
|
4116
4342
|
async function createPrStack(input, onSetupLine) {
|
|
4117
4343
|
await requireAgent(input.agent);
|
|
4118
4344
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
4119
|
-
if (!
|
|
4345
|
+
if (!existsSync12(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
|
|
4120
4346
|
if (!input.branches.length) throw new Error("At least one branch name required");
|
|
4121
4347
|
const status = await detectGhStack(repoPath);
|
|
4122
4348
|
if (!status.available) throw new Error(status.reason);
|
|
@@ -4183,7 +4409,7 @@ async function createPrStack(input, onSetupLine) {
|
|
|
4183
4409
|
}
|
|
4184
4410
|
}
|
|
4185
4411
|
const claimed = new Set(threads.map((t) => t.worktreePath));
|
|
4186
|
-
if (!claimed.has(bootstrap.worktreePath) &&
|
|
4412
|
+
if (!claimed.has(bootstrap.worktreePath) && existsSync12(bootstrap.worktreePath)) {
|
|
4187
4413
|
try {
|
|
4188
4414
|
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
4189
4415
|
deleteBranch: bootstrap.branchName
|
|
@@ -4205,10 +4431,10 @@ function stackAgentDefaultsFrom(input) {
|
|
|
4205
4431
|
}
|
|
4206
4432
|
|
|
4207
4433
|
// src/diff/diff.ts
|
|
4208
|
-
import { existsSync as
|
|
4209
|
-
import { dirname as
|
|
4434
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
4435
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
4210
4436
|
async function inspectGitWorktree(worktreePath) {
|
|
4211
|
-
if (!worktreePath || !
|
|
4437
|
+
if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
|
|
4212
4438
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
4213
4439
|
reject: false
|
|
4214
4440
|
});
|
|
@@ -4216,7 +4442,7 @@ async function inspectGitWorktree(worktreePath) {
|
|
|
4216
4442
|
return "ok";
|
|
4217
4443
|
}
|
|
4218
4444
|
async function initializeGitRepository(worktreePath) {
|
|
4219
|
-
if (!worktreePath || !
|
|
4445
|
+
if (!worktreePath || !existsSync13(worktreePath)) {
|
|
4220
4446
|
throw new Error("Worktree not found");
|
|
4221
4447
|
}
|
|
4222
4448
|
const status = await inspectGitWorktree(worktreePath);
|
|
@@ -4352,11 +4578,11 @@ new file mode 100644
|
|
|
4352
4578
|
};
|
|
4353
4579
|
}
|
|
4354
4580
|
async function untrackedPatch(worktreePath, path, maxHunk) {
|
|
4355
|
-
const abs =
|
|
4581
|
+
const abs = join12(worktreePath, path);
|
|
4356
4582
|
try {
|
|
4357
4583
|
const st = statSync2(abs);
|
|
4358
4584
|
if (st.isFile() && st.size > maxHunk) {
|
|
4359
|
-
const buf =
|
|
4585
|
+
const buf = readFileSync10(abs).subarray(0, maxHunk);
|
|
4360
4586
|
return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
|
|
4361
4587
|
}
|
|
4362
4588
|
} catch {
|
|
@@ -4857,7 +5083,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
|
|
|
4857
5083
|
function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
4858
5084
|
assertSafeRelativePath(relativePath);
|
|
4859
5085
|
const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
4860
|
-
const abs =
|
|
5086
|
+
const abs = join12(worktreePath, relativePath);
|
|
4861
5087
|
const st = statSync2(abs);
|
|
4862
5088
|
if (!st.isFile()) {
|
|
4863
5089
|
throw new Error(`Not a file: ${relativePath}`);
|
|
@@ -4867,7 +5093,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
4867
5093
|
`File too large to upload (${st.size} bytes; max ${maxBytes})`
|
|
4868
5094
|
);
|
|
4869
5095
|
}
|
|
4870
|
-
const buf =
|
|
5096
|
+
const buf = readFileSync10(abs);
|
|
4871
5097
|
return {
|
|
4872
5098
|
path: relativePath,
|
|
4873
5099
|
contentBase64: buf.toString("base64"),
|
|
@@ -4877,12 +5103,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
4877
5103
|
function readWorktreeFile(worktreePath, relativePath, opts) {
|
|
4878
5104
|
assertSafeRelativePath(relativePath);
|
|
4879
5105
|
const maxBytes = opts?.maxBytes ?? 2e5;
|
|
4880
|
-
const abs =
|
|
5106
|
+
const abs = join12(worktreePath, relativePath);
|
|
4881
5107
|
const st = statSync2(abs);
|
|
4882
5108
|
if (!st.isFile()) {
|
|
4883
5109
|
throw new Error(`Not a file: ${relativePath}`);
|
|
4884
5110
|
}
|
|
4885
|
-
const buf =
|
|
5111
|
+
const buf = readFileSync10(abs);
|
|
4886
5112
|
if (isImageRelativePath(relativePath)) {
|
|
4887
5113
|
const maxImageBytes = Math.max(maxBytes, 15e6);
|
|
4888
5114
|
const truncated2 = buf.length > maxImageBytes;
|
|
@@ -4925,9 +5151,9 @@ function assertSafeRelativePath(relativePath) {
|
|
|
4925
5151
|
}
|
|
4926
5152
|
function writeWorktreeFile(worktreePath, relativePath, content) {
|
|
4927
5153
|
assertSafeRelativePath(relativePath);
|
|
4928
|
-
const abs =
|
|
4929
|
-
|
|
4930
|
-
|
|
5154
|
+
const abs = join12(worktreePath, relativePath);
|
|
5155
|
+
mkdirSync4(dirname4(abs), { recursive: true });
|
|
5156
|
+
writeFileSync6(abs, content, "utf8");
|
|
4931
5157
|
return { path: relativePath };
|
|
4932
5158
|
}
|
|
4933
5159
|
async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
@@ -5052,9 +5278,9 @@ async function confirmLand(thread, opts) {
|
|
|
5052
5278
|
}
|
|
5053
5279
|
|
|
5054
5280
|
// src/skills/discover.ts
|
|
5055
|
-
import { existsSync as
|
|
5281
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
|
|
5056
5282
|
import { homedir } from "os";
|
|
5057
|
-
import { join as
|
|
5283
|
+
import { join as join13 } from "path";
|
|
5058
5284
|
|
|
5059
5285
|
// src/skills/bundled/long-running.ts
|
|
5060
5286
|
var LONG_RUNNING_SKILL_COMMAND = "long-running";
|
|
@@ -5065,7 +5291,7 @@ var LONG_RUNNING_SKILL_BODY = `# Long-running jobs
|
|
|
5065
5291
|
|
|
5066
5292
|
A Sideboard or Cursor **worktree turn** SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. \`block_until_ms: 0\` is not enough \u2014 the child stays in that group.
|
|
5067
5293
|
|
|
5068
|
-
Do **not** ask the human to check back. Detach, then **wait** in 45s slices (same idea as
|
|
5294
|
+
Do **not** ask the human to check back. Detach, then **wait** in 45s slices (MCP \`wait_for_job\`, same idea as \`wait_for_turn\`) until \`stillRunning\` is false. Do not end the turn with \u201CI\u2019ll let you know.\u201D
|
|
5069
5295
|
|
|
5070
5296
|
## Tool
|
|
5071
5297
|
|
|
@@ -5075,7 +5301,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
|
|
|
5075
5301
|
# Start (exits in ~1s; job survives this turn)
|
|
5076
5302
|
node <detached-job.js> start <id> -- <command> [args...]
|
|
5077
5303
|
|
|
5078
|
-
# Wait \u2014
|
|
5304
|
+
# Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
|
|
5305
|
+
# Shell fallback:
|
|
5079
5306
|
node <detached-job.js> wait <id>
|
|
5080
5307
|
|
|
5081
5308
|
# Block until the process exits (humans / a turn that will not be interrupted)
|
|
@@ -5088,7 +5315,7 @@ node <detached-job.js> status <id>
|
|
|
5088
5315
|
|
|
5089
5316
|
Wait JSON:
|
|
5090
5317
|
|
|
5091
|
-
- \`stillRunning: true\` \u2192
|
|
5318
|
+
- \`stillRunning: true\` \u2192 **call wait_for_job again** (or shell wait). Progress is in \`progress\` / \`phase\`. Do not start a second job. Do not ping the user.
|
|
5092
5319
|
- \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
|
|
5093
5320
|
- \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
|
|
5094
5321
|
|
|
@@ -5118,7 +5345,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
5118
5345
|
|
|
5119
5346
|
1. \`start\` once. If JSON says \`already-running\`, do not start again.
|
|
5120
5347
|
2. Immediately \`present_artifact\` \`type=log\` (same \`artifact_id\`, \`status=running\`) \u2014 the human should see **working** in the side column, not a \u201Ccheck back later\u201D message.
|
|
5121
|
-
3. Loop \`
|
|
5348
|
+
3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
|
|
5122
5349
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
5123
5350
|
|
|
5124
5351
|
Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
|
|
@@ -5183,7 +5410,7 @@ function parseFrontmatter(content) {
|
|
|
5183
5410
|
}
|
|
5184
5411
|
function readSkill(skillMd, source) {
|
|
5185
5412
|
try {
|
|
5186
|
-
const content =
|
|
5413
|
+
const content = readFileSync11(skillMd, "utf8");
|
|
5187
5414
|
const { name: fmName, description } = parseFrontmatter(content);
|
|
5188
5415
|
const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
|
|
5189
5416
|
const name = fmName || dirName;
|
|
@@ -5202,17 +5429,17 @@ function readSkill(skillMd, source) {
|
|
|
5202
5429
|
}
|
|
5203
5430
|
}
|
|
5204
5431
|
function scanSkillsDir(dir, source, out) {
|
|
5205
|
-
if (!
|
|
5432
|
+
if (!existsSync14(dir)) return;
|
|
5206
5433
|
let entries;
|
|
5207
5434
|
try {
|
|
5208
|
-
entries =
|
|
5435
|
+
entries = readdirSync5(dir);
|
|
5209
5436
|
} catch {
|
|
5210
5437
|
return;
|
|
5211
5438
|
}
|
|
5212
5439
|
for (const entry of entries) {
|
|
5213
5440
|
if (entry.startsWith(".")) continue;
|
|
5214
|
-
const skillMd =
|
|
5215
|
-
if (!
|
|
5441
|
+
const skillMd = join13(dir, entry, "SKILL.md");
|
|
5442
|
+
if (!existsSync14(skillMd)) continue;
|
|
5216
5443
|
try {
|
|
5217
5444
|
if (!statSync3(skillMd).isFile()) continue;
|
|
5218
5445
|
} catch {
|
|
@@ -5223,22 +5450,22 @@ function scanSkillsDir(dir, source, out) {
|
|
|
5223
5450
|
}
|
|
5224
5451
|
}
|
|
5225
5452
|
function scanClaudePluginSkills(pluginsRoot, out) {
|
|
5226
|
-
if (!
|
|
5453
|
+
if (!existsSync14(pluginsRoot)) return;
|
|
5227
5454
|
const walk = (dir, depth, lookingForSkillsDir) => {
|
|
5228
5455
|
if (depth > 7) return;
|
|
5229
5456
|
let entries;
|
|
5230
5457
|
try {
|
|
5231
|
-
entries =
|
|
5458
|
+
entries = readdirSync5(dir);
|
|
5232
5459
|
} catch {
|
|
5233
5460
|
return;
|
|
5234
5461
|
}
|
|
5235
5462
|
if (lookingForSkillsDir && entries.includes("SKILL.md")) {
|
|
5236
|
-
const skill = readSkill(
|
|
5463
|
+
const skill = readSkill(join13(dir, "SKILL.md"), "cli");
|
|
5237
5464
|
if (skill) out.push(skill);
|
|
5238
5465
|
}
|
|
5239
5466
|
for (const entry of entries) {
|
|
5240
5467
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
5241
|
-
const full =
|
|
5468
|
+
const full = join13(dir, entry);
|
|
5242
5469
|
try {
|
|
5243
5470
|
if (!statSync3(full).isDirectory()) continue;
|
|
5244
5471
|
} catch {
|
|
@@ -5258,17 +5485,17 @@ function discoverSkills(worktreePath) {
|
|
|
5258
5485
|
const home = homedir();
|
|
5259
5486
|
const collected = [];
|
|
5260
5487
|
for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
|
|
5261
|
-
scanSkillsDir(
|
|
5488
|
+
scanSkillsDir(join13(worktreePath, rel), "workspace", collected);
|
|
5262
5489
|
}
|
|
5263
5490
|
for (const abs of [
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5491
|
+
join13(home, ".claude/skills"),
|
|
5492
|
+
join13(home, ".cursor/skills"),
|
|
5493
|
+
join13(home, ".sideboard/skills"),
|
|
5494
|
+
join13(home, ".brightsy/skills")
|
|
5268
5495
|
]) {
|
|
5269
5496
|
scanSkillsDir(abs, "user", collected);
|
|
5270
5497
|
}
|
|
5271
|
-
scanClaudePluginSkills(
|
|
5498
|
+
scanClaudePluginSkills(join13(home, ".claude/plugins"), collected);
|
|
5272
5499
|
collected.push(...bundledSkills());
|
|
5273
5500
|
const rank = {
|
|
5274
5501
|
workspace: 0,
|
|
@@ -5294,7 +5521,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
|
|
|
5294
5521
|
\u2026(truncated)` : body;
|
|
5295
5522
|
}
|
|
5296
5523
|
}
|
|
5297
|
-
const raw =
|
|
5524
|
+
const raw = readFileSync11(skillPath, "utf8");
|
|
5298
5525
|
if (raw.startsWith("---")) {
|
|
5299
5526
|
const end = raw.indexOf("\n---", 3);
|
|
5300
5527
|
if (end >= 0) {
|
|
@@ -5387,27 +5614,27 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
5387
5614
|
}
|
|
5388
5615
|
|
|
5389
5616
|
// src/agents/instructions.ts
|
|
5390
|
-
import { existsSync as
|
|
5391
|
-
import { join as
|
|
5617
|
+
import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
|
|
5618
|
+
import { join as join15 } from "path";
|
|
5392
5619
|
|
|
5393
5620
|
// src/skills/detached-job-path.ts
|
|
5394
|
-
import { existsSync as
|
|
5395
|
-
import { dirname as
|
|
5621
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5622
|
+
import { dirname as dirname5, join as join14 } from "path";
|
|
5396
5623
|
import { fileURLToPath } from "url";
|
|
5397
5624
|
function packagedDetachedJobPath() {
|
|
5398
5625
|
const dir = packagedMcpDir();
|
|
5399
5626
|
if (!dir) return null;
|
|
5400
|
-
const script =
|
|
5401
|
-
return
|
|
5627
|
+
const script = join14(dir, "scripts", "detached-job.js");
|
|
5628
|
+
return existsSync15(script) ? script : null;
|
|
5402
5629
|
}
|
|
5403
5630
|
function resolveDetachedJobScript() {
|
|
5404
5631
|
const packaged = packagedDetachedJobPath();
|
|
5405
5632
|
if (packaged) return packaged;
|
|
5406
|
-
let dir =
|
|
5633
|
+
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
5407
5634
|
for (let i = 0; i < 8; i++) {
|
|
5408
|
-
const candidate =
|
|
5409
|
-
if (
|
|
5410
|
-
const parent =
|
|
5635
|
+
const candidate = join14(dir, "scripts", "detached-job.js");
|
|
5636
|
+
if (existsSync15(candidate)) return candidate;
|
|
5637
|
+
const parent = dirname5(dir);
|
|
5411
5638
|
if (parent === dir) break;
|
|
5412
5639
|
dir = parent;
|
|
5413
5640
|
}
|
|
@@ -5635,14 +5862,15 @@ function formatLongRunningDirective(opts) {
|
|
|
5635
5862
|
`Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
|
|
5636
5863
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
|
|
5637
5864
|
"- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
|
|
5638
|
-
`- Loop
|
|
5865
|
+
`- Loop Sideboard MCP \`wait_for_job\` with the same id (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait_for_job again. Shell fallback: \`${invoke} wait <id>\`. Do not resend the full log or HTML.`,
|
|
5866
|
+
"- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false.",
|
|
5639
5867
|
"- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
|
|
5640
5868
|
"State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
|
|
5641
5869
|
].join("\n");
|
|
5642
5870
|
}
|
|
5643
5871
|
function formatLongRunningReminder(opts) {
|
|
5644
5872
|
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
5645
|
-
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not
|
|
5873
|
+
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait), present_artifact type=log (delta). Do not say you will let the user know later \u2014 stay in the turn.`;
|
|
5646
5874
|
}
|
|
5647
5875
|
function formatArtifactDirective() {
|
|
5648
5876
|
return [
|
|
@@ -5693,11 +5921,11 @@ function loadAgentInstructions(worktreePath, agent) {
|
|
|
5693
5921
|
const out = [];
|
|
5694
5922
|
for (const rel of candidates) {
|
|
5695
5923
|
if (seenPaths.has(rel)) continue;
|
|
5696
|
-
const abs =
|
|
5697
|
-
if (!
|
|
5924
|
+
const abs = join15(worktreePath, rel);
|
|
5925
|
+
if (!existsSync16(abs)) continue;
|
|
5698
5926
|
try {
|
|
5699
5927
|
if (!statSync4(abs).isFile()) continue;
|
|
5700
|
-
let content =
|
|
5928
|
+
let content = readFileSync12(abs, "utf8");
|
|
5701
5929
|
if (!content.trim()) continue;
|
|
5702
5930
|
if (content.length > MAX_CHARS_PER_FILE) {
|
|
5703
5931
|
content = `${content.slice(0, MAX_CHARS_PER_FILE)}
|
|
@@ -6017,8 +6245,8 @@ async function syncThreadBranchFromGit(threadId) {
|
|
|
6017
6245
|
|
|
6018
6246
|
// src/store/schedules.ts
|
|
6019
6247
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6020
|
-
import { existsSync as
|
|
6021
|
-
import { join as
|
|
6248
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
6249
|
+
import { join as join16 } from "path";
|
|
6022
6250
|
import { Cron } from "croner";
|
|
6023
6251
|
var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
|
|
6024
6252
|
var UNIT_MS = {
|
|
@@ -6028,7 +6256,7 @@ var UNIT_MS = {
|
|
|
6028
6256
|
d: 864e5
|
|
6029
6257
|
};
|
|
6030
6258
|
function schedulesPath() {
|
|
6031
|
-
return
|
|
6259
|
+
return join16(appDataDir(), "schedules.json");
|
|
6032
6260
|
}
|
|
6033
6261
|
function parseDurationMs(every) {
|
|
6034
6262
|
const m = every.trim().match(DURATION_RE);
|
|
@@ -6134,9 +6362,9 @@ function normalizeTask(raw) {
|
|
|
6134
6362
|
}
|
|
6135
6363
|
function readAll() {
|
|
6136
6364
|
const path = schedulesPath();
|
|
6137
|
-
if (!
|
|
6365
|
+
if (!existsSync17(path)) return [];
|
|
6138
6366
|
try {
|
|
6139
|
-
const parsed = JSON.parse(
|
|
6367
|
+
const parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
6140
6368
|
if (!Array.isArray(parsed)) return [];
|
|
6141
6369
|
return parsed.map((row) => {
|
|
6142
6370
|
try {
|
|
@@ -6262,7 +6490,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
6262
6490
|
${prompt}`;
|
|
6263
6491
|
}
|
|
6264
6492
|
async function defaultDeps() {
|
|
6265
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
6493
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-ZVW2AC6B.js");
|
|
6266
6494
|
const orch = getOrchestrator2();
|
|
6267
6495
|
return {
|
|
6268
6496
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -6419,6 +6647,9 @@ var Orchestrator = class {
|
|
|
6419
6647
|
* finish or a user send so a later crash can recover again.
|
|
6420
6648
|
*/
|
|
6421
6649
|
crashContinued = /* @__PURE__ */ new Set();
|
|
6650
|
+
/** Auto-continues after a worktree turn ended while a detached job still runs. */
|
|
6651
|
+
jobContinueCount = /* @__PURE__ */ new Map();
|
|
6652
|
+
jobContinueNudged = /* @__PURE__ */ new Set();
|
|
6422
6653
|
maxConcurrent;
|
|
6423
6654
|
runningCount = 0;
|
|
6424
6655
|
constructor(opts) {
|
|
@@ -6502,7 +6733,7 @@ var Orchestrator = class {
|
|
|
6502
6733
|
}
|
|
6503
6734
|
continue;
|
|
6504
6735
|
}
|
|
6505
|
-
if (!
|
|
6736
|
+
if (!existsSync18(thread.worktreePath)) {
|
|
6506
6737
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
6507
6738
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
6508
6739
|
continue;
|
|
@@ -6717,6 +6948,37 @@ var Orchestrator = class {
|
|
|
6717
6948
|
this.emit({ type: "queue_changed", threadId, queue });
|
|
6718
6949
|
this.haltDrain.delete(threadId);
|
|
6719
6950
|
}
|
|
6951
|
+
/**
|
|
6952
|
+
* Worktree agent ended the turn after “I’ll let you know” (or left a
|
|
6953
|
+
* detached test/pack job running). Queue a continue so the chat does not
|
|
6954
|
+
* go idle with nobody watching the log.
|
|
6955
|
+
*/
|
|
6956
|
+
maybeEnqueueJobContinue(threadId, chatText, parts = []) {
|
|
6957
|
+
if (this.haltDrain.has(threadId)) return;
|
|
6958
|
+
const thread = readThread(threadId);
|
|
6959
|
+
if (!thread || thread.status === "archived") return;
|
|
6960
|
+
const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
|
|
6961
|
+
const decision = planJobContinue({
|
|
6962
|
+
runningJobIds,
|
|
6963
|
+
chatText,
|
|
6964
|
+
queueLength: thread.queue.length,
|
|
6965
|
+
continueCount: this.jobContinueCount.get(threadId) ?? 0,
|
|
6966
|
+
alreadyNudged: this.jobContinueNudged.has(threadId),
|
|
6967
|
+
isOrchestrator: isOrchestratorThread(thread),
|
|
6968
|
+
agent: thread.agent,
|
|
6969
|
+
watchedJob: turnWatchedDetachedJob(parts)
|
|
6970
|
+
});
|
|
6971
|
+
if (decision.action === "none") {
|
|
6972
|
+
if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
|
|
6973
|
+
return;
|
|
6974
|
+
}
|
|
6975
|
+
if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
|
|
6976
|
+
else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
|
|
6977
|
+
const queue = [decision.prompt, ...thread.queue];
|
|
6978
|
+
updateThread(threadId, { queue });
|
|
6979
|
+
this.emit({ type: "queue_changed", threadId, queue });
|
|
6980
|
+
this.haltDrain.delete(threadId);
|
|
6981
|
+
}
|
|
6720
6982
|
getThreads(includeArchived = false) {
|
|
6721
6983
|
return listThreads({ includeArchived });
|
|
6722
6984
|
}
|
|
@@ -6816,6 +7078,8 @@ var Orchestrator = class {
|
|
|
6816
7078
|
}
|
|
6817
7079
|
const queue = [...current.queue, prompt];
|
|
6818
7080
|
this.crashContinued.delete(thread.id);
|
|
7081
|
+
this.jobContinueCount.delete(thread.id);
|
|
7082
|
+
this.jobContinueNudged.delete(thread.id);
|
|
6819
7083
|
this.haltDrain.delete(thread.id);
|
|
6820
7084
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
|
|
6821
7085
|
shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
|
|
@@ -7330,6 +7594,7 @@ var Orchestrator = class {
|
|
|
7330
7594
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
7331
7595
|
if (exitCode === 0) {
|
|
7332
7596
|
this.crashContinued.delete(threadId);
|
|
7597
|
+
this.maybeEnqueueJobContinue(threadId, chatText, parts);
|
|
7333
7598
|
} else {
|
|
7334
7599
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
7335
7600
|
void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
|
|
@@ -8330,7 +8595,7 @@ var Orchestrator = class {
|
|
|
8330
8595
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
8331
8596
|
return restored2;
|
|
8332
8597
|
}
|
|
8333
|
-
if (!
|
|
8598
|
+
if (!existsSync18(thread.worktreePath)) {
|
|
8334
8599
|
if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
|
|
8335
8600
|
throw new Error(
|
|
8336
8601
|
`Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
|
|
@@ -8592,6 +8857,11 @@ export {
|
|
|
8592
8857
|
worktreeCleanupSettings,
|
|
8593
8858
|
applyThreadIntoMain,
|
|
8594
8859
|
cloneRepoIntoSideboard,
|
|
8860
|
+
mcpWaitForTurnTimeoutMs,
|
|
8861
|
+
mcpWaitStillRunningHint,
|
|
8862
|
+
mcpWaitFinishedHint,
|
|
8863
|
+
mcpWaitForJobTimeoutMs,
|
|
8864
|
+
waitForDetachedJob,
|
|
8595
8865
|
detectAgents,
|
|
8596
8866
|
requireAgent,
|
|
8597
8867
|
isHomeBoardThread,
|