@joshski/dust 0.1.14 → 0.1.15
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/dust.js +152 -42
- package/package.json +1 -1
package/dist/dust.js
CHANGED
|
@@ -1121,7 +1121,8 @@ async function* spawnClaudeCode(prompt, options = {}, dependencies = defaultDepe
|
|
|
1121
1121
|
model,
|
|
1122
1122
|
systemPrompt,
|
|
1123
1123
|
sessionId,
|
|
1124
|
-
dangerouslySkipPermissions
|
|
1124
|
+
dangerouslySkipPermissions,
|
|
1125
|
+
env
|
|
1125
1126
|
} = options;
|
|
1126
1127
|
const claudeArguments = [
|
|
1127
1128
|
"-p",
|
|
@@ -1151,7 +1152,8 @@ async function* spawnClaudeCode(prompt, options = {}, dependencies = defaultDepe
|
|
|
1151
1152
|
}
|
|
1152
1153
|
const proc = dependencies.spawn("claude", claudeArguments, {
|
|
1153
1154
|
cwd,
|
|
1154
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1155
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1156
|
+
env: { ...process.env, ...env }
|
|
1155
1157
|
});
|
|
1156
1158
|
if (!proc.stdout) {
|
|
1157
1159
|
throw new Error("Failed to get stdout from claude process");
|
|
@@ -1563,11 +1565,71 @@ async function next(dependencies) {
|
|
|
1563
1565
|
}
|
|
1564
1566
|
|
|
1565
1567
|
// lib/cli/commands/loop.ts
|
|
1568
|
+
function formatEvent(event) {
|
|
1569
|
+
switch (event.type) {
|
|
1570
|
+
case "loop.warning":
|
|
1571
|
+
return "⚠️ WARNING: This command skips all permission checks. Only use in a sandbox environment!";
|
|
1572
|
+
case "loop.started":
|
|
1573
|
+
return `\uD83D\uDD04 Starting dust loop claude (max ${event.maxIterations} iterations)...`;
|
|
1574
|
+
case "loop.syncing":
|
|
1575
|
+
return "\uD83D\uDD04 Syncing with remote...";
|
|
1576
|
+
case "loop.sync_skipped":
|
|
1577
|
+
return `Note: git pull skipped (${event.reason})`;
|
|
1578
|
+
case "loop.checking_tasks":
|
|
1579
|
+
return "\uD83D\uDD0D Checking for available tasks...";
|
|
1580
|
+
case "loop.no_tasks":
|
|
1581
|
+
return "\uD83D\uDCA4 No tasks available. Sleeping...";
|
|
1582
|
+
case "loop.tasks_found":
|
|
1583
|
+
return "✨ Found task(s). \uD83E\uDD16 Starting Claude...";
|
|
1584
|
+
case "claude.started":
|
|
1585
|
+
return "\uD83E\uDD16 Claude session started";
|
|
1586
|
+
case "claude.ended":
|
|
1587
|
+
return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
|
|
1588
|
+
case "loop.iteration_complete":
|
|
1589
|
+
return `\uD83D\uDCCB Completed iteration ${event.iteration}/${event.maxIterations}`;
|
|
1590
|
+
case "loop.ended":
|
|
1591
|
+
return `\uD83C\uDFC1 Reached max iterations (${event.maxIterations}). Exiting.`;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
async function defaultPostEvent(url, payload) {
|
|
1595
|
+
await fetch(url, {
|
|
1596
|
+
method: "POST",
|
|
1597
|
+
headers: { "Content-Type": "application/json" },
|
|
1598
|
+
body: JSON.stringify(payload)
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1566
1601
|
function createDefaultDependencies() {
|
|
1567
1602
|
return {
|
|
1568
1603
|
spawn: nodeSpawn2,
|
|
1569
1604
|
run,
|
|
1570
|
-
sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms))
|
|
1605
|
+
sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)),
|
|
1606
|
+
postEvent: defaultPostEvent
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
function createEventPoster(eventsUrl, sessionId, postEvent, onError) {
|
|
1610
|
+
let sequence = 0;
|
|
1611
|
+
let currentAgentSessionId;
|
|
1612
|
+
return (event) => {
|
|
1613
|
+
if (!eventsUrl)
|
|
1614
|
+
return;
|
|
1615
|
+
sequence++;
|
|
1616
|
+
if (event.type === "claude.started") {
|
|
1617
|
+
currentAgentSessionId = crypto.randomUUID();
|
|
1618
|
+
}
|
|
1619
|
+
const payload = {
|
|
1620
|
+
sequence,
|
|
1621
|
+
timestamp: new Date().toISOString(),
|
|
1622
|
+
sessionId,
|
|
1623
|
+
event
|
|
1624
|
+
};
|
|
1625
|
+
if (event.type.startsWith("claude.") && currentAgentSessionId) {
|
|
1626
|
+
payload.agentSessionId = currentAgentSessionId;
|
|
1627
|
+
payload.agentType = "claude";
|
|
1628
|
+
}
|
|
1629
|
+
postEvent(eventsUrl, payload).catch(onError);
|
|
1630
|
+
if (event.type === "claude.ended") {
|
|
1631
|
+
currentAgentSessionId = undefined;
|
|
1632
|
+
}
|
|
1571
1633
|
};
|
|
1572
1634
|
}
|
|
1573
1635
|
var SLEEP_INTERVAL_MS = 30000;
|
|
@@ -1605,16 +1667,17 @@ async function hasAvailableTasks(dependencies) {
|
|
|
1605
1667
|
await next({ ...dependencies, context: captureContext });
|
|
1606
1668
|
return hasOutput;
|
|
1607
1669
|
}
|
|
1608
|
-
async function runOneIteration(dependencies, loopDependencies) {
|
|
1670
|
+
async function runOneIteration(dependencies, loopDependencies, emit) {
|
|
1609
1671
|
const { context } = dependencies;
|
|
1610
1672
|
const { spawn: spawn2, run: run2 } = loopDependencies;
|
|
1611
|
-
|
|
1673
|
+
emit({ type: "loop.syncing" });
|
|
1612
1674
|
const pullResult = await gitPull(context.cwd, spawn2);
|
|
1613
1675
|
if (!pullResult.success) {
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1676
|
+
emit({
|
|
1677
|
+
type: "loop.sync_skipped",
|
|
1678
|
+
reason: pullResult.message ?? "unknown error"
|
|
1679
|
+
});
|
|
1680
|
+
emit({ type: "claude.started" });
|
|
1618
1681
|
const prompt = `git pull failed with the following error:
|
|
1619
1682
|
|
|
1620
1683
|
${pullResult.message}
|
|
@@ -1626,42 +1689,39 @@ Please resolve this issue. Common approaches:
|
|
|
1626
1689
|
|
|
1627
1690
|
Make sure the repository is in a clean state and synced with remote before finishing.`;
|
|
1628
1691
|
try {
|
|
1629
|
-
await run2(prompt, {
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1692
|
+
await run2(prompt, {
|
|
1693
|
+
cwd: context.cwd,
|
|
1694
|
+
dangerouslySkipPermissions: true,
|
|
1695
|
+
env: { DUST_UNATTENDED: "1" }
|
|
1696
|
+
});
|
|
1697
|
+
emit({ type: "claude.ended", success: true });
|
|
1633
1698
|
return "resolved_pull_conflict";
|
|
1634
1699
|
} catch (error) {
|
|
1635
|
-
const
|
|
1636
|
-
context.stderr(`Claude failed to resolve git pull conflict: ${
|
|
1637
|
-
|
|
1638
|
-
context.stdout("⚠️ Continuing loop despite unresolved conflict...");
|
|
1639
|
-
context.stdout("");
|
|
1700
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1701
|
+
context.stderr(`Claude failed to resolve git pull conflict: ${errorMessage}`);
|
|
1702
|
+
emit({ type: "claude.ended", success: false, error: errorMessage });
|
|
1640
1703
|
}
|
|
1641
1704
|
}
|
|
1642
|
-
|
|
1705
|
+
emit({ type: "loop.checking_tasks" });
|
|
1643
1706
|
const hasTasks = await hasAvailableTasks(dependencies);
|
|
1644
1707
|
if (!hasTasks) {
|
|
1645
|
-
|
|
1646
|
-
context.stdout("");
|
|
1708
|
+
emit({ type: "loop.no_tasks" });
|
|
1647
1709
|
return "no_tasks";
|
|
1648
1710
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
context.stdout("\uD83E\uDD16 Starting Claude...");
|
|
1652
|
-
context.stdout("");
|
|
1711
|
+
emit({ type: "loop.tasks_found" });
|
|
1712
|
+
emit({ type: "claude.started" });
|
|
1653
1713
|
try {
|
|
1654
|
-
await run2("go", {
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1714
|
+
await run2("go", {
|
|
1715
|
+
cwd: context.cwd,
|
|
1716
|
+
dangerouslySkipPermissions: true,
|
|
1717
|
+
env: { DUST_UNATTENDED: "1" }
|
|
1718
|
+
});
|
|
1719
|
+
emit({ type: "claude.ended", success: true });
|
|
1658
1720
|
return "ran_claude";
|
|
1659
1721
|
} catch (error) {
|
|
1660
|
-
const
|
|
1661
|
-
context.stderr(`Claude exited with error: ${
|
|
1662
|
-
|
|
1663
|
-
context.stdout("✅ Claude session complete. Continuing loop...");
|
|
1664
|
-
context.stdout("");
|
|
1722
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1723
|
+
context.stderr(`Claude exited with error: ${errorMessage}`);
|
|
1724
|
+
emit({ type: "claude.ended", success: false, error: errorMessage });
|
|
1665
1725
|
return "claude_error";
|
|
1666
1726
|
}
|
|
1667
1727
|
}
|
|
@@ -1676,25 +1736,38 @@ function parseMaxIterations(commandArguments) {
|
|
|
1676
1736
|
return parsed;
|
|
1677
1737
|
}
|
|
1678
1738
|
async function loopClaude(dependencies, loopDependencies = createDefaultDependencies()) {
|
|
1679
|
-
const { context } = dependencies;
|
|
1739
|
+
const { context, settings } = dependencies;
|
|
1740
|
+
const { postEvent } = loopDependencies;
|
|
1680
1741
|
const maxIterations = parseMaxIterations(dependencies.arguments);
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1742
|
+
const eventsUrl = settings.eventsUrl;
|
|
1743
|
+
const sessionId = crypto.randomUUID();
|
|
1744
|
+
const postTypedEvent = createEventPoster(eventsUrl, sessionId, postEvent, (error) => {
|
|
1745
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1746
|
+
context.stderr(`Event POST failed: ${message}`);
|
|
1747
|
+
});
|
|
1748
|
+
const emit = (event) => {
|
|
1749
|
+
context.stdout(formatEvent(event));
|
|
1750
|
+
postTypedEvent(event);
|
|
1751
|
+
};
|
|
1752
|
+
emit({ type: "loop.warning" });
|
|
1753
|
+
emit({ type: "loop.started", maxIterations });
|
|
1684
1754
|
context.stdout(" Press Ctrl+C to stop");
|
|
1685
1755
|
context.stdout("");
|
|
1686
1756
|
let completedIterations = 0;
|
|
1687
1757
|
while (completedIterations < maxIterations) {
|
|
1688
|
-
const result = await runOneIteration(dependencies, loopDependencies);
|
|
1758
|
+
const result = await runOneIteration(dependencies, loopDependencies, emit);
|
|
1689
1759
|
if (result === "no_tasks") {
|
|
1690
1760
|
await loopDependencies.sleep(SLEEP_INTERVAL_MS);
|
|
1691
1761
|
} else {
|
|
1692
1762
|
completedIterations++;
|
|
1693
|
-
|
|
1694
|
-
|
|
1763
|
+
emit({
|
|
1764
|
+
type: "loop.iteration_complete",
|
|
1765
|
+
iteration: completedIterations,
|
|
1766
|
+
maxIterations
|
|
1767
|
+
});
|
|
1695
1768
|
}
|
|
1696
1769
|
}
|
|
1697
|
-
|
|
1770
|
+
emit({ type: "loop.ended", maxIterations });
|
|
1698
1771
|
return { exitCode: 0 };
|
|
1699
1772
|
}
|
|
1700
1773
|
|
|
@@ -1795,8 +1868,45 @@ async function getChangesFromRemote(cwd, gitRunner) {
|
|
|
1795
1868
|
}
|
|
1796
1869
|
return parseGitDiffNameStatus(diffResult.output);
|
|
1797
1870
|
}
|
|
1871
|
+
async function getUncommittedFiles(cwd, gitRunner) {
|
|
1872
|
+
const result = await gitRunner.run(["status", "--porcelain"], cwd);
|
|
1873
|
+
if (result.exitCode !== 0 || !result.output.trim()) {
|
|
1874
|
+
return [];
|
|
1875
|
+
}
|
|
1876
|
+
const files = [];
|
|
1877
|
+
const lines = result.output.split(`
|
|
1878
|
+
`).filter((line) => line.length > 0);
|
|
1879
|
+
for (const line of lines) {
|
|
1880
|
+
if (line.length > 3) {
|
|
1881
|
+
const path = line.substring(3);
|
|
1882
|
+
const arrowIndex = path.indexOf(" -> ");
|
|
1883
|
+
if (arrowIndex !== -1) {
|
|
1884
|
+
files.push(path.substring(arrowIndex + 4));
|
|
1885
|
+
} else {
|
|
1886
|
+
files.push(path);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
return files;
|
|
1891
|
+
}
|
|
1798
1892
|
async function prePush(dependencies, gitRunner = defaultGitRunner, env = process.env) {
|
|
1799
1893
|
const { context } = dependencies;
|
|
1894
|
+
if (env.DUST_UNATTENDED) {
|
|
1895
|
+
const uncommittedFiles = await getUncommittedFiles(context.cwd, gitRunner);
|
|
1896
|
+
if (uncommittedFiles.length > 0) {
|
|
1897
|
+
context.stderr("");
|
|
1898
|
+
context.stderr("⚠️ Push blocked: uncommitted changes detected in unattended mode.");
|
|
1899
|
+
context.stderr("");
|
|
1900
|
+
context.stderr("You are running in unattended mode (DUST_UNATTENDED=1) and have uncommitted files:");
|
|
1901
|
+
for (const file of uncommittedFiles) {
|
|
1902
|
+
context.stderr(` → ${file}`);
|
|
1903
|
+
}
|
|
1904
|
+
context.stderr("");
|
|
1905
|
+
context.stderr("Commit or discard these changes before pushing to avoid broken builds.");
|
|
1906
|
+
context.stderr("");
|
|
1907
|
+
return { exitCode: 1 };
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1800
1910
|
const changes = await getChangesFromRemote(context.cwd, gitRunner);
|
|
1801
1911
|
if (changes.length > 0) {
|
|
1802
1912
|
const analysis = analyzeChangesForTaskOnlyPattern(changes);
|