@joshski/dust 0.1.83 → 0.1.85
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/biome/no-vitest-mocking.grit +25 -0
- package/dist/agent-events.d.ts +7 -6
- package/dist/config/settings.d.ts +1 -1
- package/dist/dust.js +504 -200
- package/dist/logging/index.d.ts +4 -1
- package/dist/logging.js +7 -6
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
`vi.mock($...)` where {
|
|
5
|
+
register_diagnostic(span=$match, message="Avoid vi.mock(). Use dependency injection or a test helper instead.")
|
|
6
|
+
},
|
|
7
|
+
`vi.spyOn($...)` where {
|
|
8
|
+
register_diagnostic(span=$match, message="Avoid vi.spyOn(). Use dependency injection or a test helper instead.")
|
|
9
|
+
},
|
|
10
|
+
`vi.useFakeTimers($...)` where {
|
|
11
|
+
register_diagnostic(span=$match, message="Avoid vi.useFakeTimers(). Use a test helper instead.")
|
|
12
|
+
},
|
|
13
|
+
`vi.useRealTimers($...)` where {
|
|
14
|
+
register_diagnostic(span=$match, message="Avoid vi.useRealTimers(). Use a test helper instead.")
|
|
15
|
+
},
|
|
16
|
+
`vi.runAllTimers($...)` where {
|
|
17
|
+
register_diagnostic(span=$match, message="Avoid vi.runAllTimers(). Use a test helper instead.")
|
|
18
|
+
},
|
|
19
|
+
`vi.advanceTimersByTime($...)` where {
|
|
20
|
+
register_diagnostic(span=$match, message="Avoid vi.advanceTimersByTime(). Use a test helper instead.")
|
|
21
|
+
},
|
|
22
|
+
`vi.fn($...)` where {
|
|
23
|
+
register_diagnostic(span=$match, message="Avoid vi.fn(). Use a typed test double or test helper instead.")
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/agent-events.d.ts
CHANGED
|
@@ -22,7 +22,8 @@ export type AgentSessionEvent = {
|
|
|
22
22
|
} | {
|
|
23
23
|
type: 'agent-session-activity';
|
|
24
24
|
} | {
|
|
25
|
-
type: '
|
|
25
|
+
type: 'agent-event';
|
|
26
|
+
provider: string;
|
|
26
27
|
rawEvent: Record<string, unknown>;
|
|
27
28
|
};
|
|
28
29
|
export interface EventMessage {
|
|
@@ -35,19 +36,19 @@ export interface EventMessage {
|
|
|
35
36
|
event: AgentSessionEvent;
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
|
-
* Convert a raw
|
|
39
|
+
* Convert a raw agent streaming event to an AgentSessionEvent.
|
|
39
40
|
* stream_event types become activity heartbeats; everything else
|
|
40
|
-
* is forwarded as
|
|
41
|
+
* is forwarded as an agent-event with provider info.
|
|
41
42
|
*/
|
|
42
|
-
export declare function rawEventToAgentEvent(rawEvent: Record<string, unknown
|
|
43
|
+
export declare function rawEventToAgentEvent(rawEvent: Record<string, unknown>, provider: string): AgentSessionEvent;
|
|
43
44
|
/**
|
|
44
45
|
* Create a heartbeat throttler that limits agent-session-activity events
|
|
45
46
|
* to at most once per interval (default: 5 seconds).
|
|
46
47
|
*
|
|
47
|
-
* The returned callback converts raw
|
|
48
|
+
* The returned callback converts raw agent events to AgentSessionEvents,
|
|
48
49
|
* throttling stream_event heartbeats while forwarding all other events.
|
|
49
50
|
*/
|
|
50
|
-
export declare function createHeartbeatThrottler(onAgentEvent: (event: AgentSessionEvent) => void, options?: {
|
|
51
|
+
export declare function createHeartbeatThrottler(onAgentEvent: (event: AgentSessionEvent) => void, provider: string, options?: {
|
|
51
52
|
intervalMs?: number;
|
|
52
53
|
now?: () => number;
|
|
53
54
|
}): (rawEvent: Record<string, unknown>) => void;
|
|
@@ -12,7 +12,7 @@ export declare function validateSettingsJson(content: string): SettingsViolation
|
|
|
12
12
|
/**
|
|
13
13
|
* Detects the appropriate dust command based on lockfiles and environment.
|
|
14
14
|
* Priority:
|
|
15
|
-
* 1. bun.lockb exists → bunx dust
|
|
15
|
+
* 1. bun.lock or bun.lockb exists → bunx dust
|
|
16
16
|
* 2. pnpm-lock.yaml exists → pnpx dust
|
|
17
17
|
* 3. package-lock.json exists → npx dust
|
|
18
18
|
* 4. No lockfile + BUN_INSTALL env var set → bunx dust
|
package/dist/dust.js
CHANGED
|
@@ -185,7 +185,7 @@ var DEFAULT_SETTINGS = {
|
|
|
185
185
|
dustCommand: "npx dust"
|
|
186
186
|
};
|
|
187
187
|
function detectDustCommand(cwd, fileSystem) {
|
|
188
|
-
if (fileSystem.exists(join(cwd, "bun.lockb"))) {
|
|
188
|
+
if (fileSystem.exists(join(cwd, "bun.lock")) || fileSystem.exists(join(cwd, "bun.lockb"))) {
|
|
189
189
|
return "bunx dust";
|
|
190
190
|
}
|
|
191
191
|
if (fileSystem.exists(join(cwd, "pnpm-lock.yaml"))) {
|
|
@@ -200,6 +200,7 @@ function detectDustCommand(cwd, fileSystem) {
|
|
|
200
200
|
return "npx dust";
|
|
201
201
|
}
|
|
202
202
|
var LOCKFILE_COMMANDS = [
|
|
203
|
+
{ file: "bun.lock", command: "bun install", ecosystem: "js" },
|
|
203
204
|
{ file: "bun.lockb", command: "bun install", ecosystem: "js" },
|
|
204
205
|
{ file: "pnpm-lock.yaml", command: "pnpm install", ecosystem: "js" },
|
|
205
206
|
{ file: "package-lock.json", command: "npm install", ecosystem: "js" },
|
|
@@ -318,7 +319,7 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
318
319
|
}
|
|
319
320
|
|
|
320
321
|
// lib/version.ts
|
|
321
|
-
var DUST_VERSION = "0.1.
|
|
322
|
+
var DUST_VERSION = "0.1.85";
|
|
322
323
|
|
|
323
324
|
// lib/session.ts
|
|
324
325
|
var DUST_UNATTENDED = "DUST_UNATTENDED";
|
|
@@ -1839,6 +1840,264 @@ function openBrowser(url) {
|
|
|
1839
1840
|
nodeSpawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
1840
1841
|
}
|
|
1841
1842
|
|
|
1843
|
+
// lib/bucket/bucket-state.ts
|
|
1844
|
+
function handleServerMessage(state, message) {
|
|
1845
|
+
const effects = [];
|
|
1846
|
+
effects.push({
|
|
1847
|
+
type: "debugLog",
|
|
1848
|
+
message: `ws message: ${message.type}`
|
|
1849
|
+
});
|
|
1850
|
+
switch (message.type) {
|
|
1851
|
+
case "repository-list": {
|
|
1852
|
+
const repos = message.repositories;
|
|
1853
|
+
effects.push({
|
|
1854
|
+
type: "log",
|
|
1855
|
+
message: `Received repository list (${repos.length} repositories):`,
|
|
1856
|
+
stream: "stdout"
|
|
1857
|
+
});
|
|
1858
|
+
if (repos.length === 0) {
|
|
1859
|
+
effects.push({
|
|
1860
|
+
type: "log",
|
|
1861
|
+
message: " (empty)",
|
|
1862
|
+
stream: "stdout"
|
|
1863
|
+
});
|
|
1864
|
+
} else {
|
|
1865
|
+
for (const r of repos) {
|
|
1866
|
+
effects.push({
|
|
1867
|
+
type: "log",
|
|
1868
|
+
message: ` - name=${r.name}`,
|
|
1869
|
+
stream: "stdout"
|
|
1870
|
+
});
|
|
1871
|
+
effects.push({
|
|
1872
|
+
type: "log",
|
|
1873
|
+
message: ` id=${r.id}`,
|
|
1874
|
+
stream: "stdout"
|
|
1875
|
+
});
|
|
1876
|
+
effects.push({
|
|
1877
|
+
type: "log",
|
|
1878
|
+
message: ` gitUrl=${r.gitUrl}`,
|
|
1879
|
+
stream: "stdout"
|
|
1880
|
+
});
|
|
1881
|
+
effects.push({
|
|
1882
|
+
type: "log",
|
|
1883
|
+
message: ` gitSshUrl=${r.gitSshUrl ?? "(none)"}`,
|
|
1884
|
+
stream: "stdout"
|
|
1885
|
+
});
|
|
1886
|
+
effects.push({
|
|
1887
|
+
type: "log",
|
|
1888
|
+
message: ` url=${r.url}`,
|
|
1889
|
+
stream: "stdout"
|
|
1890
|
+
});
|
|
1891
|
+
effects.push({
|
|
1892
|
+
type: "log",
|
|
1893
|
+
message: ` hasTask=${r.hasTask}`,
|
|
1894
|
+
stream: "stdout"
|
|
1895
|
+
});
|
|
1896
|
+
if (r.agentProvider) {
|
|
1897
|
+
effects.push({
|
|
1898
|
+
type: "log",
|
|
1899
|
+
message: ` agentProvider=${r.agentProvider}`,
|
|
1900
|
+
stream: "stdout"
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
effects.push({
|
|
1906
|
+
type: "syncUI",
|
|
1907
|
+
repositories: repos
|
|
1908
|
+
});
|
|
1909
|
+
effects.push({
|
|
1910
|
+
type: "handleRepositoryList",
|
|
1911
|
+
repositories: repos
|
|
1912
|
+
});
|
|
1913
|
+
break;
|
|
1914
|
+
}
|
|
1915
|
+
case "task-available": {
|
|
1916
|
+
const repoName = message.repository;
|
|
1917
|
+
effects.push({
|
|
1918
|
+
type: "log",
|
|
1919
|
+
message: `Received task-available for ${repoName}`,
|
|
1920
|
+
stream: "stdout"
|
|
1921
|
+
});
|
|
1922
|
+
if (state.repositoryNames.includes(repoName)) {
|
|
1923
|
+
effects.push({
|
|
1924
|
+
type: "signalTaskAvailable",
|
|
1925
|
+
repositoryName: repoName
|
|
1926
|
+
});
|
|
1927
|
+
} else {
|
|
1928
|
+
effects.push({
|
|
1929
|
+
type: "log",
|
|
1930
|
+
message: `No repository state found for ${repoName}`,
|
|
1931
|
+
stream: "stderr"
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
break;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
return { effects };
|
|
1938
|
+
}
|
|
1939
|
+
function handleMessageParseError(rawData) {
|
|
1940
|
+
return {
|
|
1941
|
+
effects: [
|
|
1942
|
+
{
|
|
1943
|
+
type: "log",
|
|
1944
|
+
message: `Failed to parse WebSocket message: ${rawData}`,
|
|
1945
|
+
stream: "stderr"
|
|
1946
|
+
}
|
|
1947
|
+
]
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
function handleInvalidMessageFormat(rawData) {
|
|
1951
|
+
return {
|
|
1952
|
+
effects: [
|
|
1953
|
+
{
|
|
1954
|
+
type: "log",
|
|
1955
|
+
message: `Invalid WebSocket message format: ${rawData}`,
|
|
1956
|
+
stream: "stderr"
|
|
1957
|
+
}
|
|
1958
|
+
]
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
var KEYS = {
|
|
1962
|
+
UP: "\x1B[A",
|
|
1963
|
+
DOWN: "\x1B[B",
|
|
1964
|
+
RIGHT: "\x1B[C",
|
|
1965
|
+
LEFT: "\x1B[D",
|
|
1966
|
+
PAGE_UP: "\x1B[5~",
|
|
1967
|
+
PAGE_DOWN: "\x1B[6~",
|
|
1968
|
+
HOME: "\x1B[H",
|
|
1969
|
+
END: "\x1B[F",
|
|
1970
|
+
CTRL_C: "\x03"
|
|
1971
|
+
};
|
|
1972
|
+
var SGR_MOUSE_RE = new RegExp(String.raw`^\x1b\[<(\d+);\d+;\d+[Mm]$`);
|
|
1973
|
+
function parseSGRMouse(key) {
|
|
1974
|
+
const match = key.match(SGR_MOUSE_RE);
|
|
1975
|
+
if (!match)
|
|
1976
|
+
return null;
|
|
1977
|
+
return Number.parseInt(match[1], 10);
|
|
1978
|
+
}
|
|
1979
|
+
function handleKeypress(state, key) {
|
|
1980
|
+
const effects = [];
|
|
1981
|
+
const mouseButton = parseSGRMouse(key);
|
|
1982
|
+
if (mouseButton !== null) {
|
|
1983
|
+
if (mouseButton === 64) {
|
|
1984
|
+
effects.push({ type: "scroll", direction: "up" });
|
|
1985
|
+
effects.push({ type: "scroll", direction: "up" });
|
|
1986
|
+
effects.push({ type: "scroll", direction: "up" });
|
|
1987
|
+
} else if (mouseButton === 65) {
|
|
1988
|
+
effects.push({ type: "scroll", direction: "down" });
|
|
1989
|
+
effects.push({ type: "scroll", direction: "down" });
|
|
1990
|
+
effects.push({ type: "scroll", direction: "down" });
|
|
1991
|
+
}
|
|
1992
|
+
return { effects };
|
|
1993
|
+
}
|
|
1994
|
+
switch (key) {
|
|
1995
|
+
case "q":
|
|
1996
|
+
case KEYS.CTRL_C:
|
|
1997
|
+
effects.push({ type: "quit" });
|
|
1998
|
+
break;
|
|
1999
|
+
case KEYS.LEFT:
|
|
2000
|
+
effects.push({ type: "selectPrevious" });
|
|
2001
|
+
break;
|
|
2002
|
+
case KEYS.RIGHT:
|
|
2003
|
+
effects.push({ type: "selectNext" });
|
|
2004
|
+
break;
|
|
2005
|
+
case KEYS.UP:
|
|
2006
|
+
effects.push({ type: "scroll", direction: "up" });
|
|
2007
|
+
break;
|
|
2008
|
+
case KEYS.DOWN:
|
|
2009
|
+
effects.push({ type: "scroll", direction: "down" });
|
|
2010
|
+
break;
|
|
2011
|
+
case KEYS.PAGE_UP:
|
|
2012
|
+
effects.push({ type: "scroll", direction: "pageUp" });
|
|
2013
|
+
break;
|
|
2014
|
+
case KEYS.PAGE_DOWN:
|
|
2015
|
+
effects.push({ type: "scroll", direction: "pageDown" });
|
|
2016
|
+
break;
|
|
2017
|
+
case "g":
|
|
2018
|
+
case KEYS.HOME:
|
|
2019
|
+
effects.push({ type: "scroll", direction: "top" });
|
|
2020
|
+
break;
|
|
2021
|
+
case "G":
|
|
2022
|
+
case KEYS.END:
|
|
2023
|
+
effects.push({ type: "scroll", direction: "bottom" });
|
|
2024
|
+
break;
|
|
2025
|
+
case "o": {
|
|
2026
|
+
if (state.selectedIndex === -1) {
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
const repoName = state.repositories[state.selectedIndex];
|
|
2030
|
+
if (!repoName)
|
|
2031
|
+
break;
|
|
2032
|
+
const url = state.repositoryUrls[repoName];
|
|
2033
|
+
if (url) {
|
|
2034
|
+
effects.push({ type: "openBrowser", url });
|
|
2035
|
+
}
|
|
2036
|
+
break;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
return { effects };
|
|
2040
|
+
}
|
|
2041
|
+
var INITIAL_RECONNECT_DELAY_MS = 1000;
|
|
2042
|
+
var MAX_RECONNECT_DELAY_MS = 30000;
|
|
2043
|
+
function handleClose(state, code, reason) {
|
|
2044
|
+
const effects = [];
|
|
2045
|
+
effects.push({
|
|
2046
|
+
type: "log",
|
|
2047
|
+
message: `bucket.disconnected code=${code} reason=${reason || "none"}`,
|
|
2048
|
+
stream: "stdout"
|
|
2049
|
+
});
|
|
2050
|
+
if (code === 4000) {
|
|
2051
|
+
effects.push({
|
|
2052
|
+
type: "log",
|
|
2053
|
+
message: "Another connection replaced this one. Not reconnecting.",
|
|
2054
|
+
stream: "stdout"
|
|
2055
|
+
});
|
|
2056
|
+
return { state, effects };
|
|
2057
|
+
}
|
|
2058
|
+
if (!state.shuttingDown) {
|
|
2059
|
+
effects.push({
|
|
2060
|
+
type: "log",
|
|
2061
|
+
message: `Reconnecting in ${state.reconnectDelay / 1000} seconds...`,
|
|
2062
|
+
stream: "stdout"
|
|
2063
|
+
});
|
|
2064
|
+
effects.push({
|
|
2065
|
+
type: "scheduleReconnect",
|
|
2066
|
+
delayMs: state.reconnectDelay
|
|
2067
|
+
});
|
|
2068
|
+
const nextDelay = Math.min(state.reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
|
|
2069
|
+
return {
|
|
2070
|
+
state: { ...state, reconnectDelay: nextDelay },
|
|
2071
|
+
effects
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
return { state, effects };
|
|
2075
|
+
}
|
|
2076
|
+
function handleError(state, errorMessage) {
|
|
2077
|
+
return {
|
|
2078
|
+
state,
|
|
2079
|
+
effects: [
|
|
2080
|
+
{
|
|
2081
|
+
type: "log",
|
|
2082
|
+
message: `WebSocket error: ${errorMessage}`,
|
|
2083
|
+
stream: "stderr"
|
|
2084
|
+
}
|
|
2085
|
+
]
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
function handleOpen(state) {
|
|
2089
|
+
return {
|
|
2090
|
+
state: { ...state, reconnectDelay: INITIAL_RECONNECT_DELAY_MS },
|
|
2091
|
+
effects: [
|
|
2092
|
+
{
|
|
2093
|
+
type: "log",
|
|
2094
|
+
message: "bucket.connected",
|
|
2095
|
+
stream: "stdout"
|
|
2096
|
+
}
|
|
2097
|
+
]
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
|
|
1842
2101
|
// lib/bucket/events.ts
|
|
1843
2102
|
var WS_OPEN = 1;
|
|
1844
2103
|
function formatBucketEvent(event) {
|
|
@@ -1964,7 +2223,8 @@ class FileSink {
|
|
|
1964
2223
|
|
|
1965
2224
|
// lib/logging/index.ts
|
|
1966
2225
|
var DUST_LOG_FILE = "DUST_LOG_FILE";
|
|
1967
|
-
function createLoggingService() {
|
|
2226
|
+
function createLoggingService(options) {
|
|
2227
|
+
const writeStdout = options?.stdout ?? process.stdout.write.bind(process.stdout);
|
|
1968
2228
|
let patterns = null;
|
|
1969
2229
|
let initialized = false;
|
|
1970
2230
|
let activeFileSink = null;
|
|
@@ -1994,12 +2254,12 @@ function createLoggingService() {
|
|
|
1994
2254
|
}
|
|
1995
2255
|
activeFileSink = sinkForTesting ?? new FileSink(path);
|
|
1996
2256
|
},
|
|
1997
|
-
createLogger(name,
|
|
2257
|
+
createLogger(name, options2) {
|
|
1998
2258
|
let perLoggerSink;
|
|
1999
|
-
if (
|
|
2259
|
+
if (options2?.file === false) {
|
|
2000
2260
|
perLoggerSink = null;
|
|
2001
|
-
} else if (typeof
|
|
2002
|
-
perLoggerSink = getOrCreateFileSink(
|
|
2261
|
+
} else if (typeof options2?.file === "string") {
|
|
2262
|
+
perLoggerSink = getOrCreateFileSink(options2.file);
|
|
2003
2263
|
}
|
|
2004
2264
|
return (...messages) => {
|
|
2005
2265
|
init();
|
|
@@ -2012,7 +2272,7 @@ function createLoggingService() {
|
|
|
2012
2272
|
activeFileSink.write(line);
|
|
2013
2273
|
}
|
|
2014
2274
|
if (patterns && matchesAny(name, patterns)) {
|
|
2015
|
-
|
|
2275
|
+
writeStdout(line);
|
|
2016
2276
|
}
|
|
2017
2277
|
};
|
|
2018
2278
|
},
|
|
@@ -2550,19 +2810,19 @@ import os2 from "node:os";
|
|
|
2550
2810
|
import path3 from "node:path";
|
|
2551
2811
|
|
|
2552
2812
|
// lib/agent-events.ts
|
|
2553
|
-
function rawEventToAgentEvent(rawEvent) {
|
|
2813
|
+
function rawEventToAgentEvent(rawEvent, provider) {
|
|
2554
2814
|
if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
|
|
2555
2815
|
return { type: "agent-session-activity" };
|
|
2556
2816
|
}
|
|
2557
|
-
return { type: "
|
|
2817
|
+
return { type: "agent-event", provider, rawEvent };
|
|
2558
2818
|
}
|
|
2559
2819
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
|
|
2560
|
-
function createHeartbeatThrottler(onAgentEvent, options) {
|
|
2820
|
+
function createHeartbeatThrottler(onAgentEvent, provider, options) {
|
|
2561
2821
|
const intervalMs = options?.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
2562
2822
|
const now = options?.now ?? Date.now;
|
|
2563
2823
|
let lastHeartbeatTime;
|
|
2564
2824
|
return (rawEvent) => {
|
|
2565
|
-
const event = rawEventToAgentEvent(rawEvent);
|
|
2825
|
+
const event = rawEventToAgentEvent(rawEvent, provider);
|
|
2566
2826
|
if (event.type === "agent-session-activity") {
|
|
2567
2827
|
const currentTime = now();
|
|
2568
2828
|
if (lastHeartbeatTime !== undefined && currentTime - lastHeartbeatTime < intervalMs) {
|
|
@@ -2587,7 +2847,7 @@ function formatAgentEvent(event) {
|
|
|
2587
2847
|
case "agent-session-ended":
|
|
2588
2848
|
return event.success ? "\uD83E\uDD16 Agent session ended (success)" : `\uD83E\uDD16 Agent session ended (error: ${event.error})`;
|
|
2589
2849
|
case "agent-session-activity":
|
|
2590
|
-
case "
|
|
2850
|
+
case "agent-event":
|
|
2591
2851
|
return null;
|
|
2592
2852
|
}
|
|
2593
2853
|
}
|
|
@@ -3089,7 +3349,7 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
3089
3349
|
log2(`found ${tasks.length} task(s), picking: ${task.title ?? task.path}`);
|
|
3090
3350
|
onLoopEvent({ type: "loop.tasks_found" });
|
|
3091
3351
|
const taskContent = await dependencies.fileSystem.readFile(`${dependencies.context.cwd}/${task.path}`);
|
|
3092
|
-
const { dustCommand, installCommand
|
|
3352
|
+
const { dustCommand, installCommand } = dependencies.settings;
|
|
3093
3353
|
const instructions = buildImplementationInstructions(dustCommand, hooksInstalled, task.title ?? undefined, task.path, installCommand);
|
|
3094
3354
|
const prompt = `Implement the task at \`${task.path}\`:
|
|
3095
3355
|
|
|
@@ -3226,7 +3486,7 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
3226
3486
|
docker: dockerConfig
|
|
3227
3487
|
};
|
|
3228
3488
|
if (eventsUrl) {
|
|
3229
|
-
iterationOptions.onRawEvent = createHeartbeatThrottler(onAgentEvent);
|
|
3489
|
+
iterationOptions.onRawEvent = createHeartbeatThrottler(onAgentEvent, loopDependencies.agentType ?? "claude");
|
|
3230
3490
|
}
|
|
3231
3491
|
while (completedIterations < maxIterations) {
|
|
3232
3492
|
agentSessionId = crypto.randomUUID();
|
|
@@ -3509,6 +3769,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3509
3769
|
};
|
|
3510
3770
|
const isCodex = repoState.repository.agentProvider === "codex";
|
|
3511
3771
|
const agentType = isCodex ? "codex" : "claude";
|
|
3772
|
+
log3(`${repoName}: agentProvider=${repoState.repository.agentProvider ?? "(unset)"}, using ${agentType}`);
|
|
3512
3773
|
const createStdoutSink2 = () => createBufferStdoutSink(loopState, repoState.logBuffer);
|
|
3513
3774
|
let bufferRun;
|
|
3514
3775
|
if (isCodex) {
|
|
@@ -3592,7 +3853,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3592
3853
|
hooksInstalled,
|
|
3593
3854
|
signal: abortController.signal,
|
|
3594
3855
|
repositoryId: repoState.repository.id.toString(),
|
|
3595
|
-
onRawEvent: createHeartbeatThrottler(onAgentEvent),
|
|
3856
|
+
onRawEvent: createHeartbeatThrottler(onAgentEvent, agentType),
|
|
3596
3857
|
docker: dockerConfig
|
|
3597
3858
|
});
|
|
3598
3859
|
} catch (error) {
|
|
@@ -3652,7 +3913,8 @@ function parseRepository(data) {
|
|
|
3652
3913
|
gitUrl: repositoryData.gitUrl,
|
|
3653
3914
|
gitSshUrl: typeof repositoryData.gitSshUrl === "string" ? repositoryData.gitSshUrl : undefined,
|
|
3654
3915
|
url: repositoryData.url,
|
|
3655
|
-
id: repositoryData.id
|
|
3916
|
+
id: repositoryData.id,
|
|
3917
|
+
agentProvider: typeof repositoryData.agentProvider === "string" ? repositoryData.agentProvider : undefined
|
|
3656
3918
|
};
|
|
3657
3919
|
}
|
|
3658
3920
|
}
|
|
@@ -3730,6 +3992,16 @@ async function handleRepositoryList(repositories, manager, repoDeps, context) {
|
|
|
3730
3992
|
for (const [name, repo] of incomingRepos) {
|
|
3731
3993
|
if (!manager.repositories.has(name)) {
|
|
3732
3994
|
await addRepository(repo, manager, repoDeps, context);
|
|
3995
|
+
} else {
|
|
3996
|
+
const existing = manager.repositories.get(name);
|
|
3997
|
+
if (!existing)
|
|
3998
|
+
continue;
|
|
3999
|
+
if (existing.repository.agentProvider !== repo.agentProvider) {
|
|
4000
|
+
const from = existing.repository.agentProvider ?? "(unset)";
|
|
4001
|
+
const to = repo.agentProvider ?? "(unset)";
|
|
4002
|
+
log4(`${name}: agentProvider changed from ${from} to ${to}`);
|
|
4003
|
+
existing.repository.agentProvider = repo.agentProvider;
|
|
4004
|
+
}
|
|
3733
4005
|
}
|
|
3734
4006
|
}
|
|
3735
4007
|
for (const name of manager.repositories.keys()) {
|
|
@@ -4135,90 +4407,37 @@ function enterAlternateScreen() {
|
|
|
4135
4407
|
function exitAlternateScreen() {
|
|
4136
4408
|
return `\x1B[?1006l\x1B[?1000l${ANSI.EXIT_ALT_SCREEN}${ANSI.SHOW_CURSOR}`;
|
|
4137
4409
|
}
|
|
4138
|
-
var
|
|
4139
|
-
UP: "\x1B[A",
|
|
4140
|
-
DOWN: "\x1B[B",
|
|
4141
|
-
RIGHT: "\x1B[C",
|
|
4142
|
-
LEFT: "\x1B[D",
|
|
4143
|
-
PAGE_UP: "\x1B[5~",
|
|
4144
|
-
PAGE_DOWN: "\x1B[6~",
|
|
4145
|
-
HOME: "\x1B[H",
|
|
4146
|
-
END: "\x1B[F",
|
|
4147
|
-
CTRL_C: "\x03"
|
|
4148
|
-
};
|
|
4149
|
-
var SGR_MOUSE_RE = new RegExp(String.raw`^\x1b\[<(\d+);\d+;\d+[Mm]$`);
|
|
4150
|
-
function parseSGRMouse(key) {
|
|
4151
|
-
const match = key.match(SGR_MOUSE_RE);
|
|
4152
|
-
if (!match)
|
|
4153
|
-
return null;
|
|
4154
|
-
return Number.parseInt(match[1], 10);
|
|
4155
|
-
}
|
|
4156
|
-
function handleKeyInput(state, key, options) {
|
|
4157
|
-
const mouseButton = parseSGRMouse(key);
|
|
4158
|
-
if (mouseButton !== null) {
|
|
4159
|
-
if (mouseButton === 64) {
|
|
4160
|
-
scrollUp(state, 3);
|
|
4161
|
-
} else if (mouseButton === 65) {
|
|
4162
|
-
scrollDown(state, 3);
|
|
4163
|
-
}
|
|
4164
|
-
return false;
|
|
4165
|
-
}
|
|
4166
|
-
switch (key) {
|
|
4167
|
-
case "q":
|
|
4168
|
-
case KEYS.CTRL_C:
|
|
4169
|
-
return true;
|
|
4170
|
-
case KEYS.LEFT:
|
|
4171
|
-
selectPrevious(state);
|
|
4172
|
-
state.scrollOffset = 0;
|
|
4173
|
-
state.autoScroll = true;
|
|
4174
|
-
break;
|
|
4175
|
-
case KEYS.RIGHT:
|
|
4176
|
-
selectNext(state);
|
|
4177
|
-
state.scrollOffset = 0;
|
|
4178
|
-
state.autoScroll = true;
|
|
4179
|
-
break;
|
|
4180
|
-
case KEYS.UP:
|
|
4181
|
-
scrollUp(state, 1);
|
|
4182
|
-
break;
|
|
4183
|
-
case KEYS.DOWN:
|
|
4184
|
-
scrollDown(state, 1);
|
|
4185
|
-
break;
|
|
4186
|
-
case KEYS.PAGE_UP:
|
|
4187
|
-
scrollUp(state, getLogAreaHeight(state));
|
|
4188
|
-
break;
|
|
4189
|
-
case KEYS.PAGE_DOWN:
|
|
4190
|
-
scrollDown(state, getLogAreaHeight(state));
|
|
4191
|
-
break;
|
|
4192
|
-
case "g":
|
|
4193
|
-
case KEYS.HOME:
|
|
4194
|
-
scrollToTop(state);
|
|
4195
|
-
break;
|
|
4196
|
-
case "G":
|
|
4197
|
-
case KEYS.END:
|
|
4198
|
-
scrollToBottom(state);
|
|
4199
|
-
break;
|
|
4200
|
-
case "o": {
|
|
4201
|
-
if (state.selectedIndex === -1) {
|
|
4202
|
-
break;
|
|
4203
|
-
}
|
|
4204
|
-
const repoName = state.repositories[state.selectedIndex];
|
|
4205
|
-
if (!repoName)
|
|
4206
|
-
break;
|
|
4207
|
-
const url = state.repositoryUrls.get(repoName);
|
|
4208
|
-
if (url && options?.openBrowser) {
|
|
4209
|
-
options.openBrowser(url);
|
|
4210
|
-
}
|
|
4211
|
-
break;
|
|
4212
|
-
}
|
|
4213
|
-
}
|
|
4214
|
-
return false;
|
|
4215
|
-
}
|
|
4410
|
+
var SGR_MOUSE_RE2 = new RegExp(String.raw`^\x1b\[<(\d+);\d+;\d+[Mm]$`);
|
|
4216
4411
|
|
|
4217
4412
|
// lib/cli/commands/bucket.ts
|
|
4218
4413
|
var log5 = createLogger("dust:cli:commands:bucket");
|
|
4219
4414
|
var DEFAULT_DUSTBUCKET_WS_URL = "wss://dustbucket.com/agent/connect";
|
|
4220
|
-
|
|
4221
|
-
|
|
4415
|
+
function createAuthFileSystem(dependencies) {
|
|
4416
|
+
return {
|
|
4417
|
+
exists: (path4) => {
|
|
4418
|
+
try {
|
|
4419
|
+
dependencies.accessSync(path4);
|
|
4420
|
+
return true;
|
|
4421
|
+
} catch {
|
|
4422
|
+
return false;
|
|
4423
|
+
}
|
|
4424
|
+
},
|
|
4425
|
+
isDirectory: (path4) => {
|
|
4426
|
+
try {
|
|
4427
|
+
return dependencies.statSync(path4).isDirectory();
|
|
4428
|
+
} catch {
|
|
4429
|
+
return false;
|
|
4430
|
+
}
|
|
4431
|
+
},
|
|
4432
|
+
getFileCreationTime: (path4) => dependencies.statSync(path4).birthtimeMs,
|
|
4433
|
+
readFile: (path4) => dependencies.readFile(path4, "utf8"),
|
|
4434
|
+
writeFile: (path4, content) => dependencies.writeFile(path4, content, "utf8"),
|
|
4435
|
+
mkdir: (path4, options) => dependencies.mkdir(path4, options).then(() => {}),
|
|
4436
|
+
readdir: (path4) => dependencies.readdir(path4),
|
|
4437
|
+
chmod: (path4, mode) => dependencies.chmod(path4, mode),
|
|
4438
|
+
rename: (oldPath, newPath) => dependencies.rename(oldPath, newPath)
|
|
4439
|
+
};
|
|
4440
|
+
}
|
|
4222
4441
|
function defaultCreateWebSocket(url, token) {
|
|
4223
4442
|
const ws = new WebSocket(url, {
|
|
4224
4443
|
headers: {
|
|
@@ -4273,32 +4492,6 @@ function defaultGetTerminalSize() {
|
|
|
4273
4492
|
function defaultWriteStdout(data) {
|
|
4274
4493
|
process.stdout.write(data);
|
|
4275
4494
|
}
|
|
4276
|
-
function createAuthFileSystem(dependencies) {
|
|
4277
|
-
return {
|
|
4278
|
-
exists: (path4) => {
|
|
4279
|
-
try {
|
|
4280
|
-
dependencies.accessSync(path4);
|
|
4281
|
-
return true;
|
|
4282
|
-
} catch {
|
|
4283
|
-
return false;
|
|
4284
|
-
}
|
|
4285
|
-
},
|
|
4286
|
-
isDirectory: (path4) => {
|
|
4287
|
-
try {
|
|
4288
|
-
return dependencies.statSync(path4).isDirectory();
|
|
4289
|
-
} catch {
|
|
4290
|
-
return false;
|
|
4291
|
-
}
|
|
4292
|
-
},
|
|
4293
|
-
getFileCreationTime: (path4) => dependencies.statSync(path4).birthtimeMs,
|
|
4294
|
-
readFile: (path4) => dependencies.readFile(path4, "utf8"),
|
|
4295
|
-
writeFile: (path4, content) => dependencies.writeFile(path4, content, "utf8"),
|
|
4296
|
-
mkdir: (path4, options) => dependencies.mkdir(path4, options).then(() => {}),
|
|
4297
|
-
readdir: (path4) => dependencies.readdir(path4),
|
|
4298
|
-
chmod: (path4, mode) => dependencies.chmod(path4, mode),
|
|
4299
|
-
rename: (oldPath, newPath) => dependencies.rename(oldPath, newPath)
|
|
4300
|
-
};
|
|
4301
|
-
}
|
|
4302
4495
|
function createDefaultBucketDependencies() {
|
|
4303
4496
|
const authFileSystem = createAuthFileSystem({
|
|
4304
4497
|
accessSync,
|
|
@@ -4469,8 +4662,13 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
4469
4662
|
state.ws = ws;
|
|
4470
4663
|
ws.onopen = () => {
|
|
4471
4664
|
state.emit({ type: "bucket.connected" });
|
|
4472
|
-
|
|
4473
|
-
|
|
4665
|
+
const lifecycleState = {
|
|
4666
|
+
reconnectDelay: state.reconnectDelay,
|
|
4667
|
+
shuttingDown: state.shuttingDown
|
|
4668
|
+
};
|
|
4669
|
+
const result = handleOpen(lifecycleState);
|
|
4670
|
+
state.reconnectDelay = result.state.reconnectDelay;
|
|
4671
|
+
executeLifecycleEffects(result.effects, { state, context, useTUI, bucketDependencies, fileSystem }, token);
|
|
4474
4672
|
};
|
|
4475
4673
|
}
|
|
4476
4674
|
ws.onclose = (event) => {
|
|
@@ -4480,80 +4678,122 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
4480
4678
|
reason: event.reason || "none"
|
|
4481
4679
|
};
|
|
4482
4680
|
state.emit(disconnectEvent);
|
|
4483
|
-
logMessage(state, context, useTUI, formatBucketEvent(disconnectEvent));
|
|
4484
4681
|
state.ws = null;
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
}
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
connectWebSocket(token, state, bucketDependencies, context, fileSystem, useTUI);
|
|
4493
|
-
}, state.reconnectDelay);
|
|
4494
|
-
state.reconnectDelay = Math.min(state.reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
|
|
4495
|
-
}
|
|
4682
|
+
const lifecycleState = {
|
|
4683
|
+
reconnectDelay: state.reconnectDelay,
|
|
4684
|
+
shuttingDown: state.shuttingDown
|
|
4685
|
+
};
|
|
4686
|
+
const result = handleClose(lifecycleState, event.code, event.reason || "");
|
|
4687
|
+
state.reconnectDelay = result.state.reconnectDelay;
|
|
4688
|
+
executeLifecycleEffects(result.effects, { state, context, useTUI, bucketDependencies, fileSystem }, token);
|
|
4496
4689
|
};
|
|
4497
4690
|
ws.onerror = (error) => {
|
|
4498
|
-
|
|
4691
|
+
const lifecycleState = {
|
|
4692
|
+
reconnectDelay: state.reconnectDelay,
|
|
4693
|
+
shuttingDown: state.shuttingDown
|
|
4694
|
+
};
|
|
4695
|
+
const result = handleError(lifecycleState, error.message);
|
|
4696
|
+
executeLifecycleEffects(result.effects, { state, context, useTUI, bucketDependencies, fileSystem }, token);
|
|
4499
4697
|
};
|
|
4500
4698
|
ws.onmessage = (event) => {
|
|
4501
4699
|
let rawData;
|
|
4502
4700
|
try {
|
|
4503
4701
|
rawData = JSON.parse(event.data);
|
|
4504
4702
|
} catch {
|
|
4505
|
-
|
|
4703
|
+
const result2 = handleMessageParseError(event.data);
|
|
4704
|
+
executeEffects(result2.effects, {
|
|
4705
|
+
state,
|
|
4706
|
+
context,
|
|
4707
|
+
useTUI,
|
|
4708
|
+
bucketDependencies,
|
|
4709
|
+
fileSystem
|
|
4710
|
+
});
|
|
4506
4711
|
return;
|
|
4507
4712
|
}
|
|
4508
4713
|
const message = parseServerMessage(rawData);
|
|
4509
4714
|
if (!message) {
|
|
4510
|
-
|
|
4715
|
+
const result2 = handleInvalidMessageFormat(event.data);
|
|
4716
|
+
executeEffects(result2.effects, {
|
|
4717
|
+
state,
|
|
4718
|
+
context,
|
|
4719
|
+
useTUI,
|
|
4720
|
+
bucketDependencies,
|
|
4721
|
+
fileSystem
|
|
4722
|
+
});
|
|
4511
4723
|
return;
|
|
4512
4724
|
}
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4725
|
+
const handlerState = {
|
|
4726
|
+
repositoryNames: Array.from(state.repositories.keys())
|
|
4727
|
+
};
|
|
4728
|
+
const result = handleServerMessage(handlerState, message);
|
|
4729
|
+
executeEffects(result.effects, {
|
|
4730
|
+
state,
|
|
4731
|
+
context,
|
|
4732
|
+
useTUI,
|
|
4733
|
+
bucketDependencies,
|
|
4734
|
+
fileSystem
|
|
4735
|
+
});
|
|
4736
|
+
};
|
|
4737
|
+
}
|
|
4738
|
+
function executeEffects(effects, dependencies) {
|
|
4739
|
+
const { state, context, useTUI, bucketDependencies, fileSystem } = dependencies;
|
|
4740
|
+
for (const effect of effects) {
|
|
4741
|
+
switch (effect.type) {
|
|
4742
|
+
case "log":
|
|
4743
|
+
logMessage(state, context, useTUI, effect.message, effect.stream);
|
|
4744
|
+
break;
|
|
4745
|
+
case "debugLog":
|
|
4746
|
+
log5(effect.message);
|
|
4747
|
+
break;
|
|
4748
|
+
case "syncUI":
|
|
4749
|
+
syncUIWithRepoList(state, effect.repositories);
|
|
4750
|
+
break;
|
|
4751
|
+
case "handleRepositoryList": {
|
|
4752
|
+
const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
|
|
4753
|
+
const repoContext = createTUIContext(state, context, useTUI);
|
|
4754
|
+
const repos = effect.repositories;
|
|
4755
|
+
handleRepositoryList(repos, state, repoDeps, repoContext).then(() => {
|
|
4756
|
+
syncTUI(state);
|
|
4757
|
+
for (const repoData of repos) {
|
|
4758
|
+
if (repoData.hasTask) {
|
|
4759
|
+
const repoState = state.repositories.get(repoData.name);
|
|
4760
|
+
if (repoState) {
|
|
4761
|
+
signalTaskAvailable(repoState, state, repoDeps, context, useTUI);
|
|
4762
|
+
}
|
|
4539
4763
|
}
|
|
4540
4764
|
}
|
|
4765
|
+
}).catch((error) => {
|
|
4766
|
+
logMessage(state, context, useTUI, `Failed to handle repository list: ${error.message}`, "stderr");
|
|
4767
|
+
});
|
|
4768
|
+
break;
|
|
4769
|
+
}
|
|
4770
|
+
case "signalTaskAvailable": {
|
|
4771
|
+
const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
|
|
4772
|
+
const repoState = state.repositories.get(effect.repositoryName);
|
|
4773
|
+
if (repoState) {
|
|
4774
|
+
signalTaskAvailable(repoState, state, repoDeps, context, useTUI);
|
|
4541
4775
|
}
|
|
4542
|
-
|
|
4543
|
-
logMessage(state, context, useTUI, `Failed to handle repository list: ${error.message}`, "stderr");
|
|
4544
|
-
});
|
|
4545
|
-
} else if (message.type === "task-available") {
|
|
4546
|
-
const repoName = message.repository;
|
|
4547
|
-
const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
|
|
4548
|
-
logMessage(state, context, useTUI, `Received task-available for ${repoName}`);
|
|
4549
|
-
const repoState = state.repositories.get(repoName);
|
|
4550
|
-
if (repoState) {
|
|
4551
|
-
signalTaskAvailable(repoState, state, repoDeps, context, useTUI);
|
|
4552
|
-
} else {
|
|
4553
|
-
logMessage(state, context, useTUI, `No repository state found for ${repoName}`, "stderr");
|
|
4776
|
+
break;
|
|
4554
4777
|
}
|
|
4778
|
+
case "scheduleReconnect":
|
|
4779
|
+
break;
|
|
4555
4780
|
}
|
|
4556
|
-
}
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
function executeLifecycleEffects(effects, dependencies, token) {
|
|
4784
|
+
const { state, context, useTUI, bucketDependencies, fileSystem } = dependencies;
|
|
4785
|
+
for (const effect of effects) {
|
|
4786
|
+
switch (effect.type) {
|
|
4787
|
+
case "log":
|
|
4788
|
+
logMessage(state, context, useTUI, effect.message, effect.stream);
|
|
4789
|
+
break;
|
|
4790
|
+
case "scheduleReconnect":
|
|
4791
|
+
state.reconnectTimer = setTimeout(() => {
|
|
4792
|
+
connectWebSocket(token, state, bucketDependencies, context, fileSystem, useTUI);
|
|
4793
|
+
}, effect.delayMs);
|
|
4794
|
+
break;
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4557
4797
|
}
|
|
4558
4798
|
async function shutdown(state, bucketDeps, context) {
|
|
4559
4799
|
if (state.shuttingDown)
|
|
@@ -4607,17 +4847,80 @@ function setupTUI(state, bucketDeps) {
|
|
|
4607
4847
|
}
|
|
4608
4848
|
};
|
|
4609
4849
|
}
|
|
4850
|
+
function createKeypressHandlerState(ui) {
|
|
4851
|
+
const repositoryUrls = {};
|
|
4852
|
+
for (const [name, url] of ui.repositoryUrls) {
|
|
4853
|
+
repositoryUrls[name] = url;
|
|
4854
|
+
}
|
|
4855
|
+
return {
|
|
4856
|
+
selectedIndex: ui.selectedIndex,
|
|
4857
|
+
repositories: ui.repositories,
|
|
4858
|
+
repositoryUrls
|
|
4859
|
+
};
|
|
4860
|
+
}
|
|
4861
|
+
function executeKeypressEffects(ui, effects, onQuit, options) {
|
|
4862
|
+
for (const effect of effects) {
|
|
4863
|
+
switch (effect.type) {
|
|
4864
|
+
case "quit":
|
|
4865
|
+
onQuit();
|
|
4866
|
+
break;
|
|
4867
|
+
case "openBrowser":
|
|
4868
|
+
if (options?.openBrowser) {
|
|
4869
|
+
options.openBrowser(effect.url);
|
|
4870
|
+
}
|
|
4871
|
+
break;
|
|
4872
|
+
case "selectNext":
|
|
4873
|
+
selectNext(ui);
|
|
4874
|
+
ui.scrollOffset = 0;
|
|
4875
|
+
ui.autoScroll = true;
|
|
4876
|
+
break;
|
|
4877
|
+
case "selectPrevious":
|
|
4878
|
+
selectPrevious(ui);
|
|
4879
|
+
ui.scrollOffset = 0;
|
|
4880
|
+
ui.autoScroll = true;
|
|
4881
|
+
break;
|
|
4882
|
+
case "scroll":
|
|
4883
|
+
switch (effect.direction) {
|
|
4884
|
+
case "up":
|
|
4885
|
+
scrollUp(ui, 1);
|
|
4886
|
+
break;
|
|
4887
|
+
case "down":
|
|
4888
|
+
scrollDown(ui, 1);
|
|
4889
|
+
break;
|
|
4890
|
+
case "pageUp":
|
|
4891
|
+
scrollUp(ui, getLogAreaHeight(ui));
|
|
4892
|
+
break;
|
|
4893
|
+
case "pageDown":
|
|
4894
|
+
scrollDown(ui, getLogAreaHeight(ui));
|
|
4895
|
+
break;
|
|
4896
|
+
case "top":
|
|
4897
|
+
scrollToTop(ui);
|
|
4898
|
+
break;
|
|
4899
|
+
case "bottom":
|
|
4900
|
+
scrollToBottom(ui);
|
|
4901
|
+
break;
|
|
4902
|
+
}
|
|
4903
|
+
break;
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4610
4907
|
function createKeypressHandler(useTUI, state, onQuit, options) {
|
|
4611
4908
|
if (useTUI) {
|
|
4612
4909
|
return (key) => {
|
|
4613
|
-
const
|
|
4614
|
-
|
|
4615
|
-
|
|
4910
|
+
const keypressState = createKeypressHandlerState(state.ui);
|
|
4911
|
+
const { effects } = handleKeypress(keypressState, key);
|
|
4912
|
+
executeKeypressEffects(state.ui, effects, onQuit, options);
|
|
4616
4913
|
};
|
|
4617
4914
|
}
|
|
4618
4915
|
return (key) => {
|
|
4619
|
-
|
|
4620
|
-
|
|
4916
|
+
const keypressState = createKeypressHandlerState(state.ui);
|
|
4917
|
+
const { effects } = handleKeypress(keypressState, key);
|
|
4918
|
+
for (const effect of effects) {
|
|
4919
|
+
if (effect.type === "quit") {
|
|
4920
|
+
onQuit();
|
|
4921
|
+
break;
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4621
4924
|
};
|
|
4622
4925
|
}
|
|
4623
4926
|
async function resolveToken(authDeps, context) {
|
|
@@ -4867,7 +5170,8 @@ async function bucketAssetUpload(dependencies, uploadDeps = createDefaultUploadD
|
|
|
4867
5170
|
}
|
|
4868
5171
|
const fileBytes = await uploadDeps.readFileBytes(filePath);
|
|
4869
5172
|
const contentType = getContentType(filePath);
|
|
4870
|
-
const
|
|
5173
|
+
const parts = filePath.split("/");
|
|
5174
|
+
const fileName = parts[parts.length - 1];
|
|
4871
5175
|
const uploadUrl = `${getDustbucketHost()}/api/assets?repositoryId=${encodeURIComponent(repositoryId)}`;
|
|
4872
5176
|
try {
|
|
4873
5177
|
const result = await uploadDeps.uploadFile(uploadUrl, token, fileBytes, contentType, fileName);
|
|
@@ -5800,13 +6104,13 @@ function truncateOutput(output) {
|
|
|
5800
6104
|
].join(`
|
|
5801
6105
|
`);
|
|
5802
6106
|
}
|
|
5803
|
-
async function runSingleCheck(check, cwd, runner, emitEvent) {
|
|
6107
|
+
async function runSingleCheck(check, cwd, runner, emitEvent, clock = Date.now) {
|
|
5804
6108
|
const timeoutMs = check.timeoutMilliseconds ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
5805
6109
|
log6(`running check ${check.name}: ${check.command}`);
|
|
5806
6110
|
emitEvent?.({ type: "check-started", name: check.name });
|
|
5807
|
-
const startTime =
|
|
6111
|
+
const startTime = clock();
|
|
5808
6112
|
const result = await runner.run(check.command, cwd, timeoutMs);
|
|
5809
|
-
const durationMs =
|
|
6113
|
+
const durationMs = clock() - startTime;
|
|
5810
6114
|
const status = result.timedOut ? "timed out" : result.exitCode === 0 ? "passed" : "failed";
|
|
5811
6115
|
log6(`check ${check.name} ${status} (${durationMs}ms)`);
|
|
5812
6116
|
if (result.exitCode === 0) {
|
|
@@ -5832,18 +6136,18 @@ async function runSingleCheck(check, cwd, runner, emitEvent) {
|
|
|
5832
6136
|
timeoutSeconds: timeoutMs / 1000
|
|
5833
6137
|
};
|
|
5834
6138
|
}
|
|
5835
|
-
async function runConfiguredChecks(checks, cwd, runner, emitEvent) {
|
|
5836
|
-
const promises = checks.map((check) => runSingleCheck(check, cwd, runner, emitEvent));
|
|
6139
|
+
async function runConfiguredChecks(checks, cwd, runner, emitEvent, clock = Date.now) {
|
|
6140
|
+
const promises = checks.map((check) => runSingleCheck(check, cwd, runner, emitEvent, clock));
|
|
5837
6141
|
return Promise.all(promises);
|
|
5838
6142
|
}
|
|
5839
|
-
async function runConfiguredChecksSerially(checks, cwd, runner, emitEvent) {
|
|
6143
|
+
async function runConfiguredChecksSerially(checks, cwd, runner, emitEvent, clock = Date.now) {
|
|
5840
6144
|
const results = [];
|
|
5841
6145
|
for (const check of checks) {
|
|
5842
|
-
results.push(await runSingleCheck(check, cwd, runner, emitEvent));
|
|
6146
|
+
results.push(await runSingleCheck(check, cwd, runner, emitEvent, clock));
|
|
5843
6147
|
}
|
|
5844
6148
|
return results;
|
|
5845
6149
|
}
|
|
5846
|
-
async function runValidationCheck(dependencies, emitEvent) {
|
|
6150
|
+
async function runValidationCheck(dependencies, emitEvent, clock = Date.now) {
|
|
5847
6151
|
const outputLines = [];
|
|
5848
6152
|
const bufferedContext = {
|
|
5849
6153
|
cwd: dependencies.context.cwd,
|
|
@@ -5852,13 +6156,13 @@ async function runValidationCheck(dependencies, emitEvent) {
|
|
|
5852
6156
|
};
|
|
5853
6157
|
log6("running built-in check: dust lint");
|
|
5854
6158
|
emitEvent?.({ type: "check-started", name: "lint" });
|
|
5855
|
-
const startTime =
|
|
6159
|
+
const startTime = clock();
|
|
5856
6160
|
const result = await lintMarkdown({
|
|
5857
6161
|
...dependencies,
|
|
5858
6162
|
context: bufferedContext,
|
|
5859
6163
|
arguments: []
|
|
5860
6164
|
});
|
|
5861
|
-
const durationMs =
|
|
6165
|
+
const durationMs = clock() - startTime;
|
|
5862
6166
|
const lintStatus = result.exitCode === 0 ? "passed" : "failed";
|
|
5863
6167
|
log6(`built-in check dust lint ${lintStatus} (${durationMs}ms)`);
|
|
5864
6168
|
const output = outputLines.join(`
|
|
@@ -5922,7 +6226,7 @@ function displayResults(results, context) {
|
|
|
5922
6226
|
context.stdout(`${indicator} ${passed.length}/${results.length} checks passed`);
|
|
5923
6227
|
return failed.length > 0 ? 1 : 0;
|
|
5924
6228
|
}
|
|
5925
|
-
async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
6229
|
+
async function check(dependencies, shellRunner = defaultShellRunner, clock = Date.now) {
|
|
5926
6230
|
const {
|
|
5927
6231
|
arguments: commandArguments,
|
|
5928
6232
|
context,
|
|
@@ -5948,18 +6252,18 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
|
5948
6252
|
if (serial) {
|
|
5949
6253
|
const results2 = [];
|
|
5950
6254
|
if (hasDustDir) {
|
|
5951
|
-
results2.push(await runValidationCheck(dependencies, context.emitEvent));
|
|
6255
|
+
results2.push(await runValidationCheck(dependencies, context.emitEvent, clock));
|
|
5952
6256
|
}
|
|
5953
|
-
const configuredResults = await runConfiguredChecksSerially(settings.checks, context.cwd, shellRunner, context.emitEvent);
|
|
6257
|
+
const configuredResults = await runConfiguredChecksSerially(settings.checks, context.cwd, shellRunner, context.emitEvent, clock);
|
|
5954
6258
|
results2.push(...configuredResults);
|
|
5955
6259
|
const exitCode2 = displayResults(results2, context);
|
|
5956
6260
|
return { exitCode: exitCode2 };
|
|
5957
6261
|
}
|
|
5958
6262
|
const checkPromises = [];
|
|
5959
6263
|
if (hasDustDir) {
|
|
5960
|
-
checkPromises.push(runValidationCheck(dependencies, context.emitEvent));
|
|
6264
|
+
checkPromises.push(runValidationCheck(dependencies, context.emitEvent, clock));
|
|
5961
6265
|
}
|
|
5962
|
-
checkPromises.push(runConfiguredChecks(settings.checks, context.cwd, shellRunner, context.emitEvent));
|
|
6266
|
+
checkPromises.push(runConfiguredChecks(settings.checks, context.cwd, shellRunner, context.emitEvent, clock));
|
|
5963
6267
|
const promiseResults = await Promise.all(checkPromises);
|
|
5964
6268
|
const results = [];
|
|
5965
6269
|
for (const result of promiseResults) {
|
package/dist/logging/index.d.ts
CHANGED
|
@@ -65,7 +65,10 @@ export interface LoggingService {
|
|
|
65
65
|
* Create an isolated logging service instance. All mutable state is
|
|
66
66
|
* encapsulated inside the returned object.
|
|
67
67
|
*/
|
|
68
|
-
export
|
|
68
|
+
export interface LoggingServiceOptions {
|
|
69
|
+
stdout?: (line: string) => boolean;
|
|
70
|
+
}
|
|
71
|
+
export declare function createLoggingService(options?: LoggingServiceOptions): LoggingService;
|
|
69
72
|
/**
|
|
70
73
|
* Activate file logging for this command. See {@link LoggingService.enableFileLogs}.
|
|
71
74
|
*/
|
package/dist/logging.js
CHANGED
|
@@ -60,7 +60,8 @@ class FileSink {
|
|
|
60
60
|
|
|
61
61
|
// lib/logging/index.ts
|
|
62
62
|
var DUST_LOG_FILE = "DUST_LOG_FILE";
|
|
63
|
-
function createLoggingService() {
|
|
63
|
+
function createLoggingService(options) {
|
|
64
|
+
const writeStdout = options?.stdout ?? process.stdout.write.bind(process.stdout);
|
|
64
65
|
let patterns = null;
|
|
65
66
|
let initialized = false;
|
|
66
67
|
let activeFileSink = null;
|
|
@@ -90,12 +91,12 @@ function createLoggingService() {
|
|
|
90
91
|
}
|
|
91
92
|
activeFileSink = sinkForTesting ?? new FileSink(path);
|
|
92
93
|
},
|
|
93
|
-
createLogger(name,
|
|
94
|
+
createLogger(name, options2) {
|
|
94
95
|
let perLoggerSink;
|
|
95
|
-
if (
|
|
96
|
+
if (options2?.file === false) {
|
|
96
97
|
perLoggerSink = null;
|
|
97
|
-
} else if (typeof
|
|
98
|
-
perLoggerSink = getOrCreateFileSink(
|
|
98
|
+
} else if (typeof options2?.file === "string") {
|
|
99
|
+
perLoggerSink = getOrCreateFileSink(options2.file);
|
|
99
100
|
}
|
|
100
101
|
return (...messages) => {
|
|
101
102
|
init();
|
|
@@ -108,7 +109,7 @@ function createLoggingService() {
|
|
|
108
109
|
activeFileSink.write(line);
|
|
109
110
|
}
|
|
110
111
|
if (patterns && matchesAny(name, patterns)) {
|
|
111
|
-
|
|
112
|
+
writeStdout(line);
|
|
112
113
|
}
|
|
113
114
|
};
|
|
114
115
|
},
|