@joshski/dust 0.1.84 → 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/dust.js +501 -198
- 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;
|
package/dist/dust.js
CHANGED
|
@@ -319,7 +319,7 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
// lib/version.ts
|
|
322
|
-
var DUST_VERSION = "0.1.
|
|
322
|
+
var DUST_VERSION = "0.1.85";
|
|
323
323
|
|
|
324
324
|
// lib/session.ts
|
|
325
325
|
var DUST_UNATTENDED = "DUST_UNATTENDED";
|
|
@@ -1840,6 +1840,264 @@ function openBrowser(url) {
|
|
|
1840
1840
|
nodeSpawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
1841
1841
|
}
|
|
1842
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
|
+
|
|
1843
2101
|
// lib/bucket/events.ts
|
|
1844
2102
|
var WS_OPEN = 1;
|
|
1845
2103
|
function formatBucketEvent(event) {
|
|
@@ -1965,7 +2223,8 @@ class FileSink {
|
|
|
1965
2223
|
|
|
1966
2224
|
// lib/logging/index.ts
|
|
1967
2225
|
var DUST_LOG_FILE = "DUST_LOG_FILE";
|
|
1968
|
-
function createLoggingService() {
|
|
2226
|
+
function createLoggingService(options) {
|
|
2227
|
+
const writeStdout = options?.stdout ?? process.stdout.write.bind(process.stdout);
|
|
1969
2228
|
let patterns = null;
|
|
1970
2229
|
let initialized = false;
|
|
1971
2230
|
let activeFileSink = null;
|
|
@@ -1995,12 +2254,12 @@ function createLoggingService() {
|
|
|
1995
2254
|
}
|
|
1996
2255
|
activeFileSink = sinkForTesting ?? new FileSink(path);
|
|
1997
2256
|
},
|
|
1998
|
-
createLogger(name,
|
|
2257
|
+
createLogger(name, options2) {
|
|
1999
2258
|
let perLoggerSink;
|
|
2000
|
-
if (
|
|
2259
|
+
if (options2?.file === false) {
|
|
2001
2260
|
perLoggerSink = null;
|
|
2002
|
-
} else if (typeof
|
|
2003
|
-
perLoggerSink = getOrCreateFileSink(
|
|
2261
|
+
} else if (typeof options2?.file === "string") {
|
|
2262
|
+
perLoggerSink = getOrCreateFileSink(options2.file);
|
|
2004
2263
|
}
|
|
2005
2264
|
return (...messages) => {
|
|
2006
2265
|
init();
|
|
@@ -2013,7 +2272,7 @@ function createLoggingService() {
|
|
|
2013
2272
|
activeFileSink.write(line);
|
|
2014
2273
|
}
|
|
2015
2274
|
if (patterns && matchesAny(name, patterns)) {
|
|
2016
|
-
|
|
2275
|
+
writeStdout(line);
|
|
2017
2276
|
}
|
|
2018
2277
|
};
|
|
2019
2278
|
},
|
|
@@ -2551,19 +2810,19 @@ import os2 from "node:os";
|
|
|
2551
2810
|
import path3 from "node:path";
|
|
2552
2811
|
|
|
2553
2812
|
// lib/agent-events.ts
|
|
2554
|
-
function rawEventToAgentEvent(rawEvent) {
|
|
2813
|
+
function rawEventToAgentEvent(rawEvent, provider) {
|
|
2555
2814
|
if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
|
|
2556
2815
|
return { type: "agent-session-activity" };
|
|
2557
2816
|
}
|
|
2558
|
-
return { type: "
|
|
2817
|
+
return { type: "agent-event", provider, rawEvent };
|
|
2559
2818
|
}
|
|
2560
2819
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
|
|
2561
|
-
function createHeartbeatThrottler(onAgentEvent, options) {
|
|
2820
|
+
function createHeartbeatThrottler(onAgentEvent, provider, options) {
|
|
2562
2821
|
const intervalMs = options?.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
2563
2822
|
const now = options?.now ?? Date.now;
|
|
2564
2823
|
let lastHeartbeatTime;
|
|
2565
2824
|
return (rawEvent) => {
|
|
2566
|
-
const event = rawEventToAgentEvent(rawEvent);
|
|
2825
|
+
const event = rawEventToAgentEvent(rawEvent, provider);
|
|
2567
2826
|
if (event.type === "agent-session-activity") {
|
|
2568
2827
|
const currentTime = now();
|
|
2569
2828
|
if (lastHeartbeatTime !== undefined && currentTime - lastHeartbeatTime < intervalMs) {
|
|
@@ -2588,7 +2847,7 @@ function formatAgentEvent(event) {
|
|
|
2588
2847
|
case "agent-session-ended":
|
|
2589
2848
|
return event.success ? "\uD83E\uDD16 Agent session ended (success)" : `\uD83E\uDD16 Agent session ended (error: ${event.error})`;
|
|
2590
2849
|
case "agent-session-activity":
|
|
2591
|
-
case "
|
|
2850
|
+
case "agent-event":
|
|
2592
2851
|
return null;
|
|
2593
2852
|
}
|
|
2594
2853
|
}
|
|
@@ -3227,7 +3486,7 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
3227
3486
|
docker: dockerConfig
|
|
3228
3487
|
};
|
|
3229
3488
|
if (eventsUrl) {
|
|
3230
|
-
iterationOptions.onRawEvent = createHeartbeatThrottler(onAgentEvent);
|
|
3489
|
+
iterationOptions.onRawEvent = createHeartbeatThrottler(onAgentEvent, loopDependencies.agentType ?? "claude");
|
|
3231
3490
|
}
|
|
3232
3491
|
while (completedIterations < maxIterations) {
|
|
3233
3492
|
agentSessionId = crypto.randomUUID();
|
|
@@ -3510,6 +3769,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3510
3769
|
};
|
|
3511
3770
|
const isCodex = repoState.repository.agentProvider === "codex";
|
|
3512
3771
|
const agentType = isCodex ? "codex" : "claude";
|
|
3772
|
+
log3(`${repoName}: agentProvider=${repoState.repository.agentProvider ?? "(unset)"}, using ${agentType}`);
|
|
3513
3773
|
const createStdoutSink2 = () => createBufferStdoutSink(loopState, repoState.logBuffer);
|
|
3514
3774
|
let bufferRun;
|
|
3515
3775
|
if (isCodex) {
|
|
@@ -3593,7 +3853,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3593
3853
|
hooksInstalled,
|
|
3594
3854
|
signal: abortController.signal,
|
|
3595
3855
|
repositoryId: repoState.repository.id.toString(),
|
|
3596
|
-
onRawEvent: createHeartbeatThrottler(onAgentEvent),
|
|
3856
|
+
onRawEvent: createHeartbeatThrottler(onAgentEvent, agentType),
|
|
3597
3857
|
docker: dockerConfig
|
|
3598
3858
|
});
|
|
3599
3859
|
} catch (error) {
|
|
@@ -3653,7 +3913,8 @@ function parseRepository(data) {
|
|
|
3653
3913
|
gitUrl: repositoryData.gitUrl,
|
|
3654
3914
|
gitSshUrl: typeof repositoryData.gitSshUrl === "string" ? repositoryData.gitSshUrl : undefined,
|
|
3655
3915
|
url: repositoryData.url,
|
|
3656
|
-
id: repositoryData.id
|
|
3916
|
+
id: repositoryData.id,
|
|
3917
|
+
agentProvider: typeof repositoryData.agentProvider === "string" ? repositoryData.agentProvider : undefined
|
|
3657
3918
|
};
|
|
3658
3919
|
}
|
|
3659
3920
|
}
|
|
@@ -3731,6 +3992,16 @@ async function handleRepositoryList(repositories, manager, repoDeps, context) {
|
|
|
3731
3992
|
for (const [name, repo] of incomingRepos) {
|
|
3732
3993
|
if (!manager.repositories.has(name)) {
|
|
3733
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
|
+
}
|
|
3734
4005
|
}
|
|
3735
4006
|
}
|
|
3736
4007
|
for (const name of manager.repositories.keys()) {
|
|
@@ -4136,90 +4407,37 @@ function enterAlternateScreen() {
|
|
|
4136
4407
|
function exitAlternateScreen() {
|
|
4137
4408
|
return `\x1B[?1006l\x1B[?1000l${ANSI.EXIT_ALT_SCREEN}${ANSI.SHOW_CURSOR}`;
|
|
4138
4409
|
}
|
|
4139
|
-
var
|
|
4140
|
-
UP: "\x1B[A",
|
|
4141
|
-
DOWN: "\x1B[B",
|
|
4142
|
-
RIGHT: "\x1B[C",
|
|
4143
|
-
LEFT: "\x1B[D",
|
|
4144
|
-
PAGE_UP: "\x1B[5~",
|
|
4145
|
-
PAGE_DOWN: "\x1B[6~",
|
|
4146
|
-
HOME: "\x1B[H",
|
|
4147
|
-
END: "\x1B[F",
|
|
4148
|
-
CTRL_C: "\x03"
|
|
4149
|
-
};
|
|
4150
|
-
var SGR_MOUSE_RE = new RegExp(String.raw`^\x1b\[<(\d+);\d+;\d+[Mm]$`);
|
|
4151
|
-
function parseSGRMouse(key) {
|
|
4152
|
-
const match = key.match(SGR_MOUSE_RE);
|
|
4153
|
-
if (!match)
|
|
4154
|
-
return null;
|
|
4155
|
-
return Number.parseInt(match[1], 10);
|
|
4156
|
-
}
|
|
4157
|
-
function handleKeyInput(state, key, options) {
|
|
4158
|
-
const mouseButton = parseSGRMouse(key);
|
|
4159
|
-
if (mouseButton !== null) {
|
|
4160
|
-
if (mouseButton === 64) {
|
|
4161
|
-
scrollUp(state, 3);
|
|
4162
|
-
} else if (mouseButton === 65) {
|
|
4163
|
-
scrollDown(state, 3);
|
|
4164
|
-
}
|
|
4165
|
-
return false;
|
|
4166
|
-
}
|
|
4167
|
-
switch (key) {
|
|
4168
|
-
case "q":
|
|
4169
|
-
case KEYS.CTRL_C:
|
|
4170
|
-
return true;
|
|
4171
|
-
case KEYS.LEFT:
|
|
4172
|
-
selectPrevious(state);
|
|
4173
|
-
state.scrollOffset = 0;
|
|
4174
|
-
state.autoScroll = true;
|
|
4175
|
-
break;
|
|
4176
|
-
case KEYS.RIGHT:
|
|
4177
|
-
selectNext(state);
|
|
4178
|
-
state.scrollOffset = 0;
|
|
4179
|
-
state.autoScroll = true;
|
|
4180
|
-
break;
|
|
4181
|
-
case KEYS.UP:
|
|
4182
|
-
scrollUp(state, 1);
|
|
4183
|
-
break;
|
|
4184
|
-
case KEYS.DOWN:
|
|
4185
|
-
scrollDown(state, 1);
|
|
4186
|
-
break;
|
|
4187
|
-
case KEYS.PAGE_UP:
|
|
4188
|
-
scrollUp(state, getLogAreaHeight(state));
|
|
4189
|
-
break;
|
|
4190
|
-
case KEYS.PAGE_DOWN:
|
|
4191
|
-
scrollDown(state, getLogAreaHeight(state));
|
|
4192
|
-
break;
|
|
4193
|
-
case "g":
|
|
4194
|
-
case KEYS.HOME:
|
|
4195
|
-
scrollToTop(state);
|
|
4196
|
-
break;
|
|
4197
|
-
case "G":
|
|
4198
|
-
case KEYS.END:
|
|
4199
|
-
scrollToBottom(state);
|
|
4200
|
-
break;
|
|
4201
|
-
case "o": {
|
|
4202
|
-
if (state.selectedIndex === -1) {
|
|
4203
|
-
break;
|
|
4204
|
-
}
|
|
4205
|
-
const repoName = state.repositories[state.selectedIndex];
|
|
4206
|
-
if (!repoName)
|
|
4207
|
-
break;
|
|
4208
|
-
const url = state.repositoryUrls.get(repoName);
|
|
4209
|
-
if (url && options?.openBrowser) {
|
|
4210
|
-
options.openBrowser(url);
|
|
4211
|
-
}
|
|
4212
|
-
break;
|
|
4213
|
-
}
|
|
4214
|
-
}
|
|
4215
|
-
return false;
|
|
4216
|
-
}
|
|
4410
|
+
var SGR_MOUSE_RE2 = new RegExp(String.raw`^\x1b\[<(\d+);\d+;\d+[Mm]$`);
|
|
4217
4411
|
|
|
4218
4412
|
// lib/cli/commands/bucket.ts
|
|
4219
4413
|
var log5 = createLogger("dust:cli:commands:bucket");
|
|
4220
4414
|
var DEFAULT_DUSTBUCKET_WS_URL = "wss://dustbucket.com/agent/connect";
|
|
4221
|
-
|
|
4222
|
-
|
|
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
|
+
}
|
|
4223
4441
|
function defaultCreateWebSocket(url, token) {
|
|
4224
4442
|
const ws = new WebSocket(url, {
|
|
4225
4443
|
headers: {
|
|
@@ -4274,32 +4492,6 @@ function defaultGetTerminalSize() {
|
|
|
4274
4492
|
function defaultWriteStdout(data) {
|
|
4275
4493
|
process.stdout.write(data);
|
|
4276
4494
|
}
|
|
4277
|
-
function createAuthFileSystem(dependencies) {
|
|
4278
|
-
return {
|
|
4279
|
-
exists: (path4) => {
|
|
4280
|
-
try {
|
|
4281
|
-
dependencies.accessSync(path4);
|
|
4282
|
-
return true;
|
|
4283
|
-
} catch {
|
|
4284
|
-
return false;
|
|
4285
|
-
}
|
|
4286
|
-
},
|
|
4287
|
-
isDirectory: (path4) => {
|
|
4288
|
-
try {
|
|
4289
|
-
return dependencies.statSync(path4).isDirectory();
|
|
4290
|
-
} catch {
|
|
4291
|
-
return false;
|
|
4292
|
-
}
|
|
4293
|
-
},
|
|
4294
|
-
getFileCreationTime: (path4) => dependencies.statSync(path4).birthtimeMs,
|
|
4295
|
-
readFile: (path4) => dependencies.readFile(path4, "utf8"),
|
|
4296
|
-
writeFile: (path4, content) => dependencies.writeFile(path4, content, "utf8"),
|
|
4297
|
-
mkdir: (path4, options) => dependencies.mkdir(path4, options).then(() => {}),
|
|
4298
|
-
readdir: (path4) => dependencies.readdir(path4),
|
|
4299
|
-
chmod: (path4, mode) => dependencies.chmod(path4, mode),
|
|
4300
|
-
rename: (oldPath, newPath) => dependencies.rename(oldPath, newPath)
|
|
4301
|
-
};
|
|
4302
|
-
}
|
|
4303
4495
|
function createDefaultBucketDependencies() {
|
|
4304
4496
|
const authFileSystem = createAuthFileSystem({
|
|
4305
4497
|
accessSync,
|
|
@@ -4470,8 +4662,13 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
4470
4662
|
state.ws = ws;
|
|
4471
4663
|
ws.onopen = () => {
|
|
4472
4664
|
state.emit({ type: "bucket.connected" });
|
|
4473
|
-
|
|
4474
|
-
|
|
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);
|
|
4475
4672
|
};
|
|
4476
4673
|
}
|
|
4477
4674
|
ws.onclose = (event) => {
|
|
@@ -4481,80 +4678,122 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
4481
4678
|
reason: event.reason || "none"
|
|
4482
4679
|
};
|
|
4483
4680
|
state.emit(disconnectEvent);
|
|
4484
|
-
logMessage(state, context, useTUI, formatBucketEvent(disconnectEvent));
|
|
4485
4681
|
state.ws = null;
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
}
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
connectWebSocket(token, state, bucketDependencies, context, fileSystem, useTUI);
|
|
4494
|
-
}, state.reconnectDelay);
|
|
4495
|
-
state.reconnectDelay = Math.min(state.reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
|
|
4496
|
-
}
|
|
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);
|
|
4497
4689
|
};
|
|
4498
4690
|
ws.onerror = (error) => {
|
|
4499
|
-
|
|
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);
|
|
4500
4697
|
};
|
|
4501
4698
|
ws.onmessage = (event) => {
|
|
4502
4699
|
let rawData;
|
|
4503
4700
|
try {
|
|
4504
4701
|
rawData = JSON.parse(event.data);
|
|
4505
4702
|
} catch {
|
|
4506
|
-
|
|
4703
|
+
const result2 = handleMessageParseError(event.data);
|
|
4704
|
+
executeEffects(result2.effects, {
|
|
4705
|
+
state,
|
|
4706
|
+
context,
|
|
4707
|
+
useTUI,
|
|
4708
|
+
bucketDependencies,
|
|
4709
|
+
fileSystem
|
|
4710
|
+
});
|
|
4507
4711
|
return;
|
|
4508
4712
|
}
|
|
4509
4713
|
const message = parseServerMessage(rawData);
|
|
4510
4714
|
if (!message) {
|
|
4511
|
-
|
|
4715
|
+
const result2 = handleInvalidMessageFormat(event.data);
|
|
4716
|
+
executeEffects(result2.effects, {
|
|
4717
|
+
state,
|
|
4718
|
+
context,
|
|
4719
|
+
useTUI,
|
|
4720
|
+
bucketDependencies,
|
|
4721
|
+
fileSystem
|
|
4722
|
+
});
|
|
4512
4723
|
return;
|
|
4513
4724
|
}
|
|
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
|
-
|
|
4539
|
-
|
|
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
|
+
}
|
|
4540
4763
|
}
|
|
4541
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);
|
|
4542
4775
|
}
|
|
4543
|
-
|
|
4544
|
-
logMessage(state, context, useTUI, `Failed to handle repository list: ${error.message}`, "stderr");
|
|
4545
|
-
});
|
|
4546
|
-
} else if (message.type === "task-available") {
|
|
4547
|
-
const repoName = message.repository;
|
|
4548
|
-
const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
|
|
4549
|
-
logMessage(state, context, useTUI, `Received task-available for ${repoName}`);
|
|
4550
|
-
const repoState = state.repositories.get(repoName);
|
|
4551
|
-
if (repoState) {
|
|
4552
|
-
signalTaskAvailable(repoState, state, repoDeps, context, useTUI);
|
|
4553
|
-
} else {
|
|
4554
|
-
logMessage(state, context, useTUI, `No repository state found for ${repoName}`, "stderr");
|
|
4776
|
+
break;
|
|
4555
4777
|
}
|
|
4778
|
+
case "scheduleReconnect":
|
|
4779
|
+
break;
|
|
4556
4780
|
}
|
|
4557
|
-
}
|
|
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
|
+
}
|
|
4558
4797
|
}
|
|
4559
4798
|
async function shutdown(state, bucketDeps, context) {
|
|
4560
4799
|
if (state.shuttingDown)
|
|
@@ -4608,17 +4847,80 @@ function setupTUI(state, bucketDeps) {
|
|
|
4608
4847
|
}
|
|
4609
4848
|
};
|
|
4610
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
|
+
}
|
|
4611
4907
|
function createKeypressHandler(useTUI, state, onQuit, options) {
|
|
4612
4908
|
if (useTUI) {
|
|
4613
4909
|
return (key) => {
|
|
4614
|
-
const
|
|
4615
|
-
|
|
4616
|
-
|
|
4910
|
+
const keypressState = createKeypressHandlerState(state.ui);
|
|
4911
|
+
const { effects } = handleKeypress(keypressState, key);
|
|
4912
|
+
executeKeypressEffects(state.ui, effects, onQuit, options);
|
|
4617
4913
|
};
|
|
4618
4914
|
}
|
|
4619
4915
|
return (key) => {
|
|
4620
|
-
|
|
4621
|
-
|
|
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
|
+
}
|
|
4622
4924
|
};
|
|
4623
4925
|
}
|
|
4624
4926
|
async function resolveToken(authDeps, context) {
|
|
@@ -4868,7 +5170,8 @@ async function bucketAssetUpload(dependencies, uploadDeps = createDefaultUploadD
|
|
|
4868
5170
|
}
|
|
4869
5171
|
const fileBytes = await uploadDeps.readFileBytes(filePath);
|
|
4870
5172
|
const contentType = getContentType(filePath);
|
|
4871
|
-
const
|
|
5173
|
+
const parts = filePath.split("/");
|
|
5174
|
+
const fileName = parts[parts.length - 1];
|
|
4872
5175
|
const uploadUrl = `${getDustbucketHost()}/api/assets?repositoryId=${encodeURIComponent(repositoryId)}`;
|
|
4873
5176
|
try {
|
|
4874
5177
|
const result = await uploadDeps.uploadFile(uploadUrl, token, fileBytes, contentType, fileName);
|
|
@@ -5801,13 +6104,13 @@ function truncateOutput(output) {
|
|
|
5801
6104
|
].join(`
|
|
5802
6105
|
`);
|
|
5803
6106
|
}
|
|
5804
|
-
async function runSingleCheck(check, cwd, runner, emitEvent) {
|
|
6107
|
+
async function runSingleCheck(check, cwd, runner, emitEvent, clock = Date.now) {
|
|
5805
6108
|
const timeoutMs = check.timeoutMilliseconds ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
5806
6109
|
log6(`running check ${check.name}: ${check.command}`);
|
|
5807
6110
|
emitEvent?.({ type: "check-started", name: check.name });
|
|
5808
|
-
const startTime =
|
|
6111
|
+
const startTime = clock();
|
|
5809
6112
|
const result = await runner.run(check.command, cwd, timeoutMs);
|
|
5810
|
-
const durationMs =
|
|
6113
|
+
const durationMs = clock() - startTime;
|
|
5811
6114
|
const status = result.timedOut ? "timed out" : result.exitCode === 0 ? "passed" : "failed";
|
|
5812
6115
|
log6(`check ${check.name} ${status} (${durationMs}ms)`);
|
|
5813
6116
|
if (result.exitCode === 0) {
|
|
@@ -5833,18 +6136,18 @@ async function runSingleCheck(check, cwd, runner, emitEvent) {
|
|
|
5833
6136
|
timeoutSeconds: timeoutMs / 1000
|
|
5834
6137
|
};
|
|
5835
6138
|
}
|
|
5836
|
-
async function runConfiguredChecks(checks, cwd, runner, emitEvent) {
|
|
5837
|
-
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));
|
|
5838
6141
|
return Promise.all(promises);
|
|
5839
6142
|
}
|
|
5840
|
-
async function runConfiguredChecksSerially(checks, cwd, runner, emitEvent) {
|
|
6143
|
+
async function runConfiguredChecksSerially(checks, cwd, runner, emitEvent, clock = Date.now) {
|
|
5841
6144
|
const results = [];
|
|
5842
6145
|
for (const check of checks) {
|
|
5843
|
-
results.push(await runSingleCheck(check, cwd, runner, emitEvent));
|
|
6146
|
+
results.push(await runSingleCheck(check, cwd, runner, emitEvent, clock));
|
|
5844
6147
|
}
|
|
5845
6148
|
return results;
|
|
5846
6149
|
}
|
|
5847
|
-
async function runValidationCheck(dependencies, emitEvent) {
|
|
6150
|
+
async function runValidationCheck(dependencies, emitEvent, clock = Date.now) {
|
|
5848
6151
|
const outputLines = [];
|
|
5849
6152
|
const bufferedContext = {
|
|
5850
6153
|
cwd: dependencies.context.cwd,
|
|
@@ -5853,13 +6156,13 @@ async function runValidationCheck(dependencies, emitEvent) {
|
|
|
5853
6156
|
};
|
|
5854
6157
|
log6("running built-in check: dust lint");
|
|
5855
6158
|
emitEvent?.({ type: "check-started", name: "lint" });
|
|
5856
|
-
const startTime =
|
|
6159
|
+
const startTime = clock();
|
|
5857
6160
|
const result = await lintMarkdown({
|
|
5858
6161
|
...dependencies,
|
|
5859
6162
|
context: bufferedContext,
|
|
5860
6163
|
arguments: []
|
|
5861
6164
|
});
|
|
5862
|
-
const durationMs =
|
|
6165
|
+
const durationMs = clock() - startTime;
|
|
5863
6166
|
const lintStatus = result.exitCode === 0 ? "passed" : "failed";
|
|
5864
6167
|
log6(`built-in check dust lint ${lintStatus} (${durationMs}ms)`);
|
|
5865
6168
|
const output = outputLines.join(`
|
|
@@ -5923,7 +6226,7 @@ function displayResults(results, context) {
|
|
|
5923
6226
|
context.stdout(`${indicator} ${passed.length}/${results.length} checks passed`);
|
|
5924
6227
|
return failed.length > 0 ? 1 : 0;
|
|
5925
6228
|
}
|
|
5926
|
-
async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
6229
|
+
async function check(dependencies, shellRunner = defaultShellRunner, clock = Date.now) {
|
|
5927
6230
|
const {
|
|
5928
6231
|
arguments: commandArguments,
|
|
5929
6232
|
context,
|
|
@@ -5949,18 +6252,18 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
|
5949
6252
|
if (serial) {
|
|
5950
6253
|
const results2 = [];
|
|
5951
6254
|
if (hasDustDir) {
|
|
5952
|
-
results2.push(await runValidationCheck(dependencies, context.emitEvent));
|
|
6255
|
+
results2.push(await runValidationCheck(dependencies, context.emitEvent, clock));
|
|
5953
6256
|
}
|
|
5954
|
-
const configuredResults = await runConfiguredChecksSerially(settings.checks, context.cwd, shellRunner, context.emitEvent);
|
|
6257
|
+
const configuredResults = await runConfiguredChecksSerially(settings.checks, context.cwd, shellRunner, context.emitEvent, clock);
|
|
5955
6258
|
results2.push(...configuredResults);
|
|
5956
6259
|
const exitCode2 = displayResults(results2, context);
|
|
5957
6260
|
return { exitCode: exitCode2 };
|
|
5958
6261
|
}
|
|
5959
6262
|
const checkPromises = [];
|
|
5960
6263
|
if (hasDustDir) {
|
|
5961
|
-
checkPromises.push(runValidationCheck(dependencies, context.emitEvent));
|
|
6264
|
+
checkPromises.push(runValidationCheck(dependencies, context.emitEvent, clock));
|
|
5962
6265
|
}
|
|
5963
|
-
checkPromises.push(runConfiguredChecks(settings.checks, context.cwd, shellRunner, context.emitEvent));
|
|
6266
|
+
checkPromises.push(runConfiguredChecks(settings.checks, context.cwd, shellRunner, context.emitEvent, clock));
|
|
5964
6267
|
const promiseResults = await Promise.all(checkPromises);
|
|
5965
6268
|
const results = [];
|
|
5966
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
|
},
|