@joshski/dust 0.1.79 → 0.1.80
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/bucket/repository-loop.d.ts +42 -0
- package/dist/bucket/repository.d.ts +1 -0
- package/dist/claude/types.d.ts +12 -0
- package/dist/cli/commands/loop.d.ts +23 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/codex/event-parser.d.ts +13 -0
- package/dist/codex/run.d.ts +16 -0
- package/dist/codex/spawn-codex.d.ts +9 -0
- package/dist/codex/streamer.d.ts +6 -0
- package/dist/command-events.d.ts +62 -0
- package/dist/docker/docker-agent.d.ts +44 -0
- package/dist/dust.js +644 -286
- package/package.json +1 -1
package/dist/dust.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// lib/cli/run.ts
|
|
6
|
-
import { existsSync, statSync as statSync3 } from "node:fs";
|
|
6
|
+
import { existsSync as existsSync2, statSync as statSync3, writeSync } from "node:fs";
|
|
7
7
|
import {
|
|
8
8
|
chmod as chmod3,
|
|
9
9
|
mkdir as mkdir3,
|
|
@@ -13,6 +13,18 @@ import {
|
|
|
13
13
|
writeFile as writeFile3
|
|
14
14
|
} from "node:fs/promises";
|
|
15
15
|
|
|
16
|
+
// lib/command-events.ts
|
|
17
|
+
function createEventEmitter(writeEvent) {
|
|
18
|
+
let sequence = 0;
|
|
19
|
+
return (event) => {
|
|
20
|
+
writeEvent({
|
|
21
|
+
sequence: sequence++,
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
event
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
// lib/git/file-sorter.ts
|
|
17
29
|
function createGitDirectoryFileSorter(gitRunner) {
|
|
18
30
|
return async (dir, files) => {
|
|
@@ -306,7 +318,7 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
306
318
|
}
|
|
307
319
|
|
|
308
320
|
// lib/version.ts
|
|
309
|
-
var DUST_VERSION = "0.1.
|
|
321
|
+
var DUST_VERSION = "0.1.80";
|
|
310
322
|
|
|
311
323
|
// lib/session.ts
|
|
312
324
|
var DUST_UNATTENDED = "DUST_UNATTENDED";
|
|
@@ -1664,7 +1676,7 @@ async function audit(dependencies) {
|
|
|
1664
1676
|
}
|
|
1665
1677
|
|
|
1666
1678
|
// lib/cli/commands/bucket.ts
|
|
1667
|
-
import { spawn as
|
|
1679
|
+
import { spawn as nodeSpawn5 } from "node:child_process";
|
|
1668
1680
|
import { accessSync, statSync } from "node:fs";
|
|
1669
1681
|
import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
1670
1682
|
import { homedir } from "node:os";
|
|
@@ -1710,9 +1722,9 @@ async function clearToken(fileSystem, homeDir) {
|
|
|
1710
1722
|
throw error;
|
|
1711
1723
|
}
|
|
1712
1724
|
}
|
|
1713
|
-
async function defaultExchangeCode(code) {
|
|
1725
|
+
async function defaultExchangeCode(code, fetchFn = fetch) {
|
|
1714
1726
|
const host = getDustbucketHost();
|
|
1715
|
-
const response = await
|
|
1727
|
+
const response = await fetchFn(`${host}/auth/cli/exchange`, {
|
|
1716
1728
|
method: "POST",
|
|
1717
1729
|
headers: { "Content-Type": "application/json" },
|
|
1718
1730
|
body: JSON.stringify({ code })
|
|
@@ -2021,6 +2033,31 @@ var defaultDependencies = {
|
|
|
2021
2033
|
spawn: nodeSpawn2,
|
|
2022
2034
|
createInterface: nodeCreateInterface
|
|
2023
2035
|
};
|
|
2036
|
+
function buildDockerRunArguments(docker, claudeArguments, env) {
|
|
2037
|
+
const dockerArguments = [
|
|
2038
|
+
"run",
|
|
2039
|
+
"--rm",
|
|
2040
|
+
"-i",
|
|
2041
|
+
"-v",
|
|
2042
|
+
`${docker.repoPath}:/workspace`,
|
|
2043
|
+
"-w",
|
|
2044
|
+
"/workspace",
|
|
2045
|
+
"-v",
|
|
2046
|
+
`${docker.homeDir}/.claude:/root/.claude:ro`,
|
|
2047
|
+
"-v",
|
|
2048
|
+
`${docker.homeDir}/.ssh:/root/.ssh:ro`
|
|
2049
|
+
];
|
|
2050
|
+
if (docker.hasGitconfig) {
|
|
2051
|
+
dockerArguments.push("-v", `${docker.homeDir}/.gitconfig:/root/.gitconfig:ro`);
|
|
2052
|
+
}
|
|
2053
|
+
for (const [key, value] of Object.entries(env)) {
|
|
2054
|
+
dockerArguments.push("-e", `${key}=${value}`);
|
|
2055
|
+
}
|
|
2056
|
+
dockerArguments.push(docker.imageTag);
|
|
2057
|
+
dockerArguments.push("claude");
|
|
2058
|
+
dockerArguments.push(...claudeArguments);
|
|
2059
|
+
return dockerArguments;
|
|
2060
|
+
}
|
|
2024
2061
|
async function* spawnClaudeCode(prompt, options = {}, dependencies = defaultDependencies) {
|
|
2025
2062
|
const {
|
|
2026
2063
|
cwd,
|
|
@@ -2031,7 +2068,8 @@ async function* spawnClaudeCode(prompt, options = {}, dependencies = defaultDepe
|
|
|
2031
2068
|
sessionId,
|
|
2032
2069
|
dangerouslySkipPermissions,
|
|
2033
2070
|
env,
|
|
2034
|
-
signal
|
|
2071
|
+
signal,
|
|
2072
|
+
docker
|
|
2035
2073
|
} = options;
|
|
2036
2074
|
const claudeArguments = [
|
|
2037
2075
|
"-p",
|
|
@@ -2059,10 +2097,15 @@ async function* spawnClaudeCode(prompt, options = {}, dependencies = defaultDepe
|
|
|
2059
2097
|
if (dangerouslySkipPermissions) {
|
|
2060
2098
|
claudeArguments.push("--dangerously-skip-permissions");
|
|
2061
2099
|
}
|
|
2062
|
-
const
|
|
2100
|
+
const mergedEnv = { ...process.env, ...env };
|
|
2101
|
+
const proc = docker ? dependencies.spawn("docker", buildDockerRunArguments(docker, claudeArguments, env ?? {}), {
|
|
2063
2102
|
cwd,
|
|
2064
2103
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2065
|
-
env:
|
|
2104
|
+
env: mergedEnv
|
|
2105
|
+
}) : dependencies.spawn("claude", claudeArguments, {
|
|
2106
|
+
cwd,
|
|
2107
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2108
|
+
env: mergedEnv
|
|
2066
2109
|
});
|
|
2067
2110
|
if (!proc.stdout) {
|
|
2068
2111
|
throw new Error("Failed to get stdout from claude process");
|
|
@@ -2442,7 +2485,11 @@ async function cloneRepository(repository, targetPath, spawn, context) {
|
|
|
2442
2485
|
return new Promise((resolve) => {
|
|
2443
2486
|
const proc = spawn("git", ["clone", repository.gitUrl, targetPath], {
|
|
2444
2487
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2445
|
-
env: {
|
|
2488
|
+
env: {
|
|
2489
|
+
...process.env,
|
|
2490
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
2491
|
+
GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=accept-new"
|
|
2492
|
+
}
|
|
2446
2493
|
});
|
|
2447
2494
|
let stderr = "";
|
|
2448
2495
|
proc.stderr?.on("data", (data) => {
|
|
@@ -2522,7 +2569,66 @@ function formatAgentEvent(event) {
|
|
|
2522
2569
|
|
|
2523
2570
|
// lib/cli/commands/loop.ts
|
|
2524
2571
|
import { spawn as nodeSpawn3 } from "node:child_process";
|
|
2572
|
+
import { existsSync } from "node:fs";
|
|
2525
2573
|
import os from "node:os";
|
|
2574
|
+
import path2 from "node:path";
|
|
2575
|
+
|
|
2576
|
+
// lib/docker/docker-agent.ts
|
|
2577
|
+
import path from "node:path";
|
|
2578
|
+
var log = createLogger("dust:docker:agent");
|
|
2579
|
+
async function isDockerAvailable(dependencies) {
|
|
2580
|
+
return new Promise((resolve) => {
|
|
2581
|
+
const proc = dependencies.spawn("docker", ["--version"], {
|
|
2582
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2583
|
+
});
|
|
2584
|
+
proc.on("close", (code) => {
|
|
2585
|
+
resolve(code === 0);
|
|
2586
|
+
});
|
|
2587
|
+
proc.on("error", () => {
|
|
2588
|
+
resolve(false);
|
|
2589
|
+
});
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
function generateImageTag(repoPath) {
|
|
2593
|
+
const repoName = path.basename(repoPath);
|
|
2594
|
+
const sanitized = repoName.toLowerCase().replace(/[^a-z0-9._-]/g, "-");
|
|
2595
|
+
return `dust-agent-${sanitized}`;
|
|
2596
|
+
}
|
|
2597
|
+
async function buildDockerImage(config, dependencies) {
|
|
2598
|
+
const dockerfilePath = path.join(config.repoPath, ".dust", "Dockerfile");
|
|
2599
|
+
log(`building Docker image ${config.imageTag} from ${dockerfilePath}`);
|
|
2600
|
+
return new Promise((resolve) => {
|
|
2601
|
+
const proc = dependencies.spawn("docker", ["build", "-t", config.imageTag, "-f", dockerfilePath, config.repoPath], {
|
|
2602
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2603
|
+
});
|
|
2604
|
+
let stderr = "";
|
|
2605
|
+
proc.stderr?.on("data", (data) => {
|
|
2606
|
+
stderr += data.toString();
|
|
2607
|
+
});
|
|
2608
|
+
proc.on("close", (code) => {
|
|
2609
|
+
if (code === 0) {
|
|
2610
|
+
log(`Docker image ${config.imageTag} built successfully`);
|
|
2611
|
+
resolve({ success: true });
|
|
2612
|
+
} else {
|
|
2613
|
+
log(`Docker build failed: ${stderr}`);
|
|
2614
|
+
resolve({
|
|
2615
|
+
success: false,
|
|
2616
|
+
error: `Docker build failed with exit code ${code}: ${stderr.trim()}`
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
});
|
|
2620
|
+
proc.on("error", (error) => {
|
|
2621
|
+
resolve({
|
|
2622
|
+
success: false,
|
|
2623
|
+
error: `Docker build failed: ${error.message}`
|
|
2624
|
+
});
|
|
2625
|
+
});
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
function hasDockerfile(repoPath, dependencies) {
|
|
2629
|
+
const dockerfilePath = path.join(repoPath, ".dust", "Dockerfile");
|
|
2630
|
+
return dependencies.existsSync(dockerfilePath);
|
|
2631
|
+
}
|
|
2526
2632
|
|
|
2527
2633
|
// lib/artifacts/workflow-tasks.ts
|
|
2528
2634
|
var IDEA_TRANSITION_PREFIXES = [
|
|
@@ -2530,10 +2636,79 @@ var IDEA_TRANSITION_PREFIXES = [
|
|
|
2530
2636
|
"Decompose Idea: ",
|
|
2531
2637
|
"Shelve Idea: "
|
|
2532
2638
|
];
|
|
2639
|
+
var CAPTURE_IDEA_PREFIX = "Add Idea: ";
|
|
2533
2640
|
var EXPEDITE_IDEA_PREFIX = "Expedite Idea: ";
|
|
2534
2641
|
function titleToFilename(title) {
|
|
2535
2642
|
return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
2536
2643
|
}
|
|
2644
|
+
var WORKFLOW_SECTION_HEADINGS = [
|
|
2645
|
+
{ type: "refine", heading: "Refines Idea" },
|
|
2646
|
+
{ type: "decompose-idea", heading: "Decomposes Idea" },
|
|
2647
|
+
{ type: "shelve", heading: "Shelves Idea" }
|
|
2648
|
+
];
|
|
2649
|
+
function extractIdeaSlugFromSection(content, sectionHeading) {
|
|
2650
|
+
const lines = content.split(`
|
|
2651
|
+
`);
|
|
2652
|
+
let inSection = false;
|
|
2653
|
+
for (const line of lines) {
|
|
2654
|
+
if (line.startsWith("## ")) {
|
|
2655
|
+
inSection = line.trimEnd() === `## ${sectionHeading}`;
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2658
|
+
if (!inSection)
|
|
2659
|
+
continue;
|
|
2660
|
+
if (line.startsWith("# "))
|
|
2661
|
+
break;
|
|
2662
|
+
const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
|
|
2663
|
+
if (linkMatch) {
|
|
2664
|
+
const target = linkMatch[2];
|
|
2665
|
+
const slugMatch = target.match(/([^/]+)\.md$/);
|
|
2666
|
+
if (slugMatch) {
|
|
2667
|
+
return slugMatch[1];
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
return null;
|
|
2672
|
+
}
|
|
2673
|
+
async function findAllWorkflowTasks(fileSystem, dustPath) {
|
|
2674
|
+
const tasksPath = `${dustPath}/tasks`;
|
|
2675
|
+
const captureIdeaTasks = [];
|
|
2676
|
+
const workflowTasksByIdeaSlug = new Map;
|
|
2677
|
+
if (!fileSystem.exists(tasksPath)) {
|
|
2678
|
+
return { captureIdeaTasks, workflowTasksByIdeaSlug };
|
|
2679
|
+
}
|
|
2680
|
+
const files = await fileSystem.readdir(tasksPath);
|
|
2681
|
+
for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
|
|
2682
|
+
const content = await fileSystem.readFile(`${tasksPath}/${file}`);
|
|
2683
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
2684
|
+
if (!titleMatch)
|
|
2685
|
+
continue;
|
|
2686
|
+
const title = titleMatch[1].trim();
|
|
2687
|
+
const taskSlug = file.replace(/\.md$/, "");
|
|
2688
|
+
if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
|
|
2689
|
+
captureIdeaTasks.push({
|
|
2690
|
+
taskSlug,
|
|
2691
|
+
ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
|
|
2692
|
+
});
|
|
2693
|
+
} else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
|
|
2694
|
+
captureIdeaTasks.push({
|
|
2695
|
+
taskSlug,
|
|
2696
|
+
ideaTitle: title.slice(EXPEDITE_IDEA_PREFIX.length)
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
|
|
2700
|
+
const linkedSlug = extractIdeaSlugFromSection(content, heading);
|
|
2701
|
+
if (linkedSlug) {
|
|
2702
|
+
workflowTasksByIdeaSlug.set(linkedSlug, {
|
|
2703
|
+
type,
|
|
2704
|
+
ideaSlug: linkedSlug,
|
|
2705
|
+
taskSlug
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
return { captureIdeaTasks, workflowTasksByIdeaSlug };
|
|
2711
|
+
}
|
|
2537
2712
|
|
|
2538
2713
|
// lib/cli/commands/focus.ts
|
|
2539
2714
|
function buildImplementationInstructions(bin, hooksInstalled, taskTitle, taskPath, installCommand) {
|
|
@@ -2669,9 +2844,25 @@ async function next(dependencies) {
|
|
|
2669
2844
|
return { exitCode: 1 };
|
|
2670
2845
|
}
|
|
2671
2846
|
if (result.tasks.length === 0) {
|
|
2847
|
+
context.emitEvent?.({
|
|
2848
|
+
type: "tasks-listed",
|
|
2849
|
+
tasks: []
|
|
2850
|
+
});
|
|
2672
2851
|
return { exitCode: 0 };
|
|
2673
2852
|
}
|
|
2674
2853
|
printTaskList(context, result.tasks);
|
|
2854
|
+
context.emitEvent?.({
|
|
2855
|
+
type: "tasks-listed",
|
|
2856
|
+
tasks: result.tasks.map((task) => {
|
|
2857
|
+
const parts = task.path.split("/");
|
|
2858
|
+
const filename = parts[parts.length - 1];
|
|
2859
|
+
return {
|
|
2860
|
+
path: task.path,
|
|
2861
|
+
title: task.title ?? filename.replace(".md", ""),
|
|
2862
|
+
blockedBy: []
|
|
2863
|
+
};
|
|
2864
|
+
})
|
|
2865
|
+
});
|
|
2675
2866
|
return { exitCode: 0 };
|
|
2676
2867
|
}
|
|
2677
2868
|
|
|
@@ -2708,6 +2899,14 @@ function formatLoopEvent(event) {
|
|
|
2708
2899
|
return `Completed iteration ${event.iteration}/${event.maxIterations}`;
|
|
2709
2900
|
case "loop.ended":
|
|
2710
2901
|
return `Reached max iterations (${event.maxIterations}). Exiting.`;
|
|
2902
|
+
case "loop.docker_detected":
|
|
2903
|
+
return `Docker mode: found .dust/Dockerfile (image: ${event.imageTag})`;
|
|
2904
|
+
case "loop.docker_building":
|
|
2905
|
+
return `Building Docker image ${event.imageTag}...`;
|
|
2906
|
+
case "loop.docker_built":
|
|
2907
|
+
return `Docker image ${event.imageTag} ready`;
|
|
2908
|
+
case "loop.docker_error":
|
|
2909
|
+
return `Docker error: ${event.error}`;
|
|
2711
2910
|
}
|
|
2712
2911
|
}
|
|
2713
2912
|
function createPostEvent(fetchFn) {
|
|
@@ -2747,7 +2946,7 @@ function createWireEventSender(eventsUrl, sessionId, postEvent, onError, getAgen
|
|
|
2747
2946
|
postEvent(eventsUrl, payload).catch(onError);
|
|
2748
2947
|
};
|
|
2749
2948
|
}
|
|
2750
|
-
var
|
|
2949
|
+
var log2 = createLogger("dust:cli:commands:loop");
|
|
2751
2950
|
var SLEEP_INTERVAL_MS = 30000;
|
|
2752
2951
|
var SLEEP_STEP_MS = 1000;
|
|
2753
2952
|
var DEFAULT_MAX_ITERATIONS = 10;
|
|
@@ -2797,15 +2996,16 @@ async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAg
|
|
|
2797
2996
|
onRawEvent,
|
|
2798
2997
|
hooksInstalled = false,
|
|
2799
2998
|
signal,
|
|
2800
|
-
logger =
|
|
2801
|
-
repositoryId
|
|
2999
|
+
logger = log2,
|
|
3000
|
+
repositoryId,
|
|
3001
|
+
docker
|
|
2802
3002
|
} = options;
|
|
2803
3003
|
const baseEnv = buildUnattendedEnv({ repositoryId });
|
|
2804
|
-
|
|
3004
|
+
log2("syncing with remote");
|
|
2805
3005
|
onLoopEvent({ type: "loop.syncing" });
|
|
2806
3006
|
const pullResult = await gitPull(context.cwd, spawn);
|
|
2807
3007
|
if (!pullResult.success) {
|
|
2808
|
-
|
|
3008
|
+
log2(`git pull failed: ${pullResult.message}`);
|
|
2809
3009
|
onLoopEvent({
|
|
2810
3010
|
type: "loop.sync_skipped",
|
|
2811
3011
|
reason: pullResult.message
|
|
@@ -2836,7 +3036,8 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
2836
3036
|
cwd: context.cwd,
|
|
2837
3037
|
dangerouslySkipPermissions: true,
|
|
2838
3038
|
env: baseEnv,
|
|
2839
|
-
signal
|
|
3039
|
+
signal,
|
|
3040
|
+
docker
|
|
2840
3041
|
},
|
|
2841
3042
|
onRawEvent
|
|
2842
3043
|
});
|
|
@@ -2855,12 +3056,12 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
2855
3056
|
onLoopEvent({ type: "loop.checking_tasks" });
|
|
2856
3057
|
const tasks = await findAvailableTasks(dependencies);
|
|
2857
3058
|
if (tasks.length === 0) {
|
|
2858
|
-
|
|
3059
|
+
log2("no tasks available");
|
|
2859
3060
|
onLoopEvent({ type: "loop.no_tasks" });
|
|
2860
3061
|
return "no_tasks";
|
|
2861
3062
|
}
|
|
2862
3063
|
const task = tasks[0];
|
|
2863
|
-
|
|
3064
|
+
log2(`found ${tasks.length} task(s), picking: ${task.title ?? task.path}`);
|
|
2864
3065
|
onLoopEvent({ type: "loop.tasks_found" });
|
|
2865
3066
|
const taskContent = await dependencies.fileSystem.readFile(`${dependencies.context.cwd}/${task.path}`);
|
|
2866
3067
|
const { dustCommand, installCommand = "npm install" } = dependencies.settings;
|
|
@@ -2888,11 +3089,12 @@ ${instructions}`;
|
|
|
2888
3089
|
cwd: context.cwd,
|
|
2889
3090
|
dangerouslySkipPermissions: true,
|
|
2890
3091
|
env: baseEnv,
|
|
2891
|
-
signal
|
|
3092
|
+
signal,
|
|
3093
|
+
docker
|
|
2892
3094
|
},
|
|
2893
3095
|
onRawEvent
|
|
2894
3096
|
});
|
|
2895
|
-
|
|
3097
|
+
log2(`${agentName} completed task: ${task.title ?? task.path}`);
|
|
2896
3098
|
onAgentEvent?.({ type: "agent-session-ended", success: true });
|
|
2897
3099
|
return "ran_claude";
|
|
2898
3100
|
} catch (error) {
|
|
@@ -2947,7 +3149,36 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
2947
3149
|
sendWireEvent(event);
|
|
2948
3150
|
};
|
|
2949
3151
|
const hooksInstalled = await manageGitHooks(dependencies);
|
|
2950
|
-
|
|
3152
|
+
let dockerConfig;
|
|
3153
|
+
const dockerDeps = {
|
|
3154
|
+
spawn: loopDependencies.dockerDeps?.spawn ?? loopDependencies.spawn,
|
|
3155
|
+
homedir: loopDependencies.dockerDeps?.homedir ?? os.homedir,
|
|
3156
|
+
existsSync: loopDependencies.dockerDeps?.existsSync ?? existsSync
|
|
3157
|
+
};
|
|
3158
|
+
if (hasDockerfile(context.cwd, dockerDeps)) {
|
|
3159
|
+
const imageTag = generateImageTag(context.cwd);
|
|
3160
|
+
onLoopEvent({ type: "loop.docker_detected", imageTag });
|
|
3161
|
+
if (!await isDockerAvailable(dockerDeps)) {
|
|
3162
|
+
context.stderr("Docker not available. Install Docker or remove .dust/Dockerfile to run without Docker.");
|
|
3163
|
+
return { exitCode: 1 };
|
|
3164
|
+
}
|
|
3165
|
+
onLoopEvent({ type: "loop.docker_building", imageTag });
|
|
3166
|
+
const buildResult = await buildDockerImage({ repoPath: context.cwd, imageTag }, dockerDeps);
|
|
3167
|
+
if (!buildResult.success) {
|
|
3168
|
+
onLoopEvent({ type: "loop.docker_error", error: buildResult.error });
|
|
3169
|
+
context.stderr(buildResult.error);
|
|
3170
|
+
return { exitCode: 1 };
|
|
3171
|
+
}
|
|
3172
|
+
onLoopEvent({ type: "loop.docker_built", imageTag });
|
|
3173
|
+
const homeDir = os.homedir();
|
|
3174
|
+
dockerConfig = {
|
|
3175
|
+
imageTag,
|
|
3176
|
+
repoPath: context.cwd,
|
|
3177
|
+
homeDir,
|
|
3178
|
+
hasGitconfig: existsSync(path2.join(homeDir, ".gitconfig"))
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
log2(`starting loop, maxIterations=${maxIterations}, sessionId=${sessionId}`);
|
|
2951
3182
|
onLoopEvent({ type: "loop.warning" });
|
|
2952
3183
|
onLoopEvent({
|
|
2953
3184
|
type: "loop.started",
|
|
@@ -2957,7 +3188,10 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
2957
3188
|
context.stdout(" Press Ctrl+C to stop");
|
|
2958
3189
|
context.stdout("");
|
|
2959
3190
|
let completedIterations = 0;
|
|
2960
|
-
const iterationOptions = {
|
|
3191
|
+
const iterationOptions = {
|
|
3192
|
+
hooksInstalled,
|
|
3193
|
+
docker: dockerConfig
|
|
3194
|
+
};
|
|
2961
3195
|
if (eventsUrl) {
|
|
2962
3196
|
iterationOptions.onRawEvent = createHeartbeatThrottler(onAgentEvent);
|
|
2963
3197
|
}
|
|
@@ -2965,12 +3199,12 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
2965
3199
|
agentSessionId = crypto.randomUUID();
|
|
2966
3200
|
const result = await runOneIteration(dependencies, loopDependencies, onLoopEvent, onAgentEvent, iterationOptions);
|
|
2967
3201
|
if (result === "no_tasks") {
|
|
2968
|
-
|
|
3202
|
+
log2("sleeping, no tasks");
|
|
2969
3203
|
const writeInline = context.stdoutInline ?? context.stdout;
|
|
2970
3204
|
await sleepWithProgress(loopDependencies.sleep, SLEEP_INTERVAL_MS, writeInline, context.stdout);
|
|
2971
3205
|
} else {
|
|
2972
3206
|
completedIterations++;
|
|
2973
|
-
|
|
3207
|
+
log2(`iteration ${completedIterations}/${maxIterations} complete, result=${result}`);
|
|
2974
3208
|
onLoopEvent({
|
|
2975
3209
|
type: "loop.iteration_complete",
|
|
2976
3210
|
iteration: completedIterations,
|
|
@@ -2978,13 +3212,135 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
2978
3212
|
});
|
|
2979
3213
|
}
|
|
2980
3214
|
}
|
|
2981
|
-
|
|
3215
|
+
log2(`loop ended after ${completedIterations} iterations`);
|
|
2982
3216
|
onLoopEvent({ type: "loop.ended", maxIterations });
|
|
2983
3217
|
return { exitCode: 0 };
|
|
2984
3218
|
}
|
|
2985
3219
|
|
|
3220
|
+
// lib/codex/spawn-codex.ts
|
|
3221
|
+
import { spawn as nodeSpawn4 } from "node:child_process";
|
|
3222
|
+
import { createInterface as nodeCreateInterface2 } from "node:readline";
|
|
3223
|
+
var debug2 = createLogger("dust.codex.spawn-codex");
|
|
3224
|
+
var defaultDependencies2 = {
|
|
3225
|
+
spawn: nodeSpawn4,
|
|
3226
|
+
createInterface: nodeCreateInterface2
|
|
3227
|
+
};
|
|
3228
|
+
async function* spawnCodex(prompt, options = {}, dependencies = defaultDependencies2) {
|
|
3229
|
+
const { cwd, env, signal } = options;
|
|
3230
|
+
const codexArguments = ["exec", prompt, "--json", "--yolo"];
|
|
3231
|
+
if (cwd) {
|
|
3232
|
+
codexArguments.push("--cd", cwd);
|
|
3233
|
+
}
|
|
3234
|
+
const proc = dependencies.spawn("codex", codexArguments, {
|
|
3235
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3236
|
+
env: { ...process.env, ...env }
|
|
3237
|
+
});
|
|
3238
|
+
if (!proc.stdout) {
|
|
3239
|
+
throw new Error("Failed to get stdout from codex process");
|
|
3240
|
+
}
|
|
3241
|
+
let stderrOutput = "";
|
|
3242
|
+
proc.stderr?.on("data", (data) => {
|
|
3243
|
+
stderrOutput += data.toString();
|
|
3244
|
+
});
|
|
3245
|
+
const closePromise = new Promise((resolve, reject) => {
|
|
3246
|
+
proc.on("close", (code) => {
|
|
3247
|
+
if (code === 0 || code === null)
|
|
3248
|
+
resolve();
|
|
3249
|
+
else {
|
|
3250
|
+
const errMsg = stderrOutput.trim() ? `codex exited with code ${code}: ${stderrOutput.trim()}` : `codex exited with code ${code}`;
|
|
3251
|
+
reject(new Error(errMsg));
|
|
3252
|
+
}
|
|
3253
|
+
});
|
|
3254
|
+
proc.on("error", reject);
|
|
3255
|
+
});
|
|
3256
|
+
const abortHandler = () => {
|
|
3257
|
+
if (!proc.killed) {
|
|
3258
|
+
proc.kill();
|
|
3259
|
+
}
|
|
3260
|
+
};
|
|
3261
|
+
if (signal?.aborted) {
|
|
3262
|
+
abortHandler();
|
|
3263
|
+
} else if (signal) {
|
|
3264
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
3265
|
+
}
|
|
3266
|
+
const rl = dependencies.createInterface({ input: proc.stdout });
|
|
3267
|
+
try {
|
|
3268
|
+
for await (const line of rl) {
|
|
3269
|
+
if (!line.trim())
|
|
3270
|
+
continue;
|
|
3271
|
+
try {
|
|
3272
|
+
yield JSON.parse(line);
|
|
3273
|
+
} catch {
|
|
3274
|
+
debug2("Skipping malformed JSON line: %s", line.slice(0, 200));
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
await closePromise;
|
|
3278
|
+
} finally {
|
|
3279
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
3280
|
+
rl.close?.();
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
// lib/codex/event-parser.ts
|
|
3285
|
+
function* parseCodexRawEvent(raw) {
|
|
3286
|
+
if (raw.type !== "item.completed")
|
|
3287
|
+
return;
|
|
3288
|
+
const item = raw.item;
|
|
3289
|
+
if (!item)
|
|
3290
|
+
return;
|
|
3291
|
+
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
3292
|
+
yield { type: "text_delta", text: `${item.text}
|
|
3293
|
+
` };
|
|
3294
|
+
} else if (item.type === "command_execution" && typeof item.command === "string") {
|
|
3295
|
+
yield {
|
|
3296
|
+
type: "tool_use",
|
|
3297
|
+
id: typeof item.id === "string" ? item.id : "",
|
|
3298
|
+
name: "command_execution",
|
|
3299
|
+
input: { command: item.command }
|
|
3300
|
+
};
|
|
3301
|
+
if (typeof item.aggregated_output === "string") {
|
|
3302
|
+
yield {
|
|
3303
|
+
type: "tool_result",
|
|
3304
|
+
toolUseId: typeof item.id === "string" ? item.id : "",
|
|
3305
|
+
content: item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})`
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
// lib/codex/streamer.ts
|
|
3312
|
+
async function streamCodexEvents(events, sink, onRawEvent) {
|
|
3313
|
+
let hadTextOutput = false;
|
|
3314
|
+
for await (const raw of events) {
|
|
3315
|
+
onRawEvent?.(raw);
|
|
3316
|
+
for (const event of parseCodexRawEvent(raw)) {
|
|
3317
|
+
processEvent(event, sink, { hadTextOutput });
|
|
3318
|
+
if (event.type === "text_delta") {
|
|
3319
|
+
hadTextOutput = true;
|
|
3320
|
+
} else if (event.type === "tool_use") {
|
|
3321
|
+
hadTextOutput = false;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
// lib/codex/run.ts
|
|
3328
|
+
var defaultRunnerDependencies2 = {
|
|
3329
|
+
spawnCodex,
|
|
3330
|
+
createStdoutSink,
|
|
3331
|
+
streamCodexEvents
|
|
3332
|
+
};
|
|
3333
|
+
async function run2(prompt, options = {}, dependencies = defaultRunnerDependencies2) {
|
|
3334
|
+
const isRunOptions = (opt) => ("spawnOptions" in opt) || ("onRawEvent" in opt);
|
|
3335
|
+
const spawnOptions = isRunOptions(options) ? options.spawnOptions ?? {} : options;
|
|
3336
|
+
const onRawEvent = isRunOptions(options) ? options.onRawEvent : undefined;
|
|
3337
|
+
const events = dependencies.spawnCodex(prompt, spawnOptions);
|
|
3338
|
+
const sink = dependencies.createStdoutSink();
|
|
3339
|
+
await dependencies.streamCodexEvents(events, sink, onRawEvent);
|
|
3340
|
+
}
|
|
3341
|
+
|
|
2986
3342
|
// lib/bucket/repository-loop.ts
|
|
2987
|
-
var
|
|
3343
|
+
var log3 = createLogger("dust:bucket:repository-loop");
|
|
2988
3344
|
var FALLBACK_TIMEOUT_MS = 300000;
|
|
2989
3345
|
function createLogCallbacks(logBuffer) {
|
|
2990
3346
|
return {
|
|
@@ -3030,59 +3386,40 @@ function createWakeUpHandler(repoState, resolve) {
|
|
|
3030
3386
|
}
|
|
3031
3387
|
function createNoOpGlobScanner() {
|
|
3032
3388
|
return {
|
|
3033
|
-
scan: async function* () {}
|
|
3389
|
+
scan: async function* noOpScan() {}
|
|
3034
3390
|
};
|
|
3035
3391
|
}
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
const commandDeps = {
|
|
3042
|
-
arguments: [],
|
|
3043
|
-
context: {
|
|
3044
|
-
cwd: repoState.path,
|
|
3045
|
-
stdout: (msg) => logCallbacks.stdout(msg),
|
|
3046
|
-
stderr: (msg) => logCallbacks.stderr(msg)
|
|
3047
|
-
},
|
|
3048
|
-
fileSystem,
|
|
3049
|
-
globScanner: createNoOpGlobScanner(),
|
|
3050
|
-
settings
|
|
3051
|
-
};
|
|
3052
|
-
let partialLine = "";
|
|
3053
|
-
const bufferSinkDeps = {
|
|
3054
|
-
...defaultRunnerDependencies,
|
|
3055
|
-
createStdoutSink: () => ({
|
|
3056
|
-
write: (text) => {
|
|
3057
|
-
partialLine += text;
|
|
3058
|
-
const lines = partialLine.split(`
|
|
3392
|
+
function createBufferStdoutSink(loopState, logBuffer) {
|
|
3393
|
+
return {
|
|
3394
|
+
write(text) {
|
|
3395
|
+
loopState.partialLine += text;
|
|
3396
|
+
const lines = loopState.partialLine.split(`
|
|
3059
3397
|
`);
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
}
|
|
3063
|
-
partialLine = lines[lines.length - 1];
|
|
3064
|
-
},
|
|
3065
|
-
line: (text) => {
|
|
3066
|
-
partialLine = flushAndLogMultiLine(partialLine, text, repoState.logBuffer);
|
|
3398
|
+
for (let i = 0;i < lines.length - 1; i++) {
|
|
3399
|
+
appendLogLine(logBuffer, createLogLine(lines[i], "stdout"));
|
|
3067
3400
|
}
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
run: bufferRun,
|
|
3074
|
-
sleep,
|
|
3075
|
-
postEvent: async () => {}
|
|
3401
|
+
loopState.partialLine = lines[lines.length - 1];
|
|
3402
|
+
},
|
|
3403
|
+
line(text) {
|
|
3404
|
+
loopState.partialLine = flushAndLogMultiLine(loopState.partialLine, text, logBuffer);
|
|
3405
|
+
}
|
|
3076
3406
|
};
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3407
|
+
}
|
|
3408
|
+
function createBufferRun(run3, bufferSinkDeps) {
|
|
3409
|
+
return (prompt, options) => run3(prompt, options, bufferSinkDeps);
|
|
3410
|
+
}
|
|
3411
|
+
async function noOpPostEvent() {}
|
|
3412
|
+
function createLoopEventHandler(logBuffer) {
|
|
3413
|
+
return function onLoopEvent(event) {
|
|
3080
3414
|
const formatted = formatLoopEvent(event);
|
|
3081
3415
|
if (formatted !== null) {
|
|
3082
|
-
appendLogLine(
|
|
3416
|
+
appendLogLine(logBuffer, createLogLine(formatted, "stdout"));
|
|
3083
3417
|
}
|
|
3084
3418
|
};
|
|
3085
|
-
|
|
3419
|
+
}
|
|
3420
|
+
function createAgentEventHandler(parameters) {
|
|
3421
|
+
const { repoState, sendEvent, sessionId, repoName, loopState } = parameters;
|
|
3422
|
+
return function onAgentEvent(event) {
|
|
3086
3423
|
if (event.type === "agent-session-started") {
|
|
3087
3424
|
repoState.agentStatus = "busy";
|
|
3088
3425
|
} else if (event.type === "agent-session-ended") {
|
|
@@ -3093,26 +3430,88 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3093
3430
|
appendLogLine(repoState.logBuffer, createLogLine(formatted, "stdout"));
|
|
3094
3431
|
}
|
|
3095
3432
|
if (sendEvent && sessionId) {
|
|
3096
|
-
sequence++;
|
|
3433
|
+
loopState.sequence++;
|
|
3097
3434
|
sendEvent(buildEventMessage({
|
|
3098
|
-
sequence,
|
|
3435
|
+
sequence: loopState.sequence,
|
|
3099
3436
|
sessionId,
|
|
3100
3437
|
repository: repoName,
|
|
3101
3438
|
repoId: repoState.repository.id,
|
|
3102
3439
|
event,
|
|
3103
|
-
agentSessionId
|
|
3440
|
+
agentSessionId: loopState.agentSessionId
|
|
3104
3441
|
}));
|
|
3105
3442
|
}
|
|
3106
3443
|
};
|
|
3444
|
+
}
|
|
3445
|
+
function createCancelHandler(abortController) {
|
|
3446
|
+
return abortController.abort.bind(abortController);
|
|
3447
|
+
}
|
|
3448
|
+
function setupFallbackTimeout(repoState, sleep, resolve, wakeUpForThisWait) {
|
|
3449
|
+
sleep(FALLBACK_TIMEOUT_MS).then(function onFallbackTimeout() {
|
|
3450
|
+
if (repoState.wakeUp === wakeUpForThisWait) {
|
|
3451
|
+
repoState.wakeUp = undefined;
|
|
3452
|
+
resolve();
|
|
3453
|
+
}
|
|
3454
|
+
});
|
|
3455
|
+
}
|
|
3456
|
+
async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
3457
|
+
const { spawn, run: run3, fileSystem, sleep } = repoDeps;
|
|
3458
|
+
const repoName = repoState.repository.name;
|
|
3459
|
+
const settings = await loadSettings(repoState.path, fileSystem);
|
|
3460
|
+
const logCallbacks = createLogCallbacks(repoState.logBuffer);
|
|
3461
|
+
const commandDeps = {
|
|
3462
|
+
arguments: [],
|
|
3463
|
+
context: {
|
|
3464
|
+
cwd: repoState.path,
|
|
3465
|
+
stdout: logCallbacks.stdout,
|
|
3466
|
+
stderr: logCallbacks.stderr
|
|
3467
|
+
},
|
|
3468
|
+
fileSystem,
|
|
3469
|
+
globScanner: createNoOpGlobScanner(),
|
|
3470
|
+
settings
|
|
3471
|
+
};
|
|
3472
|
+
const loopState = {
|
|
3473
|
+
partialLine: "",
|
|
3474
|
+
sequence: 0,
|
|
3475
|
+
agentSessionId: undefined
|
|
3476
|
+
};
|
|
3477
|
+
const isCodex = repoState.repository.agentProvider === "codex";
|
|
3478
|
+
const agentType = isCodex ? "codex" : "claude";
|
|
3479
|
+
const createStdoutSink2 = () => createBufferStdoutSink(loopState, repoState.logBuffer);
|
|
3480
|
+
let bufferRun;
|
|
3481
|
+
if (isCodex) {
|
|
3482
|
+
const codexBufferSinkDeps = {
|
|
3483
|
+
...defaultRunnerDependencies2,
|
|
3484
|
+
createStdoutSink: createStdoutSink2
|
|
3485
|
+
};
|
|
3486
|
+
bufferRun = (prompt, options) => run2(prompt, options, codexBufferSinkDeps);
|
|
3487
|
+
} else {
|
|
3488
|
+
const bufferSinkDeps = {
|
|
3489
|
+
...defaultRunnerDependencies,
|
|
3490
|
+
createStdoutSink: createStdoutSink2
|
|
3491
|
+
};
|
|
3492
|
+
bufferRun = createBufferRun(run3, bufferSinkDeps);
|
|
3493
|
+
}
|
|
3494
|
+
const loopDeps = {
|
|
3495
|
+
spawn,
|
|
3496
|
+
run: bufferRun,
|
|
3497
|
+
sleep,
|
|
3498
|
+
postEvent: noOpPostEvent,
|
|
3499
|
+
agentType
|
|
3500
|
+
};
|
|
3501
|
+
const onLoopEvent = createLoopEventHandler(repoState.logBuffer);
|
|
3502
|
+
const onAgentEvent = createAgentEventHandler({
|
|
3503
|
+
repoState,
|
|
3504
|
+
sendEvent,
|
|
3505
|
+
sessionId,
|
|
3506
|
+
repoName,
|
|
3507
|
+
loopState
|
|
3508
|
+
});
|
|
3107
3509
|
const hooksInstalled = await manageGitHooks(commandDeps);
|
|
3108
|
-
|
|
3109
|
-
log2(`loop started for ${repoName} at ${repoState.path}`);
|
|
3510
|
+
log3(`loop started for ${repoName} at ${repoState.path}`);
|
|
3110
3511
|
while (!repoState.stopRequested) {
|
|
3111
|
-
agentSessionId = crypto.randomUUID();
|
|
3512
|
+
loopState.agentSessionId = crypto.randomUUID();
|
|
3112
3513
|
const abortController = new AbortController;
|
|
3113
|
-
const cancelCurrentIteration = ()
|
|
3114
|
-
abortController.abort();
|
|
3115
|
-
};
|
|
3514
|
+
const cancelCurrentIteration = createCancelHandler(abortController);
|
|
3116
3515
|
repoState.cancelCurrentIteration = cancelCurrentIteration;
|
|
3117
3516
|
let result;
|
|
3118
3517
|
try {
|
|
@@ -3124,7 +3523,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3124
3523
|
});
|
|
3125
3524
|
} catch (error) {
|
|
3126
3525
|
const msg = error instanceof Error ? error.message : String(error);
|
|
3127
|
-
|
|
3526
|
+
log3(`iteration error for ${repoName}: ${msg}`);
|
|
3128
3527
|
appendLogLine(repoState.logBuffer, createLogLine(`Loop error: ${msg}`, "stderr"));
|
|
3129
3528
|
await sleep(1e4);
|
|
3130
3529
|
continue;
|
|
@@ -3136,39 +3535,34 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3136
3535
|
if (result === "no_tasks") {
|
|
3137
3536
|
if (repoState.taskAvailablePending) {
|
|
3138
3537
|
repoState.taskAvailablePending = false;
|
|
3139
|
-
|
|
3140
|
-
|
|
3538
|
+
log3(`${repoName}: task signal received during iteration, rechecking`);
|
|
3539
|
+
appendLogLine(repoState.logBuffer, createLogLine("Task signal received during iteration, rechecking...", "stdout"));
|
|
3141
3540
|
continue;
|
|
3142
3541
|
}
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
await new Promise((resolve)
|
|
3542
|
+
log3(`${repoName}: no tasks available, waiting`);
|
|
3543
|
+
appendLogLine(repoState.logBuffer, createLogLine("Waiting for tasks...", "stdout"));
|
|
3544
|
+
await new Promise(function waitForTasks(resolve) {
|
|
3146
3545
|
const wakeUpForThisWait = createWakeUpHandler(repoState, resolve);
|
|
3147
3546
|
repoState.wakeUp = wakeUpForThisWait;
|
|
3148
|
-
|
|
3149
|
-
if (repoState.wakeUp === wakeUpForThisWait) {
|
|
3150
|
-
repoState.wakeUp = undefined;
|
|
3151
|
-
resolve();
|
|
3152
|
-
}
|
|
3153
|
-
});
|
|
3547
|
+
setupFallbackTimeout(repoState, sleep, resolve, wakeUpForThisWait);
|
|
3154
3548
|
});
|
|
3155
3549
|
}
|
|
3156
3550
|
}
|
|
3157
|
-
|
|
3551
|
+
log3(`loop stopped for ${repoName}`);
|
|
3158
3552
|
appendLogLine(repoState.logBuffer, createLogLine(`Stopped loop for ${repoName}`, "stdout"));
|
|
3159
3553
|
}
|
|
3160
3554
|
|
|
3161
3555
|
// lib/bucket/repository.ts
|
|
3162
|
-
var
|
|
3556
|
+
var log4 = createLogger("dust:bucket:repository");
|
|
3163
3557
|
function startRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
3164
|
-
|
|
3558
|
+
log4(`starting loop for ${repoState.repository.name}`);
|
|
3165
3559
|
repoState.stopRequested = false;
|
|
3166
3560
|
repoState.loopPromise = runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId).catch((error) => {
|
|
3167
3561
|
const message = error instanceof Error ? error.message : String(error);
|
|
3168
|
-
|
|
3562
|
+
log4(`loop crashed for ${repoState.repository.name}: ${message}`);
|
|
3169
3563
|
appendLogLine(repoState.logBuffer, createLogLine(`Repository loop crashed: ${message}`, "stderr"));
|
|
3170
3564
|
}).finally(() => {
|
|
3171
|
-
|
|
3565
|
+
log4(`loop finished for ${repoState.repository.name}`);
|
|
3172
3566
|
repoState.loopPromise = null;
|
|
3173
3567
|
repoState.agentStatus = "idle";
|
|
3174
3568
|
repoState.wakeUp = undefined;
|
|
@@ -3191,10 +3585,10 @@ function parseRepository(data) {
|
|
|
3191
3585
|
}
|
|
3192
3586
|
async function addRepository(repository, manager, repoDeps, context) {
|
|
3193
3587
|
if (manager.repositories.has(repository.name)) {
|
|
3194
|
-
|
|
3588
|
+
log4(`repository ${repository.name} already exists, skipping add`);
|
|
3195
3589
|
return;
|
|
3196
3590
|
}
|
|
3197
|
-
|
|
3591
|
+
log4(`adding repository ${repository.name}`);
|
|
3198
3592
|
const repoPath = getRepoPath(repository.name, repoDeps.getReposDir());
|
|
3199
3593
|
await repoDeps.fileSystem.mkdir(dirname2(repoPath), { recursive: true });
|
|
3200
3594
|
if (repoDeps.fileSystem.exists(repoPath)) {
|
|
@@ -3234,7 +3628,7 @@ async function removeRepositoryFromManager(repoName, manager, repoDeps, context)
|
|
|
3234
3628
|
if (!repoState) {
|
|
3235
3629
|
return;
|
|
3236
3630
|
}
|
|
3237
|
-
|
|
3631
|
+
log4(`removing repository ${repoName}`);
|
|
3238
3632
|
repoState.stopRequested = true;
|
|
3239
3633
|
repoState.cancelCurrentIteration?.();
|
|
3240
3634
|
repoState.wakeUp?.();
|
|
@@ -3292,13 +3686,17 @@ function parseServerMessage(data) {
|
|
|
3292
3686
|
if (typeof repo.id !== "number" || typeof repo.url !== "string" || typeof repo.hasTask !== "boolean") {
|
|
3293
3687
|
return null;
|
|
3294
3688
|
}
|
|
3295
|
-
|
|
3689
|
+
const item = {
|
|
3296
3690
|
id: repo.id,
|
|
3297
3691
|
name: repo.name,
|
|
3298
3692
|
gitUrl: repo.gitUrl,
|
|
3299
3693
|
url: repo.url,
|
|
3300
3694
|
hasTask: repo.hasTask
|
|
3301
|
-
}
|
|
3695
|
+
};
|
|
3696
|
+
if (typeof repo.agentProvider === "string") {
|
|
3697
|
+
item.agentProvider = repo.agentProvider;
|
|
3698
|
+
}
|
|
3699
|
+
repositories.push(item);
|
|
3302
3700
|
}
|
|
3303
3701
|
return { type: "repository-list", repositories };
|
|
3304
3702
|
}
|
|
@@ -3739,7 +4137,7 @@ function handleKeyInput(state, key, options) {
|
|
|
3739
4137
|
}
|
|
3740
4138
|
|
|
3741
4139
|
// lib/cli/commands/bucket.ts
|
|
3742
|
-
var
|
|
4140
|
+
var log5 = createLogger("dust:cli:commands:bucket");
|
|
3743
4141
|
var DEFAULT_DUSTBUCKET_WS_URL = "wss://dustbucket.com/agent/connect";
|
|
3744
4142
|
var INITIAL_RECONNECT_DELAY_MS = 1000;
|
|
3745
4143
|
var MAX_RECONNECT_DELAY_MS = 30000;
|
|
@@ -3799,27 +4197,27 @@ function defaultWriteStdout(data) {
|
|
|
3799
4197
|
}
|
|
3800
4198
|
function createAuthFileSystem(dependencies) {
|
|
3801
4199
|
return {
|
|
3802
|
-
exists: (
|
|
4200
|
+
exists: (path3) => {
|
|
3803
4201
|
try {
|
|
3804
|
-
dependencies.accessSync(
|
|
4202
|
+
dependencies.accessSync(path3);
|
|
3805
4203
|
return true;
|
|
3806
4204
|
} catch {
|
|
3807
4205
|
return false;
|
|
3808
4206
|
}
|
|
3809
4207
|
},
|
|
3810
|
-
isDirectory: (
|
|
4208
|
+
isDirectory: (path3) => {
|
|
3811
4209
|
try {
|
|
3812
|
-
return dependencies.statSync(
|
|
4210
|
+
return dependencies.statSync(path3).isDirectory();
|
|
3813
4211
|
} catch {
|
|
3814
4212
|
return false;
|
|
3815
4213
|
}
|
|
3816
4214
|
},
|
|
3817
|
-
getFileCreationTime: (
|
|
3818
|
-
readFile: (
|
|
3819
|
-
writeFile: (
|
|
3820
|
-
mkdir: (
|
|
3821
|
-
readdir: (
|
|
3822
|
-
chmod: (
|
|
4215
|
+
getFileCreationTime: (path3) => dependencies.statSync(path3).birthtimeMs,
|
|
4216
|
+
readFile: (path3) => dependencies.readFile(path3, "utf8"),
|
|
4217
|
+
writeFile: (path3, content) => dependencies.writeFile(path3, content, "utf8"),
|
|
4218
|
+
mkdir: (path3, options) => dependencies.mkdir(path3, options).then(() => {}),
|
|
4219
|
+
readdir: (path3) => dependencies.readdir(path3),
|
|
4220
|
+
chmod: (path3, mode) => dependencies.chmod(path3, mode),
|
|
3823
4221
|
rename: (oldPath, newPath) => dependencies.rename(oldPath, newPath)
|
|
3824
4222
|
};
|
|
3825
4223
|
}
|
|
@@ -3835,7 +4233,7 @@ function createDefaultBucketDependencies() {
|
|
|
3835
4233
|
rename: (oldPath, newPath) => import("node:fs/promises").then((mod) => mod.rename(oldPath, newPath))
|
|
3836
4234
|
});
|
|
3837
4235
|
return {
|
|
3838
|
-
spawn:
|
|
4236
|
+
spawn: nodeSpawn5,
|
|
3839
4237
|
createWebSocket: defaultCreateWebSocket,
|
|
3840
4238
|
setupKeypress: defaultSetupKeypress,
|
|
3841
4239
|
setupSignals: defaultSetupSignals,
|
|
@@ -3887,20 +4285,20 @@ function toRepositoryDependencies(bucketDeps, fileSystem) {
|
|
|
3887
4285
|
}
|
|
3888
4286
|
function ensureRepositoryLoopRunning(repoState, state, repoDeps, context, useTUI) {
|
|
3889
4287
|
if (repoState.loopPromise || repoState.wakeUp || repoState.stopRequested) {
|
|
3890
|
-
|
|
4288
|
+
log5(`loop already running/waiting for ${repoState.repository.name}`);
|
|
3891
4289
|
return;
|
|
3892
4290
|
}
|
|
3893
4291
|
logMessage(state, context, useTUI, `Repository loop not running for ${repoState.repository.name}; restarting`);
|
|
3894
4292
|
startRepositoryLoop(repoState, repoDeps, state.sendEvent, state.sessionId);
|
|
3895
4293
|
}
|
|
3896
4294
|
function signalTaskAvailable(repoState, state, repoDeps, context, useTUI) {
|
|
3897
|
-
|
|
4295
|
+
log5(`task-available signal for ${repoState.repository.name}`);
|
|
3898
4296
|
ensureRepositoryLoopRunning(repoState, state, repoDeps, context, useTUI);
|
|
3899
4297
|
if (repoState.wakeUp) {
|
|
3900
|
-
|
|
4298
|
+
log5(`waking loop for ${repoState.repository.name}`);
|
|
3901
4299
|
repoState.wakeUp();
|
|
3902
4300
|
} else {
|
|
3903
|
-
|
|
4301
|
+
log5(`marking task pending for ${repoState.repository.name} (loop busy)`);
|
|
3904
4302
|
repoState.taskAvailablePending = true;
|
|
3905
4303
|
}
|
|
3906
4304
|
}
|
|
@@ -4034,7 +4432,7 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
4034
4432
|
logMessage(state, context, useTUI, `Invalid WebSocket message format: ${event.data}`, "stderr");
|
|
4035
4433
|
return;
|
|
4036
4434
|
}
|
|
4037
|
-
|
|
4435
|
+
log5(`ws message: ${message.type}`);
|
|
4038
4436
|
if (message.type === "repository-list") {
|
|
4039
4437
|
const repos = message.repositories;
|
|
4040
4438
|
logMessage(state, context, useTUI, `Received repository list (${repos.length} repositories):`);
|
|
@@ -4084,7 +4482,7 @@ async function shutdown(state, bucketDeps, context) {
|
|
|
4084
4482
|
if (state.shuttingDown)
|
|
4085
4483
|
return;
|
|
4086
4484
|
state.shuttingDown = true;
|
|
4087
|
-
|
|
4485
|
+
log5("shutdown initiated");
|
|
4088
4486
|
context.stdout("Shutting down...");
|
|
4089
4487
|
if (state.reconnectTimer) {
|
|
4090
4488
|
clearTimeout(state.reconnectTimer);
|
|
@@ -4287,16 +4685,16 @@ function createDefaultUploadDependencies() {
|
|
|
4287
4685
|
getHomeDir: () => homedir2(),
|
|
4288
4686
|
fileSystem: authFileSystem
|
|
4289
4687
|
},
|
|
4290
|
-
readFileBytes: async (
|
|
4291
|
-
const buffer = await Bun.file(
|
|
4688
|
+
readFileBytes: async (path3) => {
|
|
4689
|
+
const buffer = await Bun.file(path3).arrayBuffer();
|
|
4292
4690
|
return new Uint8Array(buffer);
|
|
4293
4691
|
},
|
|
4294
|
-
getFileSize: async (
|
|
4295
|
-
const file = Bun.file(
|
|
4692
|
+
getFileSize: async (path3) => {
|
|
4693
|
+
const file = Bun.file(path3);
|
|
4296
4694
|
return file.size;
|
|
4297
4695
|
},
|
|
4298
|
-
fileExists: async (
|
|
4299
|
-
const file = Bun.file(
|
|
4696
|
+
fileExists: async (path3) => {
|
|
4697
|
+
const file = Bun.file(path3);
|
|
4300
4698
|
return file.exists();
|
|
4301
4699
|
},
|
|
4302
4700
|
uploadFile: async (url, token, fileBytes, contentType, fileName) => {
|
|
@@ -5090,12 +5488,12 @@ function validateNoCycles(allPrincipleRelationships) {
|
|
|
5090
5488
|
}
|
|
5091
5489
|
for (const rel of allPrincipleRelationships) {
|
|
5092
5490
|
const visited = new Set;
|
|
5093
|
-
const
|
|
5491
|
+
const path3 = [];
|
|
5094
5492
|
let current = rel.filePath;
|
|
5095
5493
|
while (current) {
|
|
5096
5494
|
if (visited.has(current)) {
|
|
5097
|
-
const cycleStart =
|
|
5098
|
-
const cyclePath =
|
|
5495
|
+
const cycleStart = path3.indexOf(current);
|
|
5496
|
+
const cyclePath = path3.slice(cycleStart).concat(current);
|
|
5099
5497
|
violations.push({
|
|
5100
5498
|
file: rel.filePath,
|
|
5101
5499
|
message: `Cycle detected in principle hierarchy: ${cyclePath.join(" -> ")}`
|
|
@@ -5103,7 +5501,7 @@ function validateNoCycles(allPrincipleRelationships) {
|
|
|
5103
5501
|
break;
|
|
5104
5502
|
}
|
|
5105
5503
|
visited.add(current);
|
|
5106
|
-
|
|
5504
|
+
path3.push(current);
|
|
5107
5505
|
const currentRel = relationshipMap.get(current);
|
|
5108
5506
|
if (currentRel && currentRel.parentPrinciples.length > 0) {
|
|
5109
5507
|
current = currentRel.parentPrinciples[0];
|
|
@@ -5305,7 +5703,7 @@ async function lintMarkdown(dependencies) {
|
|
|
5305
5703
|
}
|
|
5306
5704
|
|
|
5307
5705
|
// lib/cli/commands/check.ts
|
|
5308
|
-
var
|
|
5706
|
+
var log6 = createLogger("dust:cli:commands:check");
|
|
5309
5707
|
var DEFAULT_CHECK_TIMEOUT_MS = 13000;
|
|
5310
5708
|
var MAX_OUTPUT_LINES = 500;
|
|
5311
5709
|
var KEEP_LINES = 250;
|
|
@@ -5325,14 +5723,27 @@ function truncateOutput(output) {
|
|
|
5325
5723
|
].join(`
|
|
5326
5724
|
`);
|
|
5327
5725
|
}
|
|
5328
|
-
async function runSingleCheck(check, cwd, runner) {
|
|
5726
|
+
async function runSingleCheck(check, cwd, runner, emitEvent) {
|
|
5329
5727
|
const timeoutMs = check.timeoutMilliseconds ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
5330
|
-
|
|
5728
|
+
log6(`running check ${check.name}: ${check.command}`);
|
|
5729
|
+
emitEvent?.({ type: "check-started", name: check.name });
|
|
5331
5730
|
const startTime = Date.now();
|
|
5332
5731
|
const result = await runner.run(check.command, cwd, timeoutMs);
|
|
5333
5732
|
const durationMs = Date.now() - startTime;
|
|
5334
5733
|
const status = result.timedOut ? "timed out" : result.exitCode === 0 ? "passed" : "failed";
|
|
5335
|
-
|
|
5734
|
+
log6(`check ${check.name} ${status} (${durationMs}ms)`);
|
|
5735
|
+
if (result.exitCode === 0) {
|
|
5736
|
+
emitEvent?.({ type: "check-passed", name: check.name, durationMs });
|
|
5737
|
+
} else {
|
|
5738
|
+
const failedEvent = {
|
|
5739
|
+
type: "check-failed",
|
|
5740
|
+
name: check.name,
|
|
5741
|
+
durationMs
|
|
5742
|
+
};
|
|
5743
|
+
if (result.output)
|
|
5744
|
+
failedEvent.output = result.output;
|
|
5745
|
+
emitEvent?.(failedEvent);
|
|
5746
|
+
}
|
|
5336
5747
|
return {
|
|
5337
5748
|
name: check.name,
|
|
5338
5749
|
command: check.command,
|
|
@@ -5344,25 +5755,26 @@ async function runSingleCheck(check, cwd, runner) {
|
|
|
5344
5755
|
timeoutSeconds: timeoutMs / 1000
|
|
5345
5756
|
};
|
|
5346
5757
|
}
|
|
5347
|
-
async function runConfiguredChecks(checks, cwd, runner) {
|
|
5348
|
-
const promises = checks.map((check) => runSingleCheck(check, cwd, runner));
|
|
5758
|
+
async function runConfiguredChecks(checks, cwd, runner, emitEvent) {
|
|
5759
|
+
const promises = checks.map((check) => runSingleCheck(check, cwd, runner, emitEvent));
|
|
5349
5760
|
return Promise.all(promises);
|
|
5350
5761
|
}
|
|
5351
|
-
async function runConfiguredChecksSerially(checks, cwd, runner) {
|
|
5762
|
+
async function runConfiguredChecksSerially(checks, cwd, runner, emitEvent) {
|
|
5352
5763
|
const results = [];
|
|
5353
5764
|
for (const check of checks) {
|
|
5354
|
-
results.push(await runSingleCheck(check, cwd, runner));
|
|
5765
|
+
results.push(await runSingleCheck(check, cwd, runner, emitEvent));
|
|
5355
5766
|
}
|
|
5356
5767
|
return results;
|
|
5357
5768
|
}
|
|
5358
|
-
async function runValidationCheck(dependencies) {
|
|
5769
|
+
async function runValidationCheck(dependencies, emitEvent) {
|
|
5359
5770
|
const outputLines = [];
|
|
5360
5771
|
const bufferedContext = {
|
|
5361
5772
|
cwd: dependencies.context.cwd,
|
|
5362
5773
|
stdout: (msg) => outputLines.push(msg),
|
|
5363
5774
|
stderr: (msg) => outputLines.push(msg)
|
|
5364
5775
|
};
|
|
5365
|
-
|
|
5776
|
+
log6("running built-in check: dust lint");
|
|
5777
|
+
emitEvent?.({ type: "check-started", name: "lint" });
|
|
5366
5778
|
const startTime = Date.now();
|
|
5367
5779
|
const result = await lintMarkdown({
|
|
5368
5780
|
...dependencies,
|
|
@@ -5371,13 +5783,26 @@ async function runValidationCheck(dependencies) {
|
|
|
5371
5783
|
});
|
|
5372
5784
|
const durationMs = Date.now() - startTime;
|
|
5373
5785
|
const lintStatus = result.exitCode === 0 ? "passed" : "failed";
|
|
5374
|
-
|
|
5786
|
+
log6(`built-in check dust lint ${lintStatus} (${durationMs}ms)`);
|
|
5787
|
+
const output = outputLines.join(`
|
|
5788
|
+
`);
|
|
5789
|
+
if (result.exitCode === 0) {
|
|
5790
|
+
emitEvent?.({ type: "check-passed", name: "lint", durationMs });
|
|
5791
|
+
} else {
|
|
5792
|
+
const failedEvent = {
|
|
5793
|
+
type: "check-failed",
|
|
5794
|
+
name: "lint",
|
|
5795
|
+
durationMs
|
|
5796
|
+
};
|
|
5797
|
+
if (output)
|
|
5798
|
+
failedEvent.output = output;
|
|
5799
|
+
emitEvent?.(failedEvent);
|
|
5800
|
+
}
|
|
5375
5801
|
return {
|
|
5376
5802
|
name: "lint",
|
|
5377
5803
|
command: "dust lint",
|
|
5378
5804
|
exitCode: result.exitCode,
|
|
5379
|
-
output
|
|
5380
|
-
`),
|
|
5805
|
+
output,
|
|
5381
5806
|
isBuiltIn: true,
|
|
5382
5807
|
durationMs,
|
|
5383
5808
|
timedOut: false
|
|
@@ -5446,18 +5871,18 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
|
5446
5871
|
if (serial) {
|
|
5447
5872
|
const results2 = [];
|
|
5448
5873
|
if (hasDustDir) {
|
|
5449
|
-
results2.push(await runValidationCheck(dependencies));
|
|
5874
|
+
results2.push(await runValidationCheck(dependencies, context.emitEvent));
|
|
5450
5875
|
}
|
|
5451
|
-
const configuredResults = await runConfiguredChecksSerially(settings.checks, context.cwd, shellRunner);
|
|
5876
|
+
const configuredResults = await runConfiguredChecksSerially(settings.checks, context.cwd, shellRunner, context.emitEvent);
|
|
5452
5877
|
results2.push(...configuredResults);
|
|
5453
5878
|
const exitCode2 = displayResults(results2, context);
|
|
5454
5879
|
return { exitCode: exitCode2 };
|
|
5455
5880
|
}
|
|
5456
5881
|
const checkPromises = [];
|
|
5457
5882
|
if (hasDustDir) {
|
|
5458
|
-
checkPromises.push(runValidationCheck(dependencies));
|
|
5883
|
+
checkPromises.push(runValidationCheck(dependencies, context.emitEvent));
|
|
5459
5884
|
}
|
|
5460
|
-
checkPromises.push(runConfiguredChecks(settings.checks, context.cwd, shellRunner));
|
|
5885
|
+
checkPromises.push(runConfiguredChecks(settings.checks, context.cwd, shellRunner, context.emitEvent));
|
|
5461
5886
|
const promiseResults = await Promise.all(checkPromises);
|
|
5462
5887
|
const results = [];
|
|
5463
5888
|
for (const result of promiseResults) {
|
|
@@ -5638,6 +6063,16 @@ async function init(dependencies) {
|
|
|
5638
6063
|
|
|
5639
6064
|
// lib/cli/commands/list.ts
|
|
5640
6065
|
import { basename as basename2 } from "node:path";
|
|
6066
|
+
function workflowTypeToStatus(type) {
|
|
6067
|
+
switch (type) {
|
|
6068
|
+
case "refine":
|
|
6069
|
+
return "refining";
|
|
6070
|
+
case "decompose-idea":
|
|
6071
|
+
return "decomposing";
|
|
6072
|
+
case "shelve":
|
|
6073
|
+
return "shelving";
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
5641
6076
|
var VALID_TYPES = ["tasks", "ideas", "principles", "facts"];
|
|
5642
6077
|
var SECTION_HEADERS = {
|
|
5643
6078
|
tasks: "\uD83D\uDCCB Tasks",
|
|
@@ -5712,6 +6147,7 @@ async function list(dependencies) {
|
|
|
5712
6147
|
return { exitCode: 1 };
|
|
5713
6148
|
}
|
|
5714
6149
|
const specificTypeRequested = commandArguments.length > 0;
|
|
6150
|
+
const workflowTasks = typesToList.includes("ideas") && fileSystem.exists(dustPath) ? await findAllWorkflowTasks(fileSystem, dustPath) : null;
|
|
5715
6151
|
for (const type of typesToList) {
|
|
5716
6152
|
const dirPath = `${dustPath}/${type}`;
|
|
5717
6153
|
const dirExists = fileSystem.exists(dirPath);
|
|
@@ -5726,6 +6162,13 @@ async function list(dependencies) {
|
|
|
5726
6162
|
context.stdout(`No ${type} found.`);
|
|
5727
6163
|
context.stdout("");
|
|
5728
6164
|
}
|
|
6165
|
+
if (type === "facts") {
|
|
6166
|
+
context.emitEvent?.({ type: "facts-listed", facts: [] });
|
|
6167
|
+
} else if (type === "ideas") {
|
|
6168
|
+
context.emitEvent?.({ type: "ideas-listed", ideas: [] });
|
|
6169
|
+
} else if (type === "principles") {
|
|
6170
|
+
context.emitEvent?.({ type: "principles-listed", principles: [] });
|
|
6171
|
+
}
|
|
5729
6172
|
continue;
|
|
5730
6173
|
}
|
|
5731
6174
|
context.stdout(SECTION_HEADERS[type]);
|
|
@@ -5740,16 +6183,26 @@ async function list(dependencies) {
|
|
|
5740
6183
|
context.stdout("");
|
|
5741
6184
|
}
|
|
5742
6185
|
}
|
|
6186
|
+
const collectedItems = [];
|
|
5743
6187
|
for (const file of mdFiles) {
|
|
5744
6188
|
const filePath = `${dirPath}/${file}`;
|
|
5745
6189
|
const content = await fileSystem.readFile(filePath);
|
|
5746
6190
|
const title = extractTitle(content);
|
|
5747
6191
|
const openingSentence = extractOpeningSentence(content);
|
|
5748
6192
|
const relativePath = `.dust/${type}/${file}`;
|
|
6193
|
+
const slug = file.replace(".md", "");
|
|
6194
|
+
const displayTitle = title || slug;
|
|
6195
|
+
if (type === "ideas") {
|
|
6196
|
+
const workflowTask = workflowTasks?.workflowTasksByIdeaSlug.get(slug);
|
|
6197
|
+
const status = workflowTask ? workflowTypeToStatus(workflowTask.type) : "draft";
|
|
6198
|
+
collectedItems.push({ path: relativePath, title: displayTitle, status });
|
|
6199
|
+
} else if (type === "facts" || type === "principles") {
|
|
6200
|
+
collectedItems.push({ path: relativePath, title: displayTitle });
|
|
6201
|
+
}
|
|
5749
6202
|
if (title) {
|
|
5750
6203
|
context.stdout(`${colors.bold}# ${title}${colors.reset}`);
|
|
5751
6204
|
} else {
|
|
5752
|
-
context.stdout(`${colors.bold}# ${
|
|
6205
|
+
context.stdout(`${colors.bold}# ${slug}${colors.reset}`);
|
|
5753
6206
|
}
|
|
5754
6207
|
if (openingSentence) {
|
|
5755
6208
|
context.stdout(`${colors.dim}${openingSentence}${colors.reset}`);
|
|
@@ -5757,130 +6210,28 @@ async function list(dependencies) {
|
|
|
5757
6210
|
context.stdout(`${colors.cyan}→ ${relativePath}${colors.reset}`);
|
|
5758
6211
|
context.stdout("");
|
|
5759
6212
|
}
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
5780
|
-
env: { ...process.env, ...env }
|
|
5781
|
-
});
|
|
5782
|
-
if (!proc.stdout) {
|
|
5783
|
-
throw new Error("Failed to get stdout from codex process");
|
|
5784
|
-
}
|
|
5785
|
-
let stderrOutput = "";
|
|
5786
|
-
proc.stderr?.on("data", (data) => {
|
|
5787
|
-
stderrOutput += data.toString();
|
|
5788
|
-
});
|
|
5789
|
-
const closePromise = new Promise((resolve3, reject) => {
|
|
5790
|
-
proc.on("close", (code) => {
|
|
5791
|
-
if (code === 0 || code === null)
|
|
5792
|
-
resolve3();
|
|
5793
|
-
else {
|
|
5794
|
-
const errMsg = stderrOutput.trim() ? `codex exited with code ${code}: ${stderrOutput.trim()}` : `codex exited with code ${code}`;
|
|
5795
|
-
reject(new Error(errMsg));
|
|
5796
|
-
}
|
|
5797
|
-
});
|
|
5798
|
-
proc.on("error", reject);
|
|
5799
|
-
});
|
|
5800
|
-
const abortHandler = () => {
|
|
5801
|
-
if (!proc.killed) {
|
|
5802
|
-
proc.kill();
|
|
5803
|
-
}
|
|
5804
|
-
};
|
|
5805
|
-
if (signal?.aborted) {
|
|
5806
|
-
abortHandler();
|
|
5807
|
-
} else if (signal) {
|
|
5808
|
-
signal.addEventListener("abort", abortHandler, { once: true });
|
|
5809
|
-
}
|
|
5810
|
-
const rl = dependencies.createInterface({ input: proc.stdout });
|
|
5811
|
-
try {
|
|
5812
|
-
for await (const line of rl) {
|
|
5813
|
-
if (!line.trim())
|
|
5814
|
-
continue;
|
|
5815
|
-
try {
|
|
5816
|
-
yield JSON.parse(line);
|
|
5817
|
-
} catch {
|
|
5818
|
-
debug2("Skipping malformed JSON line: %s", line.slice(0, 200));
|
|
5819
|
-
}
|
|
5820
|
-
}
|
|
5821
|
-
await closePromise;
|
|
5822
|
-
} finally {
|
|
5823
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
5824
|
-
rl.close?.();
|
|
5825
|
-
}
|
|
5826
|
-
}
|
|
5827
|
-
|
|
5828
|
-
// lib/codex/event-parser.ts
|
|
5829
|
-
function* parseCodexRawEvent(raw) {
|
|
5830
|
-
if (raw.type !== "item.completed")
|
|
5831
|
-
return;
|
|
5832
|
-
const item = raw.item;
|
|
5833
|
-
if (!item)
|
|
5834
|
-
return;
|
|
5835
|
-
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
5836
|
-
yield { type: "text_delta", text: `${item.text}
|
|
5837
|
-
` };
|
|
5838
|
-
} else if (item.type === "command_execution" && typeof item.command === "string") {
|
|
5839
|
-
yield {
|
|
5840
|
-
type: "tool_use",
|
|
5841
|
-
id: typeof item.id === "string" ? item.id : "",
|
|
5842
|
-
name: "command_execution",
|
|
5843
|
-
input: { command: item.command }
|
|
5844
|
-
};
|
|
5845
|
-
if (typeof item.aggregated_output === "string") {
|
|
5846
|
-
yield {
|
|
5847
|
-
type: "tool_result",
|
|
5848
|
-
toolUseId: typeof item.id === "string" ? item.id : "",
|
|
5849
|
-
content: item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})`
|
|
5850
|
-
};
|
|
5851
|
-
}
|
|
5852
|
-
}
|
|
5853
|
-
}
|
|
5854
|
-
|
|
5855
|
-
// lib/codex/streamer.ts
|
|
5856
|
-
async function streamCodexEvents(events, sink, onRawEvent) {
|
|
5857
|
-
let hadTextOutput = false;
|
|
5858
|
-
for await (const raw of events) {
|
|
5859
|
-
onRawEvent?.(raw);
|
|
5860
|
-
for (const event of parseCodexRawEvent(raw)) {
|
|
5861
|
-
processEvent(event, sink, { hadTextOutput });
|
|
5862
|
-
if (event.type === "text_delta") {
|
|
5863
|
-
hadTextOutput = true;
|
|
5864
|
-
} else if (event.type === "tool_use") {
|
|
5865
|
-
hadTextOutput = false;
|
|
5866
|
-
}
|
|
6213
|
+
if (type === "facts") {
|
|
6214
|
+
context.emitEvent?.({
|
|
6215
|
+
type: "facts-listed",
|
|
6216
|
+
facts: collectedItems.map((i) => ({ path: i.path, title: i.title }))
|
|
6217
|
+
});
|
|
6218
|
+
} else if (type === "ideas") {
|
|
6219
|
+
context.emitEvent?.({
|
|
6220
|
+
type: "ideas-listed",
|
|
6221
|
+
ideas: collectedItems.map((i) => ({
|
|
6222
|
+
path: i.path,
|
|
6223
|
+
title: i.title,
|
|
6224
|
+
status: i.status ?? "draft"
|
|
6225
|
+
}))
|
|
6226
|
+
});
|
|
6227
|
+
} else if (type === "principles") {
|
|
6228
|
+
context.emitEvent?.({
|
|
6229
|
+
type: "principles-listed",
|
|
6230
|
+
principles: collectedItems.map((i) => ({ path: i.path, title: i.title }))
|
|
6231
|
+
});
|
|
5867
6232
|
}
|
|
5868
6233
|
}
|
|
5869
|
-
}
|
|
5870
|
-
|
|
5871
|
-
// lib/codex/run.ts
|
|
5872
|
-
var defaultRunnerDependencies2 = {
|
|
5873
|
-
spawnCodex,
|
|
5874
|
-
createStdoutSink,
|
|
5875
|
-
streamCodexEvents
|
|
5876
|
-
};
|
|
5877
|
-
async function run2(prompt, options = {}, dependencies = defaultRunnerDependencies2) {
|
|
5878
|
-
const isRunOptions = (opt) => ("spawnOptions" in opt) || ("onRawEvent" in opt);
|
|
5879
|
-
const spawnOptions = isRunOptions(options) ? options.spawnOptions ?? {} : options;
|
|
5880
|
-
const onRawEvent = isRunOptions(options) ? options.onRawEvent : undefined;
|
|
5881
|
-
const events = dependencies.spawnCodex(prompt, spawnOptions);
|
|
5882
|
-
const sink = dependencies.createStdoutSink();
|
|
5883
|
-
await dependencies.streamCodexEvents(events, sink, onRawEvent);
|
|
6234
|
+
return { exitCode: 0 };
|
|
5884
6235
|
}
|
|
5885
6236
|
|
|
5886
6237
|
// lib/cli/commands/loop-codex.ts
|
|
@@ -6137,8 +6488,8 @@ function parseGitDiffNameStatus(output) {
|
|
|
6137
6488
|
const parts = line.split("\t");
|
|
6138
6489
|
if (parts.length >= 2) {
|
|
6139
6490
|
const statusChar = parts[0].charAt(0);
|
|
6140
|
-
const
|
|
6141
|
-
changes.push({ status: statusChar, path });
|
|
6491
|
+
const path3 = parts.length > 2 ? parts[2] : parts[1];
|
|
6492
|
+
changes.push({ status: statusChar, path: path3 });
|
|
6142
6493
|
}
|
|
6143
6494
|
}
|
|
6144
6495
|
return changes;
|
|
@@ -6199,12 +6550,12 @@ async function getUncommittedFiles(cwd, gitRunner) {
|
|
|
6199
6550
|
`).filter((line) => line.length > 0);
|
|
6200
6551
|
for (const line of lines) {
|
|
6201
6552
|
if (line.length > 3) {
|
|
6202
|
-
const
|
|
6203
|
-
const arrowIndex =
|
|
6553
|
+
const path3 = line.substring(3);
|
|
6554
|
+
const arrowIndex = path3.indexOf(" -> ");
|
|
6204
6555
|
if (arrowIndex !== -1) {
|
|
6205
|
-
files.push(
|
|
6556
|
+
files.push(path3.substring(arrowIndex + 4));
|
|
6206
6557
|
} else {
|
|
6207
|
-
files.push(
|
|
6558
|
+
files.push(path3);
|
|
6208
6559
|
}
|
|
6209
6560
|
}
|
|
6210
6561
|
}
|
|
@@ -6355,24 +6706,24 @@ async function main(options) {
|
|
|
6355
6706
|
function createFileSystem(primitives) {
|
|
6356
6707
|
return {
|
|
6357
6708
|
exists: primitives.existsSync,
|
|
6358
|
-
isDirectory: (
|
|
6709
|
+
isDirectory: (path3) => {
|
|
6359
6710
|
try {
|
|
6360
|
-
return primitives.statSync(
|
|
6711
|
+
return primitives.statSync(path3).isDirectory();
|
|
6361
6712
|
} catch {
|
|
6362
6713
|
return false;
|
|
6363
6714
|
}
|
|
6364
6715
|
},
|
|
6365
|
-
readFile: (
|
|
6366
|
-
writeFile: (
|
|
6716
|
+
readFile: (path3) => primitives.readFile(path3, "utf-8"),
|
|
6717
|
+
writeFile: (path3, content, options) => primitives.writeFile(path3, content, {
|
|
6367
6718
|
encoding: "utf-8",
|
|
6368
6719
|
flag: options?.flag
|
|
6369
6720
|
}),
|
|
6370
|
-
mkdir: async (
|
|
6371
|
-
await primitives.mkdir(
|
|
6721
|
+
mkdir: async (path3, options) => {
|
|
6722
|
+
await primitives.mkdir(path3, options);
|
|
6372
6723
|
},
|
|
6373
|
-
getFileCreationTime: (
|
|
6374
|
-
readdir: (
|
|
6375
|
-
chmod: (
|
|
6724
|
+
getFileCreationTime: (path3) => primitives.statSync(path3).birthtimeMs,
|
|
6725
|
+
readdir: (path3) => primitives.readdir(path3),
|
|
6726
|
+
chmod: (path3, mode) => primitives.chmod(path3, mode),
|
|
6376
6727
|
rename: (oldPath, newPath) => primitives.rename(oldPath, newPath)
|
|
6377
6728
|
};
|
|
6378
6729
|
}
|
|
@@ -6396,7 +6747,8 @@ async function wireEntry(fsPrimitives, processPrimitives, consolePrimitives) {
|
|
|
6396
6747
|
cwd: processPrimitives.cwd(),
|
|
6397
6748
|
stdout: consolePrimitives.log,
|
|
6398
6749
|
stdoutInline: consolePrimitives.write,
|
|
6399
|
-
stderr: consolePrimitives.error
|
|
6750
|
+
stderr: consolePrimitives.error,
|
|
6751
|
+
emitEvent: consolePrimitives.emitEvent
|
|
6400
6752
|
},
|
|
6401
6753
|
fileSystem,
|
|
6402
6754
|
glob,
|
|
@@ -6406,7 +6758,12 @@ async function wireEntry(fsPrimitives, processPrimitives, consolePrimitives) {
|
|
|
6406
6758
|
}
|
|
6407
6759
|
|
|
6408
6760
|
// lib/cli/run.ts
|
|
6409
|
-
|
|
6761
|
+
var eventsFd = process.env.DUST_EVENTS_FD ? Number.parseInt(process.env.DUST_EVENTS_FD, 10) : undefined;
|
|
6762
|
+
var emitEvent = eventsFd !== undefined && !Number.isNaN(eventsFd) ? createEventEmitter((message) => {
|
|
6763
|
+
writeSync(eventsFd, `${JSON.stringify(message)}
|
|
6764
|
+
`);
|
|
6765
|
+
}) : undefined;
|
|
6766
|
+
await wireEntry({ existsSync: existsSync2, statSync: statSync3, readFile: readFile3, writeFile: writeFile3, mkdir: mkdir3, readdir: readdir3, chmod: chmod3, rename }, {
|
|
6410
6767
|
argv: process.argv,
|
|
6411
6768
|
cwd: () => process.cwd(),
|
|
6412
6769
|
exit: (code) => {
|
|
@@ -6417,5 +6774,6 @@ await wireEntry({ existsSync, statSync: statSync3, readFile: readFile3, writeFil
|
|
|
6417
6774
|
write: (message) => {
|
|
6418
6775
|
process.stdout.write(message);
|
|
6419
6776
|
},
|
|
6420
|
-
error: console.error
|
|
6777
|
+
error: console.error,
|
|
6778
|
+
emitEvent
|
|
6421
6779
|
});
|