@joshski/dust 0.1.79 → 0.1.81
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-git.d.ts +2 -0
- package/dist/bucket/repository-loop.d.ts +42 -0
- package/dist/bucket/repository.d.ts +2 -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 +668 -296
- 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.81";
|
|
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");
|
|
@@ -2438,30 +2481,45 @@ function getRepoPath(repoName, reposDir) {
|
|
|
2438
2481
|
const safeName = repoName.replace(/[^a-zA-Z0-9-_/]/g, "-");
|
|
2439
2482
|
return join7(reposDir, safeName);
|
|
2440
2483
|
}
|
|
2441
|
-
|
|
2484
|
+
function cloneWithUrl(url, targetPath, spawn) {
|
|
2442
2485
|
return new Promise((resolve) => {
|
|
2443
|
-
const proc = spawn("git", ["clone",
|
|
2486
|
+
const proc = spawn("git", ["clone", url, 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) => {
|
|
2449
2496
|
stderr += data.toString();
|
|
2450
2497
|
});
|
|
2451
2498
|
proc.on("close", (code) => {
|
|
2452
|
-
|
|
2453
|
-
resolve(true);
|
|
2454
|
-
} else {
|
|
2455
|
-
context.stderr(`Failed to clone ${repository.name}: ${stderr.trim()}`);
|
|
2456
|
-
resolve(false);
|
|
2457
|
-
}
|
|
2499
|
+
resolve({ success: code === 0, stderr: stderr.trim() });
|
|
2458
2500
|
});
|
|
2459
2501
|
proc.on("error", (error) => {
|
|
2460
|
-
|
|
2461
|
-
resolve(false);
|
|
2502
|
+
resolve({ success: false, stderr: error.message });
|
|
2462
2503
|
});
|
|
2463
2504
|
});
|
|
2464
2505
|
}
|
|
2506
|
+
async function cloneRepository(repository, targetPath, spawn, context) {
|
|
2507
|
+
const httpsResult = await cloneWithUrl(repository.gitUrl, targetPath, spawn);
|
|
2508
|
+
if (httpsResult.success) {
|
|
2509
|
+
return true;
|
|
2510
|
+
}
|
|
2511
|
+
if (repository.gitSshUrl) {
|
|
2512
|
+
context.stderr(`HTTPS clone failed for ${repository.name}, trying SSH: ${httpsResult.stderr}`);
|
|
2513
|
+
const sshResult = await cloneWithUrl(repository.gitSshUrl, targetPath, spawn);
|
|
2514
|
+
if (sshResult.success) {
|
|
2515
|
+
return true;
|
|
2516
|
+
}
|
|
2517
|
+
context.stderr(`Failed to clone ${repository.name} via SSH: ${sshResult.stderr}`);
|
|
2518
|
+
return false;
|
|
2519
|
+
}
|
|
2520
|
+
context.stderr(`Failed to clone ${repository.name}: ${httpsResult.stderr}`);
|
|
2521
|
+
return false;
|
|
2522
|
+
}
|
|
2465
2523
|
async function removeRepository(path, spawn, context) {
|
|
2466
2524
|
return new Promise((resolve) => {
|
|
2467
2525
|
const proc = spawn("rm", ["-rf", path], {
|
|
@@ -2522,7 +2580,66 @@ function formatAgentEvent(event) {
|
|
|
2522
2580
|
|
|
2523
2581
|
// lib/cli/commands/loop.ts
|
|
2524
2582
|
import { spawn as nodeSpawn3 } from "node:child_process";
|
|
2583
|
+
import { existsSync } from "node:fs";
|
|
2525
2584
|
import os from "node:os";
|
|
2585
|
+
import path2 from "node:path";
|
|
2586
|
+
|
|
2587
|
+
// lib/docker/docker-agent.ts
|
|
2588
|
+
import path from "node:path";
|
|
2589
|
+
var log = createLogger("dust:docker:agent");
|
|
2590
|
+
async function isDockerAvailable(dependencies) {
|
|
2591
|
+
return new Promise((resolve) => {
|
|
2592
|
+
const proc = dependencies.spawn("docker", ["--version"], {
|
|
2593
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2594
|
+
});
|
|
2595
|
+
proc.on("close", (code) => {
|
|
2596
|
+
resolve(code === 0);
|
|
2597
|
+
});
|
|
2598
|
+
proc.on("error", () => {
|
|
2599
|
+
resolve(false);
|
|
2600
|
+
});
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
function generateImageTag(repoPath) {
|
|
2604
|
+
const repoName = path.basename(repoPath);
|
|
2605
|
+
const sanitized = repoName.toLowerCase().replace(/[^a-z0-9._-]/g, "-");
|
|
2606
|
+
return `dust-agent-${sanitized}`;
|
|
2607
|
+
}
|
|
2608
|
+
async function buildDockerImage(config, dependencies) {
|
|
2609
|
+
const dockerfilePath = path.join(config.repoPath, ".dust", "Dockerfile");
|
|
2610
|
+
log(`building Docker image ${config.imageTag} from ${dockerfilePath}`);
|
|
2611
|
+
return new Promise((resolve) => {
|
|
2612
|
+
const proc = dependencies.spawn("docker", ["build", "-t", config.imageTag, "-f", dockerfilePath, config.repoPath], {
|
|
2613
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2614
|
+
});
|
|
2615
|
+
let stderr = "";
|
|
2616
|
+
proc.stderr?.on("data", (data) => {
|
|
2617
|
+
stderr += data.toString();
|
|
2618
|
+
});
|
|
2619
|
+
proc.on("close", (code) => {
|
|
2620
|
+
if (code === 0) {
|
|
2621
|
+
log(`Docker image ${config.imageTag} built successfully`);
|
|
2622
|
+
resolve({ success: true });
|
|
2623
|
+
} else {
|
|
2624
|
+
log(`Docker build failed: ${stderr}`);
|
|
2625
|
+
resolve({
|
|
2626
|
+
success: false,
|
|
2627
|
+
error: `Docker build failed with exit code ${code}: ${stderr.trim()}`
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2631
|
+
proc.on("error", (error) => {
|
|
2632
|
+
resolve({
|
|
2633
|
+
success: false,
|
|
2634
|
+
error: `Docker build failed: ${error.message}`
|
|
2635
|
+
});
|
|
2636
|
+
});
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
function hasDockerfile(repoPath, dependencies) {
|
|
2640
|
+
const dockerfilePath = path.join(repoPath, ".dust", "Dockerfile");
|
|
2641
|
+
return dependencies.existsSync(dockerfilePath);
|
|
2642
|
+
}
|
|
2526
2643
|
|
|
2527
2644
|
// lib/artifacts/workflow-tasks.ts
|
|
2528
2645
|
var IDEA_TRANSITION_PREFIXES = [
|
|
@@ -2530,10 +2647,79 @@ var IDEA_TRANSITION_PREFIXES = [
|
|
|
2530
2647
|
"Decompose Idea: ",
|
|
2531
2648
|
"Shelve Idea: "
|
|
2532
2649
|
];
|
|
2650
|
+
var CAPTURE_IDEA_PREFIX = "Add Idea: ";
|
|
2533
2651
|
var EXPEDITE_IDEA_PREFIX = "Expedite Idea: ";
|
|
2534
2652
|
function titleToFilename(title) {
|
|
2535
2653
|
return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
2536
2654
|
}
|
|
2655
|
+
var WORKFLOW_SECTION_HEADINGS = [
|
|
2656
|
+
{ type: "refine", heading: "Refines Idea" },
|
|
2657
|
+
{ type: "decompose-idea", heading: "Decomposes Idea" },
|
|
2658
|
+
{ type: "shelve", heading: "Shelves Idea" }
|
|
2659
|
+
];
|
|
2660
|
+
function extractIdeaSlugFromSection(content, sectionHeading) {
|
|
2661
|
+
const lines = content.split(`
|
|
2662
|
+
`);
|
|
2663
|
+
let inSection = false;
|
|
2664
|
+
for (const line of lines) {
|
|
2665
|
+
if (line.startsWith("## ")) {
|
|
2666
|
+
inSection = line.trimEnd() === `## ${sectionHeading}`;
|
|
2667
|
+
continue;
|
|
2668
|
+
}
|
|
2669
|
+
if (!inSection)
|
|
2670
|
+
continue;
|
|
2671
|
+
if (line.startsWith("# "))
|
|
2672
|
+
break;
|
|
2673
|
+
const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
|
|
2674
|
+
if (linkMatch) {
|
|
2675
|
+
const target = linkMatch[2];
|
|
2676
|
+
const slugMatch = target.match(/([^/]+)\.md$/);
|
|
2677
|
+
if (slugMatch) {
|
|
2678
|
+
return slugMatch[1];
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
return null;
|
|
2683
|
+
}
|
|
2684
|
+
async function findAllWorkflowTasks(fileSystem, dustPath) {
|
|
2685
|
+
const tasksPath = `${dustPath}/tasks`;
|
|
2686
|
+
const captureIdeaTasks = [];
|
|
2687
|
+
const workflowTasksByIdeaSlug = new Map;
|
|
2688
|
+
if (!fileSystem.exists(tasksPath)) {
|
|
2689
|
+
return { captureIdeaTasks, workflowTasksByIdeaSlug };
|
|
2690
|
+
}
|
|
2691
|
+
const files = await fileSystem.readdir(tasksPath);
|
|
2692
|
+
for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
|
|
2693
|
+
const content = await fileSystem.readFile(`${tasksPath}/${file}`);
|
|
2694
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
2695
|
+
if (!titleMatch)
|
|
2696
|
+
continue;
|
|
2697
|
+
const title = titleMatch[1].trim();
|
|
2698
|
+
const taskSlug = file.replace(/\.md$/, "");
|
|
2699
|
+
if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
|
|
2700
|
+
captureIdeaTasks.push({
|
|
2701
|
+
taskSlug,
|
|
2702
|
+
ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
|
|
2703
|
+
});
|
|
2704
|
+
} else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
|
|
2705
|
+
captureIdeaTasks.push({
|
|
2706
|
+
taskSlug,
|
|
2707
|
+
ideaTitle: title.slice(EXPEDITE_IDEA_PREFIX.length)
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
|
|
2711
|
+
const linkedSlug = extractIdeaSlugFromSection(content, heading);
|
|
2712
|
+
if (linkedSlug) {
|
|
2713
|
+
workflowTasksByIdeaSlug.set(linkedSlug, {
|
|
2714
|
+
type,
|
|
2715
|
+
ideaSlug: linkedSlug,
|
|
2716
|
+
taskSlug
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
return { captureIdeaTasks, workflowTasksByIdeaSlug };
|
|
2722
|
+
}
|
|
2537
2723
|
|
|
2538
2724
|
// lib/cli/commands/focus.ts
|
|
2539
2725
|
function buildImplementationInstructions(bin, hooksInstalled, taskTitle, taskPath, installCommand) {
|
|
@@ -2669,9 +2855,25 @@ async function next(dependencies) {
|
|
|
2669
2855
|
return { exitCode: 1 };
|
|
2670
2856
|
}
|
|
2671
2857
|
if (result.tasks.length === 0) {
|
|
2858
|
+
context.emitEvent?.({
|
|
2859
|
+
type: "tasks-listed",
|
|
2860
|
+
tasks: []
|
|
2861
|
+
});
|
|
2672
2862
|
return { exitCode: 0 };
|
|
2673
2863
|
}
|
|
2674
2864
|
printTaskList(context, result.tasks);
|
|
2865
|
+
context.emitEvent?.({
|
|
2866
|
+
type: "tasks-listed",
|
|
2867
|
+
tasks: result.tasks.map((task) => {
|
|
2868
|
+
const parts = task.path.split("/");
|
|
2869
|
+
const filename = parts[parts.length - 1];
|
|
2870
|
+
return {
|
|
2871
|
+
path: task.path,
|
|
2872
|
+
title: task.title ?? filename.replace(".md", ""),
|
|
2873
|
+
blockedBy: []
|
|
2874
|
+
};
|
|
2875
|
+
})
|
|
2876
|
+
});
|
|
2675
2877
|
return { exitCode: 0 };
|
|
2676
2878
|
}
|
|
2677
2879
|
|
|
@@ -2708,6 +2910,14 @@ function formatLoopEvent(event) {
|
|
|
2708
2910
|
return `Completed iteration ${event.iteration}/${event.maxIterations}`;
|
|
2709
2911
|
case "loop.ended":
|
|
2710
2912
|
return `Reached max iterations (${event.maxIterations}). Exiting.`;
|
|
2913
|
+
case "loop.docker_detected":
|
|
2914
|
+
return `Docker mode: found .dust/Dockerfile (image: ${event.imageTag})`;
|
|
2915
|
+
case "loop.docker_building":
|
|
2916
|
+
return `Building Docker image ${event.imageTag}...`;
|
|
2917
|
+
case "loop.docker_built":
|
|
2918
|
+
return `Docker image ${event.imageTag} ready`;
|
|
2919
|
+
case "loop.docker_error":
|
|
2920
|
+
return `Docker error: ${event.error}`;
|
|
2711
2921
|
}
|
|
2712
2922
|
}
|
|
2713
2923
|
function createPostEvent(fetchFn) {
|
|
@@ -2747,7 +2957,7 @@ function createWireEventSender(eventsUrl, sessionId, postEvent, onError, getAgen
|
|
|
2747
2957
|
postEvent(eventsUrl, payload).catch(onError);
|
|
2748
2958
|
};
|
|
2749
2959
|
}
|
|
2750
|
-
var
|
|
2960
|
+
var log2 = createLogger("dust:cli:commands:loop");
|
|
2751
2961
|
var SLEEP_INTERVAL_MS = 30000;
|
|
2752
2962
|
var SLEEP_STEP_MS = 1000;
|
|
2753
2963
|
var DEFAULT_MAX_ITERATIONS = 10;
|
|
@@ -2797,15 +3007,16 @@ async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAg
|
|
|
2797
3007
|
onRawEvent,
|
|
2798
3008
|
hooksInstalled = false,
|
|
2799
3009
|
signal,
|
|
2800
|
-
logger =
|
|
2801
|
-
repositoryId
|
|
3010
|
+
logger = log2,
|
|
3011
|
+
repositoryId,
|
|
3012
|
+
docker
|
|
2802
3013
|
} = options;
|
|
2803
3014
|
const baseEnv = buildUnattendedEnv({ repositoryId });
|
|
2804
|
-
|
|
3015
|
+
log2("syncing with remote");
|
|
2805
3016
|
onLoopEvent({ type: "loop.syncing" });
|
|
2806
3017
|
const pullResult = await gitPull(context.cwd, spawn);
|
|
2807
3018
|
if (!pullResult.success) {
|
|
2808
|
-
|
|
3019
|
+
log2(`git pull failed: ${pullResult.message}`);
|
|
2809
3020
|
onLoopEvent({
|
|
2810
3021
|
type: "loop.sync_skipped",
|
|
2811
3022
|
reason: pullResult.message
|
|
@@ -2836,7 +3047,8 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
2836
3047
|
cwd: context.cwd,
|
|
2837
3048
|
dangerouslySkipPermissions: true,
|
|
2838
3049
|
env: baseEnv,
|
|
2839
|
-
signal
|
|
3050
|
+
signal,
|
|
3051
|
+
docker
|
|
2840
3052
|
},
|
|
2841
3053
|
onRawEvent
|
|
2842
3054
|
});
|
|
@@ -2855,12 +3067,12 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
2855
3067
|
onLoopEvent({ type: "loop.checking_tasks" });
|
|
2856
3068
|
const tasks = await findAvailableTasks(dependencies);
|
|
2857
3069
|
if (tasks.length === 0) {
|
|
2858
|
-
|
|
3070
|
+
log2("no tasks available");
|
|
2859
3071
|
onLoopEvent({ type: "loop.no_tasks" });
|
|
2860
3072
|
return "no_tasks";
|
|
2861
3073
|
}
|
|
2862
3074
|
const task = tasks[0];
|
|
2863
|
-
|
|
3075
|
+
log2(`found ${tasks.length} task(s), picking: ${task.title ?? task.path}`);
|
|
2864
3076
|
onLoopEvent({ type: "loop.tasks_found" });
|
|
2865
3077
|
const taskContent = await dependencies.fileSystem.readFile(`${dependencies.context.cwd}/${task.path}`);
|
|
2866
3078
|
const { dustCommand, installCommand = "npm install" } = dependencies.settings;
|
|
@@ -2888,11 +3100,12 @@ ${instructions}`;
|
|
|
2888
3100
|
cwd: context.cwd,
|
|
2889
3101
|
dangerouslySkipPermissions: true,
|
|
2890
3102
|
env: baseEnv,
|
|
2891
|
-
signal
|
|
3103
|
+
signal,
|
|
3104
|
+
docker
|
|
2892
3105
|
},
|
|
2893
3106
|
onRawEvent
|
|
2894
3107
|
});
|
|
2895
|
-
|
|
3108
|
+
log2(`${agentName} completed task: ${task.title ?? task.path}`);
|
|
2896
3109
|
onAgentEvent?.({ type: "agent-session-ended", success: true });
|
|
2897
3110
|
return "ran_claude";
|
|
2898
3111
|
} catch (error) {
|
|
@@ -2947,7 +3160,36 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
2947
3160
|
sendWireEvent(event);
|
|
2948
3161
|
};
|
|
2949
3162
|
const hooksInstalled = await manageGitHooks(dependencies);
|
|
2950
|
-
|
|
3163
|
+
let dockerConfig;
|
|
3164
|
+
const dockerDeps = {
|
|
3165
|
+
spawn: loopDependencies.dockerDeps?.spawn ?? loopDependencies.spawn,
|
|
3166
|
+
homedir: loopDependencies.dockerDeps?.homedir ?? os.homedir,
|
|
3167
|
+
existsSync: loopDependencies.dockerDeps?.existsSync ?? existsSync
|
|
3168
|
+
};
|
|
3169
|
+
if (hasDockerfile(context.cwd, dockerDeps)) {
|
|
3170
|
+
const imageTag = generateImageTag(context.cwd);
|
|
3171
|
+
onLoopEvent({ type: "loop.docker_detected", imageTag });
|
|
3172
|
+
if (!await isDockerAvailable(dockerDeps)) {
|
|
3173
|
+
context.stderr("Docker not available. Install Docker or remove .dust/Dockerfile to run without Docker.");
|
|
3174
|
+
return { exitCode: 1 };
|
|
3175
|
+
}
|
|
3176
|
+
onLoopEvent({ type: "loop.docker_building", imageTag });
|
|
3177
|
+
const buildResult = await buildDockerImage({ repoPath: context.cwd, imageTag }, dockerDeps);
|
|
3178
|
+
if (!buildResult.success) {
|
|
3179
|
+
onLoopEvent({ type: "loop.docker_error", error: buildResult.error });
|
|
3180
|
+
context.stderr(buildResult.error);
|
|
3181
|
+
return { exitCode: 1 };
|
|
3182
|
+
}
|
|
3183
|
+
onLoopEvent({ type: "loop.docker_built", imageTag });
|
|
3184
|
+
const homeDir = os.homedir();
|
|
3185
|
+
dockerConfig = {
|
|
3186
|
+
imageTag,
|
|
3187
|
+
repoPath: context.cwd,
|
|
3188
|
+
homeDir,
|
|
3189
|
+
hasGitconfig: existsSync(path2.join(homeDir, ".gitconfig"))
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
log2(`starting loop, maxIterations=${maxIterations}, sessionId=${sessionId}`);
|
|
2951
3193
|
onLoopEvent({ type: "loop.warning" });
|
|
2952
3194
|
onLoopEvent({
|
|
2953
3195
|
type: "loop.started",
|
|
@@ -2957,7 +3199,10 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
2957
3199
|
context.stdout(" Press Ctrl+C to stop");
|
|
2958
3200
|
context.stdout("");
|
|
2959
3201
|
let completedIterations = 0;
|
|
2960
|
-
const iterationOptions = {
|
|
3202
|
+
const iterationOptions = {
|
|
3203
|
+
hooksInstalled,
|
|
3204
|
+
docker: dockerConfig
|
|
3205
|
+
};
|
|
2961
3206
|
if (eventsUrl) {
|
|
2962
3207
|
iterationOptions.onRawEvent = createHeartbeatThrottler(onAgentEvent);
|
|
2963
3208
|
}
|
|
@@ -2965,12 +3210,12 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
2965
3210
|
agentSessionId = crypto.randomUUID();
|
|
2966
3211
|
const result = await runOneIteration(dependencies, loopDependencies, onLoopEvent, onAgentEvent, iterationOptions);
|
|
2967
3212
|
if (result === "no_tasks") {
|
|
2968
|
-
|
|
3213
|
+
log2("sleeping, no tasks");
|
|
2969
3214
|
const writeInline = context.stdoutInline ?? context.stdout;
|
|
2970
3215
|
await sleepWithProgress(loopDependencies.sleep, SLEEP_INTERVAL_MS, writeInline, context.stdout);
|
|
2971
3216
|
} else {
|
|
2972
3217
|
completedIterations++;
|
|
2973
|
-
|
|
3218
|
+
log2(`iteration ${completedIterations}/${maxIterations} complete, result=${result}`);
|
|
2974
3219
|
onLoopEvent({
|
|
2975
3220
|
type: "loop.iteration_complete",
|
|
2976
3221
|
iteration: completedIterations,
|
|
@@ -2978,13 +3223,135 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
2978
3223
|
});
|
|
2979
3224
|
}
|
|
2980
3225
|
}
|
|
2981
|
-
|
|
3226
|
+
log2(`loop ended after ${completedIterations} iterations`);
|
|
2982
3227
|
onLoopEvent({ type: "loop.ended", maxIterations });
|
|
2983
3228
|
return { exitCode: 0 };
|
|
2984
3229
|
}
|
|
2985
3230
|
|
|
3231
|
+
// lib/codex/spawn-codex.ts
|
|
3232
|
+
import { spawn as nodeSpawn4 } from "node:child_process";
|
|
3233
|
+
import { createInterface as nodeCreateInterface2 } from "node:readline";
|
|
3234
|
+
var debug2 = createLogger("dust.codex.spawn-codex");
|
|
3235
|
+
var defaultDependencies2 = {
|
|
3236
|
+
spawn: nodeSpawn4,
|
|
3237
|
+
createInterface: nodeCreateInterface2
|
|
3238
|
+
};
|
|
3239
|
+
async function* spawnCodex(prompt, options = {}, dependencies = defaultDependencies2) {
|
|
3240
|
+
const { cwd, env, signal } = options;
|
|
3241
|
+
const codexArguments = ["exec", prompt, "--json", "--yolo"];
|
|
3242
|
+
if (cwd) {
|
|
3243
|
+
codexArguments.push("--cd", cwd);
|
|
3244
|
+
}
|
|
3245
|
+
const proc = dependencies.spawn("codex", codexArguments, {
|
|
3246
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3247
|
+
env: { ...process.env, ...env }
|
|
3248
|
+
});
|
|
3249
|
+
if (!proc.stdout) {
|
|
3250
|
+
throw new Error("Failed to get stdout from codex process");
|
|
3251
|
+
}
|
|
3252
|
+
let stderrOutput = "";
|
|
3253
|
+
proc.stderr?.on("data", (data) => {
|
|
3254
|
+
stderrOutput += data.toString();
|
|
3255
|
+
});
|
|
3256
|
+
const closePromise = new Promise((resolve, reject) => {
|
|
3257
|
+
proc.on("close", (code) => {
|
|
3258
|
+
if (code === 0 || code === null)
|
|
3259
|
+
resolve();
|
|
3260
|
+
else {
|
|
3261
|
+
const errMsg = stderrOutput.trim() ? `codex exited with code ${code}: ${stderrOutput.trim()}` : `codex exited with code ${code}`;
|
|
3262
|
+
reject(new Error(errMsg));
|
|
3263
|
+
}
|
|
3264
|
+
});
|
|
3265
|
+
proc.on("error", reject);
|
|
3266
|
+
});
|
|
3267
|
+
const abortHandler = () => {
|
|
3268
|
+
if (!proc.killed) {
|
|
3269
|
+
proc.kill();
|
|
3270
|
+
}
|
|
3271
|
+
};
|
|
3272
|
+
if (signal?.aborted) {
|
|
3273
|
+
abortHandler();
|
|
3274
|
+
} else if (signal) {
|
|
3275
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
3276
|
+
}
|
|
3277
|
+
const rl = dependencies.createInterface({ input: proc.stdout });
|
|
3278
|
+
try {
|
|
3279
|
+
for await (const line of rl) {
|
|
3280
|
+
if (!line.trim())
|
|
3281
|
+
continue;
|
|
3282
|
+
try {
|
|
3283
|
+
yield JSON.parse(line);
|
|
3284
|
+
} catch {
|
|
3285
|
+
debug2("Skipping malformed JSON line: %s", line.slice(0, 200));
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
await closePromise;
|
|
3289
|
+
} finally {
|
|
3290
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
3291
|
+
rl.close?.();
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
// lib/codex/event-parser.ts
|
|
3296
|
+
function* parseCodexRawEvent(raw) {
|
|
3297
|
+
if (raw.type !== "item.completed")
|
|
3298
|
+
return;
|
|
3299
|
+
const item = raw.item;
|
|
3300
|
+
if (!item)
|
|
3301
|
+
return;
|
|
3302
|
+
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
3303
|
+
yield { type: "text_delta", text: `${item.text}
|
|
3304
|
+
` };
|
|
3305
|
+
} else if (item.type === "command_execution" && typeof item.command === "string") {
|
|
3306
|
+
yield {
|
|
3307
|
+
type: "tool_use",
|
|
3308
|
+
id: typeof item.id === "string" ? item.id : "",
|
|
3309
|
+
name: "command_execution",
|
|
3310
|
+
input: { command: item.command }
|
|
3311
|
+
};
|
|
3312
|
+
if (typeof item.aggregated_output === "string") {
|
|
3313
|
+
yield {
|
|
3314
|
+
type: "tool_result",
|
|
3315
|
+
toolUseId: typeof item.id === "string" ? item.id : "",
|
|
3316
|
+
content: item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})`
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// lib/codex/streamer.ts
|
|
3323
|
+
async function streamCodexEvents(events, sink, onRawEvent) {
|
|
3324
|
+
let hadTextOutput = false;
|
|
3325
|
+
for await (const raw of events) {
|
|
3326
|
+
onRawEvent?.(raw);
|
|
3327
|
+
for (const event of parseCodexRawEvent(raw)) {
|
|
3328
|
+
processEvent(event, sink, { hadTextOutput });
|
|
3329
|
+
if (event.type === "text_delta") {
|
|
3330
|
+
hadTextOutput = true;
|
|
3331
|
+
} else if (event.type === "tool_use") {
|
|
3332
|
+
hadTextOutput = false;
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
// lib/codex/run.ts
|
|
3339
|
+
var defaultRunnerDependencies2 = {
|
|
3340
|
+
spawnCodex,
|
|
3341
|
+
createStdoutSink,
|
|
3342
|
+
streamCodexEvents
|
|
3343
|
+
};
|
|
3344
|
+
async function run2(prompt, options = {}, dependencies = defaultRunnerDependencies2) {
|
|
3345
|
+
const isRunOptions = (opt) => ("spawnOptions" in opt) || ("onRawEvent" in opt);
|
|
3346
|
+
const spawnOptions = isRunOptions(options) ? options.spawnOptions ?? {} : options;
|
|
3347
|
+
const onRawEvent = isRunOptions(options) ? options.onRawEvent : undefined;
|
|
3348
|
+
const events = dependencies.spawnCodex(prompt, spawnOptions);
|
|
3349
|
+
const sink = dependencies.createStdoutSink();
|
|
3350
|
+
await dependencies.streamCodexEvents(events, sink, onRawEvent);
|
|
3351
|
+
}
|
|
3352
|
+
|
|
2986
3353
|
// lib/bucket/repository-loop.ts
|
|
2987
|
-
var
|
|
3354
|
+
var log3 = createLogger("dust:bucket:repository-loop");
|
|
2988
3355
|
var FALLBACK_TIMEOUT_MS = 300000;
|
|
2989
3356
|
function createLogCallbacks(logBuffer) {
|
|
2990
3357
|
return {
|
|
@@ -3030,59 +3397,40 @@ function createWakeUpHandler(repoState, resolve) {
|
|
|
3030
3397
|
}
|
|
3031
3398
|
function createNoOpGlobScanner() {
|
|
3032
3399
|
return {
|
|
3033
|
-
scan: async function* () {}
|
|
3400
|
+
scan: async function* noOpScan() {}
|
|
3034
3401
|
};
|
|
3035
3402
|
}
|
|
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(`
|
|
3403
|
+
function createBufferStdoutSink(loopState, logBuffer) {
|
|
3404
|
+
return {
|
|
3405
|
+
write(text) {
|
|
3406
|
+
loopState.partialLine += text;
|
|
3407
|
+
const lines = loopState.partialLine.split(`
|
|
3059
3408
|
`);
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
}
|
|
3063
|
-
partialLine = lines[lines.length - 1];
|
|
3064
|
-
},
|
|
3065
|
-
line: (text) => {
|
|
3066
|
-
partialLine = flushAndLogMultiLine(partialLine, text, repoState.logBuffer);
|
|
3409
|
+
for (let i = 0;i < lines.length - 1; i++) {
|
|
3410
|
+
appendLogLine(logBuffer, createLogLine(lines[i], "stdout"));
|
|
3067
3411
|
}
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
run: bufferRun,
|
|
3074
|
-
sleep,
|
|
3075
|
-
postEvent: async () => {}
|
|
3412
|
+
loopState.partialLine = lines[lines.length - 1];
|
|
3413
|
+
},
|
|
3414
|
+
line(text) {
|
|
3415
|
+
loopState.partialLine = flushAndLogMultiLine(loopState.partialLine, text, logBuffer);
|
|
3416
|
+
}
|
|
3076
3417
|
};
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3418
|
+
}
|
|
3419
|
+
function createBufferRun(run3, bufferSinkDeps) {
|
|
3420
|
+
return (prompt, options) => run3(prompt, options, bufferSinkDeps);
|
|
3421
|
+
}
|
|
3422
|
+
async function noOpPostEvent() {}
|
|
3423
|
+
function createLoopEventHandler(logBuffer) {
|
|
3424
|
+
return function onLoopEvent(event) {
|
|
3080
3425
|
const formatted = formatLoopEvent(event);
|
|
3081
3426
|
if (formatted !== null) {
|
|
3082
|
-
appendLogLine(
|
|
3427
|
+
appendLogLine(logBuffer, createLogLine(formatted, "stdout"));
|
|
3083
3428
|
}
|
|
3084
3429
|
};
|
|
3085
|
-
|
|
3430
|
+
}
|
|
3431
|
+
function createAgentEventHandler(parameters) {
|
|
3432
|
+
const { repoState, sendEvent, sessionId, repoName, loopState } = parameters;
|
|
3433
|
+
return function onAgentEvent(event) {
|
|
3086
3434
|
if (event.type === "agent-session-started") {
|
|
3087
3435
|
repoState.agentStatus = "busy";
|
|
3088
3436
|
} else if (event.type === "agent-session-ended") {
|
|
@@ -3093,26 +3441,88 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3093
3441
|
appendLogLine(repoState.logBuffer, createLogLine(formatted, "stdout"));
|
|
3094
3442
|
}
|
|
3095
3443
|
if (sendEvent && sessionId) {
|
|
3096
|
-
sequence++;
|
|
3444
|
+
loopState.sequence++;
|
|
3097
3445
|
sendEvent(buildEventMessage({
|
|
3098
|
-
sequence,
|
|
3446
|
+
sequence: loopState.sequence,
|
|
3099
3447
|
sessionId,
|
|
3100
3448
|
repository: repoName,
|
|
3101
3449
|
repoId: repoState.repository.id,
|
|
3102
3450
|
event,
|
|
3103
|
-
agentSessionId
|
|
3451
|
+
agentSessionId: loopState.agentSessionId
|
|
3104
3452
|
}));
|
|
3105
3453
|
}
|
|
3106
3454
|
};
|
|
3455
|
+
}
|
|
3456
|
+
function createCancelHandler(abortController) {
|
|
3457
|
+
return abortController.abort.bind(abortController);
|
|
3458
|
+
}
|
|
3459
|
+
function setupFallbackTimeout(repoState, sleep, resolve, wakeUpForThisWait) {
|
|
3460
|
+
sleep(FALLBACK_TIMEOUT_MS).then(function onFallbackTimeout() {
|
|
3461
|
+
if (repoState.wakeUp === wakeUpForThisWait) {
|
|
3462
|
+
repoState.wakeUp = undefined;
|
|
3463
|
+
resolve();
|
|
3464
|
+
}
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3467
|
+
async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
3468
|
+
const { spawn, run: run3, fileSystem, sleep } = repoDeps;
|
|
3469
|
+
const repoName = repoState.repository.name;
|
|
3470
|
+
const settings = await loadSettings(repoState.path, fileSystem);
|
|
3471
|
+
const logCallbacks = createLogCallbacks(repoState.logBuffer);
|
|
3472
|
+
const commandDeps = {
|
|
3473
|
+
arguments: [],
|
|
3474
|
+
context: {
|
|
3475
|
+
cwd: repoState.path,
|
|
3476
|
+
stdout: logCallbacks.stdout,
|
|
3477
|
+
stderr: logCallbacks.stderr
|
|
3478
|
+
},
|
|
3479
|
+
fileSystem,
|
|
3480
|
+
globScanner: createNoOpGlobScanner(),
|
|
3481
|
+
settings
|
|
3482
|
+
};
|
|
3483
|
+
const loopState = {
|
|
3484
|
+
partialLine: "",
|
|
3485
|
+
sequence: 0,
|
|
3486
|
+
agentSessionId: undefined
|
|
3487
|
+
};
|
|
3488
|
+
const isCodex = repoState.repository.agentProvider === "codex";
|
|
3489
|
+
const agentType = isCodex ? "codex" : "claude";
|
|
3490
|
+
const createStdoutSink2 = () => createBufferStdoutSink(loopState, repoState.logBuffer);
|
|
3491
|
+
let bufferRun;
|
|
3492
|
+
if (isCodex) {
|
|
3493
|
+
const codexBufferSinkDeps = {
|
|
3494
|
+
...defaultRunnerDependencies2,
|
|
3495
|
+
createStdoutSink: createStdoutSink2
|
|
3496
|
+
};
|
|
3497
|
+
bufferRun = (prompt, options) => run2(prompt, options, codexBufferSinkDeps);
|
|
3498
|
+
} else {
|
|
3499
|
+
const bufferSinkDeps = {
|
|
3500
|
+
...defaultRunnerDependencies,
|
|
3501
|
+
createStdoutSink: createStdoutSink2
|
|
3502
|
+
};
|
|
3503
|
+
bufferRun = createBufferRun(run3, bufferSinkDeps);
|
|
3504
|
+
}
|
|
3505
|
+
const loopDeps = {
|
|
3506
|
+
spawn,
|
|
3507
|
+
run: bufferRun,
|
|
3508
|
+
sleep,
|
|
3509
|
+
postEvent: noOpPostEvent,
|
|
3510
|
+
agentType
|
|
3511
|
+
};
|
|
3512
|
+
const onLoopEvent = createLoopEventHandler(repoState.logBuffer);
|
|
3513
|
+
const onAgentEvent = createAgentEventHandler({
|
|
3514
|
+
repoState,
|
|
3515
|
+
sendEvent,
|
|
3516
|
+
sessionId,
|
|
3517
|
+
repoName,
|
|
3518
|
+
loopState
|
|
3519
|
+
});
|
|
3107
3520
|
const hooksInstalled = await manageGitHooks(commandDeps);
|
|
3108
|
-
|
|
3109
|
-
log2(`loop started for ${repoName} at ${repoState.path}`);
|
|
3521
|
+
log3(`loop started for ${repoName} at ${repoState.path}`);
|
|
3110
3522
|
while (!repoState.stopRequested) {
|
|
3111
|
-
agentSessionId = crypto.randomUUID();
|
|
3523
|
+
loopState.agentSessionId = crypto.randomUUID();
|
|
3112
3524
|
const abortController = new AbortController;
|
|
3113
|
-
const cancelCurrentIteration = ()
|
|
3114
|
-
abortController.abort();
|
|
3115
|
-
};
|
|
3525
|
+
const cancelCurrentIteration = createCancelHandler(abortController);
|
|
3116
3526
|
repoState.cancelCurrentIteration = cancelCurrentIteration;
|
|
3117
3527
|
let result;
|
|
3118
3528
|
try {
|
|
@@ -3124,7 +3534,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3124
3534
|
});
|
|
3125
3535
|
} catch (error) {
|
|
3126
3536
|
const msg = error instanceof Error ? error.message : String(error);
|
|
3127
|
-
|
|
3537
|
+
log3(`iteration error for ${repoName}: ${msg}`);
|
|
3128
3538
|
appendLogLine(repoState.logBuffer, createLogLine(`Loop error: ${msg}`, "stderr"));
|
|
3129
3539
|
await sleep(1e4);
|
|
3130
3540
|
continue;
|
|
@@ -3136,39 +3546,34 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3136
3546
|
if (result === "no_tasks") {
|
|
3137
3547
|
if (repoState.taskAvailablePending) {
|
|
3138
3548
|
repoState.taskAvailablePending = false;
|
|
3139
|
-
|
|
3140
|
-
|
|
3549
|
+
log3(`${repoName}: task signal received during iteration, rechecking`);
|
|
3550
|
+
appendLogLine(repoState.logBuffer, createLogLine("Task signal received during iteration, rechecking...", "stdout"));
|
|
3141
3551
|
continue;
|
|
3142
3552
|
}
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
await new Promise((resolve)
|
|
3553
|
+
log3(`${repoName}: no tasks available, waiting`);
|
|
3554
|
+
appendLogLine(repoState.logBuffer, createLogLine("Waiting for tasks...", "stdout"));
|
|
3555
|
+
await new Promise(function waitForTasks(resolve) {
|
|
3146
3556
|
const wakeUpForThisWait = createWakeUpHandler(repoState, resolve);
|
|
3147
3557
|
repoState.wakeUp = wakeUpForThisWait;
|
|
3148
|
-
|
|
3149
|
-
if (repoState.wakeUp === wakeUpForThisWait) {
|
|
3150
|
-
repoState.wakeUp = undefined;
|
|
3151
|
-
resolve();
|
|
3152
|
-
}
|
|
3153
|
-
});
|
|
3558
|
+
setupFallbackTimeout(repoState, sleep, resolve, wakeUpForThisWait);
|
|
3154
3559
|
});
|
|
3155
3560
|
}
|
|
3156
3561
|
}
|
|
3157
|
-
|
|
3562
|
+
log3(`loop stopped for ${repoName}`);
|
|
3158
3563
|
appendLogLine(repoState.logBuffer, createLogLine(`Stopped loop for ${repoName}`, "stdout"));
|
|
3159
3564
|
}
|
|
3160
3565
|
|
|
3161
3566
|
// lib/bucket/repository.ts
|
|
3162
|
-
var
|
|
3567
|
+
var log4 = createLogger("dust:bucket:repository");
|
|
3163
3568
|
function startRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
3164
|
-
|
|
3569
|
+
log4(`starting loop for ${repoState.repository.name}`);
|
|
3165
3570
|
repoState.stopRequested = false;
|
|
3166
3571
|
repoState.loopPromise = runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId).catch((error) => {
|
|
3167
3572
|
const message = error instanceof Error ? error.message : String(error);
|
|
3168
|
-
|
|
3573
|
+
log4(`loop crashed for ${repoState.repository.name}: ${message}`);
|
|
3169
3574
|
appendLogLine(repoState.logBuffer, createLogLine(`Repository loop crashed: ${message}`, "stderr"));
|
|
3170
3575
|
}).finally(() => {
|
|
3171
|
-
|
|
3576
|
+
log4(`loop finished for ${repoState.repository.name}`);
|
|
3172
3577
|
repoState.loopPromise = null;
|
|
3173
3578
|
repoState.agentStatus = "idle";
|
|
3174
3579
|
repoState.wakeUp = undefined;
|
|
@@ -3191,10 +3596,10 @@ function parseRepository(data) {
|
|
|
3191
3596
|
}
|
|
3192
3597
|
async function addRepository(repository, manager, repoDeps, context) {
|
|
3193
3598
|
if (manager.repositories.has(repository.name)) {
|
|
3194
|
-
|
|
3599
|
+
log4(`repository ${repository.name} already exists, skipping add`);
|
|
3195
3600
|
return;
|
|
3196
3601
|
}
|
|
3197
|
-
|
|
3602
|
+
log4(`adding repository ${repository.name}`);
|
|
3198
3603
|
const repoPath = getRepoPath(repository.name, repoDeps.getReposDir());
|
|
3199
3604
|
await repoDeps.fileSystem.mkdir(dirname2(repoPath), { recursive: true });
|
|
3200
3605
|
if (repoDeps.fileSystem.exists(repoPath)) {
|
|
@@ -3234,7 +3639,7 @@ async function removeRepositoryFromManager(repoName, manager, repoDeps, context)
|
|
|
3234
3639
|
if (!repoState) {
|
|
3235
3640
|
return;
|
|
3236
3641
|
}
|
|
3237
|
-
|
|
3642
|
+
log4(`removing repository ${repoName}`);
|
|
3238
3643
|
repoState.stopRequested = true;
|
|
3239
3644
|
repoState.cancelCurrentIteration?.();
|
|
3240
3645
|
repoState.wakeUp?.();
|
|
@@ -3292,13 +3697,20 @@ function parseServerMessage(data) {
|
|
|
3292
3697
|
if (typeof repo.id !== "number" || typeof repo.url !== "string" || typeof repo.hasTask !== "boolean") {
|
|
3293
3698
|
return null;
|
|
3294
3699
|
}
|
|
3295
|
-
|
|
3700
|
+
const item = {
|
|
3296
3701
|
id: repo.id,
|
|
3297
3702
|
name: repo.name,
|
|
3298
3703
|
gitUrl: repo.gitUrl,
|
|
3299
3704
|
url: repo.url,
|
|
3300
3705
|
hasTask: repo.hasTask
|
|
3301
|
-
}
|
|
3706
|
+
};
|
|
3707
|
+
if (typeof repo.gitSshUrl === "string") {
|
|
3708
|
+
item.gitSshUrl = repo.gitSshUrl;
|
|
3709
|
+
}
|
|
3710
|
+
if (typeof repo.agentProvider === "string") {
|
|
3711
|
+
item.agentProvider = repo.agentProvider;
|
|
3712
|
+
}
|
|
3713
|
+
repositories.push(item);
|
|
3302
3714
|
}
|
|
3303
3715
|
return { type: "repository-list", repositories };
|
|
3304
3716
|
}
|
|
@@ -3739,7 +4151,7 @@ function handleKeyInput(state, key, options) {
|
|
|
3739
4151
|
}
|
|
3740
4152
|
|
|
3741
4153
|
// lib/cli/commands/bucket.ts
|
|
3742
|
-
var
|
|
4154
|
+
var log5 = createLogger("dust:cli:commands:bucket");
|
|
3743
4155
|
var DEFAULT_DUSTBUCKET_WS_URL = "wss://dustbucket.com/agent/connect";
|
|
3744
4156
|
var INITIAL_RECONNECT_DELAY_MS = 1000;
|
|
3745
4157
|
var MAX_RECONNECT_DELAY_MS = 30000;
|
|
@@ -3799,27 +4211,27 @@ function defaultWriteStdout(data) {
|
|
|
3799
4211
|
}
|
|
3800
4212
|
function createAuthFileSystem(dependencies) {
|
|
3801
4213
|
return {
|
|
3802
|
-
exists: (
|
|
4214
|
+
exists: (path3) => {
|
|
3803
4215
|
try {
|
|
3804
|
-
dependencies.accessSync(
|
|
4216
|
+
dependencies.accessSync(path3);
|
|
3805
4217
|
return true;
|
|
3806
4218
|
} catch {
|
|
3807
4219
|
return false;
|
|
3808
4220
|
}
|
|
3809
4221
|
},
|
|
3810
|
-
isDirectory: (
|
|
4222
|
+
isDirectory: (path3) => {
|
|
3811
4223
|
try {
|
|
3812
|
-
return dependencies.statSync(
|
|
4224
|
+
return dependencies.statSync(path3).isDirectory();
|
|
3813
4225
|
} catch {
|
|
3814
4226
|
return false;
|
|
3815
4227
|
}
|
|
3816
4228
|
},
|
|
3817
|
-
getFileCreationTime: (
|
|
3818
|
-
readFile: (
|
|
3819
|
-
writeFile: (
|
|
3820
|
-
mkdir: (
|
|
3821
|
-
readdir: (
|
|
3822
|
-
chmod: (
|
|
4229
|
+
getFileCreationTime: (path3) => dependencies.statSync(path3).birthtimeMs,
|
|
4230
|
+
readFile: (path3) => dependencies.readFile(path3, "utf8"),
|
|
4231
|
+
writeFile: (path3, content) => dependencies.writeFile(path3, content, "utf8"),
|
|
4232
|
+
mkdir: (path3, options) => dependencies.mkdir(path3, options).then(() => {}),
|
|
4233
|
+
readdir: (path3) => dependencies.readdir(path3),
|
|
4234
|
+
chmod: (path3, mode) => dependencies.chmod(path3, mode),
|
|
3823
4235
|
rename: (oldPath, newPath) => dependencies.rename(oldPath, newPath)
|
|
3824
4236
|
};
|
|
3825
4237
|
}
|
|
@@ -3835,7 +4247,7 @@ function createDefaultBucketDependencies() {
|
|
|
3835
4247
|
rename: (oldPath, newPath) => import("node:fs/promises").then((mod) => mod.rename(oldPath, newPath))
|
|
3836
4248
|
});
|
|
3837
4249
|
return {
|
|
3838
|
-
spawn:
|
|
4250
|
+
spawn: nodeSpawn5,
|
|
3839
4251
|
createWebSocket: defaultCreateWebSocket,
|
|
3840
4252
|
setupKeypress: defaultSetupKeypress,
|
|
3841
4253
|
setupSignals: defaultSetupSignals,
|
|
@@ -3887,20 +4299,20 @@ function toRepositoryDependencies(bucketDeps, fileSystem) {
|
|
|
3887
4299
|
}
|
|
3888
4300
|
function ensureRepositoryLoopRunning(repoState, state, repoDeps, context, useTUI) {
|
|
3889
4301
|
if (repoState.loopPromise || repoState.wakeUp || repoState.stopRequested) {
|
|
3890
|
-
|
|
4302
|
+
log5(`loop already running/waiting for ${repoState.repository.name}`);
|
|
3891
4303
|
return;
|
|
3892
4304
|
}
|
|
3893
4305
|
logMessage(state, context, useTUI, `Repository loop not running for ${repoState.repository.name}; restarting`);
|
|
3894
4306
|
startRepositoryLoop(repoState, repoDeps, state.sendEvent, state.sessionId);
|
|
3895
4307
|
}
|
|
3896
4308
|
function signalTaskAvailable(repoState, state, repoDeps, context, useTUI) {
|
|
3897
|
-
|
|
4309
|
+
log5(`task-available signal for ${repoState.repository.name}`);
|
|
3898
4310
|
ensureRepositoryLoopRunning(repoState, state, repoDeps, context, useTUI);
|
|
3899
4311
|
if (repoState.wakeUp) {
|
|
3900
|
-
|
|
4312
|
+
log5(`waking loop for ${repoState.repository.name}`);
|
|
3901
4313
|
repoState.wakeUp();
|
|
3902
4314
|
} else {
|
|
3903
|
-
|
|
4315
|
+
log5(`marking task pending for ${repoState.repository.name} (loop busy)`);
|
|
3904
4316
|
repoState.taskAvailablePending = true;
|
|
3905
4317
|
}
|
|
3906
4318
|
}
|
|
@@ -4034,7 +4446,7 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
4034
4446
|
logMessage(state, context, useTUI, `Invalid WebSocket message format: ${event.data}`, "stderr");
|
|
4035
4447
|
return;
|
|
4036
4448
|
}
|
|
4037
|
-
|
|
4449
|
+
log5(`ws message: ${message.type}`);
|
|
4038
4450
|
if (message.type === "repository-list") {
|
|
4039
4451
|
const repos = message.repositories;
|
|
4040
4452
|
logMessage(state, context, useTUI, `Received repository list (${repos.length} repositories):`);
|
|
@@ -4084,7 +4496,7 @@ async function shutdown(state, bucketDeps, context) {
|
|
|
4084
4496
|
if (state.shuttingDown)
|
|
4085
4497
|
return;
|
|
4086
4498
|
state.shuttingDown = true;
|
|
4087
|
-
|
|
4499
|
+
log5("shutdown initiated");
|
|
4088
4500
|
context.stdout("Shutting down...");
|
|
4089
4501
|
if (state.reconnectTimer) {
|
|
4090
4502
|
clearTimeout(state.reconnectTimer);
|
|
@@ -4287,16 +4699,16 @@ function createDefaultUploadDependencies() {
|
|
|
4287
4699
|
getHomeDir: () => homedir2(),
|
|
4288
4700
|
fileSystem: authFileSystem
|
|
4289
4701
|
},
|
|
4290
|
-
readFileBytes: async (
|
|
4291
|
-
const buffer = await Bun.file(
|
|
4702
|
+
readFileBytes: async (path3) => {
|
|
4703
|
+
const buffer = await Bun.file(path3).arrayBuffer();
|
|
4292
4704
|
return new Uint8Array(buffer);
|
|
4293
4705
|
},
|
|
4294
|
-
getFileSize: async (
|
|
4295
|
-
const file = Bun.file(
|
|
4706
|
+
getFileSize: async (path3) => {
|
|
4707
|
+
const file = Bun.file(path3);
|
|
4296
4708
|
return file.size;
|
|
4297
4709
|
},
|
|
4298
|
-
fileExists: async (
|
|
4299
|
-
const file = Bun.file(
|
|
4710
|
+
fileExists: async (path3) => {
|
|
4711
|
+
const file = Bun.file(path3);
|
|
4300
4712
|
return file.exists();
|
|
4301
4713
|
},
|
|
4302
4714
|
uploadFile: async (url, token, fileBytes, contentType, fileName) => {
|
|
@@ -5090,12 +5502,12 @@ function validateNoCycles(allPrincipleRelationships) {
|
|
|
5090
5502
|
}
|
|
5091
5503
|
for (const rel of allPrincipleRelationships) {
|
|
5092
5504
|
const visited = new Set;
|
|
5093
|
-
const
|
|
5505
|
+
const path3 = [];
|
|
5094
5506
|
let current = rel.filePath;
|
|
5095
5507
|
while (current) {
|
|
5096
5508
|
if (visited.has(current)) {
|
|
5097
|
-
const cycleStart =
|
|
5098
|
-
const cyclePath =
|
|
5509
|
+
const cycleStart = path3.indexOf(current);
|
|
5510
|
+
const cyclePath = path3.slice(cycleStart).concat(current);
|
|
5099
5511
|
violations.push({
|
|
5100
5512
|
file: rel.filePath,
|
|
5101
5513
|
message: `Cycle detected in principle hierarchy: ${cyclePath.join(" -> ")}`
|
|
@@ -5103,7 +5515,7 @@ function validateNoCycles(allPrincipleRelationships) {
|
|
|
5103
5515
|
break;
|
|
5104
5516
|
}
|
|
5105
5517
|
visited.add(current);
|
|
5106
|
-
|
|
5518
|
+
path3.push(current);
|
|
5107
5519
|
const currentRel = relationshipMap.get(current);
|
|
5108
5520
|
if (currentRel && currentRel.parentPrinciples.length > 0) {
|
|
5109
5521
|
current = currentRel.parentPrinciples[0];
|
|
@@ -5305,7 +5717,7 @@ async function lintMarkdown(dependencies) {
|
|
|
5305
5717
|
}
|
|
5306
5718
|
|
|
5307
5719
|
// lib/cli/commands/check.ts
|
|
5308
|
-
var
|
|
5720
|
+
var log6 = createLogger("dust:cli:commands:check");
|
|
5309
5721
|
var DEFAULT_CHECK_TIMEOUT_MS = 13000;
|
|
5310
5722
|
var MAX_OUTPUT_LINES = 500;
|
|
5311
5723
|
var KEEP_LINES = 250;
|
|
@@ -5325,14 +5737,27 @@ function truncateOutput(output) {
|
|
|
5325
5737
|
].join(`
|
|
5326
5738
|
`);
|
|
5327
5739
|
}
|
|
5328
|
-
async function runSingleCheck(check, cwd, runner) {
|
|
5740
|
+
async function runSingleCheck(check, cwd, runner, emitEvent) {
|
|
5329
5741
|
const timeoutMs = check.timeoutMilliseconds ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
5330
|
-
|
|
5742
|
+
log6(`running check ${check.name}: ${check.command}`);
|
|
5743
|
+
emitEvent?.({ type: "check-started", name: check.name });
|
|
5331
5744
|
const startTime = Date.now();
|
|
5332
5745
|
const result = await runner.run(check.command, cwd, timeoutMs);
|
|
5333
5746
|
const durationMs = Date.now() - startTime;
|
|
5334
5747
|
const status = result.timedOut ? "timed out" : result.exitCode === 0 ? "passed" : "failed";
|
|
5335
|
-
|
|
5748
|
+
log6(`check ${check.name} ${status} (${durationMs}ms)`);
|
|
5749
|
+
if (result.exitCode === 0) {
|
|
5750
|
+
emitEvent?.({ type: "check-passed", name: check.name, durationMs });
|
|
5751
|
+
} else {
|
|
5752
|
+
const failedEvent = {
|
|
5753
|
+
type: "check-failed",
|
|
5754
|
+
name: check.name,
|
|
5755
|
+
durationMs
|
|
5756
|
+
};
|
|
5757
|
+
if (result.output)
|
|
5758
|
+
failedEvent.output = result.output;
|
|
5759
|
+
emitEvent?.(failedEvent);
|
|
5760
|
+
}
|
|
5336
5761
|
return {
|
|
5337
5762
|
name: check.name,
|
|
5338
5763
|
command: check.command,
|
|
@@ -5344,25 +5769,26 @@ async function runSingleCheck(check, cwd, runner) {
|
|
|
5344
5769
|
timeoutSeconds: timeoutMs / 1000
|
|
5345
5770
|
};
|
|
5346
5771
|
}
|
|
5347
|
-
async function runConfiguredChecks(checks, cwd, runner) {
|
|
5348
|
-
const promises = checks.map((check) => runSingleCheck(check, cwd, runner));
|
|
5772
|
+
async function runConfiguredChecks(checks, cwd, runner, emitEvent) {
|
|
5773
|
+
const promises = checks.map((check) => runSingleCheck(check, cwd, runner, emitEvent));
|
|
5349
5774
|
return Promise.all(promises);
|
|
5350
5775
|
}
|
|
5351
|
-
async function runConfiguredChecksSerially(checks, cwd, runner) {
|
|
5776
|
+
async function runConfiguredChecksSerially(checks, cwd, runner, emitEvent) {
|
|
5352
5777
|
const results = [];
|
|
5353
5778
|
for (const check of checks) {
|
|
5354
|
-
results.push(await runSingleCheck(check, cwd, runner));
|
|
5779
|
+
results.push(await runSingleCheck(check, cwd, runner, emitEvent));
|
|
5355
5780
|
}
|
|
5356
5781
|
return results;
|
|
5357
5782
|
}
|
|
5358
|
-
async function runValidationCheck(dependencies) {
|
|
5783
|
+
async function runValidationCheck(dependencies, emitEvent) {
|
|
5359
5784
|
const outputLines = [];
|
|
5360
5785
|
const bufferedContext = {
|
|
5361
5786
|
cwd: dependencies.context.cwd,
|
|
5362
5787
|
stdout: (msg) => outputLines.push(msg),
|
|
5363
5788
|
stderr: (msg) => outputLines.push(msg)
|
|
5364
5789
|
};
|
|
5365
|
-
|
|
5790
|
+
log6("running built-in check: dust lint");
|
|
5791
|
+
emitEvent?.({ type: "check-started", name: "lint" });
|
|
5366
5792
|
const startTime = Date.now();
|
|
5367
5793
|
const result = await lintMarkdown({
|
|
5368
5794
|
...dependencies,
|
|
@@ -5371,13 +5797,26 @@ async function runValidationCheck(dependencies) {
|
|
|
5371
5797
|
});
|
|
5372
5798
|
const durationMs = Date.now() - startTime;
|
|
5373
5799
|
const lintStatus = result.exitCode === 0 ? "passed" : "failed";
|
|
5374
|
-
|
|
5800
|
+
log6(`built-in check dust lint ${lintStatus} (${durationMs}ms)`);
|
|
5801
|
+
const output = outputLines.join(`
|
|
5802
|
+
`);
|
|
5803
|
+
if (result.exitCode === 0) {
|
|
5804
|
+
emitEvent?.({ type: "check-passed", name: "lint", durationMs });
|
|
5805
|
+
} else {
|
|
5806
|
+
const failedEvent = {
|
|
5807
|
+
type: "check-failed",
|
|
5808
|
+
name: "lint",
|
|
5809
|
+
durationMs
|
|
5810
|
+
};
|
|
5811
|
+
if (output)
|
|
5812
|
+
failedEvent.output = output;
|
|
5813
|
+
emitEvent?.(failedEvent);
|
|
5814
|
+
}
|
|
5375
5815
|
return {
|
|
5376
5816
|
name: "lint",
|
|
5377
5817
|
command: "dust lint",
|
|
5378
5818
|
exitCode: result.exitCode,
|
|
5379
|
-
output
|
|
5380
|
-
`),
|
|
5819
|
+
output,
|
|
5381
5820
|
isBuiltIn: true,
|
|
5382
5821
|
durationMs,
|
|
5383
5822
|
timedOut: false
|
|
@@ -5446,18 +5885,18 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
|
5446
5885
|
if (serial) {
|
|
5447
5886
|
const results2 = [];
|
|
5448
5887
|
if (hasDustDir) {
|
|
5449
|
-
results2.push(await runValidationCheck(dependencies));
|
|
5888
|
+
results2.push(await runValidationCheck(dependencies, context.emitEvent));
|
|
5450
5889
|
}
|
|
5451
|
-
const configuredResults = await runConfiguredChecksSerially(settings.checks, context.cwd, shellRunner);
|
|
5890
|
+
const configuredResults = await runConfiguredChecksSerially(settings.checks, context.cwd, shellRunner, context.emitEvent);
|
|
5452
5891
|
results2.push(...configuredResults);
|
|
5453
5892
|
const exitCode2 = displayResults(results2, context);
|
|
5454
5893
|
return { exitCode: exitCode2 };
|
|
5455
5894
|
}
|
|
5456
5895
|
const checkPromises = [];
|
|
5457
5896
|
if (hasDustDir) {
|
|
5458
|
-
checkPromises.push(runValidationCheck(dependencies));
|
|
5897
|
+
checkPromises.push(runValidationCheck(dependencies, context.emitEvent));
|
|
5459
5898
|
}
|
|
5460
|
-
checkPromises.push(runConfiguredChecks(settings.checks, context.cwd, shellRunner));
|
|
5899
|
+
checkPromises.push(runConfiguredChecks(settings.checks, context.cwd, shellRunner, context.emitEvent));
|
|
5461
5900
|
const promiseResults = await Promise.all(checkPromises);
|
|
5462
5901
|
const results = [];
|
|
5463
5902
|
for (const result of promiseResults) {
|
|
@@ -5638,6 +6077,16 @@ async function init(dependencies) {
|
|
|
5638
6077
|
|
|
5639
6078
|
// lib/cli/commands/list.ts
|
|
5640
6079
|
import { basename as basename2 } from "node:path";
|
|
6080
|
+
function workflowTypeToStatus(type) {
|
|
6081
|
+
switch (type) {
|
|
6082
|
+
case "refine":
|
|
6083
|
+
return "refining";
|
|
6084
|
+
case "decompose-idea":
|
|
6085
|
+
return "decomposing";
|
|
6086
|
+
case "shelve":
|
|
6087
|
+
return "shelving";
|
|
6088
|
+
}
|
|
6089
|
+
}
|
|
5641
6090
|
var VALID_TYPES = ["tasks", "ideas", "principles", "facts"];
|
|
5642
6091
|
var SECTION_HEADERS = {
|
|
5643
6092
|
tasks: "\uD83D\uDCCB Tasks",
|
|
@@ -5712,6 +6161,7 @@ async function list(dependencies) {
|
|
|
5712
6161
|
return { exitCode: 1 };
|
|
5713
6162
|
}
|
|
5714
6163
|
const specificTypeRequested = commandArguments.length > 0;
|
|
6164
|
+
const workflowTasks = typesToList.includes("ideas") && fileSystem.exists(dustPath) ? await findAllWorkflowTasks(fileSystem, dustPath) : null;
|
|
5715
6165
|
for (const type of typesToList) {
|
|
5716
6166
|
const dirPath = `${dustPath}/${type}`;
|
|
5717
6167
|
const dirExists = fileSystem.exists(dirPath);
|
|
@@ -5726,6 +6176,13 @@ async function list(dependencies) {
|
|
|
5726
6176
|
context.stdout(`No ${type} found.`);
|
|
5727
6177
|
context.stdout("");
|
|
5728
6178
|
}
|
|
6179
|
+
if (type === "facts") {
|
|
6180
|
+
context.emitEvent?.({ type: "facts-listed", facts: [] });
|
|
6181
|
+
} else if (type === "ideas") {
|
|
6182
|
+
context.emitEvent?.({ type: "ideas-listed", ideas: [] });
|
|
6183
|
+
} else if (type === "principles") {
|
|
6184
|
+
context.emitEvent?.({ type: "principles-listed", principles: [] });
|
|
6185
|
+
}
|
|
5729
6186
|
continue;
|
|
5730
6187
|
}
|
|
5731
6188
|
context.stdout(SECTION_HEADERS[type]);
|
|
@@ -5740,16 +6197,26 @@ async function list(dependencies) {
|
|
|
5740
6197
|
context.stdout("");
|
|
5741
6198
|
}
|
|
5742
6199
|
}
|
|
6200
|
+
const collectedItems = [];
|
|
5743
6201
|
for (const file of mdFiles) {
|
|
5744
6202
|
const filePath = `${dirPath}/${file}`;
|
|
5745
6203
|
const content = await fileSystem.readFile(filePath);
|
|
5746
6204
|
const title = extractTitle(content);
|
|
5747
6205
|
const openingSentence = extractOpeningSentence(content);
|
|
5748
6206
|
const relativePath = `.dust/${type}/${file}`;
|
|
6207
|
+
const slug = file.replace(".md", "");
|
|
6208
|
+
const displayTitle = title || slug;
|
|
6209
|
+
if (type === "ideas") {
|
|
6210
|
+
const workflowTask = workflowTasks?.workflowTasksByIdeaSlug.get(slug);
|
|
6211
|
+
const status = workflowTask ? workflowTypeToStatus(workflowTask.type) : "draft";
|
|
6212
|
+
collectedItems.push({ path: relativePath, title: displayTitle, status });
|
|
6213
|
+
} else if (type === "facts" || type === "principles") {
|
|
6214
|
+
collectedItems.push({ path: relativePath, title: displayTitle });
|
|
6215
|
+
}
|
|
5749
6216
|
if (title) {
|
|
5750
6217
|
context.stdout(`${colors.bold}# ${title}${colors.reset}`);
|
|
5751
6218
|
} else {
|
|
5752
|
-
context.stdout(`${colors.bold}# ${
|
|
6219
|
+
context.stdout(`${colors.bold}# ${slug}${colors.reset}`);
|
|
5753
6220
|
}
|
|
5754
6221
|
if (openingSentence) {
|
|
5755
6222
|
context.stdout(`${colors.dim}${openingSentence}${colors.reset}`);
|
|
@@ -5757,130 +6224,28 @@ async function list(dependencies) {
|
|
|
5757
6224
|
context.stdout(`${colors.cyan}→ ${relativePath}${colors.reset}`);
|
|
5758
6225
|
context.stdout("");
|
|
5759
6226
|
}
|
|
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
|
-
}
|
|
6227
|
+
if (type === "facts") {
|
|
6228
|
+
context.emitEvent?.({
|
|
6229
|
+
type: "facts-listed",
|
|
6230
|
+
facts: collectedItems.map((i) => ({ path: i.path, title: i.title }))
|
|
6231
|
+
});
|
|
6232
|
+
} else if (type === "ideas") {
|
|
6233
|
+
context.emitEvent?.({
|
|
6234
|
+
type: "ideas-listed",
|
|
6235
|
+
ideas: collectedItems.map((i) => ({
|
|
6236
|
+
path: i.path,
|
|
6237
|
+
title: i.title,
|
|
6238
|
+
status: i.status ?? "draft"
|
|
6239
|
+
}))
|
|
6240
|
+
});
|
|
6241
|
+
} else if (type === "principles") {
|
|
6242
|
+
context.emitEvent?.({
|
|
6243
|
+
type: "principles-listed",
|
|
6244
|
+
principles: collectedItems.map((i) => ({ path: i.path, title: i.title }))
|
|
6245
|
+
});
|
|
5867
6246
|
}
|
|
5868
6247
|
}
|
|
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);
|
|
6248
|
+
return { exitCode: 0 };
|
|
5884
6249
|
}
|
|
5885
6250
|
|
|
5886
6251
|
// lib/cli/commands/loop-codex.ts
|
|
@@ -6137,8 +6502,8 @@ function parseGitDiffNameStatus(output) {
|
|
|
6137
6502
|
const parts = line.split("\t");
|
|
6138
6503
|
if (parts.length >= 2) {
|
|
6139
6504
|
const statusChar = parts[0].charAt(0);
|
|
6140
|
-
const
|
|
6141
|
-
changes.push({ status: statusChar, path });
|
|
6505
|
+
const path3 = parts.length > 2 ? parts[2] : parts[1];
|
|
6506
|
+
changes.push({ status: statusChar, path: path3 });
|
|
6142
6507
|
}
|
|
6143
6508
|
}
|
|
6144
6509
|
return changes;
|
|
@@ -6199,12 +6564,12 @@ async function getUncommittedFiles(cwd, gitRunner) {
|
|
|
6199
6564
|
`).filter((line) => line.length > 0);
|
|
6200
6565
|
for (const line of lines) {
|
|
6201
6566
|
if (line.length > 3) {
|
|
6202
|
-
const
|
|
6203
|
-
const arrowIndex =
|
|
6567
|
+
const path3 = line.substring(3);
|
|
6568
|
+
const arrowIndex = path3.indexOf(" -> ");
|
|
6204
6569
|
if (arrowIndex !== -1) {
|
|
6205
|
-
files.push(
|
|
6570
|
+
files.push(path3.substring(arrowIndex + 4));
|
|
6206
6571
|
} else {
|
|
6207
|
-
files.push(
|
|
6572
|
+
files.push(path3);
|
|
6208
6573
|
}
|
|
6209
6574
|
}
|
|
6210
6575
|
}
|
|
@@ -6355,24 +6720,24 @@ async function main(options) {
|
|
|
6355
6720
|
function createFileSystem(primitives) {
|
|
6356
6721
|
return {
|
|
6357
6722
|
exists: primitives.existsSync,
|
|
6358
|
-
isDirectory: (
|
|
6723
|
+
isDirectory: (path3) => {
|
|
6359
6724
|
try {
|
|
6360
|
-
return primitives.statSync(
|
|
6725
|
+
return primitives.statSync(path3).isDirectory();
|
|
6361
6726
|
} catch {
|
|
6362
6727
|
return false;
|
|
6363
6728
|
}
|
|
6364
6729
|
},
|
|
6365
|
-
readFile: (
|
|
6366
|
-
writeFile: (
|
|
6730
|
+
readFile: (path3) => primitives.readFile(path3, "utf-8"),
|
|
6731
|
+
writeFile: (path3, content, options) => primitives.writeFile(path3, content, {
|
|
6367
6732
|
encoding: "utf-8",
|
|
6368
6733
|
flag: options?.flag
|
|
6369
6734
|
}),
|
|
6370
|
-
mkdir: async (
|
|
6371
|
-
await primitives.mkdir(
|
|
6735
|
+
mkdir: async (path3, options) => {
|
|
6736
|
+
await primitives.mkdir(path3, options);
|
|
6372
6737
|
},
|
|
6373
|
-
getFileCreationTime: (
|
|
6374
|
-
readdir: (
|
|
6375
|
-
chmod: (
|
|
6738
|
+
getFileCreationTime: (path3) => primitives.statSync(path3).birthtimeMs,
|
|
6739
|
+
readdir: (path3) => primitives.readdir(path3),
|
|
6740
|
+
chmod: (path3, mode) => primitives.chmod(path3, mode),
|
|
6376
6741
|
rename: (oldPath, newPath) => primitives.rename(oldPath, newPath)
|
|
6377
6742
|
};
|
|
6378
6743
|
}
|
|
@@ -6396,7 +6761,8 @@ async function wireEntry(fsPrimitives, processPrimitives, consolePrimitives) {
|
|
|
6396
6761
|
cwd: processPrimitives.cwd(),
|
|
6397
6762
|
stdout: consolePrimitives.log,
|
|
6398
6763
|
stdoutInline: consolePrimitives.write,
|
|
6399
|
-
stderr: consolePrimitives.error
|
|
6764
|
+
stderr: consolePrimitives.error,
|
|
6765
|
+
emitEvent: consolePrimitives.emitEvent
|
|
6400
6766
|
},
|
|
6401
6767
|
fileSystem,
|
|
6402
6768
|
glob,
|
|
@@ -6406,7 +6772,12 @@ async function wireEntry(fsPrimitives, processPrimitives, consolePrimitives) {
|
|
|
6406
6772
|
}
|
|
6407
6773
|
|
|
6408
6774
|
// lib/cli/run.ts
|
|
6409
|
-
|
|
6775
|
+
var eventsFd = process.env.DUST_EVENTS_FD ? Number.parseInt(process.env.DUST_EVENTS_FD, 10) : undefined;
|
|
6776
|
+
var emitEvent = eventsFd !== undefined && !Number.isNaN(eventsFd) ? createEventEmitter((message) => {
|
|
6777
|
+
writeSync(eventsFd, `${JSON.stringify(message)}
|
|
6778
|
+
`);
|
|
6779
|
+
}) : undefined;
|
|
6780
|
+
await wireEntry({ existsSync: existsSync2, statSync: statSync3, readFile: readFile3, writeFile: writeFile3, mkdir: mkdir3, readdir: readdir3, chmod: chmod3, rename }, {
|
|
6410
6781
|
argv: process.argv,
|
|
6411
6782
|
cwd: () => process.cwd(),
|
|
6412
6783
|
exit: (code) => {
|
|
@@ -6417,5 +6788,6 @@ await wireEntry({ existsSync, statSync: statSync3, readFile: readFile3, writeFil
|
|
|
6417
6788
|
write: (message) => {
|
|
6418
6789
|
process.stdout.write(message);
|
|
6419
6790
|
},
|
|
6420
|
-
error: console.error
|
|
6791
|
+
error: console.error,
|
|
6792
|
+
emitEvent
|
|
6421
6793
|
});
|