@joshski/dust 0.1.44 → 0.1.45

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.
Files changed (2) hide show
  1. package/dist/dust.js +243 -187
  2. package/package.json +1 -1
package/dist/dust.js CHANGED
@@ -18,129 +18,128 @@ var KNOWN_CHECK_KEYS = new Set([
18
18
  "hints",
19
19
  "timeoutMilliseconds"
20
20
  ]);
21
- function validateSettingsJson(content) {
21
+ function validateCheckEntry(check, checkPath) {
22
22
  const violations = [];
23
- let parsed;
24
- try {
25
- parsed = JSON.parse(content);
26
- } catch (error) {
27
- violations.push({
28
- message: `Invalid JSON: ${error.message}`
29
- });
23
+ if (typeof check === "string") {
30
24
  return violations;
31
25
  }
32
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
33
- violations.push({
34
- message: "settings.json must be a JSON object"
35
- });
26
+ if (typeof check !== "object" || check === null || Array.isArray(check)) {
27
+ violations.push({ message: `${checkPath} must be a string or object` });
36
28
  return violations;
37
29
  }
38
- const settings = parsed;
39
- for (const key of Object.keys(settings)) {
40
- if (!KNOWN_SETTINGS_KEYS.has(key)) {
30
+ const checkObj = check;
31
+ for (const key of Object.keys(checkObj)) {
32
+ if (!KNOWN_CHECK_KEYS.has(key)) {
41
33
  violations.push({
42
- message: `Unknown key "${key}" in settings.json. Known keys: ${[...KNOWN_SETTINGS_KEYS].sort().join(", ")}`
34
+ message: `Unknown key "${key}" in ${checkPath}. Known keys: ${[...KNOWN_CHECK_KEYS].sort().join(", ")}`
43
35
  });
44
36
  }
45
37
  }
46
- if ("checks" in settings) {
47
- if (!Array.isArray(settings.checks)) {
38
+ if (!("name" in checkObj)) {
39
+ violations.push({
40
+ message: `${checkPath} is missing required field "name"`
41
+ });
42
+ } else if (typeof checkObj.name !== "string") {
43
+ violations.push({ message: `${checkPath}.name must be a string` });
44
+ }
45
+ if (!("command" in checkObj)) {
46
+ violations.push({
47
+ message: `${checkPath} is missing required field "command"`
48
+ });
49
+ } else if (typeof checkObj.command !== "string") {
50
+ violations.push({ message: `${checkPath}.command must be a string` });
51
+ }
52
+ if ("hints" in checkObj) {
53
+ if (!Array.isArray(checkObj.hints)) {
48
54
  violations.push({
49
- message: '"checks" must be an array'
55
+ message: `${checkPath}.hints must be an array of strings`
50
56
  });
51
57
  } else {
52
- for (let i = 0;i < settings.checks.length; i++) {
53
- const check = settings.checks[i];
54
- const checkPath = `checks[${i}]`;
55
- if (typeof check === "string") {
56
- continue;
57
- }
58
- if (typeof check !== "object" || check === null || Array.isArray(check)) {
59
- violations.push({
60
- message: `${checkPath} must be a string or object`
61
- });
62
- continue;
63
- }
64
- const checkObj = check;
65
- for (const key of Object.keys(checkObj)) {
66
- if (!KNOWN_CHECK_KEYS.has(key)) {
67
- violations.push({
68
- message: `Unknown key "${key}" in ${checkPath}. Known keys: ${[...KNOWN_CHECK_KEYS].sort().join(", ")}`
69
- });
70
- }
71
- }
72
- if (!("name" in checkObj)) {
73
- violations.push({
74
- message: `${checkPath} is missing required field "name"`
75
- });
76
- } else if (typeof checkObj.name !== "string") {
77
- violations.push({
78
- message: `${checkPath}.name must be a string`
79
- });
80
- }
81
- if (!("command" in checkObj)) {
58
+ for (let j = 0;j < checkObj.hints.length; j++) {
59
+ if (typeof checkObj.hints[j] !== "string") {
82
60
  violations.push({
83
- message: `${checkPath} is missing required field "command"`
84
- });
85
- } else if (typeof checkObj.command !== "string") {
86
- violations.push({
87
- message: `${checkPath}.command must be a string`
61
+ message: `${checkPath}.hints[${j}] must be a string`
88
62
  });
89
63
  }
90
- if ("hints" in checkObj) {
91
- if (!Array.isArray(checkObj.hints)) {
92
- violations.push({
93
- message: `${checkPath}.hints must be an array of strings`
94
- });
95
- } else {
96
- for (let j = 0;j < checkObj.hints.length; j++) {
97
- if (typeof checkObj.hints[j] !== "string") {
98
- violations.push({
99
- message: `${checkPath}.hints[${j}] must be a string`
100
- });
101
- }
102
- }
103
- }
104
- }
105
- if ("timeoutMilliseconds" in checkObj) {
106
- if (typeof checkObj.timeoutMilliseconds !== "number" || checkObj.timeoutMilliseconds <= 0) {
107
- violations.push({
108
- message: `${checkPath}.timeoutMilliseconds must be a positive number`
109
- });
110
- }
111
- }
112
64
  }
113
65
  }
114
66
  }
115
- if ("extraDirectories" in settings) {
116
- if (!Array.isArray(settings.extraDirectories)) {
67
+ if ("timeoutMilliseconds" in checkObj) {
68
+ if (typeof checkObj.timeoutMilliseconds !== "number" || checkObj.timeoutMilliseconds <= 0) {
117
69
  violations.push({
118
- message: '"extraDirectories" must be an array of strings'
70
+ message: `${checkPath}.timeoutMilliseconds must be a positive number`
119
71
  });
120
- } else {
121
- for (let i = 0;i < settings.extraDirectories.length; i++) {
122
- if (typeof settings.extraDirectories[i] !== "string") {
123
- violations.push({
124
- message: `extraDirectories[${i}] must be a string`
125
- });
126
- }
127
- }
128
72
  }
129
73
  }
130
- if ("dustCommand" in settings && typeof settings.dustCommand !== "string") {
131
- violations.push({
132
- message: '"dustCommand" must be a string'
133
- });
74
+ return violations;
75
+ }
76
+ function validateChecksConfig(settings) {
77
+ if (!("checks" in settings)) {
78
+ return [];
134
79
  }
135
- if ("installCommand" in settings && typeof settings.installCommand !== "string") {
80
+ if (!Array.isArray(settings.checks)) {
81
+ return [{ message: '"checks" must be an array' }];
82
+ }
83
+ const violations = [];
84
+ for (let i = 0;i < settings.checks.length; i++) {
85
+ violations.push(...validateCheckEntry(settings.checks[i], `checks[${i}]`));
86
+ }
87
+ return violations;
88
+ }
89
+ function validateExtraDirectories(settings) {
90
+ if (!("extraDirectories" in settings)) {
91
+ return [];
92
+ }
93
+ if (!Array.isArray(settings.extraDirectories)) {
94
+ return [{ message: '"extraDirectories" must be an array of strings' }];
95
+ }
96
+ const violations = [];
97
+ for (let i = 0;i < settings.extraDirectories.length; i++) {
98
+ if (typeof settings.extraDirectories[i] !== "string") {
99
+ violations.push({ message: `extraDirectories[${i}] must be a string` });
100
+ }
101
+ }
102
+ return violations;
103
+ }
104
+ function validateDustEventsUrl(settings) {
105
+ if ("eventsUrl" in settings && typeof settings.eventsUrl !== "string") {
106
+ return [{ message: '"eventsUrl" must be a string' }];
107
+ }
108
+ return [];
109
+ }
110
+ function validateSettingsJson(content) {
111
+ const violations = [];
112
+ let parsed;
113
+ try {
114
+ parsed = JSON.parse(content);
115
+ } catch (error) {
136
116
  violations.push({
137
- message: '"installCommand" must be a string'
117
+ message: `Invalid JSON: ${error.message}`
138
118
  });
119
+ return violations;
139
120
  }
140
- if ("eventsUrl" in settings && typeof settings.eventsUrl !== "string") {
121
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
141
122
  violations.push({
142
- message: '"eventsUrl" must be a string'
123
+ message: "settings.json must be a JSON object"
143
124
  });
125
+ return violations;
126
+ }
127
+ const settings = parsed;
128
+ for (const key of Object.keys(settings)) {
129
+ if (!KNOWN_SETTINGS_KEYS.has(key)) {
130
+ violations.push({
131
+ message: `Unknown key "${key}" in settings.json. Known keys: ${[...KNOWN_SETTINGS_KEYS].sort().join(", ")}`
132
+ });
133
+ }
134
+ }
135
+ violations.push(...validateChecksConfig(settings));
136
+ violations.push(...validateExtraDirectories(settings));
137
+ violations.push(...validateDustEventsUrl(settings));
138
+ if ("dustCommand" in settings && typeof settings.dustCommand !== "string") {
139
+ violations.push({ message: '"dustCommand" must be a string' });
140
+ }
141
+ if ("installCommand" in settings && typeof settings.installCommand !== "string") {
142
+ violations.push({ message: '"installCommand" must be a string' });
144
143
  }
145
144
  return violations;
146
145
  }
@@ -973,7 +972,7 @@ import { accessSync, statSync } from "node:fs";
973
972
  import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
974
973
  import { createServer as httpCreateServer } from "node:http";
975
974
  import { homedir } from "node:os";
976
- import { join as join7 } from "node:path";
975
+ import { join as join8 } from "node:path";
977
976
 
978
977
  // lib/bucket/auth.ts
979
978
  import { join as join4 } from "node:path";
@@ -1046,7 +1045,29 @@ async function authenticate(authDeps) {
1046
1045
  if (code) {
1047
1046
  cleanup();
1048
1047
  exchange(code).then(resolve, reject);
1049
- return new Response("<html><body><p>Authentication successful! You can close this tab.</p></body></html>", { headers: { "Content-Type": "text/html" } });
1048
+ return new Response(`<!DOCTYPE html>
1049
+ <html>
1050
+ <head>
1051
+ <meta charset="utf-8">
1052
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1053
+ <title>Authorized</title>
1054
+ <style>
1055
+ body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #0a0a0a; color: #fafafa; }
1056
+ .card { text-align: center; padding: 2rem; border: 1px solid #333; border-radius: 8px; max-width: 400px; }
1057
+ h1 { font-size: 1.25rem; margin-bottom: 1rem; }
1058
+ p { color: #999; }
1059
+ </style>
1060
+ </head>
1061
+ <body>
1062
+ <div class="card">
1063
+ <svg width="64" height="64" viewBox="0 0 512 512" fill="#fafafa" xmlns="http://www.w3.org/2000/svg" style="margin-bottom: 1rem;">
1064
+ <path d="M392.566 159.4c-.649-79.601-31.851-134.481-80.25-141.944l-1.443-10.127a8.52 8.52 0 0 0-3.339-5.619 8.52 8.52 0 0 0-6.327-1.622l-92.99 13.287c-4.671.666-7.916 4.995-7.25 9.666l1.605 11.229c-.709.179-1.417.307-2.075.692-52.122 30.126-68.688 71.677-77.346 122.859-34.293 13.773-55.008 33.157-55.008 55.316 0 11.997 6.242 23.149 17.24 33.072-.12.922 42.712 207.763 42.712 207.763.06.273.128.555.213.828 10.64 33.678 63.146 57.203 127.693 57.203 66.963 0 118.308-23.107 127.906-58.031 0 0 42.832-206.841 42.712-207.763 10.998-9.922 17.24-21.074 17.24-33.072.001-21.34-19.264-40.083-51.293-53.737m-276.281 51.072a2180 2180 0 0 1-4.022 33.712c-17.326-9.606-27.043-20.545-27.043-31.048 0-11.989 12.723-24.541 34.933-35.044-1.374 10.452-2.612 21.219-3.868 32.38m88.849-158.254.658 4.637a8.52 8.52 0 0 0 3.339 5.619 8.5 8.5 0 0 0 5.123 1.708c.401 0 .811-.026 1.213-.085l92.99-13.287c4.671-.666 7.916-4.995 7.25-9.666l-.837-5.858c35.497 9.077 58.509 53.642 60.482 117.634-32.021-10.477-73.214-16.634-119.35-16.634-43.797 0-83.051 5.593-114.312 15.123 8.205-41.8 23.439-74.573 63.444-99.191m50.867 220.691c-52.891 0-97.217-8.735-127.505-21.1 1.819-13.645 3.339-26.778 4.756-39.425 1.648-14.704 3.228-28.555 5.149-41.653 29.724-10.341 70.208-17.368 117.6-17.368 100.641 0 170.781 31.5 170.781 59.773s-70.14 59.773-170.781 59.773"/>
1065
+ </svg>
1066
+ <h1>Your agent is connected!</h1>
1067
+ <p>You can close this tab.</p>
1068
+ </div>
1069
+ </body>
1070
+ </html>`, { headers: { "Content-Type": "text/html" } });
1050
1071
  }
1051
1072
  return new Response("Missing code", { status: 400 });
1052
1073
  }
@@ -1120,26 +1141,7 @@ function getLogLines(buffer) {
1120
1141
  }
1121
1142
 
1122
1143
  // lib/bucket/repository.ts
1123
- import { dirname as dirname2, join as join6 } from "node:path";
1124
-
1125
- // lib/agent-events.ts
1126
- function rawEventToAgentEvent(rawEvent) {
1127
- if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
1128
- return { type: "agent-session-activity" };
1129
- }
1130
- return { type: "claude-event", rawEvent };
1131
- }
1132
- function formatAgentEvent(event) {
1133
- switch (event.type) {
1134
- case "agent-session-started":
1135
- return `\uD83E\uDD16 Starting Claude: ${event.title}`;
1136
- case "agent-session-ended":
1137
- return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
1138
- case "agent-session-activity":
1139
- case "claude-event":
1140
- return null;
1141
- }
1142
- }
1144
+ import { dirname as dirname2, join as join7 } from "node:path";
1143
1145
 
1144
1146
  // lib/claude/spawn-claude-code.ts
1145
1147
  import { spawn as nodeSpawn } from "node:child_process";
@@ -1540,11 +1542,74 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
1540
1542
  await dependencies.streamEvents(events, sink, onRawEvent);
1541
1543
  }
1542
1544
 
1545
+ // lib/bucket/repository-git.ts
1546
+ import { join as join5 } from "node:path";
1547
+ function getRepoPath(repoName, reposDir) {
1548
+ const safeName = repoName.replace(/[^a-zA-Z0-9-_/]/g, "-");
1549
+ return join5(reposDir, safeName);
1550
+ }
1551
+ async function cloneRepository(repository, targetPath, spawn, context) {
1552
+ return new Promise((resolve) => {
1553
+ const proc = spawn("git", ["clone", repository.gitUrl, targetPath], {
1554
+ stdio: ["ignore", "pipe", "pipe"]
1555
+ });
1556
+ let stderr = "";
1557
+ proc.stderr?.on("data", (data) => {
1558
+ stderr += data.toString();
1559
+ });
1560
+ proc.on("close", (code) => {
1561
+ if (code === 0) {
1562
+ resolve(true);
1563
+ } else {
1564
+ context.stderr(`Failed to clone ${repository.name}: ${stderr.trim()}`);
1565
+ resolve(false);
1566
+ }
1567
+ });
1568
+ proc.on("error", (error) => {
1569
+ context.stderr(`Failed to clone ${repository.name}: ${error.message}`);
1570
+ resolve(false);
1571
+ });
1572
+ });
1573
+ }
1574
+ async function removeRepository(path, spawn, context) {
1575
+ return new Promise((resolve) => {
1576
+ const proc = spawn("rm", ["-rf", path], {
1577
+ stdio: ["ignore", "pipe", "pipe"]
1578
+ });
1579
+ proc.on("close", (code) => {
1580
+ resolve(code === 0);
1581
+ });
1582
+ proc.on("error", (error) => {
1583
+ context.stderr(`Failed to remove ${path}: ${error.message}`);
1584
+ resolve(false);
1585
+ });
1586
+ });
1587
+ }
1588
+
1589
+ // lib/agent-events.ts
1590
+ function rawEventToAgentEvent(rawEvent) {
1591
+ if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
1592
+ return { type: "agent-session-activity" };
1593
+ }
1594
+ return { type: "claude-event", rawEvent };
1595
+ }
1596
+ function formatAgentEvent(event) {
1597
+ switch (event.type) {
1598
+ case "agent-session-started":
1599
+ return `\uD83E\uDD16 Starting Claude: ${event.title}`;
1600
+ case "agent-session-ended":
1601
+ return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
1602
+ case "agent-session-activity":
1603
+ case "claude-event":
1604
+ return null;
1605
+ }
1606
+ }
1607
+
1543
1608
  // lib/cli/commands/loop.ts
1544
1609
  import { spawn as nodeSpawn2 } from "node:child_process";
1545
1610
  import { readFileSync } from "node:fs";
1546
1611
  import os from "node:os";
1547
- import { dirname, join as join5 } from "node:path";
1612
+ import { dirname, join as join6 } from "node:path";
1548
1613
  import { fileURLToPath } from "node:url";
1549
1614
 
1550
1615
  // lib/workflow-tasks.ts
@@ -1688,8 +1753,8 @@ async function next(dependencies) {
1688
1753
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
1689
1754
  function getDustVersion() {
1690
1755
  const candidates = [
1691
- join5(__dirname2, "../../../package.json"),
1692
- join5(__dirname2, "../package.json")
1756
+ join6(__dirname2, "../../../package.json"),
1757
+ join6(__dirname2, "../package.json")
1693
1758
  ];
1694
1759
  for (const candidate of candidates) {
1695
1760
  try {
@@ -1965,68 +2030,8 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
1965
2030
  return { exitCode: 0 };
1966
2031
  }
1967
2032
 
1968
- // lib/bucket/repository.ts
1969
- var SLEEP_INTERVAL_MS2 = 30000;
1970
- function parseRepository(data) {
1971
- if (typeof data === "string") {
1972
- return { name: data, gitUrl: data };
1973
- }
1974
- if (typeof data === "object" && data !== null && "name" in data && "gitUrl" in data) {
1975
- const repositoryData = data;
1976
- if (typeof repositoryData.name === "string" && typeof repositoryData.gitUrl === "string") {
1977
- const repo = {
1978
- name: repositoryData.name,
1979
- gitUrl: repositoryData.gitUrl
1980
- };
1981
- if (typeof repositoryData.url === "string") {
1982
- repo.url = repositoryData.url;
1983
- }
1984
- return repo;
1985
- }
1986
- }
1987
- return null;
1988
- }
1989
- function getRepoPath(repoName, reposDir) {
1990
- const safeName = repoName.replace(/[^a-zA-Z0-9-_/]/g, "-");
1991
- return join6(reposDir, safeName);
1992
- }
1993
- async function cloneRepository(repository, targetPath, spawn, context) {
1994
- return new Promise((resolve) => {
1995
- const proc = spawn("git", ["clone", repository.gitUrl, targetPath], {
1996
- stdio: ["ignore", "pipe", "pipe"]
1997
- });
1998
- let stderr = "";
1999
- proc.stderr?.on("data", (data) => {
2000
- stderr += data.toString();
2001
- });
2002
- proc.on("close", (code) => {
2003
- if (code === 0) {
2004
- resolve(true);
2005
- } else {
2006
- context.stderr(`Failed to clone ${repository.name}: ${stderr.trim()}`);
2007
- resolve(false);
2008
- }
2009
- });
2010
- proc.on("error", (error) => {
2011
- context.stderr(`Failed to clone ${repository.name}: ${error.message}`);
2012
- resolve(false);
2013
- });
2014
- });
2015
- }
2016
- async function removeRepository(path, spawn, context) {
2017
- return new Promise((resolve) => {
2018
- const proc = spawn("rm", ["-rf", path], {
2019
- stdio: ["ignore", "pipe", "pipe"]
2020
- });
2021
- proc.on("close", (code) => {
2022
- resolve(code === 0);
2023
- });
2024
- proc.on("error", (error) => {
2025
- context.stderr(`Failed to remove ${path}: ${error.message}`);
2026
- resolve(false);
2027
- });
2028
- });
2029
- }
2033
+ // lib/bucket/repository-loop.ts
2034
+ var FALLBACK_TIMEOUT_MS = 300000;
2030
2035
  function createNoOpGlobScanner() {
2031
2036
  return {
2032
2037
  scan: async function* () {}
@@ -2120,11 +2125,42 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
2120
2125
  }
2121
2126
  });
2122
2127
  if (result === "no_tasks") {
2123
- await sleep(SLEEP_INTERVAL_MS2);
2128
+ await new Promise((resolve) => {
2129
+ repoState.wakeUp = () => {
2130
+ repoState.wakeUp = undefined;
2131
+ resolve();
2132
+ };
2133
+ sleep(FALLBACK_TIMEOUT_MS).then(() => {
2134
+ if (repoState.wakeUp) {
2135
+ repoState.wakeUp = undefined;
2136
+ resolve();
2137
+ }
2138
+ });
2139
+ });
2124
2140
  }
2125
2141
  }
2126
2142
  appendLogLine(repoState.logBuffer, createLogLine(`Stopped loop for ${repoName}`, "stdout"));
2127
2143
  }
2144
+ // lib/bucket/repository.ts
2145
+ function parseRepository(data) {
2146
+ if (typeof data === "string") {
2147
+ return { name: data, gitUrl: data };
2148
+ }
2149
+ if (typeof data === "object" && data !== null && "name" in data && "gitUrl" in data) {
2150
+ const repositoryData = data;
2151
+ if (typeof repositoryData.name === "string" && typeof repositoryData.gitUrl === "string") {
2152
+ const repo = {
2153
+ name: repositoryData.name,
2154
+ gitUrl: repositoryData.gitUrl
2155
+ };
2156
+ if (typeof repositoryData.url === "string") {
2157
+ repo.url = repositoryData.url;
2158
+ }
2159
+ return repo;
2160
+ }
2161
+ }
2162
+ return null;
2163
+ }
2128
2164
  async function addRepository(repository, manager, repoDeps, context) {
2129
2165
  if (manager.repositories.has(repository.name)) {
2130
2166
  return;
@@ -2169,6 +2205,7 @@ async function removeRepositoryFromManager(repoName, manager, repoDeps, context)
2169
2205
  return;
2170
2206
  }
2171
2207
  repoState.stopRequested = true;
2208
+ repoState.wakeUp?.();
2172
2209
  if (repoState.loopPromise) {
2173
2210
  await Promise.race([repoState.loopPromise, repoDeps.sleep(5000)]);
2174
2211
  }
@@ -2730,7 +2767,7 @@ function createDefaultBucketDependencies() {
2730
2767
  writeStdout: defaultWriteStdout,
2731
2768
  isTTY: process.stdout.isTTY ?? false,
2732
2769
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
2733
- getReposDir: () => process.env.DUST_REPOS_DIR || join7(homedir(), ".dust", "repos"),
2770
+ getReposDir: () => process.env.DUST_REPOS_DIR || join8(homedir(), ".dust", "repos"),
2734
2771
  auth: {
2735
2772
  createServer: defaultCreateServer,
2736
2773
  openBrowser: defaultOpenBrowser,
@@ -2876,6 +2913,10 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
2876
2913
  state.emit(disconnectEvent);
2877
2914
  logMessage(state, context, useTUI, formatBucketEvent(disconnectEvent));
2878
2915
  state.ws = null;
2916
+ if (event.code === 4000) {
2917
+ logMessage(state, context, useTUI, "Another connection replaced this one. Not reconnecting.");
2918
+ return;
2919
+ }
2879
2920
  if (!state.shuttingDown) {
2880
2921
  logMessage(state, context, useTUI, `Reconnecting in ${state.reconnectDelay / 1000} seconds...`);
2881
2922
  state.reconnectTimer = setTimeout(() => {
@@ -2896,9 +2937,23 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
2896
2937
  syncUIWithRepoList(state, repos);
2897
2938
  const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
2898
2939
  const repoContext = createTUIContext(state, context, useTUI);
2899
- handleRepositoryList(repos, state, repoDeps, repoContext).then(() => syncTUI(state)).catch((error) => {
2940
+ handleRepositoryList(repos, state, repoDeps, repoContext).then(() => {
2941
+ syncTUI(state);
2942
+ for (const repoData of repos) {
2943
+ if (typeof repoData === "object" && repoData !== null && "name" in repoData && "hasTask" in repoData && repoData.hasTask) {
2944
+ const repoState = state.repositories.get(repoData.name);
2945
+ repoState?.wakeUp?.();
2946
+ }
2947
+ }
2948
+ }).catch((error) => {
2900
2949
  logMessage(state, context, useTUI, `Failed to handle repository list: ${error.message}`, "stderr");
2901
2950
  });
2951
+ } else if (message.type === "task-available") {
2952
+ const repoName = message.repository;
2953
+ if (typeof repoName === "string") {
2954
+ const repoState = state.repositories.get(repoName);
2955
+ repoState?.wakeUp?.();
2956
+ }
2902
2957
  }
2903
2958
  } catch {
2904
2959
  logMessage(state, context, useTUI, `Failed to parse WebSocket message: ${event.data}`, "stderr");
@@ -2920,6 +2975,7 @@ async function shutdown(state, bucketDeps, context) {
2920
2975
  }
2921
2976
  for (const repoState of state.repositories.values()) {
2922
2977
  repoState.stopRequested = true;
2978
+ repoState.wakeUp?.();
2923
2979
  }
2924
2980
  const loopPromises = Array.from(state.repositories.values()).map((rs) => rs.loopPromise).filter((p) => p !== null);
2925
2981
  await Promise.all(loopPromises.map((p) => p.catch(() => {})));
@@ -3099,7 +3155,7 @@ function runBufferedProcess(spawnFn, command, commandArguments, cwd, shell, time
3099
3155
  }
3100
3156
 
3101
3157
  // lib/cli/commands/lint-markdown.ts
3102
- import { dirname as dirname3, join as join8, resolve } from "node:path";
3158
+ import { dirname as dirname3, join as join9, resolve } from "node:path";
3103
3159
  var REQUIRED_HEADINGS = ["## Goals", "## Blocked By", "## Definition of Done"];
3104
3160
  var REQUIRED_GOAL_HEADINGS = ["## Parent Goal", "## Sub-Goals"];
3105
3161
  var SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*\.md$/;
@@ -3636,7 +3692,7 @@ async function lintMarkdown(dependencies) {
3636
3692
  const violations = [];
3637
3693
  context.stdout("Validating directory structure...");
3638
3694
  violations.push(...await validateDirectoryStructure(dustPath, fileSystem, dependencies.settings.extraDirectories));
3639
- const settingsPath = join8(dustPath, "config", "settings.json");
3695
+ const settingsPath = join9(dustPath, "config", "settings.json");
3640
3696
  if (fileSystem.exists(settingsPath)) {
3641
3697
  context.stdout("Validating settings.json...");
3642
3698
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.44",
3
+ "version": "0.1.45",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {