@joshski/dust 0.1.78 → 0.1.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,4 +40,15 @@ export interface EventMessage {
40
40
  * is forwarded as a claude-event.
41
41
  */
42
42
  export declare function rawEventToAgentEvent(rawEvent: Record<string, unknown>): AgentSessionEvent;
43
+ /**
44
+ * Create a heartbeat throttler that limits agent-session-activity events
45
+ * to at most once per interval (default: 5 seconds).
46
+ *
47
+ * The returned callback converts raw Claude events to AgentSessionEvents,
48
+ * throttling stream_event heartbeats while forwarding all other events.
49
+ */
50
+ export declare function createHeartbeatThrottler(onAgentEvent: (event: AgentSessionEvent) => void, options?: {
51
+ intervalMs?: number;
52
+ now?: () => number;
53
+ }): (rawEvent: Record<string, unknown>) => void;
43
54
  export declare function formatAgentEvent(event: AgentSessionEvent): string | null;
@@ -18,7 +18,9 @@ export interface TaskGraph {
18
18
  }
19
19
  export { CAPTURE_IDEA_PREFIX, findAllWorkflowTasks, parseOpenQuestions };
20
20
  export type { IdeaInProgress };
21
+ export type ArtifactType = 'ideas' | 'tasks' | 'principles' | 'facts';
21
22
  export interface ArtifactsRepository {
23
+ artifactPath(type: ArtifactType, slug: string): string;
22
24
  parseIdea(options: {
23
25
  slug: string;
24
26
  }): Promise<Idea>;
@@ -63,4 +65,4 @@ export interface ArtifactsRepository {
63
65
  buildTaskGraph(): Promise<TaskGraph>;
64
66
  }
65
67
  export declare function buildArtifactsRepository(fileSystem: FileSystem, dustPath: string): ArtifactsRepository;
66
- export declare function buildReadOnlyArtifactsRepository(fileSystem: ReadableFileSystem, dustPath: string): Pick<ArtifactsRepository, 'parseIdea' | 'listIdeas' | 'parsePrinciple' | 'listPrinciples' | 'parseFact' | 'listFacts' | 'parseTask' | 'listTasks' | 'findWorkflowTaskForIdea' | 'parseCaptureIdeaTask' | 'buildTaskGraph'>;
68
+ export declare function buildReadOnlyArtifactsRepository(fileSystem: ReadableFileSystem, dustPath: string): Pick<ArtifactsRepository, 'artifactPath' | 'parseIdea' | 'listIdeas' | 'parsePrinciple' | 'listPrinciples' | 'parseFact' | 'listFacts' | 'parseTask' | 'listTasks' | 'findWorkflowTaskForIdea' | 'parseCaptureIdeaTask' | 'buildTaskGraph'>;
package/dist/artifacts.js CHANGED
@@ -548,6 +548,9 @@ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
548
548
  // lib/artifacts/index.ts
549
549
  function buildArtifactsRepository(fileSystem, dustPath) {
550
550
  return {
551
+ artifactPath(type, slug) {
552
+ return `${dustPath}/${type}/${slug}.md`;
553
+ },
551
554
  async parseIdea(options) {
552
555
  return parseIdea(fileSystem, dustPath, options.slug);
553
556
  },
@@ -635,6 +638,9 @@ function buildArtifactsRepository(fileSystem, dustPath) {
635
638
  }
636
639
  function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
637
640
  return {
641
+ artifactPath(type, slug) {
642
+ return `${dustPath}/${type}/${slug}.md`;
643
+ },
638
644
  async parseIdea(options) {
639
645
  return parseIdea(fileSystem, dustPath, options.slug);
640
646
  },
@@ -5,6 +5,9 @@
5
5
  * for a single repository.
6
6
  */
7
7
  import type { AgentSessionEvent, EventMessage } from '../agent-events';
8
+ import { type run as claudeRun, type RunnerDependencies } from '../claude/run';
9
+ import type { OutputSink } from '../claude/types';
10
+ import { type LoopEmitFn, type SendAgentEventFn } from '../cli/commands/loop';
8
11
  import type { SendEventFn } from './events';
9
12
  import { type LogBuffer } from './log-buffer';
10
13
  import type { RepositoryDependencies, RepositoryState } from './repository';
@@ -40,6 +43,45 @@ export declare function buildEventMessage(parameters: {
40
43
  * Extracted for testability (v8 coverage limitation on inline callbacks).
41
44
  */
42
45
  export declare function createWakeUpHandler(repoState: RepositoryState, resolve: () => void): () => void;
46
+ /** Mutable state shared across loop iteration callbacks. */
47
+ export interface LoopState {
48
+ partialLine: string;
49
+ sequence: number;
50
+ agentSessionId: string | undefined;
51
+ }
52
+ /**
53
+ * Create an OutputSink that buffers stdout and logs complete lines.
54
+ */
55
+ export declare function createBufferStdoutSink(loopState: LoopState, logBuffer: LogBuffer): OutputSink;
56
+ /**
57
+ * Create a run function that redirects Claude output to a log buffer.
58
+ */
59
+ export declare function createBufferRun(run: RepositoryDependencies['run'], bufferSinkDeps: RunnerDependencies): typeof claudeRun;
60
+ /** No-op postEvent for LoopDependencies. */
61
+ export declare function noOpPostEvent(): Promise<void>;
62
+ /**
63
+ * Create a handler that logs formatted loop events to a log buffer.
64
+ */
65
+ export declare function createLoopEventHandler(logBuffer: LogBuffer): LoopEmitFn;
66
+ /**
67
+ * Create a handler that logs formatted agent events and sends them over WebSocket.
68
+ */
69
+ export declare function createAgentEventHandler(parameters: {
70
+ repoState: RepositoryState;
71
+ sendEvent?: SendEventFn;
72
+ sessionId?: string;
73
+ repoName: string;
74
+ loopState: LoopState;
75
+ }): SendAgentEventFn;
76
+ /**
77
+ * Create a cancel handler that aborts the given controller.
78
+ */
79
+ export declare function createCancelHandler(abortController: AbortController): () => void;
80
+ /**
81
+ * Set up the fallback timeout for the no-tasks wait.
82
+ * Resolves the wait if this exact handler is still active after the timeout.
83
+ */
84
+ export declare function setupFallbackTimeout(repoState: RepositoryState, sleep: RepositoryDependencies['sleep'], resolve: () => void, wakeUpForThisWait: () => void): void;
43
85
  /**
44
86
  * Run the async loop for a single repository.
45
87
  */
@@ -16,6 +16,7 @@ export interface Repository {
16
16
  gitUrl: string;
17
17
  url: string;
18
18
  id: number;
19
+ agentProvider?: string;
19
20
  }
20
21
  export interface RepositoryState {
21
22
  repository: Repository;
@@ -47,6 +47,16 @@ export interface OutputSink {
47
47
  write(text: string): void;
48
48
  line(text: string): void;
49
49
  }
50
+ export interface DockerSpawnConfig {
51
+ /** Docker image tag to use */
52
+ imageTag: string;
53
+ /** Path to the repository (used for volume mounts) */
54
+ repoPath: string;
55
+ /** Home directory for credential mounts */
56
+ homeDir: string;
57
+ /** Whether .gitconfig exists (for optional mount) */
58
+ hasGitconfig: boolean;
59
+ }
50
60
  export interface SpawnOptions {
51
61
  cwd?: string;
52
62
  allowedTools?: string[];
@@ -57,5 +67,7 @@ export interface SpawnOptions {
57
67
  dangerouslySkipPermissions?: boolean;
58
68
  env?: Record<string, string>;
59
69
  signal?: AbortSignal;
70
+ /** When set, spawn claude inside a Docker container */
71
+ docker?: DockerSpawnConfig;
60
72
  }
61
73
  export type RawEventCallback = (event: RawEvent) => void;
@@ -18,6 +18,8 @@
18
18
  import { spawn as nodeSpawn } from 'node:child_process';
19
19
  import { type AgentSessionEvent, type EventMessage } from '../../agent-events';
20
20
  import { run as claudeRun } from '../../claude/run';
21
+ import type { DockerSpawnConfig } from '../../claude/types';
22
+ import { type DockerDependencies } from '../../docker/docker-agent';
21
23
  import type { CommandDependencies, CommandResult } from '../types';
22
24
  import { type UnblockedTask } from './next';
23
25
  export interface LoopWarningEvent {
@@ -53,7 +55,23 @@ export interface LoopEndedEvent {
53
55
  type: 'loop.ended';
54
56
  maxIterations: number;
55
57
  }
56
- export type LoopEvent = LoopWarningEvent | LoopStartedEvent | LoopSyncingEvent | LoopSyncSkippedEvent | LoopCheckingTasksEvent | LoopNoTasksEvent | LoopTasksFoundEvent | LoopIterationCompleteEvent | LoopEndedEvent;
58
+ export interface LoopDockerDetectedEvent {
59
+ type: 'loop.docker_detected';
60
+ imageTag: string;
61
+ }
62
+ export interface LoopDockerBuildingEvent {
63
+ type: 'loop.docker_building';
64
+ imageTag: string;
65
+ }
66
+ export interface LoopDockerBuiltEvent {
67
+ type: 'loop.docker_built';
68
+ imageTag: string;
69
+ }
70
+ export interface LoopDockerErrorEvent {
71
+ type: 'loop.docker_error';
72
+ error: string;
73
+ }
74
+ export type LoopEvent = LoopWarningEvent | LoopStartedEvent | LoopSyncingEvent | LoopSyncSkippedEvent | LoopCheckingTasksEvent | LoopNoTasksEvent | LoopTasksFoundEvent | LoopIterationCompleteEvent | LoopEndedEvent | LoopDockerDetectedEvent | LoopDockerBuildingEvent | LoopDockerBuiltEvent | LoopDockerErrorEvent;
57
75
  export type LoopEmitFn = (event: LoopEvent) => void;
58
76
  export declare function formatLoopEvent(event: LoopEvent): string | null;
59
77
  export type PostEventFn = (url: string, payload: EventMessage) => Promise<void>;
@@ -64,6 +82,8 @@ export interface LoopDependencies {
64
82
  postEvent: PostEventFn;
65
83
  agentType?: string;
66
84
  fetch?: typeof fetch;
85
+ /** Optional overrides for Docker dependency functions (for testing) */
86
+ dockerDeps?: Partial<DockerDependencies>;
67
87
  }
68
88
  export declare function createPostEvent(fetchFn: typeof fetch): PostEventFn;
69
89
  export declare function createDefaultDependencies(): LoopDependencies;
@@ -85,6 +105,8 @@ interface IterationOptions {
85
105
  signal?: AbortSignal;
86
106
  logger?: LogFn;
87
107
  repositoryId?: string;
108
+ /** Docker spawn config when running in Docker mode */
109
+ docker?: DockerSpawnConfig;
88
110
  }
89
111
  export declare function runOneIteration(dependencies: CommandDependencies, loopDependencies: LoopDependencies, onLoopEvent: LoopEmitFn, onAgentEvent?: SendAgentEventFn, options?: IterationOptions): Promise<IterationResult>;
90
112
  export declare function parseMaxIterations(commandArguments: string[]): number;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Common types for CLI commands
3
3
  */
4
+ import type { CommandEvent } from '../command-events';
4
5
  import type { FileSystem, GlobScanner } from '../filesystem/types';
5
6
  export type { FileSystem, GlobScanner, ReadableFileSystem, } from '../filesystem/types';
6
7
  export interface CommandContext {
@@ -8,6 +9,7 @@ export interface CommandContext {
8
9
  stdout: (message: string) => void;
9
10
  stdoutInline?: (message: string) => void;
10
11
  stderr: (message: string) => void;
12
+ emitEvent?: (event: CommandEvent) => void;
11
13
  }
12
14
  export interface CommandResult {
13
15
  exitCode: number;
@@ -0,0 +1,13 @@
1
+ import type { ClaudeEvent, RawEvent } from '../claude/types';
2
+ /**
3
+ * Parse a raw Codex JSON event into ClaudeEvent types.
4
+ *
5
+ * Codex `exec --json` emits JSONL with these event types:
6
+ * - { type: "item.completed", item: { type: "agent_message", text: "..." } } → text output
7
+ * - { type: "item.completed", item: { type: "command_execution", command, aggregated_output, exit_code } } → tool use + result
8
+ * - { type: "item.completed", item: { type: "reasoning", text: "..." } } → skipped (internal thinking)
9
+ * - { type: "item.started", item: { type: "command_execution", command } } → skipped (in-progress)
10
+ * - { type: "turn.started" } / { type: "turn.completed" } → skipped
11
+ * - { type: "thread.started" } → skipped
12
+ */
13
+ export declare function parseCodexRawEvent(raw: RawEvent): Generator<ClaudeEvent>;
@@ -0,0 +1,16 @@
1
+ import { createStdoutSink as defaultCreateStdoutSink } from '../claude/streamer';
2
+ import type { RawEventCallback, SpawnOptions } from '../claude/types';
3
+ import { spawnCodex as defaultSpawnCodex } from './spawn-codex';
4
+ import { streamCodexEvents as defaultStreamCodexEvents } from './streamer';
5
+ interface RunOptions {
6
+ spawnOptions?: SpawnOptions;
7
+ onRawEvent?: RawEventCallback;
8
+ }
9
+ export interface RunnerDependencies {
10
+ spawnCodex: typeof defaultSpawnCodex;
11
+ createStdoutSink: typeof defaultCreateStdoutSink;
12
+ streamCodexEvents: typeof defaultStreamCodexEvents;
13
+ }
14
+ export declare const defaultRunnerDependencies: RunnerDependencies;
15
+ export declare function run(prompt: string, options?: SpawnOptions | RunOptions, dependencies?: RunnerDependencies): Promise<void>;
16
+ export {};
@@ -0,0 +1,9 @@
1
+ import { spawn as nodeSpawn } from 'node:child_process';
2
+ import { createInterface as nodeCreateInterface } from 'node:readline';
3
+ import type { RawEvent, SpawnOptions } from '../claude/types';
4
+ export interface EventSourceDependencies {
5
+ spawn: typeof nodeSpawn;
6
+ createInterface: typeof nodeCreateInterface;
7
+ }
8
+ export declare const defaultDependencies: EventSourceDependencies;
9
+ export declare function spawnCodex(prompt: string, options?: SpawnOptions, dependencies?: EventSourceDependencies): AsyncGenerator<RawEvent>;
@@ -0,0 +1,6 @@
1
+ import type { OutputSink, RawEvent, RawEventCallback } from '../claude/types';
2
+ /**
3
+ * Process a stream of raw Codex events and write output to the sink.
4
+ * Uses the shared processEvent logic with Codex-specific event parsing.
5
+ */
6
+ export declare function streamCodexEvents(events: AsyncIterable<RawEvent>, sink: OutputSink, onRawEvent?: RawEventCallback): Promise<void>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Command event types for the dust back channel protocol.
3
+ *
4
+ * These types define structured events emitted by dust commands
5
+ * to a file descriptor specified by DUST_EVENTS_FD. Events are
6
+ * written as newline-delimited JSON using the CommandEventMessage envelope.
7
+ */
8
+ /**
9
+ * Events emitted by dust commands.
10
+ */
11
+ export type CommandEvent = {
12
+ type: 'check-started';
13
+ name: string;
14
+ } | {
15
+ type: 'check-passed';
16
+ name: string;
17
+ durationMs: number;
18
+ } | {
19
+ type: 'check-failed';
20
+ name: string;
21
+ durationMs: number;
22
+ output?: string;
23
+ } | {
24
+ type: 'facts-listed';
25
+ facts: Array<{
26
+ path: string;
27
+ title: string;
28
+ }>;
29
+ } | {
30
+ type: 'ideas-listed';
31
+ ideas: Array<{
32
+ path: string;
33
+ title: string;
34
+ status: string;
35
+ }>;
36
+ } | {
37
+ type: 'principles-listed';
38
+ principles: Array<{
39
+ path: string;
40
+ title: string;
41
+ }>;
42
+ } | {
43
+ type: 'tasks-listed';
44
+ tasks: Array<{
45
+ path: string;
46
+ title: string;
47
+ blockedBy: string[];
48
+ }>;
49
+ };
50
+ /**
51
+ * Wire format for command events, following the same pattern as EventMessage.
52
+ */
53
+ export interface CommandEventMessage {
54
+ sequence: number;
55
+ timestamp: string;
56
+ event: CommandEvent;
57
+ }
58
+ /**
59
+ * Creates an event emitter function that writes command events to a callback.
60
+ * Each event is wrapped in a CommandEventMessage envelope with sequence and timestamp.
61
+ */
62
+ export declare function createEventEmitter(writeEvent: (message: CommandEventMessage) => void): (event: CommandEvent) => void;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Docker-based agent execution for dust loop.
3
+ *
4
+ * When a repository contains a .dust/Dockerfile, the agent runs inside
5
+ * a Docker container instead of directly on the host. This provides
6
+ * isolation and lets each project define its ideal agent environment.
7
+ */
8
+ import type { spawn as nodeSpawn } from 'node:child_process';
9
+ import type os from 'node:os';
10
+ interface DockerConfig {
11
+ /** Path to the repository */
12
+ repoPath: string;
13
+ /** Docker image tag to use (e.g., 'dust-agent-myrepo') */
14
+ imageTag: string;
15
+ }
16
+ export interface DockerDependencies {
17
+ spawn: typeof nodeSpawn;
18
+ homedir: typeof os.homedir;
19
+ existsSync: (path: string) => boolean;
20
+ }
21
+ /**
22
+ * Check if Docker is available on the system.
23
+ */
24
+ export declare function isDockerAvailable(dependencies: DockerDependencies): Promise<boolean>;
25
+ /**
26
+ * Generate a deterministic Docker image tag from the repository path.
27
+ * Uses the repository directory name, sanitized for Docker tag requirements.
28
+ */
29
+ export declare function generateImageTag(repoPath: string): string;
30
+ type BuildResult = {
31
+ success: true;
32
+ } | {
33
+ success: false;
34
+ error: string;
35
+ };
36
+ /**
37
+ * Build a Docker image from the repository's .dust/Dockerfile.
38
+ */
39
+ export declare function buildDockerImage(config: DockerConfig, dependencies: DockerDependencies): Promise<BuildResult>;
40
+ /**
41
+ * Check if a Dockerfile exists at .dust/Dockerfile in the repository.
42
+ */
43
+ export declare function hasDockerfile(repoPath: string, dependencies: DockerDependencies): boolean;
44
+ export {};