@joshski/dust 0.1.40 → 0.1.41
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/agent-events.d.ts +41 -0
- package/dist/dust.js +60 -24
- package/dist/ideas.d.ts +25 -0
- package/dist/markdown/markdown-utilities.d.ts +23 -0
- package/dist/types.d.ts +9 -0
- package/package.json +4 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared agent event types for the dust event protocol.
|
|
3
|
+
*
|
|
4
|
+
* These types define the transport-agnostic event format used by both
|
|
5
|
+
* the HTTP (loop) and WebSocket (bucket) paths.
|
|
6
|
+
*/
|
|
7
|
+
export type AgentSessionEvent = {
|
|
8
|
+
type: 'agent-session-started';
|
|
9
|
+
title: string;
|
|
10
|
+
prompt: string;
|
|
11
|
+
agentType: string;
|
|
12
|
+
purpose: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'agent-session-ended';
|
|
15
|
+
success: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'agent-session-activity';
|
|
19
|
+
} | {
|
|
20
|
+
type: 'claude-event';
|
|
21
|
+
rawEvent: Record<string, unknown>;
|
|
22
|
+
};
|
|
23
|
+
export interface EventMessage {
|
|
24
|
+
sequence: number;
|
|
25
|
+
timestamp: string;
|
|
26
|
+
sessionId: string;
|
|
27
|
+
repository: string;
|
|
28
|
+
agentSessionId?: string;
|
|
29
|
+
event: AgentSessionEvent;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convert a raw Claude streaming event to an AgentSessionEvent.
|
|
33
|
+
* stream_event types become activity heartbeats; everything else
|
|
34
|
+
* is forwarded as a claude-event.
|
|
35
|
+
*/
|
|
36
|
+
export declare function rawEventToAgentEvent(rawEvent: Record<string, unknown>): AgentSessionEvent;
|
|
37
|
+
/**
|
|
38
|
+
* Format an AgentSessionEvent for console output.
|
|
39
|
+
* Returns null for events that should not be displayed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function formatAgentEvent(event: AgentSessionEvent): string | null;
|
package/dist/dust.js
CHANGED
|
@@ -336,8 +336,12 @@ async function manageGitHooks(dependencies) {
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
// lib/cli/commands/agent.ts
|
|
339
|
-
async function agent(dependencies) {
|
|
339
|
+
async function agent(dependencies, env = process.env) {
|
|
340
340
|
const { context, fileSystem, settings } = dependencies;
|
|
341
|
+
if (env.DUST_SKIP_AGENT === "1") {
|
|
342
|
+
context.stdout("You're running in an automated loop - proceeding to implement the assigned task.");
|
|
343
|
+
return { exitCode: 0 };
|
|
344
|
+
}
|
|
341
345
|
const hooksInstalled = await manageGitHooks(dependencies);
|
|
342
346
|
const vars = await templateVariablesWithInstructions(context.cwd, fileSystem, settings, hooksInstalled);
|
|
343
347
|
context.stdout(loadTemplate("agent-greeting", vars));
|
|
@@ -1117,6 +1121,7 @@ import { spawn as nodeSpawn2 } from "node:child_process";
|
|
|
1117
1121
|
function buildImplementationInstructions(bin, hooksInstalled) {
|
|
1118
1122
|
const steps = [];
|
|
1119
1123
|
let step = 1;
|
|
1124
|
+
steps.push(`Note: Skip the \`${bin} agent\` step - your task has already been specified below.`, "");
|
|
1120
1125
|
steps.push(`${step}. Run \`${bin} check\` to verify the project is in a good state`);
|
|
1121
1126
|
step++;
|
|
1122
1127
|
steps.push(`${step}. Implement the task`);
|
|
@@ -1251,8 +1256,6 @@ function formatLoopEvent(event) {
|
|
|
1251
1256
|
return `\uD83D\uDCCB Completed iteration ${event.iteration}/${event.maxIterations}`;
|
|
1252
1257
|
case "loop.ended":
|
|
1253
1258
|
return `\uD83C\uDFC1 Reached max iterations (${event.maxIterations}). Exiting.`;
|
|
1254
|
-
case "loop.start_agent":
|
|
1255
|
-
return null;
|
|
1256
1259
|
}
|
|
1257
1260
|
}
|
|
1258
1261
|
async function defaultPostEvent(url, payload) {
|
|
@@ -1330,11 +1333,9 @@ async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAg
|
|
|
1330
1333
|
type: "loop.sync_skipped",
|
|
1331
1334
|
reason: pullResult.message
|
|
1332
1335
|
});
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
});
|
|
1337
|
-
const prompt2 = `git pull failed with the following error:
|
|
1336
|
+
const prompt2 = `Note: Skip the \`dust agent\` step - your task has already been specified below.
|
|
1337
|
+
|
|
1338
|
+
git pull failed with the following error:
|
|
1338
1339
|
|
|
1339
1340
|
${pullResult.message}
|
|
1340
1341
|
|
|
@@ -1344,13 +1345,19 @@ Please resolve this issue. Common approaches:
|
|
|
1344
1345
|
3. After resolving, commit any changes and push to remote
|
|
1345
1346
|
|
|
1346
1347
|
Make sure the repository is in a clean state and synced with remote before finishing.`;
|
|
1347
|
-
|
|
1348
|
+
onAgentEvent?.({
|
|
1349
|
+
type: "agent-session-started",
|
|
1350
|
+
title: "Resolving git conflict",
|
|
1351
|
+
prompt: prompt2,
|
|
1352
|
+
agentType: "claude",
|
|
1353
|
+
purpose: "git-conflict"
|
|
1354
|
+
});
|
|
1348
1355
|
try {
|
|
1349
1356
|
await run2(prompt2, {
|
|
1350
1357
|
spawnOptions: {
|
|
1351
1358
|
cwd: context.cwd,
|
|
1352
1359
|
dangerouslySkipPermissions: true,
|
|
1353
|
-
env: { DUST_UNATTENDED: "1" }
|
|
1360
|
+
env: { DUST_UNATTENDED: "1", DUST_SKIP_AGENT: "1" }
|
|
1354
1361
|
},
|
|
1355
1362
|
onRawEvent
|
|
1356
1363
|
});
|
|
@@ -1374,10 +1381,6 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
1374
1381
|
}
|
|
1375
1382
|
const task = tasks[0];
|
|
1376
1383
|
onLoopEvent({ type: "loop.tasks_found" });
|
|
1377
|
-
onAgentEvent?.({
|
|
1378
|
-
type: "agent-session-started",
|
|
1379
|
-
title: task.title ?? task.path
|
|
1380
|
-
});
|
|
1381
1384
|
const taskContent = await dependencies.fileSystem.readFile(`${dependencies.context.cwd}/${task.path}`);
|
|
1382
1385
|
const { dustCommand, installCommand = "npm install" } = dependencies.settings;
|
|
1383
1386
|
const instructions = buildImplementationInstructions(dustCommand, true);
|
|
@@ -1394,13 +1397,19 @@ When the task is complete, delete the task file \`${task.path}\`.
|
|
|
1394
1397
|
## Instructions
|
|
1395
1398
|
|
|
1396
1399
|
${instructions}`;
|
|
1397
|
-
|
|
1400
|
+
onAgentEvent?.({
|
|
1401
|
+
type: "agent-session-started",
|
|
1402
|
+
title: task.title ?? task.path,
|
|
1403
|
+
prompt,
|
|
1404
|
+
agentType: "claude",
|
|
1405
|
+
purpose: "task"
|
|
1406
|
+
});
|
|
1398
1407
|
try {
|
|
1399
1408
|
await run2(prompt, {
|
|
1400
1409
|
spawnOptions: {
|
|
1401
1410
|
cwd: context.cwd,
|
|
1402
1411
|
dangerouslySkipPermissions: true,
|
|
1403
|
-
env: { DUST_UNATTENDED: "1" }
|
|
1412
|
+
env: { DUST_UNATTENDED: "1", DUST_SKIP_AGENT: "1" }
|
|
1404
1413
|
},
|
|
1405
1414
|
onRawEvent
|
|
1406
1415
|
});
|
|
@@ -1489,7 +1498,14 @@ function parseRepository(data) {
|
|
|
1489
1498
|
if (typeof data === "object" && data !== null && "name" in data && "gitUrl" in data) {
|
|
1490
1499
|
const repositoryData = data;
|
|
1491
1500
|
if (typeof repositoryData.name === "string" && typeof repositoryData.gitUrl === "string") {
|
|
1492
|
-
|
|
1501
|
+
const repo = {
|
|
1502
|
+
name: repositoryData.name,
|
|
1503
|
+
gitUrl: repositoryData.gitUrl
|
|
1504
|
+
};
|
|
1505
|
+
if (typeof repositoryData.url === "string") {
|
|
1506
|
+
repo.url = repositoryData.url;
|
|
1507
|
+
}
|
|
1508
|
+
return repo;
|
|
1493
1509
|
}
|
|
1494
1510
|
}
|
|
1495
1511
|
return null;
|
|
@@ -1790,6 +1806,7 @@ function createTerminalUIState() {
|
|
|
1790
1806
|
selectedIndex: -1,
|
|
1791
1807
|
logBuffers: new Map,
|
|
1792
1808
|
agentStatuses: new Map,
|
|
1809
|
+
repositoryUrls: new Map,
|
|
1793
1810
|
scrollOffset: 0,
|
|
1794
1811
|
autoScroll: true,
|
|
1795
1812
|
width: 80,
|
|
@@ -1801,7 +1818,7 @@ function updateDimensions(state, width, height) {
|
|
|
1801
1818
|
state.width = width;
|
|
1802
1819
|
state.height = height;
|
|
1803
1820
|
}
|
|
1804
|
-
function addRepository2(state, name, logBuffer) {
|
|
1821
|
+
function addRepository2(state, name, logBuffer, url) {
|
|
1805
1822
|
if (!state.repositories.includes(name)) {
|
|
1806
1823
|
state.repositories.push(name);
|
|
1807
1824
|
state.repositories.sort((a, b) => {
|
|
@@ -1814,6 +1831,9 @@ function addRepository2(state, name, logBuffer) {
|
|
|
1814
1831
|
state.agentStatuses.set(name, "idle");
|
|
1815
1832
|
}
|
|
1816
1833
|
state.logBuffers.set(name, logBuffer);
|
|
1834
|
+
if (url) {
|
|
1835
|
+
state.repositoryUrls.set(name, url);
|
|
1836
|
+
}
|
|
1817
1837
|
}
|
|
1818
1838
|
function removeRepository2(state, name) {
|
|
1819
1839
|
const index = state.repositories.indexOf(name);
|
|
@@ -1821,6 +1841,7 @@ function removeRepository2(state, name) {
|
|
|
1821
1841
|
state.repositories.splice(index, 1);
|
|
1822
1842
|
state.logBuffers.delete(name);
|
|
1823
1843
|
state.agentStatuses.delete(name);
|
|
1844
|
+
state.repositoryUrls.delete(name);
|
|
1824
1845
|
if (state.selectedIndex >= state.repositories.length) {
|
|
1825
1846
|
state.selectedIndex = state.repositories.length - 1;
|
|
1826
1847
|
}
|
|
@@ -1967,7 +1988,7 @@ function renderTabs(state) {
|
|
|
1967
1988
|
return rows.map((row) => row.map((t) => t.text).join("|"));
|
|
1968
1989
|
}
|
|
1969
1990
|
function renderHelpLine() {
|
|
1970
|
-
return `${ANSI.DIM}[←→] select [↑↓] scroll [PgUp/PgDn] page [g/G] top/bottom [q] quit${ANSI.RESET}`;
|
|
1991
|
+
return `${ANSI.DIM}[←→] select [↑↓] scroll [PgUp/PgDn] page [g/G] top/bottom [o] open [q] quit${ANSI.RESET}`;
|
|
1971
1992
|
}
|
|
1972
1993
|
function renderSeparator(width) {
|
|
1973
1994
|
return "─".repeat(width);
|
|
@@ -2050,7 +2071,7 @@ function parseSGRMouse(key) {
|
|
|
2050
2071
|
return null;
|
|
2051
2072
|
return Number.parseInt(match[1], 10);
|
|
2052
2073
|
}
|
|
2053
|
-
function handleKeyInput(state, key) {
|
|
2074
|
+
function handleKeyInput(state, key, options) {
|
|
2054
2075
|
const mouseButton = parseSGRMouse(key);
|
|
2055
2076
|
if (mouseButton !== null) {
|
|
2056
2077
|
if (mouseButton === 64) {
|
|
@@ -2094,6 +2115,19 @@ function handleKeyInput(state, key) {
|
|
|
2094
2115
|
case KEYS.END:
|
|
2095
2116
|
scrollToBottom(state);
|
|
2096
2117
|
break;
|
|
2118
|
+
case "o": {
|
|
2119
|
+
if (state.selectedIndex === -1) {
|
|
2120
|
+
break;
|
|
2121
|
+
}
|
|
2122
|
+
const repoName = state.repositories[state.selectedIndex];
|
|
2123
|
+
if (!repoName)
|
|
2124
|
+
break;
|
|
2125
|
+
const url = state.repositoryUrls.get(repoName);
|
|
2126
|
+
if (url && options?.openBrowser) {
|
|
2127
|
+
options.openBrowser(url);
|
|
2128
|
+
}
|
|
2129
|
+
break;
|
|
2130
|
+
}
|
|
2097
2131
|
}
|
|
2098
2132
|
return false;
|
|
2099
2133
|
}
|
|
@@ -2265,7 +2299,9 @@ function syncUIWithRepoList(state, repos) {
|
|
|
2265
2299
|
buffer = createLogBuffer();
|
|
2266
2300
|
state.logBuffers.set(repo.name, buffer);
|
|
2267
2301
|
}
|
|
2268
|
-
addRepository2(state.ui, repo.name, buffer);
|
|
2302
|
+
addRepository2(state.ui, repo.name, buffer, repo.url);
|
|
2303
|
+
} else if (repo.url) {
|
|
2304
|
+
state.ui.repositoryUrls.set(repo.name, repo.url);
|
|
2269
2305
|
}
|
|
2270
2306
|
}
|
|
2271
2307
|
}
|
|
@@ -2429,10 +2465,10 @@ function setupTUI(state, bucketDeps) {
|
|
|
2429
2465
|
}
|
|
2430
2466
|
};
|
|
2431
2467
|
}
|
|
2432
|
-
function createKeypressHandler(useTUI, state, onQuit) {
|
|
2468
|
+
function createKeypressHandler(useTUI, state, onQuit, options) {
|
|
2433
2469
|
if (useTUI) {
|
|
2434
2470
|
return (key) => {
|
|
2435
|
-
const shouldQuit = handleKeyInput(state.ui, key);
|
|
2471
|
+
const shouldQuit = handleKeyInput(state.ui, key, options);
|
|
2436
2472
|
if (shouldQuit)
|
|
2437
2473
|
onQuit();
|
|
2438
2474
|
};
|
|
@@ -2504,7 +2540,7 @@ async function bucket(dependencies, bucketDeps = createDefaultBucketDependencies
|
|
|
2504
2540
|
};
|
|
2505
2541
|
const onKey = createKeypressHandler(useTUI, state, () => {
|
|
2506
2542
|
doShutdown();
|
|
2507
|
-
});
|
|
2543
|
+
}, { openBrowser: bucketDeps.auth.openBrowser });
|
|
2508
2544
|
cleanupKeypress = bucketDeps.setupKeypress(onKey);
|
|
2509
2545
|
cleanupSignals = bucketDeps.setupSignals(() => {
|
|
2510
2546
|
doShutdown();
|
package/dist/ideas.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FileSystem } from './cli/types';
|
|
2
|
+
export interface IdeaOption {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
}
|
|
6
|
+
export interface IdeaOpenQuestion {
|
|
7
|
+
question: string;
|
|
8
|
+
options: IdeaOption[];
|
|
9
|
+
}
|
|
10
|
+
export interface Idea {
|
|
11
|
+
slug: string;
|
|
12
|
+
title: string;
|
|
13
|
+
openingSentence: string | null;
|
|
14
|
+
content: string;
|
|
15
|
+
openQuestions: IdeaOpenQuestion[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parses the ## Open Questions section from idea markdown content.
|
|
19
|
+
* Extracts each ### question heading and its #### option children.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseOpenQuestions(content: string): IdeaOpenQuestion[];
|
|
22
|
+
/**
|
|
23
|
+
* Parses an idea markdown file into a structured Idea object.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseIdea(fileSystem: FileSystem, dustPath: string, slug: string): Promise<Idea>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared markdown utilities for dust CLI commands
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Extracts the title from markdown content (first H1 heading)
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractTitle(content: string): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Pattern for matching markdown links: [text](url)
|
|
10
|
+
* Note: Create a new RegExp with 'g' flag for global matching:
|
|
11
|
+
* `new RegExp(MARKDOWN_LINK_PATTERN.source, 'g')`
|
|
12
|
+
*/
|
|
13
|
+
export declare const MARKDOWN_LINK_PATTERN: RegExp;
|
|
14
|
+
/**
|
|
15
|
+
* Extracts the first sentence from the first paragraph after the H1 heading.
|
|
16
|
+
* Returns null if no valid opening paragraph exists.
|
|
17
|
+
*
|
|
18
|
+
* A valid opening paragraph:
|
|
19
|
+
* - Appears on the first non-blank line after the H1 heading
|
|
20
|
+
* - Is a plain paragraph (not a heading, list item, or code block)
|
|
21
|
+
* - Starts with a sentence that ends in `.` `?` or `!`
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractOpeningSentence(content: string): string | null;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public type definitions for downstream consumers of @joshski/dust.
|
|
3
|
+
*
|
|
4
|
+
* Import from '@joshski/dust/types' to get typed bindings for
|
|
5
|
+
* the event protocol, workflow tasks, and idea structures.
|
|
6
|
+
*/
|
|
7
|
+
export type { AgentSessionEvent, EventMessage } from './agent-events';
|
|
8
|
+
export type { Idea, IdeaOpenQuestion, IdeaOption } from './ideas';
|
|
9
|
+
export type { CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, IdeaInProgress, OpenQuestionResponse, ParsedCaptureIdeaTask, WorkflowTaskMatch, WorkflowTaskType, } from './workflow-tasks';
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joshski/dust",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"description": "Flow state for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"dust": "./dist/dust.js"
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
|
+
"./types": {
|
|
11
|
+
"types": "./dist/types.d.ts"
|
|
12
|
+
},
|
|
10
13
|
"./workflow-tasks": {
|
|
11
14
|
"import": "./dist/workflow-tasks.js",
|
|
12
15
|
"types": "./dist/workflow-tasks.d.ts"
|