@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
|
@@ -33,12 +33,12 @@ import {
|
|
|
33
33
|
summarizeTurnStderr,
|
|
34
34
|
toolDescription,
|
|
35
35
|
turnFailChatText
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-EPQIOAAY.js";
|
|
37
37
|
import {
|
|
38
38
|
extractPresentedPlan,
|
|
39
39
|
readPlanFile,
|
|
40
40
|
writePlanFile
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-JV4VNFMS.js";
|
|
42
42
|
import {
|
|
43
43
|
releaseCaffeinateHoldForThread
|
|
44
44
|
} from "./chunk-OCQPTUR7.js";
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
stageAbsolutePathsAsAttachments,
|
|
68
68
|
stageBuffersAsAttachments,
|
|
69
69
|
syncWorkspacesFromThreads
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-3SUZMPGL.js";
|
|
71
71
|
import {
|
|
72
72
|
addPrStackLayer,
|
|
73
73
|
allocateTeamName,
|
|
@@ -115,11 +115,13 @@ import {
|
|
|
115
115
|
withRepoGitLock,
|
|
116
116
|
worktreeDisplayLabelForGroup,
|
|
117
117
|
worktreeNameFromPath
|
|
118
|
-
} from "./chunk-
|
|
118
|
+
} from "./chunk-A7QXLYBN.js";
|
|
119
119
|
import {
|
|
120
120
|
ATTACHMENTS_DIR,
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
DETACHED_JOBS_DIR,
|
|
122
|
+
LEGACY_ATTACHMENTS_DIR,
|
|
123
|
+
LEGACY_DETACHED_JOBS_DIR
|
|
124
|
+
} from "./chunk-5U4NZLBH.js";
|
|
123
125
|
import {
|
|
124
126
|
appendMessage,
|
|
125
127
|
createEmptyThread,
|
|
@@ -394,7 +396,7 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
394
396
|
await continueOnReply(threadId, prompt);
|
|
395
397
|
return;
|
|
396
398
|
}
|
|
397
|
-
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-
|
|
399
|
+
const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-J727WIJY.js");
|
|
398
400
|
await getOrchestrator2().send(threadId, prompt);
|
|
399
401
|
} catch {
|
|
400
402
|
}
|
|
@@ -654,7 +656,7 @@ async function pollSlackOutboundWatches(opts) {
|
|
|
654
656
|
}
|
|
655
657
|
|
|
656
658
|
// src/orchestrator/orchestrator.ts
|
|
657
|
-
import { existsSync as
|
|
659
|
+
import { existsSync as existsSync18 } from "fs";
|
|
658
660
|
|
|
659
661
|
// src/agents/spawn.ts
|
|
660
662
|
import { createInterface } from "readline";
|
|
@@ -735,9 +737,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
735
737
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
736
738
|
);
|
|
737
739
|
}
|
|
738
|
-
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-
|
|
740
|
+
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-REI55FZ6.js");
|
|
739
741
|
if (isGlobalThread2(thread)) {
|
|
740
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-
|
|
742
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-GZESIQNH.js");
|
|
741
743
|
ensureGlobalCoordinatorCwd2(
|
|
742
744
|
isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
|
|
743
745
|
);
|
|
@@ -2479,8 +2481,232 @@ function notifyParentOfChildHalt(child, status, send) {
|
|
|
2479
2481
|
return true;
|
|
2480
2482
|
}
|
|
2481
2483
|
|
|
2484
|
+
// src/mcp/wait-for-job.ts
|
|
2485
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2486
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
2487
|
+
|
|
2488
|
+
// src/mcp/wait-for-turn.ts
|
|
2489
|
+
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
2490
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
2491
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
2492
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
2493
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
2494
|
+
}
|
|
2495
|
+
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.";
|
|
2496
|
+
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.";
|
|
2497
|
+
function mcpWaitStillRunningHint(status) {
|
|
2498
|
+
return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
|
|
2499
|
+
}
|
|
2500
|
+
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.";
|
|
2501
|
+
var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
2502
|
+
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.";
|
|
2503
|
+
function mcpWaitFinishedHint(status) {
|
|
2504
|
+
if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
|
|
2505
|
+
if (status === "broken") return MCP_WAIT_BROKEN_HINT;
|
|
2506
|
+
if (status === "error") return MCP_WAIT_ERROR_HINT;
|
|
2507
|
+
return void 0;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
// src/mcp/wait-for-job.ts
|
|
2511
|
+
var MAX_JOB_CONTINUES = 8;
|
|
2512
|
+
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.";
|
|
2513
|
+
var JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
2514
|
+
function mcpWaitForJobTimeoutMs(requested) {
|
|
2515
|
+
return mcpWaitForTurnTimeoutMs(requested);
|
|
2516
|
+
}
|
|
2517
|
+
function sanitizeDetachedJobId(id) {
|
|
2518
|
+
const s = id.trim();
|
|
2519
|
+
if (!JOB_ID_RE.test(s)) {
|
|
2520
|
+
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
2521
|
+
}
|
|
2522
|
+
return s;
|
|
2523
|
+
}
|
|
2524
|
+
function jobAlive(pid) {
|
|
2525
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
2526
|
+
try {
|
|
2527
|
+
process.kill(pid, 0);
|
|
2528
|
+
return true;
|
|
2529
|
+
} catch {
|
|
2530
|
+
return false;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
function readIntFile(file) {
|
|
2534
|
+
if (!existsSync8(file)) return null;
|
|
2535
|
+
const n = Number.parseInt(readFileSync7(file, "utf8").trim(), 10);
|
|
2536
|
+
return Number.isInteger(n) ? n : null;
|
|
2537
|
+
}
|
|
2538
|
+
function jobDir(root, id, legacy = false) {
|
|
2539
|
+
return join10(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
2540
|
+
}
|
|
2541
|
+
function resolveJobDir(root, id) {
|
|
2542
|
+
const modern = jobDir(root, id, false);
|
|
2543
|
+
if (existsSync8(modern)) return modern;
|
|
2544
|
+
const legacy = jobDir(root, id, true);
|
|
2545
|
+
if (existsSync8(legacy)) return legacy;
|
|
2546
|
+
return modern;
|
|
2547
|
+
}
|
|
2548
|
+
function listJobIdsIn(root, rel) {
|
|
2549
|
+
const dir = join10(root, rel);
|
|
2550
|
+
if (!existsSync8(dir)) return [];
|
|
2551
|
+
return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
2552
|
+
}
|
|
2553
|
+
function listRunningDetachedJobs(worktreePath) {
|
|
2554
|
+
const root = worktreePath.trim();
|
|
2555
|
+
if (!root) return [];
|
|
2556
|
+
const ids = /* @__PURE__ */ new Set([
|
|
2557
|
+
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
2558
|
+
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
2559
|
+
]);
|
|
2560
|
+
const running = [];
|
|
2561
|
+
for (const id of ids) {
|
|
2562
|
+
const dir = resolveJobDir(root, id);
|
|
2563
|
+
const pid = readIntFile(join10(dir, "pid"));
|
|
2564
|
+
if (pid != null && jobAlive(pid)) running.push(id);
|
|
2565
|
+
}
|
|
2566
|
+
return running.sort();
|
|
2567
|
+
}
|
|
2568
|
+
function looksLikeDeferredDonePromise(text) {
|
|
2569
|
+
const t = (text ?? "").trim();
|
|
2570
|
+
if (!t) return false;
|
|
2571
|
+
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);
|
|
2572
|
+
}
|
|
2573
|
+
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
2574
|
+
const ids = jobIds.join(", ");
|
|
2575
|
+
return [
|
|
2576
|
+
`Detached job still running: ${ids}.`,
|
|
2577
|
+
"Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
|
|
2578
|
+
"Then report the result. Do not tell the user you will let them know later."
|
|
2579
|
+
].join(" ");
|
|
2580
|
+
}
|
|
2581
|
+
function formatDeferredDoneContinuePrompt() {
|
|
2582
|
+
return [
|
|
2583
|
+
"You ended the turn after promising to report later, but no detached job is running.",
|
|
2584
|
+
"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.",
|
|
2585
|
+
"Do not say you will let the user know later."
|
|
2586
|
+
].join(" ");
|
|
2587
|
+
}
|
|
2588
|
+
function turnWatchedDetachedJob(parts) {
|
|
2589
|
+
return parts.some((p) => {
|
|
2590
|
+
if (p.type !== "tool") return false;
|
|
2591
|
+
if (/wait_for_job$/i.test(p.name ?? "")) return true;
|
|
2592
|
+
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
2593
|
+
return /detached-job\.js\b/i.test(blob);
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
function planJobContinue(opts) {
|
|
2597
|
+
if (opts.isOrchestrator) return { action: "none" };
|
|
2598
|
+
if (opts.agent === "brightsy") return { action: "none" };
|
|
2599
|
+
if (opts.queueLength > 0) return { action: "none" };
|
|
2600
|
+
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
2601
|
+
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
2602
|
+
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
2603
|
+
return {
|
|
2604
|
+
action: "wait",
|
|
2605
|
+
jobIds: opts.runningJobIds,
|
|
2606
|
+
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
2610
|
+
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
2611
|
+
}
|
|
2612
|
+
return { action: "none" };
|
|
2613
|
+
}
|
|
2614
|
+
function tailProgress(logFile, maxLines = 12) {
|
|
2615
|
+
if (!existsSync8(logFile)) return "(no log yet)";
|
|
2616
|
+
const lines = readFileSync7(logFile, "utf8").split("\n");
|
|
2617
|
+
return lines.slice(-maxLines).join("\n");
|
|
2618
|
+
}
|
|
2619
|
+
function readLogLines(file) {
|
|
2620
|
+
if (!existsSync8(file)) return [];
|
|
2621
|
+
const lines = readFileSync7(file, "utf8").split("\n");
|
|
2622
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
2623
|
+
return lines;
|
|
2624
|
+
}
|
|
2625
|
+
function takeDelta(logFile, cursorFile) {
|
|
2626
|
+
const lines = readLogLines(logFile);
|
|
2627
|
+
const cursor = readIntFile(cursorFile) ?? 0;
|
|
2628
|
+
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
2629
|
+
return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
|
|
2630
|
+
}
|
|
2631
|
+
function snapshotJob(dir) {
|
|
2632
|
+
const pid = readIntFile(join10(dir, "pid"));
|
|
2633
|
+
const running = pid != null && jobAlive(pid);
|
|
2634
|
+
return {
|
|
2635
|
+
pid,
|
|
2636
|
+
running,
|
|
2637
|
+
exitCode: readIntFile(join10(dir, "exit")),
|
|
2638
|
+
log: join10(dir, "log"),
|
|
2639
|
+
cursor: join10(dir, "present.cursor"),
|
|
2640
|
+
progress: tailProgress(join10(dir, "log"))
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
function toResult(id, snap, extra) {
|
|
2644
|
+
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
2645
|
+
const ok = !snap.running && snap.exitCode === 0;
|
|
2646
|
+
const stillRunning = snap.running && !ok;
|
|
2647
|
+
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
2648
|
+
try {
|
|
2649
|
+
mkdirSync3(dirname3(snap.cursor), { recursive: true });
|
|
2650
|
+
writeFileSync4(snap.cursor, `${nextCursor}
|
|
2651
|
+
`);
|
|
2652
|
+
} catch {
|
|
2653
|
+
}
|
|
2654
|
+
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
2655
|
+
return {
|
|
2656
|
+
stillRunning,
|
|
2657
|
+
ok,
|
|
2658
|
+
failed: Boolean(failed && !stillRunning && !ok),
|
|
2659
|
+
status,
|
|
2660
|
+
id,
|
|
2661
|
+
pid: snap.pid,
|
|
2662
|
+
exitCode: snap.exitCode ?? void 0,
|
|
2663
|
+
delta,
|
|
2664
|
+
progress: extra?.progress ?? snap.progress,
|
|
2665
|
+
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
2666
|
+
};
|
|
2667
|
+
}
|
|
2668
|
+
async function sleepMs(ms) {
|
|
2669
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2670
|
+
}
|
|
2671
|
+
async function waitForDetachedJob(cwd, id, opts) {
|
|
2672
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
2673
|
+
const root = cwd.trim() || process.cwd();
|
|
2674
|
+
const dir = resolveJobDir(root, jobId);
|
|
2675
|
+
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
2676
|
+
const sleep = opts?.sleep ?? sleepMs;
|
|
2677
|
+
if (!existsSync8(dir)) {
|
|
2678
|
+
return {
|
|
2679
|
+
stillRunning: false,
|
|
2680
|
+
ok: false,
|
|
2681
|
+
failed: true,
|
|
2682
|
+
status: "failed",
|
|
2683
|
+
id: jobId,
|
|
2684
|
+
delta: "",
|
|
2685
|
+
progress: "No detached job. Start one first.",
|
|
2686
|
+
hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
const deadline = Date.now() + timeoutMs;
|
|
2690
|
+
let snap = snapshotJob(dir);
|
|
2691
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
2692
|
+
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
2693
|
+
return toResult(jobId, snap, {
|
|
2694
|
+
failed: true,
|
|
2695
|
+
progress: "No detached job. Start one first."
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2698
|
+
while (Date.now() < deadline) {
|
|
2699
|
+
snap = snapshotJob(dir);
|
|
2700
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
2701
|
+
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
2702
|
+
await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
2703
|
+
}
|
|
2704
|
+
snap = snapshotJob(dir);
|
|
2705
|
+
return toResult(jobId, snap);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2482
2708
|
// src/threads/create.ts
|
|
2483
|
-
import { existsSync as
|
|
2709
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2484
2710
|
|
|
2485
2711
|
// src/detect/detect.ts
|
|
2486
2712
|
var REQUIRE_AGENT_TIMEOUT_MS = 8e3;
|
|
@@ -3000,7 +3226,7 @@ function reuseLiveThread(input, repoPath, match) {
|
|
|
3000
3226
|
}
|
|
3001
3227
|
async function createThread(input, _onSetupLine) {
|
|
3002
3228
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
3003
|
-
if (!
|
|
3229
|
+
if (!existsSync9(repoPath)) {
|
|
3004
3230
|
throw new Error(`Repo not found: ${repoPath}`);
|
|
3005
3231
|
}
|
|
3006
3232
|
const reused = reuseLiveThread(input, repoPath, {
|
|
@@ -3186,11 +3412,11 @@ function shouldRemoveWorktreeOnTeardown(thread) {
|
|
|
3186
3412
|
|
|
3187
3413
|
// src/store/turn-live.ts
|
|
3188
3414
|
import {
|
|
3189
|
-
existsSync as
|
|
3415
|
+
existsSync as existsSync10,
|
|
3190
3416
|
renameSync,
|
|
3191
3417
|
unlinkSync as unlinkSync2,
|
|
3192
|
-
writeFileSync as
|
|
3193
|
-
readFileSync as
|
|
3418
|
+
writeFileSync as writeFileSync5,
|
|
3419
|
+
readFileSync as readFileSync8
|
|
3194
3420
|
} from "fs";
|
|
3195
3421
|
var buffers = /* @__PURE__ */ new Map();
|
|
3196
3422
|
var FLUSH_MS = 800;
|
|
@@ -3267,7 +3493,7 @@ function writeTurnLive(threadId, progress) {
|
|
|
3267
3493
|
const path = threadLivePath(threadId);
|
|
3268
3494
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
3269
3495
|
try {
|
|
3270
|
-
|
|
3496
|
+
writeFileSync5(tmp, JSON.stringify(progress), "utf8");
|
|
3271
3497
|
renameSync(tmp, path);
|
|
3272
3498
|
} catch {
|
|
3273
3499
|
try {
|
|
@@ -3278,9 +3504,9 @@ function writeTurnLive(threadId, progress) {
|
|
|
3278
3504
|
}
|
|
3279
3505
|
function readTurnLive(threadId) {
|
|
3280
3506
|
const path = threadLivePath(threadId);
|
|
3281
|
-
if (!
|
|
3507
|
+
if (!existsSync10(path)) return null;
|
|
3282
3508
|
try {
|
|
3283
|
-
const raw = JSON.parse(
|
|
3509
|
+
const raw = JSON.parse(readFileSync8(path, "utf8"));
|
|
3284
3510
|
if (!raw || typeof raw.summary !== "string") return null;
|
|
3285
3511
|
return raw;
|
|
3286
3512
|
} catch {
|
|
@@ -3292,7 +3518,7 @@ function clearTurnLive(threadId) {
|
|
|
3292
3518
|
if (buf?.timer) clearTimeout(buf.timer);
|
|
3293
3519
|
buffers.delete(threadId);
|
|
3294
3520
|
const path = threadLivePath(threadId);
|
|
3295
|
-
if (!
|
|
3521
|
+
if (!existsSync10(path)) return;
|
|
3296
3522
|
try {
|
|
3297
3523
|
unlinkSync2(path);
|
|
3298
3524
|
} catch {
|
|
@@ -3477,26 +3703,26 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
|
3477
3703
|
import { execFileSync } from "child_process";
|
|
3478
3704
|
import {
|
|
3479
3705
|
copyFileSync as copyFileSync2,
|
|
3480
|
-
existsSync as
|
|
3706
|
+
existsSync as existsSync11,
|
|
3481
3707
|
mkdtempSync,
|
|
3482
|
-
readdirSync as
|
|
3483
|
-
readFileSync as
|
|
3708
|
+
readdirSync as readdirSync4,
|
|
3709
|
+
readFileSync as readFileSync9,
|
|
3484
3710
|
rmSync
|
|
3485
3711
|
} from "fs";
|
|
3486
3712
|
import { tmpdir } from "os";
|
|
3487
|
-
import { join as
|
|
3713
|
+
import { join as join11 } from "path";
|
|
3488
3714
|
import { createRequire } from "module";
|
|
3489
|
-
var CONDUCTOR_APP_SUPPORT =
|
|
3715
|
+
var CONDUCTOR_APP_SUPPORT = join11(
|
|
3490
3716
|
process.env.HOME ?? "",
|
|
3491
3717
|
"Library",
|
|
3492
3718
|
"Application Support",
|
|
3493
3719
|
"com.conductor.app"
|
|
3494
3720
|
);
|
|
3495
|
-
var CONDUCTOR_DB =
|
|
3496
|
-
var CURSOR_SDK_STORE =
|
|
3721
|
+
var CONDUCTOR_DB = join11(CONDUCTOR_APP_SUPPORT, "conductor.db");
|
|
3722
|
+
var CURSOR_SDK_STORE = join11(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
|
|
3497
3723
|
function thisModuleFile() {
|
|
3498
3724
|
const cjsFile = typeof __filename !== "undefined" ? __filename : "";
|
|
3499
|
-
return cjsFile || process.argv[1] ||
|
|
3725
|
+
return cjsFile || process.argv[1] || join11(process.cwd(), "package.json");
|
|
3500
3726
|
}
|
|
3501
3727
|
function openReadonlySqlite(file) {
|
|
3502
3728
|
const req = createRequire(thisModuleFile());
|
|
@@ -3514,21 +3740,21 @@ function mapAgentType(raw) {
|
|
|
3514
3740
|
return null;
|
|
3515
3741
|
}
|
|
3516
3742
|
function resolveConductorCursorAgentId(workspacePath) {
|
|
3517
|
-
if (!workspacePath || !
|
|
3743
|
+
if (!workspacePath || !existsSync11(CURSOR_SDK_STORE)) return null;
|
|
3518
3744
|
const normalized = workspacePath.replace(/\/$/, "");
|
|
3519
3745
|
let best = null;
|
|
3520
3746
|
let hashes;
|
|
3521
3747
|
try {
|
|
3522
|
-
hashes =
|
|
3748
|
+
hashes = readdirSync4(CURSOR_SDK_STORE);
|
|
3523
3749
|
} catch {
|
|
3524
3750
|
return null;
|
|
3525
3751
|
}
|
|
3526
3752
|
for (const hash of hashes) {
|
|
3527
|
-
const agentsFile =
|
|
3528
|
-
if (!
|
|
3753
|
+
const agentsFile = join11(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
3754
|
+
if (!existsSync11(agentsFile)) continue;
|
|
3529
3755
|
let text;
|
|
3530
3756
|
try {
|
|
3531
|
-
text =
|
|
3757
|
+
text = readFileSync9(agentsFile, "utf8");
|
|
3532
3758
|
} catch {
|
|
3533
3759
|
continue;
|
|
3534
3760
|
}
|
|
@@ -3552,7 +3778,7 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
3552
3778
|
return best?.agentId ?? null;
|
|
3553
3779
|
}
|
|
3554
3780
|
async function adoptThread(input) {
|
|
3555
|
-
if (!
|
|
3781
|
+
if (!existsSync11(input.worktreePath)) {
|
|
3556
3782
|
throw new Error(`Worktree not found: ${input.worktreePath}`);
|
|
3557
3783
|
}
|
|
3558
3784
|
const repoPath = await resolveRepoRoot(input.worktreePath);
|
|
@@ -3571,23 +3797,23 @@ async function adoptThread(input) {
|
|
|
3571
3797
|
messages: input.messages ?? []
|
|
3572
3798
|
});
|
|
3573
3799
|
writeThread(thread);
|
|
3574
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
3800
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-OCEPZXFD.js");
|
|
3575
3801
|
await ensureWorkspace2(repoPath);
|
|
3576
3802
|
const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-N5X5I422.js");
|
|
3577
3803
|
await ensureWorktreeSideboardIgnored2(input.worktreePath);
|
|
3578
3804
|
return thread;
|
|
3579
3805
|
}
|
|
3580
3806
|
function listConductorWorkspaces() {
|
|
3581
|
-
if (!
|
|
3807
|
+
if (!existsSync11(CONDUCTOR_DB)) {
|
|
3582
3808
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
3583
3809
|
}
|
|
3584
|
-
const tmp = mkdtempSync(
|
|
3585
|
-
const snapshot =
|
|
3810
|
+
const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
|
|
3811
|
+
const snapshot = join11(tmp, "conductor.db");
|
|
3586
3812
|
try {
|
|
3587
3813
|
copyFileSync2(CONDUCTOR_DB, snapshot);
|
|
3588
3814
|
for (const suffix of ["-wal", "-shm"]) {
|
|
3589
3815
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
3590
|
-
if (
|
|
3816
|
+
if (existsSync11(src)) {
|
|
3591
3817
|
try {
|
|
3592
3818
|
copyFileSync2(src, `${snapshot}${suffix}`);
|
|
3593
3819
|
} catch {
|
|
@@ -3669,16 +3895,16 @@ function listConductorWorkspaces() {
|
|
|
3669
3895
|
}
|
|
3670
3896
|
}
|
|
3671
3897
|
function importConductorWorkspace(workspaceId) {
|
|
3672
|
-
if (!
|
|
3898
|
+
if (!existsSync11(CONDUCTOR_DB)) {
|
|
3673
3899
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
3674
3900
|
}
|
|
3675
|
-
const tmp = mkdtempSync(
|
|
3676
|
-
const snapshot =
|
|
3901
|
+
const tmp = mkdtempSync(join11(tmpdir(), "sideboard-conductor-"));
|
|
3902
|
+
const snapshot = join11(tmp, "conductor.db");
|
|
3677
3903
|
try {
|
|
3678
3904
|
copyFileSync2(CONDUCTOR_DB, snapshot);
|
|
3679
3905
|
for (const suffix of ["-wal", "-shm"]) {
|
|
3680
3906
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
3681
|
-
if (
|
|
3907
|
+
if (existsSync11(src)) {
|
|
3682
3908
|
try {
|
|
3683
3909
|
copyFileSync2(src, `${snapshot}${suffix}`);
|
|
3684
3910
|
} catch {
|
|
@@ -3698,7 +3924,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
3698
3924
|
).get(workspaceId);
|
|
3699
3925
|
if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
|
|
3700
3926
|
const worktreePath = String(row.workspacePath);
|
|
3701
|
-
if (!
|
|
3927
|
+
if (!existsSync11(worktreePath)) {
|
|
3702
3928
|
throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
|
|
3703
3929
|
}
|
|
3704
3930
|
let sessionId = null;
|
|
@@ -3769,7 +3995,7 @@ async function importConductorWorkspaceAsync(workspaceId) {
|
|
|
3769
3995
|
}
|
|
3770
3996
|
|
|
3771
3997
|
// src/threads/stack-layers.ts
|
|
3772
|
-
import { existsSync as
|
|
3998
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3773
3999
|
function stackIdFrom(stack) {
|
|
3774
4000
|
if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
|
|
3775
4001
|
const key = stack.layers.map((l) => l.branchName).join("|");
|
|
@@ -3832,7 +4058,7 @@ async function openStackLayer(input, _onSetupLine) {
|
|
|
3832
4058
|
let createdWorktree = false;
|
|
3833
4059
|
const trees = await listWorktrees(repoPath);
|
|
3834
4060
|
const checkedOut = trees.find((w) => w.branch === branchName);
|
|
3835
|
-
if (checkedOut?.path &&
|
|
4061
|
+
if (checkedOut?.path && existsSync12(checkedOut.path)) {
|
|
3836
4062
|
if (input.reuseExistingWorktree !== false) {
|
|
3837
4063
|
worktreePath = checkedOut.path;
|
|
3838
4064
|
} else {
|
|
@@ -3974,7 +4200,7 @@ async function initStackFromThread(input, onSetupLine) {
|
|
|
3974
4200
|
async function createPrStack(input, onSetupLine) {
|
|
3975
4201
|
await requireAgent(input.agent);
|
|
3976
4202
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
3977
|
-
if (!
|
|
4203
|
+
if (!existsSync12(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
|
|
3978
4204
|
if (!input.branches.length) throw new Error("At least one branch name required");
|
|
3979
4205
|
const status = await detectGhStack(repoPath);
|
|
3980
4206
|
if (!status.available) throw new Error(status.reason);
|
|
@@ -4041,7 +4267,7 @@ async function createPrStack(input, onSetupLine) {
|
|
|
4041
4267
|
}
|
|
4042
4268
|
}
|
|
4043
4269
|
const claimed = new Set(threads.map((t) => t.worktreePath));
|
|
4044
|
-
if (!claimed.has(bootstrap.worktreePath) &&
|
|
4270
|
+
if (!claimed.has(bootstrap.worktreePath) && existsSync12(bootstrap.worktreePath)) {
|
|
4045
4271
|
try {
|
|
4046
4272
|
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
4047
4273
|
deleteBranch: bootstrap.branchName
|
|
@@ -4053,10 +4279,10 @@ async function createPrStack(input, onSetupLine) {
|
|
|
4053
4279
|
}
|
|
4054
4280
|
|
|
4055
4281
|
// src/diff/diff.ts
|
|
4056
|
-
import { existsSync as
|
|
4057
|
-
import { dirname as
|
|
4282
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
4283
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
4058
4284
|
async function inspectGitWorktree(worktreePath) {
|
|
4059
|
-
if (!worktreePath || !
|
|
4285
|
+
if (!worktreePath || !existsSync13(worktreePath)) return "missing_worktree";
|
|
4060
4286
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
4061
4287
|
reject: false
|
|
4062
4288
|
});
|
|
@@ -4064,7 +4290,7 @@ async function inspectGitWorktree(worktreePath) {
|
|
|
4064
4290
|
return "ok";
|
|
4065
4291
|
}
|
|
4066
4292
|
async function initializeGitRepository(worktreePath) {
|
|
4067
|
-
if (!worktreePath || !
|
|
4293
|
+
if (!worktreePath || !existsSync13(worktreePath)) {
|
|
4068
4294
|
throw new Error("Worktree not found");
|
|
4069
4295
|
}
|
|
4070
4296
|
const status = await inspectGitWorktree(worktreePath);
|
|
@@ -4200,11 +4426,11 @@ new file mode 100644
|
|
|
4200
4426
|
};
|
|
4201
4427
|
}
|
|
4202
4428
|
async function untrackedPatch(worktreePath, path, maxHunk) {
|
|
4203
|
-
const abs =
|
|
4429
|
+
const abs = join12(worktreePath, path);
|
|
4204
4430
|
try {
|
|
4205
4431
|
const st = statSync2(abs);
|
|
4206
4432
|
if (st.isFile() && st.size > maxHunk) {
|
|
4207
|
-
const buf =
|
|
4433
|
+
const buf = readFileSync10(abs).subarray(0, maxHunk);
|
|
4208
4434
|
return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
|
|
4209
4435
|
}
|
|
4210
4436
|
} catch {
|
|
@@ -4705,7 +4931,7 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
|
|
|
4705
4931
|
function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
4706
4932
|
assertSafeRelativePath(relativePath);
|
|
4707
4933
|
const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
4708
|
-
const abs =
|
|
4934
|
+
const abs = join12(worktreePath, relativePath);
|
|
4709
4935
|
const st = statSync2(abs);
|
|
4710
4936
|
if (!st.isFile()) {
|
|
4711
4937
|
throw new Error(`Not a file: ${relativePath}`);
|
|
@@ -4715,7 +4941,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
4715
4941
|
`File too large to upload (${st.size} bytes; max ${maxBytes})`
|
|
4716
4942
|
);
|
|
4717
4943
|
}
|
|
4718
|
-
const buf =
|
|
4944
|
+
const buf = readFileSync10(abs);
|
|
4719
4945
|
return {
|
|
4720
4946
|
path: relativePath,
|
|
4721
4947
|
contentBase64: buf.toString("base64"),
|
|
@@ -4725,12 +4951,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
4725
4951
|
function readWorktreeFile(worktreePath, relativePath, opts) {
|
|
4726
4952
|
assertSafeRelativePath(relativePath);
|
|
4727
4953
|
const maxBytes = opts?.maxBytes ?? 2e5;
|
|
4728
|
-
const abs =
|
|
4954
|
+
const abs = join12(worktreePath, relativePath);
|
|
4729
4955
|
const st = statSync2(abs);
|
|
4730
4956
|
if (!st.isFile()) {
|
|
4731
4957
|
throw new Error(`Not a file: ${relativePath}`);
|
|
4732
4958
|
}
|
|
4733
|
-
const buf =
|
|
4959
|
+
const buf = readFileSync10(abs);
|
|
4734
4960
|
if (isImageRelativePath(relativePath)) {
|
|
4735
4961
|
const maxImageBytes = Math.max(maxBytes, 15e6);
|
|
4736
4962
|
const truncated2 = buf.length > maxImageBytes;
|
|
@@ -4773,9 +4999,9 @@ function assertSafeRelativePath(relativePath) {
|
|
|
4773
4999
|
}
|
|
4774
5000
|
function writeWorktreeFile(worktreePath, relativePath, content) {
|
|
4775
5001
|
assertSafeRelativePath(relativePath);
|
|
4776
|
-
const abs =
|
|
4777
|
-
|
|
4778
|
-
|
|
5002
|
+
const abs = join12(worktreePath, relativePath);
|
|
5003
|
+
mkdirSync4(dirname4(abs), { recursive: true });
|
|
5004
|
+
writeFileSync6(abs, content, "utf8");
|
|
4779
5005
|
return { path: relativePath };
|
|
4780
5006
|
}
|
|
4781
5007
|
async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
@@ -4900,9 +5126,9 @@ async function confirmLand(thread, opts) {
|
|
|
4900
5126
|
}
|
|
4901
5127
|
|
|
4902
5128
|
// src/skills/discover.ts
|
|
4903
|
-
import { existsSync as
|
|
5129
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
|
|
4904
5130
|
import { homedir } from "os";
|
|
4905
|
-
import { join as
|
|
5131
|
+
import { join as join13 } from "path";
|
|
4906
5132
|
|
|
4907
5133
|
// src/skills/bundled/long-running.ts
|
|
4908
5134
|
var LONG_RUNNING_SKILL_COMMAND = "long-running";
|
|
@@ -4913,7 +5139,7 @@ var LONG_RUNNING_SKILL_BODY = `# Long-running jobs
|
|
|
4913
5139
|
|
|
4914
5140
|
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.
|
|
4915
5141
|
|
|
4916
|
-
Do **not** ask the human to check back. Detach, then **wait** in 45s slices (same idea as
|
|
5142
|
+
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
|
|
4917
5143
|
|
|
4918
5144
|
## Tool
|
|
4919
5145
|
|
|
@@ -4923,7 +5149,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
|
|
|
4923
5149
|
# Start (exits in ~1s; job survives this turn)
|
|
4924
5150
|
node <detached-job.js> start <id> -- <command> [args...]
|
|
4925
5151
|
|
|
4926
|
-
# Wait \u2014
|
|
5152
|
+
# Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
|
|
5153
|
+
# Shell fallback:
|
|
4927
5154
|
node <detached-job.js> wait <id>
|
|
4928
5155
|
|
|
4929
5156
|
# Block until the process exits (humans / a turn that will not be interrupted)
|
|
@@ -4936,7 +5163,7 @@ node <detached-job.js> status <id>
|
|
|
4936
5163
|
|
|
4937
5164
|
Wait JSON:
|
|
4938
5165
|
|
|
4939
|
-
- \`stillRunning: true\` \u2192
|
|
5166
|
+
- \`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.
|
|
4940
5167
|
- \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
|
|
4941
5168
|
- \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
|
|
4942
5169
|
|
|
@@ -4966,7 +5193,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
4966
5193
|
|
|
4967
5194
|
1. \`start\` once. If JSON says \`already-running\`, do not start again.
|
|
4968
5195
|
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.
|
|
4969
|
-
3. Loop \`
|
|
5196
|
+
3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
|
|
4970
5197
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
4971
5198
|
|
|
4972
5199
|
Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
|
|
@@ -5031,7 +5258,7 @@ function parseFrontmatter(content) {
|
|
|
5031
5258
|
}
|
|
5032
5259
|
function readSkill(skillMd, source) {
|
|
5033
5260
|
try {
|
|
5034
|
-
const content =
|
|
5261
|
+
const content = readFileSync11(skillMd, "utf8");
|
|
5035
5262
|
const { name: fmName, description } = parseFrontmatter(content);
|
|
5036
5263
|
const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
|
|
5037
5264
|
const name = fmName || dirName;
|
|
@@ -5050,17 +5277,17 @@ function readSkill(skillMd, source) {
|
|
|
5050
5277
|
}
|
|
5051
5278
|
}
|
|
5052
5279
|
function scanSkillsDir(dir, source, out) {
|
|
5053
|
-
if (!
|
|
5280
|
+
if (!existsSync14(dir)) return;
|
|
5054
5281
|
let entries;
|
|
5055
5282
|
try {
|
|
5056
|
-
entries =
|
|
5283
|
+
entries = readdirSync5(dir);
|
|
5057
5284
|
} catch {
|
|
5058
5285
|
return;
|
|
5059
5286
|
}
|
|
5060
5287
|
for (const entry of entries) {
|
|
5061
5288
|
if (entry.startsWith(".")) continue;
|
|
5062
|
-
const skillMd =
|
|
5063
|
-
if (!
|
|
5289
|
+
const skillMd = join13(dir, entry, "SKILL.md");
|
|
5290
|
+
if (!existsSync14(skillMd)) continue;
|
|
5064
5291
|
try {
|
|
5065
5292
|
if (!statSync3(skillMd).isFile()) continue;
|
|
5066
5293
|
} catch {
|
|
@@ -5071,22 +5298,22 @@ function scanSkillsDir(dir, source, out) {
|
|
|
5071
5298
|
}
|
|
5072
5299
|
}
|
|
5073
5300
|
function scanClaudePluginSkills(pluginsRoot, out) {
|
|
5074
|
-
if (!
|
|
5301
|
+
if (!existsSync14(pluginsRoot)) return;
|
|
5075
5302
|
const walk = (dir, depth, lookingForSkillsDir) => {
|
|
5076
5303
|
if (depth > 7) return;
|
|
5077
5304
|
let entries;
|
|
5078
5305
|
try {
|
|
5079
|
-
entries =
|
|
5306
|
+
entries = readdirSync5(dir);
|
|
5080
5307
|
} catch {
|
|
5081
5308
|
return;
|
|
5082
5309
|
}
|
|
5083
5310
|
if (lookingForSkillsDir && entries.includes("SKILL.md")) {
|
|
5084
|
-
const skill = readSkill(
|
|
5311
|
+
const skill = readSkill(join13(dir, "SKILL.md"), "cli");
|
|
5085
5312
|
if (skill) out.push(skill);
|
|
5086
5313
|
}
|
|
5087
5314
|
for (const entry of entries) {
|
|
5088
5315
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
5089
|
-
const full =
|
|
5316
|
+
const full = join13(dir, entry);
|
|
5090
5317
|
try {
|
|
5091
5318
|
if (!statSync3(full).isDirectory()) continue;
|
|
5092
5319
|
} catch {
|
|
@@ -5106,17 +5333,17 @@ function discoverSkills(worktreePath) {
|
|
|
5106
5333
|
const home = homedir();
|
|
5107
5334
|
const collected = [];
|
|
5108
5335
|
for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
|
|
5109
|
-
scanSkillsDir(
|
|
5336
|
+
scanSkillsDir(join13(worktreePath, rel), "workspace", collected);
|
|
5110
5337
|
}
|
|
5111
5338
|
for (const abs of [
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5339
|
+
join13(home, ".claude/skills"),
|
|
5340
|
+
join13(home, ".cursor/skills"),
|
|
5341
|
+
join13(home, ".sideboard/skills"),
|
|
5342
|
+
join13(home, ".brightsy/skills")
|
|
5116
5343
|
]) {
|
|
5117
5344
|
scanSkillsDir(abs, "user", collected);
|
|
5118
5345
|
}
|
|
5119
|
-
scanClaudePluginSkills(
|
|
5346
|
+
scanClaudePluginSkills(join13(home, ".claude/plugins"), collected);
|
|
5120
5347
|
collected.push(...bundledSkills());
|
|
5121
5348
|
const rank = {
|
|
5122
5349
|
workspace: 0,
|
|
@@ -5142,7 +5369,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
|
|
|
5142
5369
|
\u2026(truncated)` : body;
|
|
5143
5370
|
}
|
|
5144
5371
|
}
|
|
5145
|
-
const raw =
|
|
5372
|
+
const raw = readFileSync11(skillPath, "utf8");
|
|
5146
5373
|
if (raw.startsWith("---")) {
|
|
5147
5374
|
const end = raw.indexOf("\n---", 3);
|
|
5148
5375
|
if (end >= 0) {
|
|
@@ -5235,27 +5462,27 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
5235
5462
|
}
|
|
5236
5463
|
|
|
5237
5464
|
// src/agents/instructions.ts
|
|
5238
|
-
import { existsSync as
|
|
5239
|
-
import { join as
|
|
5465
|
+
import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
|
|
5466
|
+
import { join as join15 } from "path";
|
|
5240
5467
|
|
|
5241
5468
|
// src/skills/detached-job-path.ts
|
|
5242
|
-
import { existsSync as
|
|
5243
|
-
import { dirname as
|
|
5469
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5470
|
+
import { dirname as dirname5, join as join14 } from "path";
|
|
5244
5471
|
import { fileURLToPath } from "url";
|
|
5245
5472
|
function packagedDetachedJobPath() {
|
|
5246
5473
|
const dir = packagedMcpDir();
|
|
5247
5474
|
if (!dir) return null;
|
|
5248
|
-
const script =
|
|
5249
|
-
return
|
|
5475
|
+
const script = join14(dir, "scripts", "detached-job.js");
|
|
5476
|
+
return existsSync15(script) ? script : null;
|
|
5250
5477
|
}
|
|
5251
5478
|
function resolveDetachedJobScript() {
|
|
5252
5479
|
const packaged = packagedDetachedJobPath();
|
|
5253
5480
|
if (packaged) return packaged;
|
|
5254
|
-
let dir =
|
|
5481
|
+
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
5255
5482
|
for (let i = 0; i < 8; i++) {
|
|
5256
|
-
const candidate =
|
|
5257
|
-
if (
|
|
5258
|
-
const parent =
|
|
5483
|
+
const candidate = join14(dir, "scripts", "detached-job.js");
|
|
5484
|
+
if (existsSync15(candidate)) return candidate;
|
|
5485
|
+
const parent = dirname5(dir);
|
|
5259
5486
|
if (parent === dir) break;
|
|
5260
5487
|
dir = parent;
|
|
5261
5488
|
}
|
|
@@ -5461,14 +5688,15 @@ function formatLongRunningDirective(opts) {
|
|
|
5461
5688
|
`Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
|
|
5462
5689
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
|
|
5463
5690
|
"- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
|
|
5464
|
-
`- Loop
|
|
5691
|
+
`- 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.`,
|
|
5692
|
+
"- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false.",
|
|
5465
5693
|
"- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
|
|
5466
5694
|
"State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
|
|
5467
5695
|
].join("\n");
|
|
5468
5696
|
}
|
|
5469
5697
|
function formatLongRunningReminder(opts) {
|
|
5470
5698
|
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
5471
|
-
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not
|
|
5699
|
+
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.`;
|
|
5472
5700
|
}
|
|
5473
5701
|
function formatArtifactDirective() {
|
|
5474
5702
|
return [
|
|
@@ -5616,8 +5844,8 @@ async function syncThreadBranchFromGit(threadId) {
|
|
|
5616
5844
|
|
|
5617
5845
|
// src/store/schedules.ts
|
|
5618
5846
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5619
|
-
import { existsSync as
|
|
5620
|
-
import { join as
|
|
5847
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
5848
|
+
import { join as join16 } from "path";
|
|
5621
5849
|
import { Cron } from "croner";
|
|
5622
5850
|
var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
|
|
5623
5851
|
var UNIT_MS = {
|
|
@@ -5627,7 +5855,7 @@ var UNIT_MS = {
|
|
|
5627
5855
|
d: 864e5
|
|
5628
5856
|
};
|
|
5629
5857
|
function schedulesPath() {
|
|
5630
|
-
return
|
|
5858
|
+
return join16(appDataDir(), "schedules.json");
|
|
5631
5859
|
}
|
|
5632
5860
|
function parseDurationMs(every) {
|
|
5633
5861
|
const m = every.trim().match(DURATION_RE);
|
|
@@ -5727,9 +5955,9 @@ function normalizeTask(raw) {
|
|
|
5727
5955
|
}
|
|
5728
5956
|
function readAll() {
|
|
5729
5957
|
const path = schedulesPath();
|
|
5730
|
-
if (!
|
|
5958
|
+
if (!existsSync17(path)) return [];
|
|
5731
5959
|
try {
|
|
5732
|
-
const parsed = JSON.parse(
|
|
5960
|
+
const parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
5733
5961
|
if (!Array.isArray(parsed)) return [];
|
|
5734
5962
|
return parsed.map((row) => {
|
|
5735
5963
|
try {
|
|
@@ -5852,7 +6080,7 @@ function formatScheduledPrompt(name, prompt) {
|
|
|
5852
6080
|
${prompt}`;
|
|
5853
6081
|
}
|
|
5854
6082
|
async function defaultDeps() {
|
|
5855
|
-
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-
|
|
6083
|
+
const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-J727WIJY.js");
|
|
5856
6084
|
const orch = getOrchestrator2();
|
|
5857
6085
|
return {
|
|
5858
6086
|
findThread: (id) => findThreadByRef(id),
|
|
@@ -6009,6 +6237,9 @@ var Orchestrator = class {
|
|
|
6009
6237
|
* finish or a user send so a later crash can recover again.
|
|
6010
6238
|
*/
|
|
6011
6239
|
crashContinued = /* @__PURE__ */ new Set();
|
|
6240
|
+
/** Auto-continues after a worktree turn ended while a detached job still runs. */
|
|
6241
|
+
jobContinueCount = /* @__PURE__ */ new Map();
|
|
6242
|
+
jobContinueNudged = /* @__PURE__ */ new Set();
|
|
6012
6243
|
maxConcurrent;
|
|
6013
6244
|
runningCount = 0;
|
|
6014
6245
|
constructor(opts) {
|
|
@@ -6092,7 +6323,7 @@ var Orchestrator = class {
|
|
|
6092
6323
|
}
|
|
6093
6324
|
continue;
|
|
6094
6325
|
}
|
|
6095
|
-
if (!
|
|
6326
|
+
if (!existsSync18(thread.worktreePath)) {
|
|
6096
6327
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
6097
6328
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
6098
6329
|
continue;
|
|
@@ -6307,6 +6538,37 @@ var Orchestrator = class {
|
|
|
6307
6538
|
this.emit({ type: "queue_changed", threadId, queue });
|
|
6308
6539
|
this.haltDrain.delete(threadId);
|
|
6309
6540
|
}
|
|
6541
|
+
/**
|
|
6542
|
+
* Worktree agent ended the turn after “I’ll let you know” (or left a
|
|
6543
|
+
* detached test/pack job running). Queue a continue so the chat does not
|
|
6544
|
+
* go idle with nobody watching the log.
|
|
6545
|
+
*/
|
|
6546
|
+
maybeEnqueueJobContinue(threadId, chatText, parts = []) {
|
|
6547
|
+
if (this.haltDrain.has(threadId)) return;
|
|
6548
|
+
const thread = readThread(threadId);
|
|
6549
|
+
if (!thread || thread.status === "archived") return;
|
|
6550
|
+
const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
|
|
6551
|
+
const decision = planJobContinue({
|
|
6552
|
+
runningJobIds,
|
|
6553
|
+
chatText,
|
|
6554
|
+
queueLength: thread.queue.length,
|
|
6555
|
+
continueCount: this.jobContinueCount.get(threadId) ?? 0,
|
|
6556
|
+
alreadyNudged: this.jobContinueNudged.has(threadId),
|
|
6557
|
+
isOrchestrator: isOrchestratorThread(thread),
|
|
6558
|
+
agent: thread.agent,
|
|
6559
|
+
watchedJob: turnWatchedDetachedJob(parts)
|
|
6560
|
+
});
|
|
6561
|
+
if (decision.action === "none") {
|
|
6562
|
+
if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
|
|
6563
|
+
return;
|
|
6564
|
+
}
|
|
6565
|
+
if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
|
|
6566
|
+
else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
|
|
6567
|
+
const queue = [decision.prompt, ...thread.queue];
|
|
6568
|
+
updateThread(threadId, { queue });
|
|
6569
|
+
this.emit({ type: "queue_changed", threadId, queue });
|
|
6570
|
+
this.haltDrain.delete(threadId);
|
|
6571
|
+
}
|
|
6310
6572
|
getThreads(includeArchived = false) {
|
|
6311
6573
|
return listThreads({ includeArchived });
|
|
6312
6574
|
}
|
|
@@ -6406,6 +6668,8 @@ var Orchestrator = class {
|
|
|
6406
6668
|
}
|
|
6407
6669
|
const queue = [...current.queue, prompt];
|
|
6408
6670
|
this.crashContinued.delete(thread.id);
|
|
6671
|
+
this.jobContinueCount.delete(thread.id);
|
|
6672
|
+
this.jobContinueNudged.delete(thread.id);
|
|
6409
6673
|
this.haltDrain.delete(thread.id);
|
|
6410
6674
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
|
|
6411
6675
|
shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
|
|
@@ -6920,6 +7184,7 @@ var Orchestrator = class {
|
|
|
6920
7184
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
6921
7185
|
if (exitCode === 0) {
|
|
6922
7186
|
this.crashContinued.delete(threadId);
|
|
7187
|
+
this.maybeEnqueueJobContinue(threadId, chatText, parts);
|
|
6923
7188
|
} else {
|
|
6924
7189
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
6925
7190
|
void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
|
|
@@ -7872,7 +8137,7 @@ var Orchestrator = class {
|
|
|
7872
8137
|
this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
|
|
7873
8138
|
if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
|
|
7874
8139
|
try {
|
|
7875
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
8140
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-OCEPZXFD.js");
|
|
7876
8141
|
await ensureWorkspace2(thread.repoPath);
|
|
7877
8142
|
} catch {
|
|
7878
8143
|
}
|
|
@@ -7920,13 +8185,13 @@ var Orchestrator = class {
|
|
|
7920
8185
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
7921
8186
|
return restored2;
|
|
7922
8187
|
}
|
|
7923
|
-
if (!
|
|
8188
|
+
if (!existsSync18(thread.worktreePath)) {
|
|
7924
8189
|
if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
|
|
7925
8190
|
throw new Error(
|
|
7926
8191
|
`Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
|
|
7927
8192
|
);
|
|
7928
8193
|
}
|
|
7929
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-
|
|
8194
|
+
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-VTMBZKY6.js");
|
|
7930
8195
|
const slug = thread.worktreePath.split("/").pop();
|
|
7931
8196
|
const dest = thread.worktreePath;
|
|
7932
8197
|
await withRepoGitLock(thread.repoPath, async () => {
|
|
@@ -8036,7 +8301,7 @@ async function startOrchestration(opts) {
|
|
|
8036
8301
|
sourceRef: "default",
|
|
8037
8302
|
...createOpts
|
|
8038
8303
|
}).catch(async () => {
|
|
8039
|
-
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-
|
|
8304
|
+
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-VTMBZKY6.js");
|
|
8040
8305
|
const repo = await resolveRepoRoot2(repoPath);
|
|
8041
8306
|
const def = await resolveDefaultBranch2(repo);
|
|
8042
8307
|
return orch.createThread({
|
|
@@ -8066,6 +8331,11 @@ export {
|
|
|
8066
8331
|
recordSlackOutboundWatch,
|
|
8067
8332
|
pollSlackOutboundWatches,
|
|
8068
8333
|
AGENT_GIT_ACTIONS,
|
|
8334
|
+
mcpWaitForTurnTimeoutMs,
|
|
8335
|
+
mcpWaitStillRunningHint,
|
|
8336
|
+
mcpWaitFinishedHint,
|
|
8337
|
+
mcpWaitForJobTimeoutMs,
|
|
8338
|
+
waitForDetachedJob,
|
|
8069
8339
|
BOARD_COLUMN_DEFS,
|
|
8070
8340
|
HOME_BOARD_CACHE_TTL_MS,
|
|
8071
8341
|
dedupeBoardIssues,
|