@drisp/cli 0.4.4 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{WorkflowInstallWizard-2MC5A7W4.js → WorkflowInstallWizard-X754ND4V.js} +2 -2
- package/dist/athena-gateway.js +5 -3853
- package/dist/chunk-2OJ3GGIP.js +104 -0
- package/dist/{chunk-5VK2ZMVV.js → chunk-A54HGVML.js} +96 -95
- package/dist/chunk-D4W7RB25.js +76 -0
- package/dist/chunk-SKWAGQXF.js +4124 -0
- package/dist/{chunk-JAPBSL7D.js → chunk-TQKNZNDP.js} +1115 -770
- package/dist/{chunk-4CRZXLIP.js → chunk-WRHKXH5M.js} +136 -136
- package/dist/chunk-ZVOGOZNT.js +395 -0
- package/dist/cli.js +1458 -1110
- package/dist/dashboard-daemon.js +9 -107
- package/dist/supervisor.js +692 -0
- package/package.json +1 -1
- package/dist/chunk-6TJHAUNB.js +0 -161
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./chunk-HXBCZAP7.js";
|
|
3
|
-
import {
|
|
4
|
-
channelSidecarDir,
|
|
5
|
-
rotateGatewayToken
|
|
6
|
-
} from "./chunk-6TJHAUNB.js";
|
|
7
3
|
import {
|
|
8
4
|
CREDENTIAL_SOURCES_TRIED,
|
|
9
5
|
EXEC_EXIT_CODE,
|
|
@@ -15,6 +11,8 @@ import {
|
|
|
15
11
|
buildProbeConfigs,
|
|
16
12
|
classifyFailure,
|
|
17
13
|
connect,
|
|
14
|
+
createDashboardDecisionInbox,
|
|
15
|
+
createDashboardFeedPublisher,
|
|
18
16
|
createFeedMapper,
|
|
19
17
|
createRelayPermissionCallback,
|
|
20
18
|
createRelayQuestionCallback,
|
|
@@ -51,7 +49,6 @@ import {
|
|
|
51
49
|
processRegistry,
|
|
52
50
|
progressGlyphs,
|
|
53
51
|
readGatewayClientConfig,
|
|
54
|
-
readPidLock,
|
|
55
52
|
register,
|
|
56
53
|
registerCleanupOnExit,
|
|
57
54
|
resolveClaudeBinary,
|
|
@@ -70,10 +67,19 @@ import {
|
|
|
70
67
|
todoGlyphSet,
|
|
71
68
|
writeGatewayClientConfig,
|
|
72
69
|
wsClientOptionsForEndpoint
|
|
73
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-TQKNZNDP.js";
|
|
74
71
|
import {
|
|
75
72
|
generateId as generateId2
|
|
76
73
|
} from "./chunk-BTKQ67RE.js";
|
|
74
|
+
import {
|
|
75
|
+
rotateGatewayToken
|
|
76
|
+
} from "./chunk-D4W7RB25.js";
|
|
77
|
+
import {
|
|
78
|
+
readAttachmentMirror,
|
|
79
|
+
readPidLock,
|
|
80
|
+
removeAttachmentMirror,
|
|
81
|
+
writeAttachmentMirror
|
|
82
|
+
} from "./chunk-ZVOGOZNT.js";
|
|
77
83
|
import {
|
|
78
84
|
dashboardClientConfigPath,
|
|
79
85
|
disableTelemetry,
|
|
@@ -94,7 +100,7 @@ import {
|
|
|
94
100
|
trackTelemetryOptedOut,
|
|
95
101
|
writeDashboardClientConfig,
|
|
96
102
|
writeGatewayTrace
|
|
97
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-WRHKXH5M.js";
|
|
98
104
|
import {
|
|
99
105
|
McpOptionsStep,
|
|
100
106
|
StepSelector,
|
|
@@ -129,7 +135,7 @@ import {
|
|
|
129
135
|
useWorkflowSessionController,
|
|
130
136
|
writeGlobalConfig,
|
|
131
137
|
writeProjectConfig
|
|
132
|
-
} from "./chunk-
|
|
138
|
+
} from "./chunk-A54HGVML.js";
|
|
133
139
|
|
|
134
140
|
// src/app/entry/cli.tsx
|
|
135
141
|
import { render } from "ink";
|
|
@@ -1739,14 +1745,65 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1739
1745
|
const abortRef = useRef(new AbortController());
|
|
1740
1746
|
const feedEventsRef = useRef([]);
|
|
1741
1747
|
const notifiedRuntimeErrorRef = useRef(null);
|
|
1748
|
+
const dashboardFeedPublisher = useMemo3(
|
|
1749
|
+
() => options?.dashboardFeedPublisher ?? createDashboardFeedPublisher(),
|
|
1750
|
+
[options?.dashboardFeedPublisher]
|
|
1751
|
+
);
|
|
1752
|
+
const dashboardDecisionInboxRef = useRef(
|
|
1753
|
+
options?.dashboardDecisionInbox ?? null
|
|
1754
|
+
);
|
|
1742
1755
|
rulesRef.current = rules;
|
|
1743
1756
|
feedEventsRef.current = feedEvents;
|
|
1744
1757
|
sessionStoreRef.current = sessionStore;
|
|
1758
|
+
if (options?.dashboardDecisionInbox) {
|
|
1759
|
+
dashboardDecisionInboxRef.current = options.dashboardDecisionInbox;
|
|
1760
|
+
}
|
|
1761
|
+
const resolveAthenaSessionId = useCallback5(
|
|
1762
|
+
(fallback) => {
|
|
1763
|
+
if (options?.athenaSessionId) return options.athenaSessionId;
|
|
1764
|
+
try {
|
|
1765
|
+
const sessionId = sessionStoreRef.current?.getAthenaSession().id;
|
|
1766
|
+
if (sessionId) return sessionId;
|
|
1767
|
+
} catch {
|
|
1768
|
+
}
|
|
1769
|
+
return fallback;
|
|
1770
|
+
},
|
|
1771
|
+
[options?.athenaSessionId]
|
|
1772
|
+
);
|
|
1745
1773
|
useEffect(() => {
|
|
1746
1774
|
return () => {
|
|
1747
1775
|
sessionStoreRef.current = void 0;
|
|
1748
1776
|
};
|
|
1749
1777
|
}, []);
|
|
1778
|
+
useEffect(() => {
|
|
1779
|
+
const athenaSessionId = resolveAthenaSessionId();
|
|
1780
|
+
if (!athenaSessionId) return;
|
|
1781
|
+
if (!dashboardDecisionInboxRef.current) {
|
|
1782
|
+
if (!readDashboardClientConfig()) return;
|
|
1783
|
+
dashboardDecisionInboxRef.current = createDashboardDecisionInbox();
|
|
1784
|
+
}
|
|
1785
|
+
const inbox = dashboardDecisionInboxRef.current;
|
|
1786
|
+
const applyPending = () => {
|
|
1787
|
+
const rows = inbox.pendingForSession({ athenaSessionId, limit: 25 });
|
|
1788
|
+
for (const row of rows) {
|
|
1789
|
+
runtime.sendDecision(row.requestId, row.decision);
|
|
1790
|
+
inbox.markConsumed({ id: row.id });
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
applyPending();
|
|
1794
|
+
const interval = setInterval(
|
|
1795
|
+
applyPending,
|
|
1796
|
+
options?.dashboardDecisionPollIntervalMs ?? 1e3
|
|
1797
|
+
);
|
|
1798
|
+
return () => {
|
|
1799
|
+
clearInterval(interval);
|
|
1800
|
+
};
|
|
1801
|
+
}, [
|
|
1802
|
+
runtime,
|
|
1803
|
+
options?.dashboardDecisionInbox,
|
|
1804
|
+
options?.dashboardDecisionPollIntervalMs,
|
|
1805
|
+
resolveAthenaSessionId
|
|
1806
|
+
]);
|
|
1750
1807
|
const resetSession = useCallback5(() => {
|
|
1751
1808
|
const newMapper = createFeedMapper();
|
|
1752
1809
|
mapperRef.current = newMapper;
|
|
@@ -1857,18 +1914,37 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1857
1914
|
},
|
|
1858
1915
|
[runtime, dequeueQuestion]
|
|
1859
1916
|
);
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1917
|
+
const publishDashboardFeedEvents = useCallback5(
|
|
1918
|
+
(feedEventsToPublish, runtimeEvent) => {
|
|
1919
|
+
if (feedEventsToPublish.length === 0) return;
|
|
1920
|
+
const athenaSessionId = resolveAthenaSessionId(
|
|
1921
|
+
feedEventsToPublish[0]?.session_id ?? runtimeEvent?.sessionId
|
|
1922
|
+
);
|
|
1923
|
+
if (!athenaSessionId) return;
|
|
1924
|
+
dashboardFeedPublisher.publish({
|
|
1925
|
+
origin: options?.dashboardOrigin ?? "local",
|
|
1926
|
+
athenaSessionId,
|
|
1927
|
+
feedEvents: feedEventsToPublish
|
|
1928
|
+
});
|
|
1929
|
+
},
|
|
1930
|
+
[dashboardFeedPublisher, options?.dashboardOrigin, resolveAthenaSessionId]
|
|
1931
|
+
);
|
|
1932
|
+
const emitNotification = useCallback5(
|
|
1933
|
+
(message, title) => {
|
|
1934
|
+
const mapper = mapperRef.current;
|
|
1935
|
+
const syntheticRuntime = buildSyntheticNotificationEvent(
|
|
1936
|
+
mapper,
|
|
1937
|
+
message,
|
|
1938
|
+
title
|
|
1939
|
+
);
|
|
1940
|
+
const newEvents = mapper.mapEvent(syntheticRuntime);
|
|
1941
|
+
if (!abortRef.current.signal.aborted) {
|
|
1942
|
+
feedStoreRef.current.pushEvents(newEvents);
|
|
1943
|
+
publishDashboardFeedEvents(newEvents, syntheticRuntime);
|
|
1944
|
+
}
|
|
1945
|
+
},
|
|
1946
|
+
[publishDashboardFeedEvents]
|
|
1947
|
+
);
|
|
1872
1948
|
const refreshRuntimeStatus = useCallback5(
|
|
1873
1949
|
(notify = false) => {
|
|
1874
1950
|
const nextIsServerRunning = runtime.getStatus() === "running";
|
|
@@ -1953,6 +2029,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1953
2029
|
}
|
|
1954
2030
|
}
|
|
1955
2031
|
feedStoreRef.current.pushEvents(newFeedEvents);
|
|
2032
|
+
publishDashboardFeedEvents(newFeedEvents, runtimeEvent);
|
|
1956
2033
|
}
|
|
1957
2034
|
} finally {
|
|
1958
2035
|
doneCause();
|
|
@@ -1988,6 +2065,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1988
2065
|
});
|
|
1989
2066
|
if (feedEvent) {
|
|
1990
2067
|
feedStoreRef.current.pushEvents([feedEvent]);
|
|
2068
|
+
publishDashboardFeedEvents([feedEvent]);
|
|
1991
2069
|
if (feedEvent.kind === "permission.decision" && feedEvent.cause?.hook_request_id) {
|
|
1992
2070
|
dequeuePermission(feedEvent.cause.hook_request_id);
|
|
1993
2071
|
}
|
|
@@ -2011,7 +2089,8 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
2011
2089
|
dequeueQuestion,
|
|
2012
2090
|
refreshRuntimeStatus,
|
|
2013
2091
|
options?.relayPermission,
|
|
2014
|
-
options?.relayQuestion
|
|
2092
|
+
options?.relayQuestion,
|
|
2093
|
+
publishDashboardFeedEvents
|
|
2015
2094
|
]);
|
|
2016
2095
|
const items = useMemo3(() => {
|
|
2017
2096
|
const done = startPerfStage("state.derive", {
|
|
@@ -2108,6 +2187,7 @@ function HookProvider({
|
|
|
2108
2187
|
runtimeFactory = createRuntime,
|
|
2109
2188
|
allowedTools,
|
|
2110
2189
|
athenaSessionId,
|
|
2190
|
+
attachmentId,
|
|
2111
2191
|
children
|
|
2112
2192
|
}) {
|
|
2113
2193
|
const runtime = useMemo4(
|
|
@@ -2155,6 +2235,7 @@ function HookProvider({
|
|
|
2155
2235
|
void startSessionBridge({
|
|
2156
2236
|
runtimeId: athenaSessionId,
|
|
2157
2237
|
defaultAgentId: "main",
|
|
2238
|
+
...attachmentId !== void 0 ? { attachmentId } : {},
|
|
2158
2239
|
signal: controller.signal
|
|
2159
2240
|
}).then((bridge) => {
|
|
2160
2241
|
if (!bridge) return;
|
|
@@ -2171,7 +2252,7 @@ function HookProvider({
|
|
|
2171
2252
|
return null;
|
|
2172
2253
|
});
|
|
2173
2254
|
};
|
|
2174
|
-
}, [athenaSessionId]);
|
|
2255
|
+
}, [athenaSessionId, attachmentId]);
|
|
2175
2256
|
useEffect2(() => {
|
|
2176
2257
|
return () => {
|
|
2177
2258
|
sessionStore.close();
|
|
@@ -10948,6 +11029,350 @@ function executeCommand(command, args, ctx) {
|
|
|
10948
11029
|
}
|
|
10949
11030
|
}
|
|
10950
11031
|
|
|
11032
|
+
// src/app/runner/assignmentHandler.ts
|
|
11033
|
+
function parseRunSpec(value) {
|
|
11034
|
+
if (typeof value !== "object" || value === null) return null;
|
|
11035
|
+
const obj = value;
|
|
11036
|
+
const prompt = obj["prompt"];
|
|
11037
|
+
if (typeof prompt !== "string" || prompt.trim().length === 0) return null;
|
|
11038
|
+
const env2 = obj["env"];
|
|
11039
|
+
const workflow = obj["workflow"];
|
|
11040
|
+
return {
|
|
11041
|
+
prompt,
|
|
11042
|
+
sessionId: typeof obj["sessionId"] === "string" && obj["sessionId"].length > 0 ? obj["sessionId"] : void 0,
|
|
11043
|
+
projectDir: typeof obj["projectDir"] === "string" && obj["projectDir"].length > 0 ? obj["projectDir"] : void 0,
|
|
11044
|
+
workflow: typeof workflow === "object" && workflow !== null && typeof workflow["ref"] === "string" ? { ref: workflow["ref"] } : void 0,
|
|
11045
|
+
env: typeof env2 === "object" && env2 !== null ? Object.fromEntries(
|
|
11046
|
+
Object.entries(env2).filter(
|
|
11047
|
+
(entry) => typeof entry[1] === "string"
|
|
11048
|
+
)
|
|
11049
|
+
) : void 0,
|
|
11050
|
+
timeoutSec: typeof obj["timeoutSec"] === "number" && Number.isFinite(obj["timeoutSec"]) ? obj["timeoutSec"] : void 0
|
|
11051
|
+
};
|
|
11052
|
+
}
|
|
11053
|
+
function workflowNameFromRef(ref) {
|
|
11054
|
+
if (!ref) return void 0;
|
|
11055
|
+
const [name] = ref.split("@", 1);
|
|
11056
|
+
return name && name.length > 0 ? name : void 0;
|
|
11057
|
+
}
|
|
11058
|
+
function eventKindOf(event) {
|
|
11059
|
+
if (event.type === "exec.completed") {
|
|
11060
|
+
const data = event.data;
|
|
11061
|
+
return data?.success === false ? "error" : "completion";
|
|
11062
|
+
}
|
|
11063
|
+
return typeof event.type === "string" && event.type.length > 0 ? event.type : "progress";
|
|
11064
|
+
}
|
|
11065
|
+
function eventPayloadOf(event) {
|
|
11066
|
+
if (event.type === "exec.completed") {
|
|
11067
|
+
const data = event.data;
|
|
11068
|
+
if (data?.success === false) {
|
|
11069
|
+
return {
|
|
11070
|
+
...typeof event.data === "object" && event.data !== null ? event.data : {},
|
|
11071
|
+
message: typeof data.failure === "object" && data.failure !== null && typeof data.failure.message === "string" ? data.failure.message : "remote execution failed"
|
|
11072
|
+
};
|
|
11073
|
+
}
|
|
11074
|
+
}
|
|
11075
|
+
return event.data ?? null;
|
|
11076
|
+
}
|
|
11077
|
+
function withEnv(env2, fn) {
|
|
11078
|
+
if (!env2 || Object.keys(env2).length === 0) return fn();
|
|
11079
|
+
const previous = /* @__PURE__ */ new Map();
|
|
11080
|
+
for (const [key, value] of Object.entries(env2)) {
|
|
11081
|
+
previous.set(key, process.env[key]);
|
|
11082
|
+
process.env[key] = value;
|
|
11083
|
+
}
|
|
11084
|
+
return fn().finally(() => {
|
|
11085
|
+
for (const [key, value] of previous) {
|
|
11086
|
+
if (value === void 0) {
|
|
11087
|
+
delete process.env[key];
|
|
11088
|
+
} else {
|
|
11089
|
+
process.env[key] = value;
|
|
11090
|
+
}
|
|
11091
|
+
}
|
|
11092
|
+
});
|
|
11093
|
+
}
|
|
11094
|
+
async function executeAssignment(input) {
|
|
11095
|
+
const {
|
|
11096
|
+
envelope,
|
|
11097
|
+
bridge,
|
|
11098
|
+
dispatchId,
|
|
11099
|
+
location,
|
|
11100
|
+
projectDir: fallbackProjectDir = process.cwd(),
|
|
11101
|
+
runExecFn = runExec,
|
|
11102
|
+
bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
|
|
11103
|
+
now = Date.now,
|
|
11104
|
+
abortSignal
|
|
11105
|
+
} = input;
|
|
11106
|
+
const runId = envelope.runId;
|
|
11107
|
+
let seq = 0;
|
|
11108
|
+
let terminalSent = false;
|
|
11109
|
+
let deferredFailedCompletion = null;
|
|
11110
|
+
let lastTerminalFailureMessage = null;
|
|
11111
|
+
const nextSeq = () => {
|
|
11112
|
+
seq += 1;
|
|
11113
|
+
return seq;
|
|
11114
|
+
};
|
|
11115
|
+
const sendProgress = async (kind, payload, ts = now()) => {
|
|
11116
|
+
await bridge.sendRunEvent({
|
|
11117
|
+
location,
|
|
11118
|
+
runId,
|
|
11119
|
+
seq: nextSeq(),
|
|
11120
|
+
ts,
|
|
11121
|
+
kind,
|
|
11122
|
+
payload
|
|
11123
|
+
});
|
|
11124
|
+
};
|
|
11125
|
+
const sendTerminal = async (eventKind, payload, ts = now()) => {
|
|
11126
|
+
if (terminalSent) return;
|
|
11127
|
+
terminalSent = true;
|
|
11128
|
+
const envelopeText = JSON.stringify({
|
|
11129
|
+
kind: "run_event",
|
|
11130
|
+
runId,
|
|
11131
|
+
seq: nextSeq(),
|
|
11132
|
+
ts,
|
|
11133
|
+
eventKind,
|
|
11134
|
+
payload
|
|
11135
|
+
});
|
|
11136
|
+
await bridge.completeTurn({
|
|
11137
|
+
dispatchId,
|
|
11138
|
+
location,
|
|
11139
|
+
text: envelopeText,
|
|
11140
|
+
idempotencyKey: `run_event:${runId}:terminal`
|
|
11141
|
+
});
|
|
11142
|
+
};
|
|
11143
|
+
await sendProgress("progress", { message: "assignment received" });
|
|
11144
|
+
const spec = parseRunSpec(envelope.runSpec);
|
|
11145
|
+
if (!spec) {
|
|
11146
|
+
await sendTerminal("error", { message: "remote assignment missing prompt" });
|
|
11147
|
+
return;
|
|
11148
|
+
}
|
|
11149
|
+
const projectDir = spec.projectDir ?? fallbackProjectDir;
|
|
11150
|
+
let runtimeConfig;
|
|
11151
|
+
try {
|
|
11152
|
+
runtimeConfig = bootstrapRuntimeConfigFn({
|
|
11153
|
+
projectDir,
|
|
11154
|
+
showSetup: false,
|
|
11155
|
+
isolationPreset: "minimal",
|
|
11156
|
+
workflowOverride: workflowNameFromRef(spec.workflow?.ref)
|
|
11157
|
+
});
|
|
11158
|
+
} catch (err) {
|
|
11159
|
+
await sendTerminal("error", {
|
|
11160
|
+
message: err instanceof Error ? err.message : String(err)
|
|
11161
|
+
});
|
|
11162
|
+
return;
|
|
11163
|
+
}
|
|
11164
|
+
for (const warning of runtimeConfig.warnings) {
|
|
11165
|
+
await sendProgress("warning", { message: warning });
|
|
11166
|
+
}
|
|
11167
|
+
let buffered = "";
|
|
11168
|
+
const pendingProgress = [];
|
|
11169
|
+
const stdout = {
|
|
11170
|
+
write(chunk) {
|
|
11171
|
+
buffered += chunk;
|
|
11172
|
+
let newline = buffered.indexOf("\n");
|
|
11173
|
+
while (newline >= 0) {
|
|
11174
|
+
const line = buffered.slice(0, newline).trim();
|
|
11175
|
+
buffered = buffered.slice(newline + 1);
|
|
11176
|
+
if (line.length > 0) {
|
|
11177
|
+
try {
|
|
11178
|
+
const event = JSON.parse(line);
|
|
11179
|
+
const data = event.data;
|
|
11180
|
+
if (event.type === "exec.completed" && data?.success === false) {
|
|
11181
|
+
deferredFailedCompletion = event;
|
|
11182
|
+
} else if (event.type === "exec.completed") {
|
|
11183
|
+
deferredFailedCompletion = event;
|
|
11184
|
+
} else {
|
|
11185
|
+
pendingProgress.push(
|
|
11186
|
+
sendProgress(eventKindOf(event), eventPayloadOf(event), now())
|
|
11187
|
+
);
|
|
11188
|
+
}
|
|
11189
|
+
} catch {
|
|
11190
|
+
pendingProgress.push(sendProgress("progress", { line }));
|
|
11191
|
+
}
|
|
11192
|
+
}
|
|
11193
|
+
newline = buffered.indexOf("\n");
|
|
11194
|
+
}
|
|
11195
|
+
return true;
|
|
11196
|
+
}
|
|
11197
|
+
};
|
|
11198
|
+
const stderr = {
|
|
11199
|
+
write(chunk) {
|
|
11200
|
+
const text = chunk.trim();
|
|
11201
|
+
if (text.length > 0) pendingProgress.push(sendProgress("stderr", { text }));
|
|
11202
|
+
return true;
|
|
11203
|
+
}
|
|
11204
|
+
};
|
|
11205
|
+
try {
|
|
11206
|
+
await withEnv(spec.env, async () => {
|
|
11207
|
+
const result = await runExecFn({
|
|
11208
|
+
prompt: spec.prompt,
|
|
11209
|
+
projectDir,
|
|
11210
|
+
harness: runtimeConfig.harness,
|
|
11211
|
+
athenaSessionId: spec.sessionId ?? `athena-${runId}`,
|
|
11212
|
+
isolationConfig: runtimeConfig.isolationConfig,
|
|
11213
|
+
pluginMcpConfig: runtimeConfig.pluginMcpConfig,
|
|
11214
|
+
workflow: runtimeConfig.workflow,
|
|
11215
|
+
workflowPlan: runtimeConfig.workflowPlan,
|
|
11216
|
+
json: true,
|
|
11217
|
+
verbose: false,
|
|
11218
|
+
ephemeral: false,
|
|
11219
|
+
timeoutMs: spec.timeoutSec ? spec.timeoutSec * 1e3 : void 0,
|
|
11220
|
+
signal: abortSignal,
|
|
11221
|
+
stdout,
|
|
11222
|
+
stderr
|
|
11223
|
+
});
|
|
11224
|
+
await Promise.all(pendingProgress);
|
|
11225
|
+
if (deferredFailedCompletion) {
|
|
11226
|
+
const data = typeof deferredFailedCompletion.data === "object" && deferredFailedCompletion.data !== null ? deferredFailedCompletion.data : {};
|
|
11227
|
+
const ts = typeof deferredFailedCompletion.ts === "number" ? deferredFailedCompletion.ts : now();
|
|
11228
|
+
const success = data.success !== false;
|
|
11229
|
+
const eventKind = success ? "completion" : "error";
|
|
11230
|
+
const message = !success ? result.failure?.message ?? eventPayloadOf(deferredFailedCompletion).message ?? "remote execution failed" : void 0;
|
|
11231
|
+
if (message) lastTerminalFailureMessage = message;
|
|
11232
|
+
await sendTerminal(
|
|
11233
|
+
eventKind,
|
|
11234
|
+
success ? {
|
|
11235
|
+
...data,
|
|
11236
|
+
exitCode: result.exitCode,
|
|
11237
|
+
athenaSessionId: result.athenaSessionId,
|
|
11238
|
+
adapterSessionId: result.adapterSessionId,
|
|
11239
|
+
finalMessage: result.finalMessage,
|
|
11240
|
+
tokens: result.tokens,
|
|
11241
|
+
durationMs: result.durationMs,
|
|
11242
|
+
success: true
|
|
11243
|
+
} : {
|
|
11244
|
+
...data,
|
|
11245
|
+
success: result.success,
|
|
11246
|
+
exitCode: result.exitCode,
|
|
11247
|
+
athenaSessionId: result.athenaSessionId,
|
|
11248
|
+
adapterSessionId: result.adapterSessionId,
|
|
11249
|
+
finalMessage: result.finalMessage,
|
|
11250
|
+
tokens: result.tokens,
|
|
11251
|
+
durationMs: result.durationMs,
|
|
11252
|
+
message
|
|
11253
|
+
},
|
|
11254
|
+
ts
|
|
11255
|
+
);
|
|
11256
|
+
return;
|
|
11257
|
+
}
|
|
11258
|
+
if (result.failure && result.failure.message !== lastTerminalFailureMessage) {
|
|
11259
|
+
await sendTerminal("error", {
|
|
11260
|
+
success: result.success,
|
|
11261
|
+
exitCode: result.exitCode,
|
|
11262
|
+
athenaSessionId: result.athenaSessionId,
|
|
11263
|
+
adapterSessionId: result.adapterSessionId,
|
|
11264
|
+
finalMessage: result.finalMessage,
|
|
11265
|
+
tokens: result.tokens,
|
|
11266
|
+
durationMs: result.durationMs,
|
|
11267
|
+
message: result.failure.message
|
|
11268
|
+
});
|
|
11269
|
+
return;
|
|
11270
|
+
}
|
|
11271
|
+
await sendTerminal("completion", {
|
|
11272
|
+
success: result.success,
|
|
11273
|
+
exitCode: result.exitCode,
|
|
11274
|
+
athenaSessionId: result.athenaSessionId,
|
|
11275
|
+
adapterSessionId: result.adapterSessionId,
|
|
11276
|
+
finalMessage: result.finalMessage,
|
|
11277
|
+
tokens: result.tokens,
|
|
11278
|
+
durationMs: result.durationMs
|
|
11279
|
+
});
|
|
11280
|
+
});
|
|
11281
|
+
} catch (err) {
|
|
11282
|
+
await Promise.all(pendingProgress);
|
|
11283
|
+
await sendTerminal("error", {
|
|
11284
|
+
message: err instanceof Error ? err.message : String(err)
|
|
11285
|
+
});
|
|
11286
|
+
}
|
|
11287
|
+
}
|
|
11288
|
+
|
|
11289
|
+
// src/app/runner/envelope.ts
|
|
11290
|
+
function parseRunnerEnvelope(text) {
|
|
11291
|
+
let value;
|
|
11292
|
+
try {
|
|
11293
|
+
value = JSON.parse(text);
|
|
11294
|
+
} catch {
|
|
11295
|
+
return null;
|
|
11296
|
+
}
|
|
11297
|
+
if (typeof value !== "object" || value === null) return null;
|
|
11298
|
+
const obj = value;
|
|
11299
|
+
const runId = obj["runId"];
|
|
11300
|
+
if (typeof runId !== "string" || runId.length === 0) return null;
|
|
11301
|
+
const kind = obj["kind"];
|
|
11302
|
+
if (kind === "job_assignment") {
|
|
11303
|
+
return { kind, runId, runSpec: obj["runSpec"] };
|
|
11304
|
+
}
|
|
11305
|
+
if (kind === "cancel") {
|
|
11306
|
+
return { kind, runId };
|
|
11307
|
+
}
|
|
11308
|
+
return null;
|
|
11309
|
+
}
|
|
11310
|
+
|
|
11311
|
+
// src/app/runner/runnerSession.ts
|
|
11312
|
+
function createRunnerSession(opts) {
|
|
11313
|
+
const {
|
|
11314
|
+
bridge,
|
|
11315
|
+
projectDir,
|
|
11316
|
+
runExecFn = runExec,
|
|
11317
|
+
bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
|
|
11318
|
+
now = Date.now
|
|
11319
|
+
} = opts;
|
|
11320
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
11321
|
+
function startAssignment(envelope, dispatchId, location) {
|
|
11322
|
+
const controller = new AbortController();
|
|
11323
|
+
inflight.set(envelope.runId, controller);
|
|
11324
|
+
return executeAssignment({
|
|
11325
|
+
envelope,
|
|
11326
|
+
bridge,
|
|
11327
|
+
dispatchId,
|
|
11328
|
+
location,
|
|
11329
|
+
projectDir,
|
|
11330
|
+
runExecFn,
|
|
11331
|
+
bootstrapRuntimeConfigFn,
|
|
11332
|
+
now,
|
|
11333
|
+
abortSignal: controller.signal
|
|
11334
|
+
}).finally(() => {
|
|
11335
|
+
if (inflight.get(envelope.runId) === controller) {
|
|
11336
|
+
inflight.delete(envelope.runId);
|
|
11337
|
+
}
|
|
11338
|
+
});
|
|
11339
|
+
}
|
|
11340
|
+
return {
|
|
11341
|
+
handleDispatch(input) {
|
|
11342
|
+
const envelope = parseRunnerEnvelope(input.text);
|
|
11343
|
+
if (!envelope) {
|
|
11344
|
+
return { recognised: false, completed: Promise.resolve() };
|
|
11345
|
+
}
|
|
11346
|
+
if (envelope.kind === "job_assignment") {
|
|
11347
|
+
const completed = startAssignment(
|
|
11348
|
+
envelope,
|
|
11349
|
+
input.dispatchId,
|
|
11350
|
+
input.location
|
|
11351
|
+
);
|
|
11352
|
+
return { recognised: true, completed };
|
|
11353
|
+
}
|
|
11354
|
+
const controller = inflight.get(envelope.runId);
|
|
11355
|
+
if (controller) controller.abort();
|
|
11356
|
+
return { recognised: true, completed: Promise.resolve() };
|
|
11357
|
+
}
|
|
11358
|
+
};
|
|
11359
|
+
}
|
|
11360
|
+
|
|
11361
|
+
// src/app/runner/dispatchRouter.ts
|
|
11362
|
+
function makeDispatchRouter(opts) {
|
|
11363
|
+
return (payload) => {
|
|
11364
|
+
if (opts.runnerSession) {
|
|
11365
|
+
const result = opts.runnerSession.handleDispatch({
|
|
11366
|
+
text: payload.inbound.text,
|
|
11367
|
+
dispatchId: payload.dispatchId,
|
|
11368
|
+
location: payload.inbound.location
|
|
11369
|
+
});
|
|
11370
|
+
if (result.recognised) return;
|
|
11371
|
+
}
|
|
11372
|
+
opts.fallback(payload);
|
|
11373
|
+
};
|
|
11374
|
+
}
|
|
11375
|
+
|
|
10951
11376
|
// src/ui/components/SessionPicker.tsx
|
|
10952
11377
|
import { useState as useState11 } from "react";
|
|
10953
11378
|
import { Box as Box11, Text as Text17, useInput as useInput12, useStdout as useStdout5 } from "ink";
|
|
@@ -13318,17 +13743,25 @@ function AppContent({
|
|
|
13318
13743
|
[addMessage, currentSessionId, feedEvents.length, spawnHarness]
|
|
13319
13744
|
);
|
|
13320
13745
|
submitDispatchAsTurnRef.current = submitDispatchAsTurn;
|
|
13746
|
+
const runnerSession = useMemo17(
|
|
13747
|
+
() => sessionBridge ? createRunnerSession({ bridge: sessionBridge, projectDir }) : null,
|
|
13748
|
+
[sessionBridge, projectDir]
|
|
13749
|
+
);
|
|
13321
13750
|
useEffect14(() => {
|
|
13322
13751
|
if (!sessionBridge) return;
|
|
13323
|
-
const
|
|
13324
|
-
|
|
13325
|
-
|
|
13326
|
-
|
|
13752
|
+
const router = makeDispatchRouter({
|
|
13753
|
+
runnerSession,
|
|
13754
|
+
fallback: (payload) => {
|
|
13755
|
+
if (isHarnessRunningRef.current || pendingDispatchRef.current) {
|
|
13756
|
+
queuedDispatchRef.current = payload;
|
|
13757
|
+
return;
|
|
13758
|
+
}
|
|
13759
|
+
submitDispatchAsTurnRef.current?.(payload);
|
|
13327
13760
|
}
|
|
13328
|
-
submitDispatchAsTurnRef.current?.(payload);
|
|
13329
13761
|
});
|
|
13762
|
+
const off = sessionBridge.onTurnDispatch(router);
|
|
13330
13763
|
return off;
|
|
13331
|
-
}, [sessionBridge]);
|
|
13764
|
+
}, [sessionBridge, runnerSession]);
|
|
13332
13765
|
useEffect14(() => {
|
|
13333
13766
|
if (isHarnessRunning) return;
|
|
13334
13767
|
if (pendingDispatchRef.current) return;
|
|
@@ -14363,6 +14796,7 @@ function App({
|
|
|
14363
14796
|
isolationPreset,
|
|
14364
14797
|
ascii,
|
|
14365
14798
|
athenaSessionId: initialAthenaSessionId,
|
|
14799
|
+
attachmentId,
|
|
14366
14800
|
initialTelemetryDiagnosticsConsent
|
|
14367
14801
|
}) {
|
|
14368
14802
|
const [clearCount, setClearCount] = useState18(0);
|
|
@@ -14578,6 +15012,7 @@ function App({
|
|
|
14578
15012
|
runtimeState.isolation?.allowedTools
|
|
14579
15013
|
),
|
|
14580
15014
|
athenaSessionId,
|
|
15015
|
+
...attachmentId !== void 0 ? { attachmentId } : {},
|
|
14581
15016
|
children: /* @__PURE__ */ jsx24(
|
|
14582
15017
|
AppContent,
|
|
14583
15018
|
{
|
|
@@ -15414,744 +15849,385 @@ function runChannelCommand(input, deps = {}) {
|
|
|
15414
15849
|
|
|
15415
15850
|
// src/app/entry/dashboardCommand.ts
|
|
15416
15851
|
import crypto3 from "crypto";
|
|
15417
|
-
import { spawn
|
|
15418
|
-
import
|
|
15852
|
+
import { spawn } from "child_process";
|
|
15853
|
+
import fs6 from "fs";
|
|
15419
15854
|
import { createRequire } from "module";
|
|
15420
15855
|
import os5 from "os";
|
|
15421
|
-
import
|
|
15422
|
-
import { fileURLToPath
|
|
15856
|
+
import path6 from "path";
|
|
15857
|
+
import { fileURLToPath } from "url";
|
|
15423
15858
|
|
|
15424
|
-
// src/
|
|
15425
|
-
import { spawn } from "child_process";
|
|
15859
|
+
// src/infra/daemon/serviceUnit.ts
|
|
15426
15860
|
import fs5 from "fs";
|
|
15861
|
+
import os4 from "os";
|
|
15427
15862
|
import path5 from "path";
|
|
15428
|
-
|
|
15429
|
-
|
|
15430
|
-
|
|
15431
|
-
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15863
|
+
function installServiceUnit(options) {
|
|
15864
|
+
const platform = options.platform ?? process.platform;
|
|
15865
|
+
const env2 = options.env ?? process.env;
|
|
15866
|
+
const home = env2["HOME"] ?? os4.homedir();
|
|
15867
|
+
if (platform === "darwin") {
|
|
15868
|
+
const target = options.targetPath ?? path5.join(home, "Library", "LaunchAgents", "ai.drisp.daemon.plist");
|
|
15869
|
+
const paths = daemonStatePaths(env2);
|
|
15870
|
+
const plist = renderLaunchdPlist({
|
|
15871
|
+
label: "ai.drisp.daemon",
|
|
15872
|
+
nodeBinary: options.nodeBinary,
|
|
15873
|
+
daemonEntry: options.daemonEntry,
|
|
15874
|
+
workingDirectory: home,
|
|
15875
|
+
stdoutPath: paths.logPath,
|
|
15876
|
+
stderrPath: paths.logPath
|
|
15877
|
+
});
|
|
15878
|
+
writeIfChanged(target, plist);
|
|
15879
|
+
return {
|
|
15880
|
+
ok: true,
|
|
15881
|
+
platform: "darwin",
|
|
15882
|
+
path: target,
|
|
15883
|
+
loadCommand: `launchctl load -w ${target}`,
|
|
15884
|
+
startCommand: "launchctl start ai.drisp.daemon"
|
|
15885
|
+
};
|
|
15886
|
+
}
|
|
15887
|
+
if (platform === "linux") {
|
|
15888
|
+
const target = options.targetPath ?? path5.join(home, ".config", "systemd", "user", "drisp-daemon.service");
|
|
15889
|
+
const unit = renderSystemdUnit({
|
|
15890
|
+
description: "Drisp dashboard runtime daemon",
|
|
15891
|
+
nodeBinary: options.nodeBinary,
|
|
15892
|
+
daemonEntry: options.daemonEntry
|
|
15893
|
+
});
|
|
15894
|
+
writeIfChanged(target, unit);
|
|
15895
|
+
return {
|
|
15896
|
+
ok: true,
|
|
15897
|
+
platform: "linux",
|
|
15898
|
+
path: target,
|
|
15899
|
+
loadCommand: "systemctl --user daemon-reload",
|
|
15900
|
+
startCommand: "systemctl --user enable --now drisp-daemon.service"
|
|
15901
|
+
};
|
|
15902
|
+
}
|
|
15903
|
+
return {
|
|
15904
|
+
ok: false,
|
|
15905
|
+
platform: "unsupported",
|
|
15906
|
+
message: `service install not supported on ${platform}`
|
|
15907
|
+
};
|
|
15448
15908
|
}
|
|
15449
|
-
function
|
|
15909
|
+
function writeIfChanged(target, content) {
|
|
15910
|
+
const dir = path5.dirname(target);
|
|
15911
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
15912
|
+
let existing = null;
|
|
15450
15913
|
try {
|
|
15451
|
-
|
|
15914
|
+
existing = fs5.readFileSync(target, "utf-8");
|
|
15452
15915
|
} catch (err) {
|
|
15453
|
-
|
|
15454
|
-
if (code === "ENOENT") {
|
|
15455
|
-
throw new Error(
|
|
15456
|
-
`gateway token missing at ${tokenPath}. Start the daemon with "athena gateway start" first.`
|
|
15457
|
-
);
|
|
15458
|
-
}
|
|
15459
|
-
throw err;
|
|
15916
|
+
if (err.code !== "ENOENT") throw err;
|
|
15460
15917
|
}
|
|
15918
|
+
if (existing === content) return;
|
|
15919
|
+
fs5.writeFileSync(target, content, { mode: 384 });
|
|
15461
15920
|
}
|
|
15462
|
-
function
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
15472
|
-
|
|
15473
|
-
|
|
15474
|
-
|
|
15475
|
-
|
|
15476
|
-
|
|
15477
|
-
|
|
15478
|
-
|
|
15479
|
-
|
|
15480
|
-
|
|
15481
|
-
|
|
15482
|
-
|
|
15483
|
-
|
|
15484
|
-
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15921
|
+
function renderLaunchdPlist(input) {
|
|
15922
|
+
const argv = [input.nodeBinary, input.daemonEntry].map((s) => ` <string>${escapeXml(s)}</string>`).join("\n");
|
|
15923
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
15924
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
15925
|
+
<plist version="1.0">
|
|
15926
|
+
<dict>
|
|
15927
|
+
<key>Label</key>
|
|
15928
|
+
<string>${escapeXml(input.label)}</string>
|
|
15929
|
+
<key>ProgramArguments</key>
|
|
15930
|
+
<array>
|
|
15931
|
+
${argv}
|
|
15932
|
+
</array>
|
|
15933
|
+
<key>RunAtLoad</key>
|
|
15934
|
+
<true/>
|
|
15935
|
+
<key>KeepAlive</key>
|
|
15936
|
+
<dict>
|
|
15937
|
+
<key>SuccessfulExit</key>
|
|
15938
|
+
<false/>
|
|
15939
|
+
</dict>
|
|
15940
|
+
<key>WorkingDirectory</key>
|
|
15941
|
+
<string>${escapeXml(input.workingDirectory)}</string>
|
|
15942
|
+
<key>StandardOutPath</key>
|
|
15943
|
+
<string>${escapeXml(input.stdoutPath)}</string>
|
|
15944
|
+
<key>StandardErrorPath</key>
|
|
15945
|
+
<string>${escapeXml(input.stderrPath)}</string>
|
|
15946
|
+
<key>ProcessType</key>
|
|
15947
|
+
<string>Background</string>
|
|
15948
|
+
</dict>
|
|
15949
|
+
</plist>
|
|
15950
|
+
`;
|
|
15951
|
+
}
|
|
15952
|
+
function renderSystemdUnit(input) {
|
|
15953
|
+
return `[Unit]
|
|
15954
|
+
Description=${input.description}
|
|
15955
|
+
After=network-online.target
|
|
15956
|
+
|
|
15957
|
+
[Service]
|
|
15958
|
+
Type=simple
|
|
15959
|
+
ExecStart=${input.nodeBinary} ${input.daemonEntry}
|
|
15960
|
+
Restart=always
|
|
15961
|
+
RestartSec=5
|
|
15962
|
+
SuccessExitStatus=0
|
|
15963
|
+
StandardOutput=journal
|
|
15964
|
+
StandardError=journal
|
|
15965
|
+
|
|
15966
|
+
[Install]
|
|
15967
|
+
WantedBy=default.target
|
|
15968
|
+
`;
|
|
15969
|
+
}
|
|
15970
|
+
function escapeXml(input) {
|
|
15971
|
+
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
15972
|
+
}
|
|
15973
|
+
|
|
15974
|
+
// src/app/entry/dashboardCommand.ts
|
|
15975
|
+
var USAGE4 = `Usage: athena dashboard <subcommand> [options]
|
|
15976
|
+
|
|
15977
|
+
Subcommands:
|
|
15978
|
+
pair <token> --url <dashboard-origin> [--name <machine-name>]
|
|
15979
|
+
Pair this machine with the dashboard, install the runtime
|
|
15980
|
+
daemon, and verify it reaches the dashboard socket.
|
|
15981
|
+
status Show pairing, runner binding, daemon process state, socket
|
|
15982
|
+
health, and token freshness. Non-zero on any unhealthy axis.
|
|
15983
|
+
logs [--tail N] [--follow]
|
|
15984
|
+
Tail the daemon log file at
|
|
15985
|
+
~/.local/state/drisp/dashboard-daemon.log.
|
|
15986
|
+
runs [--active] [--limit N]
|
|
15987
|
+
List runs the daemon has handled (in-memory ring, last 100).
|
|
15988
|
+
refresh Mint a short-lived access token (rotates the refresh token).
|
|
15989
|
+
daemon foreground
|
|
15990
|
+
Run the daemon in the foreground (process supervisors,
|
|
15991
|
+
debugging).
|
|
15992
|
+
daemon start|stop|restart|reload
|
|
15993
|
+
Local IPC against the daemon UDS.
|
|
15994
|
+
daemon install
|
|
15995
|
+
Generate a launchd plist (macOS) or systemd user unit (linux)
|
|
15996
|
+
so the daemon starts automatically on login.
|
|
15997
|
+
doctor Verify pairing health. With --runner <id>, also confirms the
|
|
15998
|
+
runner is bound to this instance.
|
|
15999
|
+
list Print the local attachment mirror (the runners the dashboard
|
|
16000
|
+
reported as bound to this instance at the last pair/refresh).
|
|
16001
|
+
connect Deprecated alias for "daemon foreground".
|
|
16002
|
+
unpair Stop the daemon, revoke the refresh token, and remove the
|
|
16003
|
+
local config.
|
|
16004
|
+
|
|
16005
|
+
Options:
|
|
16006
|
+
--url <origin> Dashboard origin (required for pair)
|
|
16007
|
+
--runner <id> Runner id (optional for "doctor")
|
|
16008
|
+
--name <name> Friendly machine name (optional, defaults to hostname)
|
|
16009
|
+
--tail N Number of trailing log lines (default 20)
|
|
16010
|
+
--follow Stream new log lines until interrupted
|
|
16011
|
+
--active Show only active runs
|
|
16012
|
+
--limit N Cap the number of runs returned
|
|
16013
|
+
--json Emit machine-readable JSON output
|
|
16014
|
+
`;
|
|
16015
|
+
var require_ = createRequire(import.meta.url);
|
|
16016
|
+
var cachedVersion = null;
|
|
16017
|
+
function readPackageVersion() {
|
|
16018
|
+
if (cachedVersion !== null) return cachedVersion;
|
|
16019
|
+
try {
|
|
16020
|
+
const injected = "0.4.7";
|
|
16021
|
+
if (typeof injected === "string" && injected.length > 0) {
|
|
16022
|
+
cachedVersion = injected;
|
|
16023
|
+
return cachedVersion;
|
|
15496
16024
|
}
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
16025
|
+
} catch {
|
|
16026
|
+
}
|
|
16027
|
+
try {
|
|
16028
|
+
const pkg = require_("../../../package.json");
|
|
16029
|
+
cachedVersion = pkg.version ?? "0.0.0";
|
|
16030
|
+
} catch {
|
|
16031
|
+
cachedVersion = "0.0.0";
|
|
16032
|
+
}
|
|
16033
|
+
return cachedVersion;
|
|
16034
|
+
}
|
|
16035
|
+
function defaultFingerprint() {
|
|
16036
|
+
const seed = [
|
|
16037
|
+
os5.hostname(),
|
|
16038
|
+
os5.userInfo().username,
|
|
16039
|
+
os5.platform(),
|
|
16040
|
+
os5.arch()
|
|
16041
|
+
].join("\0");
|
|
16042
|
+
return crypto3.createHash("sha256").update(seed).digest("hex");
|
|
16043
|
+
}
|
|
16044
|
+
function defaultHostInfo(name) {
|
|
16045
|
+
return {
|
|
16046
|
+
hostname: os5.hostname(),
|
|
16047
|
+
user: os5.userInfo().username,
|
|
16048
|
+
platform: os5.platform(),
|
|
16049
|
+
arch: os5.arch(),
|
|
16050
|
+
name: name ?? os5.hostname()
|
|
16051
|
+
};
|
|
16052
|
+
}
|
|
16053
|
+
async function runDashboardCommand(input, deps = {}) {
|
|
16054
|
+
const logOut = deps.logOut ?? ((m) => process.stdout.write(m + "\n"));
|
|
16055
|
+
const logError = deps.logError ?? ((m) => process.stderr.write(m + "\n"));
|
|
16056
|
+
const fetchImpl = deps.fetch ?? fetch;
|
|
16057
|
+
const now = deps.now ?? (() => Date.now());
|
|
16058
|
+
const fingerprint = deps.fingerprint ?? defaultFingerprint;
|
|
16059
|
+
const readConfig2 = deps.readConfig ?? (() => readDashboardClientConfig());
|
|
16060
|
+
const writeConfig = deps.writeConfig ?? ((c2) => writeDashboardClientConfig(c2));
|
|
16061
|
+
const removeConfig = deps.removeConfig ?? (() => removeDashboardClientConfig());
|
|
16062
|
+
const configPath = deps.configPath ?? (() => dashboardClientConfigPath());
|
|
16063
|
+
const readMirror = deps.readMirror ?? (() => readAttachmentMirror());
|
|
16064
|
+
const writeMirror = deps.writeMirror ?? ((m) => writeAttachmentMirror(m));
|
|
16065
|
+
const removeMirror = deps.removeMirror ?? (() => removeAttachmentMirror());
|
|
16066
|
+
const packageVersion = deps.packageVersion ?? readPackageVersion();
|
|
16067
|
+
const { subcommand, subcommandArgs, flags } = input;
|
|
16068
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
16069
|
+
logOut(USAGE4);
|
|
15504
16070
|
return 0;
|
|
15505
16071
|
}
|
|
15506
|
-
if (subcommand === "
|
|
15507
|
-
const
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
logError(
|
|
16072
|
+
if (subcommand === "pair") {
|
|
16073
|
+
const token = subcommandArgs[0];
|
|
16074
|
+
if (!token) {
|
|
16075
|
+
logError("dashboard pair: missing pairing token");
|
|
16076
|
+
logError(USAGE4);
|
|
15511
16077
|
return 2;
|
|
15512
16078
|
}
|
|
15513
|
-
|
|
16079
|
+
if (subcommandArgs.length > 1) {
|
|
16080
|
+
logError(`dashboard pair: unexpected argument ${subcommandArgs[1]}`);
|
|
16081
|
+
return 2;
|
|
16082
|
+
}
|
|
16083
|
+
if (!flags.url) {
|
|
16084
|
+
logError("dashboard pair: --url <dashboard-origin> is required");
|
|
16085
|
+
return 2;
|
|
16086
|
+
}
|
|
16087
|
+
let origin;
|
|
15514
16088
|
try {
|
|
15515
|
-
|
|
15516
|
-
if (json) {
|
|
15517
|
-
logOut(JSON.stringify({ ok: true, token: newToken, tokenPath }));
|
|
15518
|
-
} else {
|
|
15519
|
-
logOut(newToken);
|
|
15520
|
-
logOut(
|
|
15521
|
-
`gateway: rotated token at ${tokenPath}. Restart the daemon to drop existing connections, then re-run "athena gateway link --token <new>" on each client.`
|
|
15522
|
-
);
|
|
15523
|
-
}
|
|
15524
|
-
return 0;
|
|
16089
|
+
origin = normalizeDashboardUrl(flags.url);
|
|
15525
16090
|
} catch (err) {
|
|
15526
|
-
|
|
15527
|
-
|
|
15528
|
-
|
|
15529
|
-
} else {
|
|
15530
|
-
logError(`gateway rotate-token: ${message}`);
|
|
15531
|
-
}
|
|
15532
|
-
return 1;
|
|
15533
|
-
}
|
|
15534
|
-
}
|
|
15535
|
-
if (subcommand === "unlink") {
|
|
15536
|
-
if (subcommandArgs.length > 0) {
|
|
15537
|
-
logError("gateway unlink does not accept arguments");
|
|
16091
|
+
logError(
|
|
16092
|
+
`dashboard pair: ${err instanceof Error ? err.message : String(err)}`
|
|
16093
|
+
);
|
|
15538
16094
|
return 2;
|
|
15539
16095
|
}
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
16096
|
+
const fp = fingerprint();
|
|
16097
|
+
const body = {
|
|
16098
|
+
token,
|
|
16099
|
+
fingerprint: fp,
|
|
16100
|
+
hostInfo: (deps.hostInfo ?? (() => defaultHostInfo(flags.name)))(),
|
|
16101
|
+
capabilities: {
|
|
16102
|
+
instanceSocket: true,
|
|
16103
|
+
runtimeDaemon: true,
|
|
16104
|
+
cliVersion: packageVersion,
|
|
16105
|
+
// Legacy field — older dashboards read `version`. Drop in a
|
|
16106
|
+
// follow-up release once the dashboard accepts only `cliVersion`.
|
|
16107
|
+
version: packageVersion
|
|
16108
|
+
}
|
|
16109
|
+
};
|
|
16110
|
+
let response;
|
|
15550
16111
|
try {
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
timeoutMs: 3e3
|
|
16112
|
+
response = await fetchImpl(`${origin}/api/instances/pair`, {
|
|
16113
|
+
method: "POST",
|
|
16114
|
+
headers: { "content-type": "application/json" },
|
|
16115
|
+
body: JSON.stringify(body)
|
|
15556
16116
|
});
|
|
15557
|
-
const res = await client.request("ping", {});
|
|
15558
|
-
client.close();
|
|
15559
|
-
const latencyMs = Date.now() - startedAt;
|
|
15560
|
-
if (json) {
|
|
15561
|
-
logOut(
|
|
15562
|
-
JSON.stringify({
|
|
15563
|
-
ok: true,
|
|
15564
|
-
reachable: true,
|
|
15565
|
-
latency_ms: latencyMs,
|
|
15566
|
-
daemon_pid: res.daemonPid,
|
|
15567
|
-
daemon_uptime_ms: res.uptimeMs
|
|
15568
|
-
})
|
|
15569
|
-
);
|
|
15570
|
-
} else {
|
|
15571
|
-
logOut(
|
|
15572
|
-
`gateway: reachable pid=${res.daemonPid} uptime=${res.uptimeMs}ms latency=${latencyMs}ms`
|
|
15573
|
-
);
|
|
15574
|
-
}
|
|
15575
|
-
return 0;
|
|
15576
16117
|
} catch (err) {
|
|
15577
|
-
|
|
16118
|
+
logError(
|
|
16119
|
+
`dashboard pair: failed to reach ${origin}: ${err instanceof Error ? err.message : String(err)}`
|
|
16120
|
+
);
|
|
16121
|
+
return 1;
|
|
15578
16122
|
}
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
return 2;
|
|
16123
|
+
if (!response.ok) {
|
|
16124
|
+
const message = await safeReadError(response);
|
|
16125
|
+
logError(
|
|
16126
|
+
`dashboard pair: ${origin} returned ${response.status}${message ? ` \u2014 ${message}` : ""}`
|
|
16127
|
+
);
|
|
16128
|
+
return 1;
|
|
15586
16129
|
}
|
|
15587
|
-
|
|
15588
|
-
const tokenPath = resolveTokenPath();
|
|
15589
|
-
const endpoint = readClientConfig();
|
|
16130
|
+
let parsed;
|
|
15590
16131
|
try {
|
|
15591
|
-
|
|
15592
|
-
endpoint,
|
|
15593
|
-
socketPath,
|
|
15594
|
-
tokenPath,
|
|
15595
|
-
timeoutMs: 3e3
|
|
15596
|
-
});
|
|
15597
|
-
const res = await client.request("channels.reload", {});
|
|
15598
|
-
client.close();
|
|
15599
|
-
if (json) {
|
|
15600
|
-
logOut(JSON.stringify({ ok: true, ...res }));
|
|
15601
|
-
} else if (res.results.length === 0) {
|
|
15602
|
-
logOut("gateway: channels reloaded (no channel sidecars found)");
|
|
15603
|
-
} else {
|
|
15604
|
-
logOut("gateway: channels reloaded");
|
|
15605
|
-
for (const result of res.results) {
|
|
15606
|
-
logOut(formatChannelReloadResult(result));
|
|
15607
|
-
}
|
|
15608
|
-
}
|
|
15609
|
-
return 0;
|
|
16132
|
+
parsed = parsePairResponse(await response.json());
|
|
15610
16133
|
} catch (err) {
|
|
15611
|
-
|
|
16134
|
+
logError(
|
|
16135
|
+
`dashboard pair: invalid response from ${origin}: ${err instanceof Error ? err.message : String(err)}`
|
|
16136
|
+
);
|
|
16137
|
+
return 1;
|
|
15612
16138
|
}
|
|
15613
|
-
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
15617
|
-
|
|
15618
|
-
|
|
16139
|
+
if (parsed.requiredCliVersion && compareSemver(packageVersion, parsed.requiredCliVersion) < 0) {
|
|
16140
|
+
logError(
|
|
16141
|
+
`dashboard pair: cli version ${packageVersion} is older than the dashboard's required >=${parsed.requiredCliVersion}.`
|
|
16142
|
+
);
|
|
16143
|
+
logError(
|
|
16144
|
+
"dashboard pair: upgrade with `npm i -g @drisp/cli` then re-run pair."
|
|
16145
|
+
);
|
|
16146
|
+
return 1;
|
|
16147
|
+
}
|
|
16148
|
+
const config = {
|
|
16149
|
+
dashboardUrl: origin,
|
|
16150
|
+
instanceId: parsed.instanceId,
|
|
16151
|
+
refreshToken: parsed.refreshToken,
|
|
16152
|
+
fingerprint: fp,
|
|
16153
|
+
pairedAt: now()
|
|
16154
|
+
};
|
|
16155
|
+
writeConfig(config);
|
|
15619
16156
|
try {
|
|
15620
|
-
|
|
15621
|
-
|
|
15622
|
-
|
|
15623
|
-
|
|
15624
|
-
|
|
16157
|
+
writeMirror({
|
|
16158
|
+
instanceId: parsed.instanceId,
|
|
16159
|
+
fetchedAt: now(),
|
|
16160
|
+
attachments: (parsed.runners ?? []).map((r) => ({
|
|
16161
|
+
runnerId: r.runnerId,
|
|
16162
|
+
...r.name !== void 0 ? { name: r.name } : {},
|
|
16163
|
+
...r.executionTarget !== void 0 ? { executionTarget: r.executionTarget } : {},
|
|
16164
|
+
...r.remoteInstanceId !== void 0 ? { remoteInstanceId: r.remoteInstanceId } : {}
|
|
16165
|
+
}))
|
|
15625
16166
|
});
|
|
15626
|
-
const res = await client.request("status", {});
|
|
15627
|
-
client.close();
|
|
15628
|
-
if (json) {
|
|
15629
|
-
logOut(JSON.stringify(res));
|
|
15630
|
-
} else {
|
|
15631
|
-
const listenerSummary = formatListenerSummary(res.listener);
|
|
15632
|
-
const channelSummary = formatChannelSummary(res.channels);
|
|
15633
|
-
const runtimeSummary = formatRuntimeSummary(res.runtimes[0]);
|
|
15634
|
-
logOut(
|
|
15635
|
-
`gateway: running pid=${res.daemonPid} uptime=${res.uptimeMs}ms version=${res.version} ${listenerSummary}${channelSummary}${runtimeSummary}`
|
|
15636
|
-
);
|
|
15637
|
-
}
|
|
15638
|
-
return 0;
|
|
15639
16167
|
} catch (err) {
|
|
15640
|
-
|
|
16168
|
+
logError(
|
|
16169
|
+
`dashboard pair: failed to write attachment mirror: ${err instanceof Error ? err.message : String(err)}`
|
|
16170
|
+
);
|
|
15641
16171
|
}
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
logError(USAGE4);
|
|
15645
|
-
return 2;
|
|
15646
|
-
}
|
|
15647
|
-
async function defaultConnectGateway(opts) {
|
|
15648
|
-
if (opts.endpoint.mode === "remote") {
|
|
15649
|
-
return connect({
|
|
15650
|
-
socketPath: opts.socketPath,
|
|
15651
|
-
token: opts.endpoint.token,
|
|
15652
|
-
timeoutMs: opts.timeoutMs,
|
|
15653
|
-
transport: createWsClientTransport(
|
|
15654
|
-
wsClientOptionsForEndpoint({
|
|
15655
|
-
url: opts.endpoint.url,
|
|
15656
|
-
timeoutMs: opts.timeoutMs,
|
|
15657
|
-
tlsCaPath: opts.endpoint.tlsCaPath
|
|
15658
|
-
})
|
|
15659
|
-
)
|
|
16172
|
+
const daemonStart = await (deps.startRuntimeDaemon ?? defaultStartRuntimeDaemon)({
|
|
16173
|
+
log: (msg) => logOut(msg)
|
|
15660
16174
|
});
|
|
15661
|
-
|
|
15662
|
-
return connect({
|
|
15663
|
-
socketPath: opts.socketPath,
|
|
15664
|
-
token: readToken(opts.tokenPath),
|
|
15665
|
-
timeoutMs: opts.timeoutMs
|
|
15666
|
-
});
|
|
15667
|
-
}
|
|
15668
|
-
function parseLinkArgs(args) {
|
|
15669
|
-
const positional = [];
|
|
15670
|
-
let token;
|
|
15671
|
-
let tlsCaPath;
|
|
15672
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
15673
|
-
const arg = args[i];
|
|
15674
|
-
if (arg === "--token") {
|
|
15675
|
-
const v = args[i + 1];
|
|
15676
|
-
if (!v || v.startsWith("--")) {
|
|
15677
|
-
return { ok: false, message: "gateway link --token requires a value" };
|
|
15678
|
-
}
|
|
15679
|
-
token = v;
|
|
15680
|
-
i += 1;
|
|
15681
|
-
continue;
|
|
15682
|
-
}
|
|
15683
|
-
if (arg === "--tls-ca") {
|
|
15684
|
-
const v = args[i + 1];
|
|
15685
|
-
if (!v || v.startsWith("--")) {
|
|
15686
|
-
return { ok: false, message: "gateway link --tls-ca requires a path" };
|
|
15687
|
-
}
|
|
15688
|
-
tlsCaPath = v;
|
|
15689
|
-
i += 1;
|
|
15690
|
-
continue;
|
|
15691
|
-
}
|
|
15692
|
-
if (arg.startsWith("--token=")) {
|
|
15693
|
-
token = arg.slice("--token=".length);
|
|
15694
|
-
continue;
|
|
15695
|
-
}
|
|
15696
|
-
if (arg.startsWith("--tls-ca=")) {
|
|
15697
|
-
tlsCaPath = arg.slice("--tls-ca=".length);
|
|
15698
|
-
continue;
|
|
15699
|
-
}
|
|
15700
|
-
if (arg.startsWith("--")) {
|
|
15701
|
-
return { ok: false, message: `gateway link: unknown option ${arg}` };
|
|
15702
|
-
}
|
|
15703
|
-
positional.push(arg);
|
|
15704
|
-
}
|
|
15705
|
-
const url = positional[0];
|
|
15706
|
-
if (!url) {
|
|
15707
|
-
return { ok: false, message: "gateway link requires a ws:// or wss:// URL" };
|
|
15708
|
-
}
|
|
15709
|
-
if (positional.length > 1) {
|
|
15710
|
-
return { ok: false, message: "gateway link accepts exactly one URL" };
|
|
15711
|
-
}
|
|
15712
|
-
if (!isSupportedGatewayUrl(url)) {
|
|
15713
|
-
return { ok: false, message: "gateway link URL must use ws:// or wss://" };
|
|
15714
|
-
}
|
|
15715
|
-
if (!token) {
|
|
15716
|
-
return { ok: false, message: "gateway link requires --token <token>" };
|
|
15717
|
-
}
|
|
15718
|
-
if (tlsCaPath !== void 0 && tlsCaPath.length === 0) {
|
|
15719
|
-
return { ok: false, message: "gateway link --tls-ca requires a path" };
|
|
15720
|
-
}
|
|
15721
|
-
return {
|
|
15722
|
-
ok: true,
|
|
15723
|
-
url,
|
|
15724
|
-
token,
|
|
15725
|
-
...tlsCaPath !== void 0 ? { tlsCaPath } : {}
|
|
15726
|
-
};
|
|
15727
|
-
}
|
|
15728
|
-
function reportProbeFailure(err, json, logOut, logError) {
|
|
15729
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
15730
|
-
if (err instanceof GatewayUnreachableError) {
|
|
15731
|
-
if (json) {
|
|
16175
|
+
if (flags.json) {
|
|
15732
16176
|
logOut(
|
|
15733
16177
|
JSON.stringify({
|
|
15734
|
-
ok:
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
16178
|
+
ok: true,
|
|
16179
|
+
instanceId: parsed.instanceId,
|
|
16180
|
+
dashboardUrl: origin,
|
|
16181
|
+
configPath: configPath(),
|
|
16182
|
+
daemon: daemonStart,
|
|
16183
|
+
...parsed.runners ? { runners: parsed.runners } : {},
|
|
16184
|
+
...parsed.capabilityAck ? { capabilityAck: parsed.capabilityAck } : {},
|
|
16185
|
+
...parsed.requiredCliVersion ? { requiredCliVersion: parsed.requiredCliVersion } : {}
|
|
15738
16186
|
})
|
|
15739
16187
|
);
|
|
15740
16188
|
} else {
|
|
15741
|
-
|
|
16189
|
+
logOut(`dashboard: paired to ${origin} as ${parsed.instanceId}`);
|
|
16190
|
+
if (parsed.runners && parsed.runners.length > 0) {
|
|
16191
|
+
for (const runner of parsed.runners) {
|
|
16192
|
+
logOut(
|
|
16193
|
+
`dashboard: bound runner ${runner.name ?? runner.runnerId} (${runner.runnerId})`
|
|
16194
|
+
);
|
|
16195
|
+
}
|
|
16196
|
+
} else {
|
|
16197
|
+
logOut("dashboard: no runner bound to this pairing token.");
|
|
16198
|
+
logOut(
|
|
16199
|
+
"dashboard: bind a runner from runner settings, then this machine will receive its runs."
|
|
16200
|
+
);
|
|
16201
|
+
}
|
|
16202
|
+
if (parsed.capabilityAck === void 0) {
|
|
16203
|
+
logOut(
|
|
16204
|
+
"dashboard: dashboard did not echo capabilityAck (older server). Continuing."
|
|
16205
|
+
);
|
|
16206
|
+
}
|
|
16207
|
+
if (daemonStart.ok) {
|
|
16208
|
+
const status = daemonStart.alreadyRunning ? "runtime daemon already running, restarted with new token" : daemonStart.connected ? "runtime daemon connected (verified socket open)" : "runtime daemon started but did not reach the socket within 10s";
|
|
16209
|
+
logOut(`dashboard: ${status}`);
|
|
16210
|
+
if (!daemonStart.connected && !daemonStart.alreadyRunning) {
|
|
16211
|
+
logOut(
|
|
16212
|
+
"dashboard pair: pairing succeeded; tail logs with `drisp dashboard logs --follow`."
|
|
16213
|
+
);
|
|
16214
|
+
}
|
|
16215
|
+
} else {
|
|
16216
|
+
logError(
|
|
16217
|
+
`dashboard: runtime daemon did not start${daemonStart.message ? ` \u2014 ${daemonStart.message}` : ""}`
|
|
16218
|
+
);
|
|
16219
|
+
logOut(
|
|
16220
|
+
"dashboard pair: pairing succeeded; start the daemon with `drisp dashboard daemon start`."
|
|
16221
|
+
);
|
|
16222
|
+
}
|
|
16223
|
+
logOut("dashboard: ready. Click Run in the dashboard.");
|
|
15742
16224
|
}
|
|
15743
|
-
return
|
|
16225
|
+
return 0;
|
|
15744
16226
|
}
|
|
15745
|
-
if (
|
|
15746
|
-
if (
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
ok: false,
|
|
15750
|
-
reachable: true,
|
|
15751
|
-
reason: "unauthorized",
|
|
15752
|
-
message
|
|
15753
|
-
})
|
|
15754
|
-
);
|
|
15755
|
-
} else {
|
|
15756
|
-
logError(`gateway: unauthorized \u2014 ${message}`);
|
|
15757
|
-
}
|
|
15758
|
-
return 1;
|
|
15759
|
-
}
|
|
15760
|
-
if (json) {
|
|
15761
|
-
logOut(JSON.stringify({ ok: false, reason: "error", message }));
|
|
15762
|
-
} else {
|
|
15763
|
-
logError(`gateway: ${message}`);
|
|
15764
|
-
}
|
|
15765
|
-
return 1;
|
|
15766
|
-
}
|
|
15767
|
-
function formatRuntimeSummary(r) {
|
|
15768
|
-
if (!r) return " runtime=<none>";
|
|
15769
|
-
const lastRebindAt = r.binding.state !== "none" ? r.binding.lastRebindAt : void 0;
|
|
15770
|
-
const rebind = lastRebindAt !== void 0 ? ` rebound=${formatElapsed(Date.now() - lastRebindAt)}` : "";
|
|
15771
|
-
return ` runtime=${r.runtimeId} binding=${r.binding.state} pid=${r.pid}${rebind}`;
|
|
15772
|
-
}
|
|
15773
|
-
function formatChannelReloadResult(result) {
|
|
15774
|
-
const suffix = result.reason ? `: ${result.reason}` : "";
|
|
15775
|
-
return ` ${result.id} ${result.action}${suffix}`;
|
|
15776
|
-
}
|
|
15777
|
-
function formatChannelSummary(channels) {
|
|
15778
|
-
if (channels.length === 0) return " channels=<none>";
|
|
15779
|
-
const parts = channels.map((channel) => {
|
|
15780
|
-
const note = channel.note ? `(${channel.note})` : "";
|
|
15781
|
-
return `${channel.id}:${channel.state}${note}`;
|
|
15782
|
-
});
|
|
15783
|
-
return ` channels=${parts.join(",")}`;
|
|
15784
|
-
}
|
|
15785
|
-
function formatListenerSummary(listener) {
|
|
15786
|
-
if (listener.kind === "uds") {
|
|
15787
|
-
return `listener=uds:${listener.socketPath}`;
|
|
15788
|
-
}
|
|
15789
|
-
const flags = [];
|
|
15790
|
-
if (listener.tls) flags.push("tls");
|
|
15791
|
-
if (listener.insecure) flags.push("insecure");
|
|
15792
|
-
const suffix = flags.length > 0 ? ` (${flags.join(",")})` : "";
|
|
15793
|
-
return `listener=${listener.url}${suffix}`;
|
|
15794
|
-
}
|
|
15795
|
-
|
|
15796
|
-
// src/infra/daemon/serviceUnit.ts
|
|
15797
|
-
import fs6 from "fs";
|
|
15798
|
-
import os4 from "os";
|
|
15799
|
-
import path6 from "path";
|
|
15800
|
-
function installServiceUnit(options) {
|
|
15801
|
-
const platform = options.platform ?? process.platform;
|
|
15802
|
-
const env2 = options.env ?? process.env;
|
|
15803
|
-
const home = env2["HOME"] ?? os4.homedir();
|
|
15804
|
-
if (platform === "darwin") {
|
|
15805
|
-
const target = options.targetPath ?? path6.join(home, "Library", "LaunchAgents", "ai.drisp.daemon.plist");
|
|
15806
|
-
const paths = daemonStatePaths(env2);
|
|
15807
|
-
const plist = renderLaunchdPlist({
|
|
15808
|
-
label: "ai.drisp.daemon",
|
|
15809
|
-
nodeBinary: options.nodeBinary,
|
|
15810
|
-
daemonEntry: options.daemonEntry,
|
|
15811
|
-
workingDirectory: home,
|
|
15812
|
-
stdoutPath: paths.logPath,
|
|
15813
|
-
stderrPath: paths.logPath
|
|
15814
|
-
});
|
|
15815
|
-
writeIfChanged(target, plist);
|
|
15816
|
-
return {
|
|
15817
|
-
ok: true,
|
|
15818
|
-
platform: "darwin",
|
|
15819
|
-
path: target,
|
|
15820
|
-
loadCommand: `launchctl load -w ${target}`,
|
|
15821
|
-
startCommand: "launchctl start ai.drisp.daemon"
|
|
15822
|
-
};
|
|
15823
|
-
}
|
|
15824
|
-
if (platform === "linux") {
|
|
15825
|
-
const target = options.targetPath ?? path6.join(home, ".config", "systemd", "user", "drisp-daemon.service");
|
|
15826
|
-
const unit = renderSystemdUnit({
|
|
15827
|
-
description: "Drisp dashboard runtime daemon",
|
|
15828
|
-
nodeBinary: options.nodeBinary,
|
|
15829
|
-
daemonEntry: options.daemonEntry
|
|
15830
|
-
});
|
|
15831
|
-
writeIfChanged(target, unit);
|
|
15832
|
-
return {
|
|
15833
|
-
ok: true,
|
|
15834
|
-
platform: "linux",
|
|
15835
|
-
path: target,
|
|
15836
|
-
loadCommand: "systemctl --user daemon-reload",
|
|
15837
|
-
startCommand: "systemctl --user enable --now drisp-daemon.service"
|
|
15838
|
-
};
|
|
15839
|
-
}
|
|
15840
|
-
return {
|
|
15841
|
-
ok: false,
|
|
15842
|
-
platform: "unsupported",
|
|
15843
|
-
message: `service install not supported on ${platform}`
|
|
15844
|
-
};
|
|
15845
|
-
}
|
|
15846
|
-
function writeIfChanged(target, content) {
|
|
15847
|
-
const dir = path6.dirname(target);
|
|
15848
|
-
fs6.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
15849
|
-
let existing = null;
|
|
15850
|
-
try {
|
|
15851
|
-
existing = fs6.readFileSync(target, "utf-8");
|
|
15852
|
-
} catch (err) {
|
|
15853
|
-
if (err.code !== "ENOENT") throw err;
|
|
15854
|
-
}
|
|
15855
|
-
if (existing === content) return;
|
|
15856
|
-
fs6.writeFileSync(target, content, { mode: 384 });
|
|
15857
|
-
}
|
|
15858
|
-
function renderLaunchdPlist(input) {
|
|
15859
|
-
const argv = [input.nodeBinary, input.daemonEntry].map((s) => ` <string>${escapeXml(s)}</string>`).join("\n");
|
|
15860
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
15861
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
15862
|
-
<plist version="1.0">
|
|
15863
|
-
<dict>
|
|
15864
|
-
<key>Label</key>
|
|
15865
|
-
<string>${escapeXml(input.label)}</string>
|
|
15866
|
-
<key>ProgramArguments</key>
|
|
15867
|
-
<array>
|
|
15868
|
-
${argv}
|
|
15869
|
-
</array>
|
|
15870
|
-
<key>RunAtLoad</key>
|
|
15871
|
-
<true/>
|
|
15872
|
-
<key>KeepAlive</key>
|
|
15873
|
-
<dict>
|
|
15874
|
-
<key>SuccessfulExit</key>
|
|
15875
|
-
<false/>
|
|
15876
|
-
</dict>
|
|
15877
|
-
<key>WorkingDirectory</key>
|
|
15878
|
-
<string>${escapeXml(input.workingDirectory)}</string>
|
|
15879
|
-
<key>StandardOutPath</key>
|
|
15880
|
-
<string>${escapeXml(input.stdoutPath)}</string>
|
|
15881
|
-
<key>StandardErrorPath</key>
|
|
15882
|
-
<string>${escapeXml(input.stderrPath)}</string>
|
|
15883
|
-
<key>ProcessType</key>
|
|
15884
|
-
<string>Background</string>
|
|
15885
|
-
</dict>
|
|
15886
|
-
</plist>
|
|
15887
|
-
`;
|
|
15888
|
-
}
|
|
15889
|
-
function renderSystemdUnit(input) {
|
|
15890
|
-
return `[Unit]
|
|
15891
|
-
Description=${input.description}
|
|
15892
|
-
After=network-online.target
|
|
15893
|
-
|
|
15894
|
-
[Service]
|
|
15895
|
-
Type=simple
|
|
15896
|
-
ExecStart=${input.nodeBinary} ${input.daemonEntry}
|
|
15897
|
-
Restart=always
|
|
15898
|
-
RestartSec=5
|
|
15899
|
-
SuccessExitStatus=0
|
|
15900
|
-
StandardOutput=journal
|
|
15901
|
-
StandardError=journal
|
|
15902
|
-
|
|
15903
|
-
[Install]
|
|
15904
|
-
WantedBy=default.target
|
|
15905
|
-
`;
|
|
15906
|
-
}
|
|
15907
|
-
function escapeXml(input) {
|
|
15908
|
-
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
15909
|
-
}
|
|
15910
|
-
|
|
15911
|
-
// src/app/entry/dashboardCommand.ts
|
|
15912
|
-
var USAGE5 = `Usage: athena dashboard <subcommand> [options]
|
|
15913
|
-
|
|
15914
|
-
Subcommands:
|
|
15915
|
-
pair <token> --url <dashboard-origin> [--name <machine-name>]
|
|
15916
|
-
Pair this machine with the dashboard, install the runtime
|
|
15917
|
-
daemon, and verify it reaches the dashboard socket.
|
|
15918
|
-
status Show pairing, runner binding, daemon process state, socket
|
|
15919
|
-
health, and token freshness. Non-zero on any unhealthy axis.
|
|
15920
|
-
logs [--tail N] [--follow]
|
|
15921
|
-
Tail the daemon log file at
|
|
15922
|
-
~/.local/state/drisp/dashboard-daemon.log.
|
|
15923
|
-
runs [--active] [--limit N]
|
|
15924
|
-
List runs the daemon has handled (in-memory ring, last 100).
|
|
15925
|
-
refresh Mint a short-lived access token (rotates the refresh token).
|
|
15926
|
-
daemon foreground
|
|
15927
|
-
Run the daemon in the foreground (process supervisors,
|
|
15928
|
-
debugging).
|
|
15929
|
-
daemon start|stop|restart|reload
|
|
15930
|
-
Local IPC against the daemon UDS.
|
|
15931
|
-
daemon install
|
|
15932
|
-
Generate a launchd plist (macOS) or systemd user unit (linux)
|
|
15933
|
-
so the daemon starts automatically on login.
|
|
15934
|
-
doctor Verify pairing health. With --runner <id>, also confirms the
|
|
15935
|
-
runner is bound to this instance.
|
|
15936
|
-
console enable <runnerId>
|
|
15937
|
-
Configure the console channel for a runner (opinionated;
|
|
15938
|
-
writes the sidecar and reloads the gateway).
|
|
15939
|
-
console link <runnerId>
|
|
15940
|
-
Primitive: write ~/.config/athena/channels/console.json for
|
|
15941
|
-
the given runner. Prefer "console enable".
|
|
15942
|
-
connect Deprecated alias for "daemon foreground".
|
|
15943
|
-
unpair Stop the daemon, revoke the refresh token, and remove the
|
|
15944
|
-
local config.
|
|
15945
|
-
|
|
15946
|
-
Options:
|
|
15947
|
-
--url <origin> Dashboard origin (required for pair)
|
|
15948
|
-
--runner <id> Runner id (required for "console enable"/"console
|
|
15949
|
-
link", optional for "doctor")
|
|
15950
|
-
--name <name> Friendly machine name (optional, defaults to hostname)
|
|
15951
|
-
--tail N Number of trailing log lines (default 20)
|
|
15952
|
-
--follow Stream new log lines until interrupted
|
|
15953
|
-
--active Show only active runs
|
|
15954
|
-
--limit N Cap the number of runs returned
|
|
15955
|
-
--json Emit machine-readable JSON output
|
|
15956
|
-
`;
|
|
15957
|
-
var require_ = createRequire(import.meta.url);
|
|
15958
|
-
var cachedVersion = null;
|
|
15959
|
-
function readPackageVersion() {
|
|
15960
|
-
if (cachedVersion !== null) return cachedVersion;
|
|
15961
|
-
try {
|
|
15962
|
-
const injected = "0.4.4";
|
|
15963
|
-
if (typeof injected === "string" && injected.length > 0) {
|
|
15964
|
-
cachedVersion = injected;
|
|
15965
|
-
return cachedVersion;
|
|
15966
|
-
}
|
|
15967
|
-
} catch {
|
|
15968
|
-
}
|
|
15969
|
-
try {
|
|
15970
|
-
const pkg = require_("../../../package.json");
|
|
15971
|
-
cachedVersion = pkg.version ?? "0.0.0";
|
|
15972
|
-
} catch {
|
|
15973
|
-
cachedVersion = "0.0.0";
|
|
15974
|
-
}
|
|
15975
|
-
return cachedVersion;
|
|
15976
|
-
}
|
|
15977
|
-
function defaultFingerprint() {
|
|
15978
|
-
const seed = [
|
|
15979
|
-
os5.hostname(),
|
|
15980
|
-
os5.userInfo().username,
|
|
15981
|
-
os5.platform(),
|
|
15982
|
-
os5.arch()
|
|
15983
|
-
].join("\0");
|
|
15984
|
-
return crypto3.createHash("sha256").update(seed).digest("hex");
|
|
15985
|
-
}
|
|
15986
|
-
function defaultHostInfo(name) {
|
|
15987
|
-
return {
|
|
15988
|
-
hostname: os5.hostname(),
|
|
15989
|
-
user: os5.userInfo().username,
|
|
15990
|
-
platform: os5.platform(),
|
|
15991
|
-
arch: os5.arch(),
|
|
15992
|
-
name: name ?? os5.hostname()
|
|
15993
|
-
};
|
|
15994
|
-
}
|
|
15995
|
-
async function runDashboardCommand(input, deps = {}) {
|
|
15996
|
-
const logOut = deps.logOut ?? ((m) => process.stdout.write(m + "\n"));
|
|
15997
|
-
const logError = deps.logError ?? ((m) => process.stderr.write(m + "\n"));
|
|
15998
|
-
const fetchImpl = deps.fetch ?? fetch;
|
|
15999
|
-
const now = deps.now ?? (() => Date.now());
|
|
16000
|
-
const fingerprint = deps.fingerprint ?? defaultFingerprint;
|
|
16001
|
-
const readConfig2 = deps.readConfig ?? (() => readDashboardClientConfig());
|
|
16002
|
-
const writeConfig = deps.writeConfig ?? ((c2) => writeDashboardClientConfig(c2));
|
|
16003
|
-
const removeConfig = deps.removeConfig ?? (() => removeDashboardClientConfig());
|
|
16004
|
-
const configPath = deps.configPath ?? (() => dashboardClientConfigPath());
|
|
16005
|
-
const packageVersion = deps.packageVersion ?? readPackageVersion();
|
|
16006
|
-
const { subcommand, subcommandArgs, flags } = input;
|
|
16007
|
-
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
16008
|
-
logOut(USAGE5);
|
|
16009
|
-
return 0;
|
|
16010
|
-
}
|
|
16011
|
-
if (subcommand === "pair") {
|
|
16012
|
-
const token = subcommandArgs[0];
|
|
16013
|
-
if (!token) {
|
|
16014
|
-
logError("dashboard pair: missing pairing token");
|
|
16015
|
-
logError(USAGE5);
|
|
16016
|
-
return 2;
|
|
16017
|
-
}
|
|
16018
|
-
if (subcommandArgs.length > 1) {
|
|
16019
|
-
logError(`dashboard pair: unexpected argument ${subcommandArgs[1]}`);
|
|
16020
|
-
return 2;
|
|
16021
|
-
}
|
|
16022
|
-
if (!flags.url) {
|
|
16023
|
-
logError("dashboard pair: --url <dashboard-origin> is required");
|
|
16024
|
-
return 2;
|
|
16025
|
-
}
|
|
16026
|
-
let origin;
|
|
16027
|
-
try {
|
|
16028
|
-
origin = normalizeDashboardUrl(flags.url);
|
|
16029
|
-
} catch (err) {
|
|
16030
|
-
logError(
|
|
16031
|
-
`dashboard pair: ${err instanceof Error ? err.message : String(err)}`
|
|
16032
|
-
);
|
|
16033
|
-
return 2;
|
|
16034
|
-
}
|
|
16035
|
-
const fp = fingerprint();
|
|
16036
|
-
const body = {
|
|
16037
|
-
token,
|
|
16038
|
-
fingerprint: fp,
|
|
16039
|
-
hostInfo: (deps.hostInfo ?? (() => defaultHostInfo(flags.name)))(),
|
|
16040
|
-
capabilities: {
|
|
16041
|
-
instanceSocket: true,
|
|
16042
|
-
consoleAdapter: true,
|
|
16043
|
-
runtimeDaemon: true,
|
|
16044
|
-
cliVersion: packageVersion,
|
|
16045
|
-
// Legacy field — older dashboards read `version`. Drop in a
|
|
16046
|
-
// follow-up release once the dashboard accepts only `cliVersion`.
|
|
16047
|
-
version: packageVersion
|
|
16048
|
-
}
|
|
16049
|
-
};
|
|
16050
|
-
let response;
|
|
16051
|
-
try {
|
|
16052
|
-
response = await fetchImpl(`${origin}/api/instances/pair`, {
|
|
16053
|
-
method: "POST",
|
|
16054
|
-
headers: { "content-type": "application/json" },
|
|
16055
|
-
body: JSON.stringify(body)
|
|
16056
|
-
});
|
|
16057
|
-
} catch (err) {
|
|
16058
|
-
logError(
|
|
16059
|
-
`dashboard pair: failed to reach ${origin}: ${err instanceof Error ? err.message : String(err)}`
|
|
16060
|
-
);
|
|
16061
|
-
return 1;
|
|
16062
|
-
}
|
|
16063
|
-
if (!response.ok) {
|
|
16064
|
-
const message = await safeReadError(response);
|
|
16065
|
-
logError(
|
|
16066
|
-
`dashboard pair: ${origin} returned ${response.status}${message ? ` \u2014 ${message}` : ""}`
|
|
16067
|
-
);
|
|
16068
|
-
return 1;
|
|
16069
|
-
}
|
|
16070
|
-
let parsed;
|
|
16071
|
-
try {
|
|
16072
|
-
parsed = parsePairResponse(await response.json());
|
|
16073
|
-
} catch (err) {
|
|
16074
|
-
logError(
|
|
16075
|
-
`dashboard pair: invalid response from ${origin}: ${err instanceof Error ? err.message : String(err)}`
|
|
16076
|
-
);
|
|
16077
|
-
return 1;
|
|
16078
|
-
}
|
|
16079
|
-
if (parsed.requiredCliVersion && compareSemver(packageVersion, parsed.requiredCliVersion) < 0) {
|
|
16080
|
-
logError(
|
|
16081
|
-
`dashboard pair: cli version ${packageVersion} is older than the dashboard's required >=${parsed.requiredCliVersion}.`
|
|
16082
|
-
);
|
|
16083
|
-
logError(
|
|
16084
|
-
"dashboard pair: upgrade with `npm i -g @drisp/cli` then re-run pair."
|
|
16085
|
-
);
|
|
16086
|
-
return 1;
|
|
16087
|
-
}
|
|
16088
|
-
const config = {
|
|
16089
|
-
dashboardUrl: origin,
|
|
16090
|
-
instanceId: parsed.instanceId,
|
|
16091
|
-
refreshToken: parsed.refreshToken,
|
|
16092
|
-
fingerprint: fp,
|
|
16093
|
-
pairedAt: now()
|
|
16094
|
-
};
|
|
16095
|
-
writeConfig(config);
|
|
16096
|
-
const daemonStart = await (deps.startRuntimeDaemon ?? defaultStartRuntimeDaemon)({
|
|
16097
|
-
log: (msg) => logOut(msg)
|
|
16098
|
-
});
|
|
16099
|
-
if (flags.json) {
|
|
16100
|
-
logOut(
|
|
16101
|
-
JSON.stringify({
|
|
16102
|
-
ok: true,
|
|
16103
|
-
instanceId: parsed.instanceId,
|
|
16104
|
-
dashboardUrl: origin,
|
|
16105
|
-
configPath: configPath(),
|
|
16106
|
-
daemon: daemonStart,
|
|
16107
|
-
...parsed.runners ? { runners: parsed.runners } : {},
|
|
16108
|
-
...parsed.capabilityAck ? { capabilityAck: parsed.capabilityAck } : {},
|
|
16109
|
-
...parsed.requiredCliVersion ? { requiredCliVersion: parsed.requiredCliVersion } : {}
|
|
16110
|
-
})
|
|
16111
|
-
);
|
|
16112
|
-
} else {
|
|
16113
|
-
logOut(`dashboard: paired to ${origin} as ${parsed.instanceId}`);
|
|
16114
|
-
if (parsed.runners && parsed.runners.length > 0) {
|
|
16115
|
-
for (const runner of parsed.runners) {
|
|
16116
|
-
logOut(
|
|
16117
|
-
`dashboard: bound runner ${runner.name ?? runner.runnerId} (${runner.runnerId})`
|
|
16118
|
-
);
|
|
16119
|
-
}
|
|
16120
|
-
} else {
|
|
16121
|
-
logOut("dashboard: no runner bound to this pairing token.");
|
|
16122
|
-
logOut(
|
|
16123
|
-
"dashboard: bind a runner from runner settings, then this machine will receive its runs."
|
|
16124
|
-
);
|
|
16125
|
-
}
|
|
16126
|
-
if (parsed.capabilityAck === void 0) {
|
|
16127
|
-
logOut(
|
|
16128
|
-
"dashboard: dashboard did not echo capabilityAck (older server). Continuing."
|
|
16129
|
-
);
|
|
16130
|
-
}
|
|
16131
|
-
if (daemonStart.ok) {
|
|
16132
|
-
const status = daemonStart.alreadyRunning ? "runtime daemon already running, restarted with new token" : daemonStart.connected ? "runtime daemon connected (verified socket open)" : "runtime daemon started but did not reach the socket within 10s";
|
|
16133
|
-
logOut(`dashboard: ${status}`);
|
|
16134
|
-
if (!daemonStart.connected && !daemonStart.alreadyRunning) {
|
|
16135
|
-
logOut(
|
|
16136
|
-
"dashboard pair: pairing succeeded; tail logs with `drisp dashboard logs --follow`."
|
|
16137
|
-
);
|
|
16138
|
-
}
|
|
16139
|
-
} else {
|
|
16140
|
-
logError(
|
|
16141
|
-
`dashboard: runtime daemon did not start${daemonStart.message ? ` \u2014 ${daemonStart.message}` : ""}`
|
|
16142
|
-
);
|
|
16143
|
-
logOut(
|
|
16144
|
-
"dashboard pair: pairing succeeded; start the daemon with `drisp dashboard daemon start`."
|
|
16145
|
-
);
|
|
16146
|
-
}
|
|
16147
|
-
logOut("dashboard: ready. Click Run in the dashboard.");
|
|
16148
|
-
}
|
|
16149
|
-
return 0;
|
|
16150
|
-
}
|
|
16151
|
-
if (subcommand === "status") {
|
|
16152
|
-
if (subcommandArgs.length > 0) {
|
|
16153
|
-
logError(`dashboard status: unexpected argument ${subcommandArgs[0]}`);
|
|
16154
|
-
return 2;
|
|
16227
|
+
if (subcommand === "status") {
|
|
16228
|
+
if (subcommandArgs.length > 0) {
|
|
16229
|
+
logError(`dashboard status: unexpected argument ${subcommandArgs[0]}`);
|
|
16230
|
+
return 2;
|
|
16155
16231
|
}
|
|
16156
16232
|
const config = readConfig2();
|
|
16157
16233
|
if (!config) {
|
|
@@ -16738,149 +16814,79 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
16738
16814
|
return reportDoctor2(runnerOk);
|
|
16739
16815
|
}
|
|
16740
16816
|
if (subcommand === "console") {
|
|
16741
|
-
const
|
|
16742
|
-
if (
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
return 2;
|
|
16747
|
-
}
|
|
16748
|
-
if (subcommandArgs.length > 2) {
|
|
16749
|
-
logError(
|
|
16750
|
-
`dashboard console enable: unexpected argument ${subcommandArgs[2]}`
|
|
16751
|
-
);
|
|
16752
|
-
return 2;
|
|
16753
|
-
}
|
|
16754
|
-
const config = readConfig2();
|
|
16755
|
-
if (!config) {
|
|
16756
|
-
logError(
|
|
16757
|
-
'dashboard console enable: not paired. Run "drisp dashboard pair" first.'
|
|
16758
|
-
);
|
|
16759
|
-
return 1;
|
|
16760
|
-
}
|
|
16761
|
-
const refreshResult = await tryRefresh("refresh");
|
|
16762
|
-
if (!refreshResult.ok) return refreshResult.code;
|
|
16763
|
-
const health = await fetchRunnerHealth(
|
|
16764
|
-
fetchImpl,
|
|
16765
|
-
config.dashboardUrl,
|
|
16766
|
-
runnerId,
|
|
16767
|
-
refreshResult.token
|
|
16768
|
-
);
|
|
16769
|
-
if (!health.matches) {
|
|
16770
|
-
logError(
|
|
16771
|
-
`dashboard console enable: runner ${runnerId} is not bound to this instance${health.error ? ` (${health.error})` : ""}`
|
|
16772
|
-
);
|
|
16773
|
-
return 1;
|
|
16774
|
-
}
|
|
16775
|
-
return await runDashboardCommand(
|
|
16776
|
-
{
|
|
16777
|
-
subcommand: "console",
|
|
16778
|
-
subcommandArgs: ["link", runnerId],
|
|
16779
|
-
flags: input.flags
|
|
16780
|
-
},
|
|
16781
|
-
deps
|
|
16782
|
-
);
|
|
16817
|
+
const message = "dashboard console is deprecated; paired dashboard feed sync now routes dashboard UI and channel decisions.";
|
|
16818
|
+
if (flags.json) {
|
|
16819
|
+
logOut(JSON.stringify({ ok: false, deprecated: true, message }));
|
|
16820
|
+
} else {
|
|
16821
|
+
logError(message);
|
|
16783
16822
|
}
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
}
|
|
16796
|
-
const config = readConfig2();
|
|
16797
|
-
if (!config) {
|
|
16798
|
-
logError(
|
|
16799
|
-
'dashboard console link: not paired. Run "drisp dashboard pair" first.'
|
|
16800
|
-
);
|
|
16801
|
-
return 1;
|
|
16802
|
-
}
|
|
16803
|
-
let brokerUrl;
|
|
16804
|
-
try {
|
|
16805
|
-
brokerUrl = consoleBrokerUrl(config.dashboardUrl, runnerId);
|
|
16806
|
-
} catch (err) {
|
|
16807
|
-
logError(
|
|
16808
|
-
`dashboard console link: ${err instanceof Error ? err.message : String(err)}`
|
|
16809
|
-
);
|
|
16810
|
-
return 1;
|
|
16811
|
-
}
|
|
16812
|
-
const dir = (deps.channelDir ?? channelSidecarDir)();
|
|
16813
|
-
let previousBroker;
|
|
16814
|
-
const target = path7.join(dir, "console.json");
|
|
16815
|
-
try {
|
|
16816
|
-
const existing = JSON.parse(fs7.readFileSync(target, "utf-8"));
|
|
16817
|
-
if (typeof existing.broker_url === "string") {
|
|
16818
|
-
previousBroker = existing.broker_url;
|
|
16819
|
-
}
|
|
16820
|
-
} catch {
|
|
16821
|
-
}
|
|
16822
|
-
try {
|
|
16823
|
-
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
16824
|
-
} catch (err) {
|
|
16825
|
-
logError(
|
|
16826
|
-
`dashboard console link: failed to create ${dir}: ${err instanceof Error ? err.message : String(err)}`
|
|
16827
|
-
);
|
|
16828
|
-
return 1;
|
|
16829
|
-
}
|
|
16830
|
-
const payload = {
|
|
16831
|
-
broker_url: brokerUrl,
|
|
16832
|
-
runner_id: runnerId,
|
|
16833
|
-
dashboard_config: true
|
|
16834
|
-
};
|
|
16835
|
-
const tmp = `${target}.tmp`;
|
|
16836
|
-
try {
|
|
16837
|
-
fs7.writeFileSync(tmp, JSON.stringify(payload, null, 2) + "\n", {
|
|
16838
|
-
mode: 384
|
|
16839
|
-
});
|
|
16840
|
-
fs7.renameSync(tmp, target);
|
|
16841
|
-
} catch (err) {
|
|
16842
|
-
try {
|
|
16843
|
-
fs7.unlinkSync(tmp);
|
|
16844
|
-
} catch {
|
|
16845
|
-
}
|
|
16823
|
+
return 1;
|
|
16824
|
+
}
|
|
16825
|
+
if (subcommand === "list") {
|
|
16826
|
+
if (subcommandArgs.length > 0) {
|
|
16827
|
+
logError(`dashboard list: unexpected argument ${subcommandArgs[0]}`);
|
|
16828
|
+
return 2;
|
|
16829
|
+
}
|
|
16830
|
+
const config = readConfig2();
|
|
16831
|
+
if (!config) {
|
|
16832
|
+
if (flags.json) {
|
|
16833
|
+
logOut(JSON.stringify({ ok: false, paired: false }));
|
|
16834
|
+
} else {
|
|
16846
16835
|
logError(
|
|
16847
|
-
|
|
16836
|
+
'dashboard list: not paired. Run "drisp dashboard pair" first.'
|
|
16848
16837
|
);
|
|
16849
|
-
return 1;
|
|
16850
16838
|
}
|
|
16851
|
-
|
|
16839
|
+
return 1;
|
|
16840
|
+
}
|
|
16841
|
+
const mirror = readMirror();
|
|
16842
|
+
if (!mirror) {
|
|
16852
16843
|
if (flags.json) {
|
|
16853
16844
|
logOut(
|
|
16854
16845
|
JSON.stringify({
|
|
16855
16846
|
ok: true,
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
gatewayReload: reload
|
|
16847
|
+
paired: true,
|
|
16848
|
+
instanceId: config.instanceId,
|
|
16849
|
+
attachments: [],
|
|
16850
|
+
mirror: null
|
|
16861
16851
|
})
|
|
16862
16852
|
);
|
|
16863
16853
|
} else {
|
|
16864
|
-
|
|
16865
|
-
|
|
16866
|
-
|
|
16867
|
-
logOut(
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
logOut(`console: gateway channels reloaded (${reload.message})`);
|
|
16871
|
-
} else {
|
|
16872
|
-
logError(`console: gateway reload skipped: ${reload.message}`);
|
|
16873
|
-
logOut(
|
|
16874
|
-
"console: start or reload the gateway before using the Console tab."
|
|
16875
|
-
);
|
|
16876
|
-
}
|
|
16854
|
+
logOut(
|
|
16855
|
+
`dashboard list: paired as ${config.instanceId}, no attachment mirror on disk.`
|
|
16856
|
+
);
|
|
16857
|
+
logOut(
|
|
16858
|
+
"dashboard list: re-run `drisp dashboard pair` to refresh the mirror."
|
|
16859
|
+
);
|
|
16877
16860
|
}
|
|
16878
16861
|
return 0;
|
|
16879
16862
|
}
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16863
|
+
if (flags.json) {
|
|
16864
|
+
logOut(
|
|
16865
|
+
JSON.stringify({
|
|
16866
|
+
ok: true,
|
|
16867
|
+
paired: true,
|
|
16868
|
+
instanceId: mirror.instanceId,
|
|
16869
|
+
fetchedAt: mirror.fetchedAt,
|
|
16870
|
+
attachments: mirror.attachments
|
|
16871
|
+
})
|
|
16872
|
+
);
|
|
16873
|
+
} else {
|
|
16874
|
+
logOut(`dashboard: instance ${mirror.instanceId}`);
|
|
16875
|
+
logOut(
|
|
16876
|
+
`dashboard: mirror fetched ${new Date(mirror.fetchedAt).toISOString()}`
|
|
16877
|
+
);
|
|
16878
|
+
if (mirror.attachments.length === 0) {
|
|
16879
|
+
logOut("dashboard: no runners attached.");
|
|
16880
|
+
} else {
|
|
16881
|
+
logOut(`dashboard: ${mirror.attachments.length} runner(s) attached:`);
|
|
16882
|
+
for (const a of mirror.attachments) {
|
|
16883
|
+
const label = a.name ? `${a.name} (${a.runnerId})` : a.runnerId;
|
|
16884
|
+
const target = a.executionTarget ? ` [${a.executionTarget}]` : "";
|
|
16885
|
+
logOut(` - ${label}${target}`);
|
|
16886
|
+
}
|
|
16887
|
+
}
|
|
16888
|
+
}
|
|
16889
|
+
return 0;
|
|
16884
16890
|
}
|
|
16885
16891
|
if (subcommand === "unpair") {
|
|
16886
16892
|
if (subcommandArgs.length > 0) {
|
|
@@ -16953,6 +16959,7 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
16953
16959
|
}
|
|
16954
16960
|
}
|
|
16955
16961
|
removeConfig();
|
|
16962
|
+
removeMirror();
|
|
16956
16963
|
if (flags.json) {
|
|
16957
16964
|
logOut(
|
|
16958
16965
|
JSON.stringify({
|
|
@@ -16972,7 +16979,7 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
16972
16979
|
return 0;
|
|
16973
16980
|
}
|
|
16974
16981
|
logError(`Unknown dashboard subcommand: ${subcommand}`);
|
|
16975
|
-
logError(
|
|
16982
|
+
logError(USAGE4);
|
|
16976
16983
|
return 2;
|
|
16977
16984
|
}
|
|
16978
16985
|
function defaultWaitForShutdown() {
|
|
@@ -16984,36 +16991,21 @@ function defaultWaitForShutdown() {
|
|
|
16984
16991
|
};
|
|
16985
16992
|
process.once("SIGINT", onSignal);
|
|
16986
16993
|
process.once("SIGTERM", onSignal);
|
|
16987
|
-
});
|
|
16988
|
-
}
|
|
16989
|
-
async function defaultReloadGatewayChannels() {
|
|
16990
|
-
const out = [];
|
|
16991
|
-
const err = [];
|
|
16992
|
-
const code = await runGatewayCommand(
|
|
16993
|
-
{ subcommand: "reload-channels", subcommandArgs: [] },
|
|
16994
|
-
{
|
|
16995
|
-
logOut: (m) => out.push(m),
|
|
16996
|
-
logError: (m) => err.push(m)
|
|
16997
|
-
}
|
|
16998
|
-
);
|
|
16999
|
-
return {
|
|
17000
|
-
ok: code === 0,
|
|
17001
|
-
message: (code === 0 ? out.join("\n") : err.join("\n")) || (code === 0 ? "gateway channels reloaded" : "gateway not reachable")
|
|
17002
|
-
};
|
|
16994
|
+
});
|
|
17003
16995
|
}
|
|
17004
16996
|
function resolveDaemonEntry() {
|
|
17005
16997
|
let here;
|
|
17006
16998
|
try {
|
|
17007
|
-
here =
|
|
16999
|
+
here = fileURLToPath(import.meta.url);
|
|
17008
17000
|
} catch {
|
|
17009
17001
|
return null;
|
|
17010
17002
|
}
|
|
17011
17003
|
const candidates = [
|
|
17012
|
-
|
|
17004
|
+
path6.join(path6.dirname(here), "dashboard-daemon.js"),
|
|
17013
17005
|
// When invoked via `npm run start`, `here` may be the unbundled source
|
|
17014
17006
|
// path. Walk up until we hit a `dist/` sibling.
|
|
17015
|
-
|
|
17016
|
-
|
|
17007
|
+
path6.join(
|
|
17008
|
+
path6.dirname(here),
|
|
17017
17009
|
"..",
|
|
17018
17010
|
"..",
|
|
17019
17011
|
"..",
|
|
@@ -17023,7 +17015,7 @@ function resolveDaemonEntry() {
|
|
|
17023
17015
|
];
|
|
17024
17016
|
for (const candidate of candidates) {
|
|
17025
17017
|
try {
|
|
17026
|
-
|
|
17018
|
+
fs6.accessSync(candidate, fs6.constants.R_OK);
|
|
17027
17019
|
return candidate;
|
|
17028
17020
|
} catch {
|
|
17029
17021
|
}
|
|
@@ -17081,7 +17073,7 @@ async function defaultStartRuntimeDaemon(opts) {
|
|
|
17081
17073
|
}
|
|
17082
17074
|
let child;
|
|
17083
17075
|
try {
|
|
17084
|
-
child =
|
|
17076
|
+
child = spawn(process.execPath, [entry], {
|
|
17085
17077
|
detached: true,
|
|
17086
17078
|
stdio: "ignore",
|
|
17087
17079
|
env: buildDaemonEnv(process.env)
|
|
@@ -17208,14 +17200,14 @@ async function defaultTailDaemonLog(opts) {
|
|
|
17208
17200
|
let stream2 = null;
|
|
17209
17201
|
let watcher = null;
|
|
17210
17202
|
try {
|
|
17211
|
-
const stat =
|
|
17203
|
+
const stat = fs6.statSync(paths.logPath);
|
|
17212
17204
|
const size = stat.size;
|
|
17213
17205
|
const buf = Buffer.alloc(Math.min(size, opts.tail * 1024));
|
|
17214
|
-
const fd =
|
|
17206
|
+
const fd = fs6.openSync(paths.logPath, "r");
|
|
17215
17207
|
try {
|
|
17216
|
-
|
|
17208
|
+
fs6.readSync(fd, buf, 0, buf.length, Math.max(0, size - buf.length));
|
|
17217
17209
|
} finally {
|
|
17218
|
-
|
|
17210
|
+
fs6.closeSync(fd);
|
|
17219
17211
|
}
|
|
17220
17212
|
const lines = buf.toString("utf-8").split("\n").filter((l) => l.length > 0);
|
|
17221
17213
|
const tail = lines.slice(-opts.tail);
|
|
@@ -17229,253 +17221,612 @@ async function defaultTailDaemonLog(opts) {
|
|
|
17229
17221
|
`dashboard logs: log file ${paths.logPath} does not exist yet
|
|
17230
17222
|
`
|
|
17231
17223
|
);
|
|
17232
|
-
return opts.follow ? 0 : 1;
|
|
17224
|
+
return opts.follow ? 0 : 1;
|
|
17225
|
+
}
|
|
17226
|
+
throw err;
|
|
17227
|
+
}
|
|
17228
|
+
let position = fs6.statSync(paths.logPath).size;
|
|
17229
|
+
return await new Promise((resolve) => {
|
|
17230
|
+
let pollTimer = null;
|
|
17231
|
+
const drain = () => {
|
|
17232
|
+
try {
|
|
17233
|
+
const stat = fs6.statSync(paths.logPath);
|
|
17234
|
+
if (stat.size < position) {
|
|
17235
|
+
position = 0;
|
|
17236
|
+
}
|
|
17237
|
+
if (stat.size > position) {
|
|
17238
|
+
const fd = fs6.openSync(paths.logPath, "r");
|
|
17239
|
+
const buf = Buffer.alloc(stat.size - position);
|
|
17240
|
+
try {
|
|
17241
|
+
fs6.readSync(fd, buf, 0, buf.length, position);
|
|
17242
|
+
} finally {
|
|
17243
|
+
fs6.closeSync(fd);
|
|
17244
|
+
}
|
|
17245
|
+
position = stat.size;
|
|
17246
|
+
process.stdout.write(buf);
|
|
17247
|
+
}
|
|
17248
|
+
} catch (err) {
|
|
17249
|
+
if (err.code !== "ENOENT") {
|
|
17250
|
+
process.stderr.write(
|
|
17251
|
+
`dashboard logs: tail error: ${err instanceof Error ? err.message : String(err)}
|
|
17252
|
+
`
|
|
17253
|
+
);
|
|
17254
|
+
}
|
|
17255
|
+
}
|
|
17256
|
+
};
|
|
17257
|
+
try {
|
|
17258
|
+
watcher = fs6.watch(paths.logPath, { persistent: true }, () => {
|
|
17259
|
+
drain();
|
|
17260
|
+
});
|
|
17261
|
+
} catch {
|
|
17262
|
+
pollTimer = setInterval(drain, 500);
|
|
17263
|
+
pollTimer.unref();
|
|
17264
|
+
}
|
|
17265
|
+
const onSignal = () => {
|
|
17266
|
+
if (watcher) {
|
|
17267
|
+
watcher.close();
|
|
17268
|
+
watcher = null;
|
|
17269
|
+
}
|
|
17270
|
+
if (pollTimer) {
|
|
17271
|
+
clearInterval(pollTimer);
|
|
17272
|
+
pollTimer = null;
|
|
17273
|
+
}
|
|
17274
|
+
if (stream2) {
|
|
17275
|
+
stream2.close();
|
|
17276
|
+
stream2 = null;
|
|
17277
|
+
}
|
|
17278
|
+
resolve(0);
|
|
17279
|
+
};
|
|
17280
|
+
process.once("SIGINT", onSignal);
|
|
17281
|
+
process.once("SIGTERM", onSignal);
|
|
17282
|
+
});
|
|
17283
|
+
}
|
|
17284
|
+
function formatDuration2(seconds) {
|
|
17285
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "?";
|
|
17286
|
+
if (seconds < 60) return `${seconds}s`;
|
|
17287
|
+
if (seconds < 3600) {
|
|
17288
|
+
const m = Math.floor(seconds / 60);
|
|
17289
|
+
const s = seconds % 60;
|
|
17290
|
+
return s > 0 ? `${m}m${s}s` : `${m}m`;
|
|
17291
|
+
}
|
|
17292
|
+
if (seconds < 86400) {
|
|
17293
|
+
const h2 = Math.floor(seconds / 3600);
|
|
17294
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
17295
|
+
return m > 0 ? `${h2}h${m}m` : `${h2}h`;
|
|
17296
|
+
}
|
|
17297
|
+
const d = Math.floor(seconds / 86400);
|
|
17298
|
+
const h = Math.floor(seconds % 86400 / 3600);
|
|
17299
|
+
return h > 0 ? `${d}d${h}h` : `${d}d`;
|
|
17300
|
+
}
|
|
17301
|
+
async function fetchRunnerHealth(fetchImpl, dashboardUrl, runnerId, token) {
|
|
17302
|
+
const url = new URL(
|
|
17303
|
+
`/api/runners/${encodeURIComponent(runnerId)}`,
|
|
17304
|
+
dashboardUrl
|
|
17305
|
+
).toString();
|
|
17306
|
+
let response;
|
|
17307
|
+
try {
|
|
17308
|
+
response = await fetchImpl(url, {
|
|
17309
|
+
method: "GET",
|
|
17310
|
+
headers: {
|
|
17311
|
+
authorization: `Bearer ${token.accessToken}`,
|
|
17312
|
+
accept: "application/json"
|
|
17313
|
+
}
|
|
17314
|
+
});
|
|
17315
|
+
} catch (err) {
|
|
17316
|
+
return {
|
|
17317
|
+
id: runnerId,
|
|
17318
|
+
matches: false,
|
|
17319
|
+
error: `request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17320
|
+
};
|
|
17321
|
+
}
|
|
17322
|
+
if (!response.ok) {
|
|
17323
|
+
return {
|
|
17324
|
+
id: runnerId,
|
|
17325
|
+
matches: false,
|
|
17326
|
+
error: `dashboard returned ${response.status}`
|
|
17327
|
+
};
|
|
17328
|
+
}
|
|
17329
|
+
let body;
|
|
17330
|
+
try {
|
|
17331
|
+
body = await response.json();
|
|
17332
|
+
} catch (err) {
|
|
17333
|
+
return {
|
|
17334
|
+
id: runnerId,
|
|
17335
|
+
matches: false,
|
|
17336
|
+
error: `invalid response body: ${err instanceof Error ? err.message : String(err)}`
|
|
17337
|
+
};
|
|
17338
|
+
}
|
|
17339
|
+
const obj = typeof body === "object" && body !== null ? body : {};
|
|
17340
|
+
const executionTarget = typeof obj["executionTarget"] === "string" ? obj["executionTarget"] : void 0;
|
|
17341
|
+
const remoteInstanceId = typeof obj["remoteInstanceId"] === "string" ? obj["remoteInstanceId"] : void 0;
|
|
17342
|
+
const matches = executionTarget === "remote" && remoteInstanceId === token.instanceId;
|
|
17343
|
+
const reasons = [];
|
|
17344
|
+
if (executionTarget !== "remote") {
|
|
17345
|
+
reasons.push(
|
|
17346
|
+
`executionTarget=${executionTarget ?? "unset"} (expected "remote")`
|
|
17347
|
+
);
|
|
17348
|
+
}
|
|
17349
|
+
if (remoteInstanceId !== token.instanceId) {
|
|
17350
|
+
reasons.push(
|
|
17351
|
+
`remoteInstanceId=${remoteInstanceId ?? "unset"} (expected "${token.instanceId}")`
|
|
17352
|
+
);
|
|
17353
|
+
}
|
|
17354
|
+
return {
|
|
17355
|
+
id: runnerId,
|
|
17356
|
+
matches,
|
|
17357
|
+
...executionTarget !== void 0 ? { executionTarget } : {},
|
|
17358
|
+
...remoteInstanceId !== void 0 ? { remoteInstanceId } : {},
|
|
17359
|
+
...reasons.length > 0 ? { error: reasons.join("; ") } : {}
|
|
17360
|
+
};
|
|
17361
|
+
}
|
|
17362
|
+
function defaultInstallServiceUnit() {
|
|
17363
|
+
const entry = resolveDaemonEntry();
|
|
17364
|
+
if (!entry) {
|
|
17365
|
+
return {
|
|
17366
|
+
ok: false,
|
|
17367
|
+
platform: "unsupported",
|
|
17368
|
+
message: "cannot resolve dashboard-daemon.js entry path"
|
|
17369
|
+
};
|
|
17370
|
+
}
|
|
17371
|
+
return installServiceUnit({
|
|
17372
|
+
daemonEntry: entry,
|
|
17373
|
+
nodeBinary: process.execPath
|
|
17374
|
+
});
|
|
17375
|
+
}
|
|
17376
|
+
function compareSemver(a, b) {
|
|
17377
|
+
const parseN = (s) => {
|
|
17378
|
+
const parts = s.replace(/^v/, "").split("-")[0].split(".");
|
|
17379
|
+
return [
|
|
17380
|
+
Number.parseInt(parts[0] ?? "0", 10) || 0,
|
|
17381
|
+
Number.parseInt(parts[1] ?? "0", 10) || 0,
|
|
17382
|
+
Number.parseInt(parts[2] ?? "0", 10) || 0
|
|
17383
|
+
];
|
|
17384
|
+
};
|
|
17385
|
+
const av = parseN(a);
|
|
17386
|
+
const bv = parseN(b);
|
|
17387
|
+
for (let i = 0; i < 3; i += 1) {
|
|
17388
|
+
if (av[i] < bv[i]) return -1;
|
|
17389
|
+
if (av[i] > bv[i]) return 1;
|
|
17390
|
+
}
|
|
17391
|
+
return 0;
|
|
17392
|
+
}
|
|
17393
|
+
async function safeReadError(response) {
|
|
17394
|
+
try {
|
|
17395
|
+
const text = await response.text();
|
|
17396
|
+
if (text.length === 0) return "";
|
|
17397
|
+
try {
|
|
17398
|
+
const parsed = JSON.parse(text);
|
|
17399
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed["error"] === "string") {
|
|
17400
|
+
return parsed["error"];
|
|
17401
|
+
}
|
|
17402
|
+
} catch {
|
|
17403
|
+
}
|
|
17404
|
+
return text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
|
|
17405
|
+
} catch {
|
|
17406
|
+
return "";
|
|
17407
|
+
}
|
|
17408
|
+
}
|
|
17409
|
+
function parsePairResponse(raw) {
|
|
17410
|
+
if (typeof raw !== "object" || raw === null) {
|
|
17411
|
+
throw new Error("expected object");
|
|
17412
|
+
}
|
|
17413
|
+
const obj = raw;
|
|
17414
|
+
const instanceId = obj["instanceId"];
|
|
17415
|
+
const refreshToken = obj["refreshToken"];
|
|
17416
|
+
if (typeof instanceId !== "string" || instanceId.length === 0) {
|
|
17417
|
+
throw new Error("missing instanceId");
|
|
17418
|
+
}
|
|
17419
|
+
if (typeof refreshToken !== "string" || refreshToken.length === 0) {
|
|
17420
|
+
throw new Error("missing refreshToken");
|
|
17421
|
+
}
|
|
17422
|
+
return {
|
|
17423
|
+
instanceId,
|
|
17424
|
+
refreshToken,
|
|
17425
|
+
...typeof obj["jti"] === "string" ? { jti: obj["jti"] } : {},
|
|
17426
|
+
...typeof obj["accessToken"] === "string" ? { accessToken: obj["accessToken"] } : {},
|
|
17427
|
+
...typeof obj["expiresInSec"] === "number" ? { expiresInSec: obj["expiresInSec"] } : {},
|
|
17428
|
+
...Array.isArray(obj["runners"]) ? {
|
|
17429
|
+
runners: obj["runners"].map(parsePairedRunner).filter((runner) => runner !== null)
|
|
17430
|
+
} : {},
|
|
17431
|
+
...typeof obj["requiredCliVersion"] === "string" ? { requiredCliVersion: obj["requiredCliVersion"] } : {},
|
|
17432
|
+
...typeof obj["capabilityAck"] === "object" && obj["capabilityAck"] !== null ? { capabilityAck: parseCapabilityAck(obj["capabilityAck"]) } : {}
|
|
17433
|
+
};
|
|
17434
|
+
}
|
|
17435
|
+
function parseCapabilityAck(raw) {
|
|
17436
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
17437
|
+
const obj = raw;
|
|
17438
|
+
const ack = {};
|
|
17439
|
+
if (typeof obj["runtimeDaemon"] === "boolean") {
|
|
17440
|
+
ack.runtimeDaemon = obj["runtimeDaemon"];
|
|
17441
|
+
}
|
|
17442
|
+
if (typeof obj["instanceSocket"] === "boolean") {
|
|
17443
|
+
ack.instanceSocket = obj["instanceSocket"];
|
|
17444
|
+
}
|
|
17445
|
+
return ack;
|
|
17446
|
+
}
|
|
17447
|
+
function parsePairedRunner(raw) {
|
|
17448
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
17449
|
+
const obj = raw;
|
|
17450
|
+
const runnerId = obj["runnerId"];
|
|
17451
|
+
if (typeof runnerId !== "string" || runnerId.length === 0) return null;
|
|
17452
|
+
return {
|
|
17453
|
+
runnerId,
|
|
17454
|
+
...typeof obj["name"] === "string" ? { name: obj["name"] } : {},
|
|
17455
|
+
...typeof obj["executionTarget"] === "string" ? { executionTarget: obj["executionTarget"] } : {},
|
|
17456
|
+
...typeof obj["remoteInstanceId"] === "string" ? { remoteInstanceId: obj["remoteInstanceId"] } : {}
|
|
17457
|
+
};
|
|
17458
|
+
}
|
|
17459
|
+
|
|
17460
|
+
// src/app/entry/gatewayCommand.ts
|
|
17461
|
+
import { spawn as spawn2 } from "child_process";
|
|
17462
|
+
import fs7 from "fs";
|
|
17463
|
+
import path7 from "path";
|
|
17464
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17465
|
+
var USAGE5 = `Usage: athena-flow gateway <subcommand> [--json]
|
|
17466
|
+
|
|
17467
|
+
Subcommands:
|
|
17468
|
+
start Run the gateway daemon in foreground (only mode in this build).
|
|
17469
|
+
Options: [--bind <host:port>] [--insecure]
|
|
17470
|
+
[--tls-cert <path>] [--tls-key <path>]
|
|
17471
|
+
[--grace-period-ms <n>]
|
|
17472
|
+
status Print daemon pid, uptime, and version.
|
|
17473
|
+
probe Send a ping RPC and report reachability + latency.
|
|
17474
|
+
link Store a remote WS/WSS gateway endpoint for this user.
|
|
17475
|
+
unlink Restore local UDS gateway mode for this user.
|
|
17476
|
+
rotate-token Regenerate the gateway token file (server-side).
|
|
17477
|
+
Restart the daemon to drop existing connections; clients
|
|
17478
|
+
must re-run "athena gateway link --token <new>".
|
|
17479
|
+
reload-channels Reload channel sidecars without restarting the daemon.
|
|
17480
|
+
`;
|
|
17481
|
+
function defaultResolveDaemonEntry() {
|
|
17482
|
+
const here = path7.dirname(fileURLToPath2(import.meta.url));
|
|
17483
|
+
return path7.resolve(here, "athena-gateway.js");
|
|
17484
|
+
}
|
|
17485
|
+
function readToken(tokenPath) {
|
|
17486
|
+
try {
|
|
17487
|
+
return fs7.readFileSync(tokenPath, "utf-8").trim();
|
|
17488
|
+
} catch (err) {
|
|
17489
|
+
const code = err.code;
|
|
17490
|
+
if (code === "ENOENT") {
|
|
17491
|
+
throw new Error(
|
|
17492
|
+
`gateway token missing at ${tokenPath}. Start the daemon with "athena gateway start" first.`
|
|
17493
|
+
);
|
|
17233
17494
|
}
|
|
17234
17495
|
throw err;
|
|
17235
17496
|
}
|
|
17236
|
-
let position = fs7.statSync(paths.logPath).size;
|
|
17237
|
-
return await new Promise((resolve) => {
|
|
17238
|
-
let pollTimer = null;
|
|
17239
|
-
const drain = () => {
|
|
17240
|
-
try {
|
|
17241
|
-
const stat = fs7.statSync(paths.logPath);
|
|
17242
|
-
if (stat.size < position) {
|
|
17243
|
-
position = 0;
|
|
17244
|
-
}
|
|
17245
|
-
if (stat.size > position) {
|
|
17246
|
-
const fd = fs7.openSync(paths.logPath, "r");
|
|
17247
|
-
const buf = Buffer.alloc(stat.size - position);
|
|
17248
|
-
try {
|
|
17249
|
-
fs7.readSync(fd, buf, 0, buf.length, position);
|
|
17250
|
-
} finally {
|
|
17251
|
-
fs7.closeSync(fd);
|
|
17252
|
-
}
|
|
17253
|
-
position = stat.size;
|
|
17254
|
-
process.stdout.write(buf);
|
|
17255
|
-
}
|
|
17256
|
-
} catch (err) {
|
|
17257
|
-
if (err.code !== "ENOENT") {
|
|
17258
|
-
process.stderr.write(
|
|
17259
|
-
`dashboard logs: tail error: ${err instanceof Error ? err.message : String(err)}
|
|
17260
|
-
`
|
|
17261
|
-
);
|
|
17262
|
-
}
|
|
17263
|
-
}
|
|
17264
|
-
};
|
|
17265
|
-
try {
|
|
17266
|
-
watcher = fs7.watch(paths.logPath, { persistent: true }, () => {
|
|
17267
|
-
drain();
|
|
17268
|
-
});
|
|
17269
|
-
} catch {
|
|
17270
|
-
pollTimer = setInterval(drain, 500);
|
|
17271
|
-
pollTimer.unref?.();
|
|
17272
|
-
}
|
|
17273
|
-
const onSignal = () => {
|
|
17274
|
-
if (watcher) {
|
|
17275
|
-
watcher.close();
|
|
17276
|
-
watcher = null;
|
|
17277
|
-
}
|
|
17278
|
-
if (pollTimer) {
|
|
17279
|
-
clearInterval(pollTimer);
|
|
17280
|
-
pollTimer = null;
|
|
17281
|
-
}
|
|
17282
|
-
if (stream2) {
|
|
17283
|
-
stream2.close();
|
|
17284
|
-
stream2 = null;
|
|
17285
|
-
}
|
|
17286
|
-
resolve(0);
|
|
17287
|
-
};
|
|
17288
|
-
process.once("SIGINT", onSignal);
|
|
17289
|
-
process.once("SIGTERM", onSignal);
|
|
17290
|
-
});
|
|
17291
17497
|
}
|
|
17292
|
-
function
|
|
17293
|
-
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17498
|
+
function flagJson(args) {
|
|
17499
|
+
return args.includes("--json");
|
|
17500
|
+
}
|
|
17501
|
+
async function runGatewayCommand(input, deps = {}) {
|
|
17502
|
+
const logOut = deps.logOut ?? ((m) => process.stdout.write(m + "\n"));
|
|
17503
|
+
const logError = deps.logError ?? ((m) => process.stderr.write(m + "\n"));
|
|
17504
|
+
const resolveDaemonEntry2 = deps.resolveDaemonEntry ?? defaultResolveDaemonEntry;
|
|
17505
|
+
const resolveSocketPath = deps.resolveSocketPath ?? (() => resolveGatewayPaths().socketPath);
|
|
17506
|
+
const resolveTokenPath = deps.resolveTokenPath ?? (() => resolveGatewayPaths().tokenPath);
|
|
17507
|
+
const readClientConfig = deps.readClientConfig ?? readGatewayClientConfig;
|
|
17508
|
+
const writeClientConfig = deps.writeClientConfig ?? ((config) => writeGatewayClientConfig(config));
|
|
17509
|
+
const connectGateway = deps.connectGateway ?? defaultConnectGateway;
|
|
17510
|
+
const spawnDaemon = deps.spawnDaemon ?? ((entry, args) => spawn2(process.execPath, [entry, ...args], { stdio: "inherit" }));
|
|
17511
|
+
const { subcommand, subcommandArgs } = input;
|
|
17512
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
17513
|
+
logOut(USAGE5);
|
|
17514
|
+
return 0;
|
|
17299
17515
|
}
|
|
17300
|
-
if (
|
|
17301
|
-
const
|
|
17302
|
-
const
|
|
17303
|
-
return
|
|
17516
|
+
if (subcommand === "start") {
|
|
17517
|
+
const entry = resolveDaemonEntry2();
|
|
17518
|
+
const child = spawnDaemon(entry, subcommandArgs);
|
|
17519
|
+
return await new Promise((resolve) => {
|
|
17520
|
+
child.once("exit", (code) => resolve(code ?? 0));
|
|
17521
|
+
child.once("error", (err) => {
|
|
17522
|
+
logError(`gateway start: failed to spawn daemon: ${err.message}`);
|
|
17523
|
+
resolve(1);
|
|
17524
|
+
});
|
|
17525
|
+
});
|
|
17304
17526
|
}
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
response = await fetchImpl(url, {
|
|
17317
|
-
method: "GET",
|
|
17318
|
-
headers: {
|
|
17319
|
-
authorization: `Bearer ${token.accessToken}`,
|
|
17320
|
-
accept: "application/json"
|
|
17321
|
-
}
|
|
17527
|
+
if (subcommand === "link") {
|
|
17528
|
+
const parsed = parseLinkArgs(subcommandArgs);
|
|
17529
|
+
if (!parsed.ok) {
|
|
17530
|
+
logError(parsed.message);
|
|
17531
|
+
return 2;
|
|
17532
|
+
}
|
|
17533
|
+
writeClientConfig({
|
|
17534
|
+
mode: "remote",
|
|
17535
|
+
url: parsed.url,
|
|
17536
|
+
token: parsed.token,
|
|
17537
|
+
...parsed.tlsCaPath !== void 0 ? { tlsCaPath: parsed.tlsCaPath } : {}
|
|
17322
17538
|
});
|
|
17323
|
-
|
|
17324
|
-
return
|
|
17325
|
-
id: runnerId,
|
|
17326
|
-
matches: false,
|
|
17327
|
-
error: `request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17328
|
-
};
|
|
17539
|
+
logOut(`gateway: linked remote endpoint ${parsed.url}`);
|
|
17540
|
+
return 0;
|
|
17329
17541
|
}
|
|
17330
|
-
if (
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17542
|
+
if (subcommand === "rotate-token") {
|
|
17543
|
+
const json = flagJson(subcommandArgs);
|
|
17544
|
+
const extras = subcommandArgs.filter((a) => a !== "--json");
|
|
17545
|
+
if (extras.length > 0) {
|
|
17546
|
+
logError(`gateway rotate-token: unexpected argument ${extras[0]}`);
|
|
17547
|
+
return 2;
|
|
17548
|
+
}
|
|
17549
|
+
const tokenPath = resolveTokenPath();
|
|
17550
|
+
try {
|
|
17551
|
+
const newToken = rotateGatewayToken(tokenPath);
|
|
17552
|
+
if (json) {
|
|
17553
|
+
logOut(JSON.stringify({ ok: true, token: newToken, tokenPath }));
|
|
17554
|
+
} else {
|
|
17555
|
+
logOut(newToken);
|
|
17556
|
+
logOut(
|
|
17557
|
+
`gateway: rotated token at ${tokenPath}. Restart the daemon to drop existing connections, then re-run "athena gateway link --token <new>" on each client.`
|
|
17558
|
+
);
|
|
17559
|
+
}
|
|
17560
|
+
return 0;
|
|
17561
|
+
} catch (err) {
|
|
17562
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17563
|
+
if (json) {
|
|
17564
|
+
logOut(JSON.stringify({ ok: false, message }));
|
|
17565
|
+
} else {
|
|
17566
|
+
logError(`gateway rotate-token: ${message}`);
|
|
17567
|
+
}
|
|
17568
|
+
return 1;
|
|
17569
|
+
}
|
|
17336
17570
|
}
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
};
|
|
17571
|
+
if (subcommand === "unlink") {
|
|
17572
|
+
if (subcommandArgs.length > 0) {
|
|
17573
|
+
logError("gateway unlink does not accept arguments");
|
|
17574
|
+
return 2;
|
|
17575
|
+
}
|
|
17576
|
+
writeClientConfig({ mode: "local" });
|
|
17577
|
+
logOut("gateway: using local gateway endpoint");
|
|
17578
|
+
return 0;
|
|
17346
17579
|
}
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17350
|
-
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
17580
|
+
if (subcommand === "probe") {
|
|
17581
|
+
const json = flagJson(subcommandArgs);
|
|
17582
|
+
const socketPath = resolveSocketPath();
|
|
17583
|
+
const tokenPath = resolveTokenPath();
|
|
17584
|
+
const endpoint = readClientConfig();
|
|
17585
|
+
const startedAt = Date.now();
|
|
17586
|
+
try {
|
|
17587
|
+
const client = await connectGateway({
|
|
17588
|
+
endpoint,
|
|
17589
|
+
socketPath,
|
|
17590
|
+
tokenPath,
|
|
17591
|
+
timeoutMs: 3e3
|
|
17592
|
+
});
|
|
17593
|
+
const res = await client.request("ping", {});
|
|
17594
|
+
client.close();
|
|
17595
|
+
const latencyMs = Date.now() - startedAt;
|
|
17596
|
+
if (json) {
|
|
17597
|
+
logOut(
|
|
17598
|
+
JSON.stringify({
|
|
17599
|
+
ok: true,
|
|
17600
|
+
reachable: true,
|
|
17601
|
+
latency_ms: latencyMs,
|
|
17602
|
+
daemon_pid: res.daemonPid,
|
|
17603
|
+
daemon_uptime_ms: res.uptimeMs
|
|
17604
|
+
})
|
|
17605
|
+
);
|
|
17606
|
+
} else {
|
|
17607
|
+
logOut(
|
|
17608
|
+
`gateway: reachable pid=${res.daemonPid} uptime=${res.uptimeMs}ms latency=${latencyMs}ms`
|
|
17609
|
+
);
|
|
17610
|
+
}
|
|
17611
|
+
return 0;
|
|
17612
|
+
} catch (err) {
|
|
17613
|
+
return reportProbeFailure(err, json, logOut, logError);
|
|
17614
|
+
}
|
|
17356
17615
|
}
|
|
17357
|
-
if (
|
|
17358
|
-
|
|
17359
|
-
|
|
17360
|
-
)
|
|
17616
|
+
if (subcommand === "reload-channels") {
|
|
17617
|
+
const json = flagJson(subcommandArgs);
|
|
17618
|
+
const extras = subcommandArgs.filter((a) => a !== "--json");
|
|
17619
|
+
if (extras.length > 0) {
|
|
17620
|
+
logError(`gateway reload-channels: unexpected argument ${extras[0]}`);
|
|
17621
|
+
return 2;
|
|
17622
|
+
}
|
|
17623
|
+
const socketPath = resolveSocketPath();
|
|
17624
|
+
const tokenPath = resolveTokenPath();
|
|
17625
|
+
const endpoint = readClientConfig();
|
|
17626
|
+
try {
|
|
17627
|
+
const client = await connectGateway({
|
|
17628
|
+
endpoint,
|
|
17629
|
+
socketPath,
|
|
17630
|
+
tokenPath,
|
|
17631
|
+
timeoutMs: 3e3
|
|
17632
|
+
});
|
|
17633
|
+
const res = await client.request("channels.reload", {});
|
|
17634
|
+
client.close();
|
|
17635
|
+
if (json) {
|
|
17636
|
+
logOut(JSON.stringify({ ok: true, ...res }));
|
|
17637
|
+
} else if (res.results.length === 0) {
|
|
17638
|
+
logOut("gateway: channels reloaded (no channel sidecars found)");
|
|
17639
|
+
} else {
|
|
17640
|
+
logOut("gateway: channels reloaded");
|
|
17641
|
+
for (const result of res.results) {
|
|
17642
|
+
logOut(formatChannelReloadResult(result));
|
|
17643
|
+
}
|
|
17644
|
+
}
|
|
17645
|
+
return 0;
|
|
17646
|
+
} catch (err) {
|
|
17647
|
+
return reportProbeFailure(err, json, logOut, logError);
|
|
17648
|
+
}
|
|
17361
17649
|
}
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17650
|
+
if (subcommand === "status") {
|
|
17651
|
+
const json = flagJson(subcommandArgs);
|
|
17652
|
+
const socketPath = resolveSocketPath();
|
|
17653
|
+
const tokenPath = resolveTokenPath();
|
|
17654
|
+
const endpoint = readClientConfig();
|
|
17655
|
+
try {
|
|
17656
|
+
const client = await connectGateway({
|
|
17657
|
+
endpoint,
|
|
17658
|
+
socketPath,
|
|
17659
|
+
tokenPath,
|
|
17660
|
+
timeoutMs: 3e3
|
|
17661
|
+
});
|
|
17662
|
+
const res = await client.request("status", {});
|
|
17663
|
+
client.close();
|
|
17664
|
+
if (json) {
|
|
17665
|
+
logOut(JSON.stringify(res));
|
|
17666
|
+
} else {
|
|
17667
|
+
const listenerSummary = formatListenerSummary(res.listener);
|
|
17668
|
+
const channelSummary = formatChannelSummary(res.channels);
|
|
17669
|
+
const runtimeSummary = formatRuntimeSummary(res.runtimes[0]);
|
|
17670
|
+
logOut(
|
|
17671
|
+
`gateway: running pid=${res.daemonPid} uptime=${res.uptimeMs}ms version=${res.version} ${listenerSummary}${channelSummary}${runtimeSummary}`
|
|
17672
|
+
);
|
|
17673
|
+
}
|
|
17674
|
+
return 0;
|
|
17675
|
+
} catch (err) {
|
|
17676
|
+
return reportProbeFailure(err, json, logOut, logError);
|
|
17677
|
+
}
|
|
17378
17678
|
}
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
});
|
|
17679
|
+
logError(`Unknown gateway subcommand: ${subcommand}`);
|
|
17680
|
+
logError(USAGE5);
|
|
17681
|
+
return 2;
|
|
17383
17682
|
}
|
|
17384
|
-
function
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
|
|
17393
|
-
|
|
17394
|
-
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
|
|
17683
|
+
async function defaultConnectGateway(opts) {
|
|
17684
|
+
if (opts.endpoint.mode === "remote") {
|
|
17685
|
+
return connect({
|
|
17686
|
+
socketPath: opts.socketPath,
|
|
17687
|
+
token: opts.endpoint.token,
|
|
17688
|
+
timeoutMs: opts.timeoutMs,
|
|
17689
|
+
transport: createWsClientTransport(
|
|
17690
|
+
wsClientOptionsForEndpoint({
|
|
17691
|
+
url: opts.endpoint.url,
|
|
17692
|
+
timeoutMs: opts.timeoutMs,
|
|
17693
|
+
tlsCaPath: opts.endpoint.tlsCaPath
|
|
17694
|
+
})
|
|
17695
|
+
)
|
|
17696
|
+
});
|
|
17398
17697
|
}
|
|
17399
|
-
return
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
else if (url.protocol === "http:") url.protocol = "ws:";
|
|
17405
|
-
else throw new Error(`unsupported dashboard protocol: ${url.protocol}`);
|
|
17406
|
-
url.pathname = `/api/runners/${encodeURIComponent(runnerId)}/console/adapter`;
|
|
17407
|
-
url.search = "";
|
|
17408
|
-
url.hash = "";
|
|
17409
|
-
return url.toString();
|
|
17698
|
+
return connect({
|
|
17699
|
+
socketPath: opts.socketPath,
|
|
17700
|
+
token: readToken(opts.tokenPath),
|
|
17701
|
+
timeoutMs: opts.timeoutMs
|
|
17702
|
+
});
|
|
17410
17703
|
}
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17704
|
+
function parseLinkArgs(args) {
|
|
17705
|
+
const positional = [];
|
|
17706
|
+
let token;
|
|
17707
|
+
let tlsCaPath;
|
|
17708
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
17709
|
+
const arg = args[i];
|
|
17710
|
+
if (arg === "--token") {
|
|
17711
|
+
const v = args[i + 1];
|
|
17712
|
+
if (!v || v.startsWith("--")) {
|
|
17713
|
+
return { ok: false, message: "gateway link --token requires a value" };
|
|
17419
17714
|
}
|
|
17420
|
-
|
|
17715
|
+
token = v;
|
|
17716
|
+
i += 1;
|
|
17717
|
+
continue;
|
|
17421
17718
|
}
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
|
|
17719
|
+
if (arg === "--tls-ca") {
|
|
17720
|
+
const v = args[i + 1];
|
|
17721
|
+
if (!v || v.startsWith("--")) {
|
|
17722
|
+
return { ok: false, message: "gateway link --tls-ca requires a path" };
|
|
17723
|
+
}
|
|
17724
|
+
tlsCaPath = v;
|
|
17725
|
+
i += 1;
|
|
17726
|
+
continue;
|
|
17727
|
+
}
|
|
17728
|
+
if (arg.startsWith("--token=")) {
|
|
17729
|
+
token = arg.slice("--token=".length);
|
|
17730
|
+
continue;
|
|
17731
|
+
}
|
|
17732
|
+
if (arg.startsWith("--tls-ca=")) {
|
|
17733
|
+
tlsCaPath = arg.slice("--tls-ca=".length);
|
|
17734
|
+
continue;
|
|
17735
|
+
}
|
|
17736
|
+
if (arg.startsWith("--")) {
|
|
17737
|
+
return { ok: false, message: `gateway link: unknown option ${arg}` };
|
|
17738
|
+
}
|
|
17739
|
+
positional.push(arg);
|
|
17425
17740
|
}
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
throw new Error("expected object");
|
|
17741
|
+
const url = positional[0];
|
|
17742
|
+
if (!url) {
|
|
17743
|
+
return { ok: false, message: "gateway link requires a ws:// or wss:// URL" };
|
|
17430
17744
|
}
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
const refreshToken = obj["refreshToken"];
|
|
17434
|
-
if (typeof instanceId !== "string" || instanceId.length === 0) {
|
|
17435
|
-
throw new Error("missing instanceId");
|
|
17745
|
+
if (positional.length > 1) {
|
|
17746
|
+
return { ok: false, message: "gateway link accepts exactly one URL" };
|
|
17436
17747
|
}
|
|
17437
|
-
if (
|
|
17438
|
-
|
|
17748
|
+
if (!isSupportedGatewayUrl(url)) {
|
|
17749
|
+
return { ok: false, message: "gateway link URL must use ws:// or wss://" };
|
|
17750
|
+
}
|
|
17751
|
+
if (!token) {
|
|
17752
|
+
return { ok: false, message: "gateway link requires --token <token>" };
|
|
17753
|
+
}
|
|
17754
|
+
if (tlsCaPath !== void 0 && tlsCaPath.length === 0) {
|
|
17755
|
+
return { ok: false, message: "gateway link --tls-ca requires a path" };
|
|
17439
17756
|
}
|
|
17440
17757
|
return {
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
...
|
|
17445
|
-
...typeof obj["expiresInSec"] === "number" ? { expiresInSec: obj["expiresInSec"] } : {},
|
|
17446
|
-
...Array.isArray(obj["runners"]) ? {
|
|
17447
|
-
runners: obj["runners"].map(parsePairedRunner).filter((runner) => runner !== null)
|
|
17448
|
-
} : {},
|
|
17449
|
-
...typeof obj["requiredCliVersion"] === "string" ? { requiredCliVersion: obj["requiredCliVersion"] } : {},
|
|
17450
|
-
...typeof obj["capabilityAck"] === "object" && obj["capabilityAck"] !== null ? { capabilityAck: parseCapabilityAck(obj["capabilityAck"]) } : {}
|
|
17758
|
+
ok: true,
|
|
17759
|
+
url,
|
|
17760
|
+
token,
|
|
17761
|
+
...tlsCaPath !== void 0 ? { tlsCaPath } : {}
|
|
17451
17762
|
};
|
|
17452
17763
|
}
|
|
17453
|
-
function
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17764
|
+
function reportProbeFailure(err, json, logOut, logError) {
|
|
17765
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17766
|
+
if (err instanceof GatewayUnreachableError) {
|
|
17767
|
+
if (json) {
|
|
17768
|
+
logOut(
|
|
17769
|
+
JSON.stringify({
|
|
17770
|
+
ok: false,
|
|
17771
|
+
reachable: false,
|
|
17772
|
+
reason: "unreachable",
|
|
17773
|
+
message
|
|
17774
|
+
})
|
|
17775
|
+
);
|
|
17776
|
+
} else {
|
|
17777
|
+
logError(`gateway: not reachable \u2014 ${message}`);
|
|
17778
|
+
}
|
|
17779
|
+
return 1;
|
|
17459
17780
|
}
|
|
17460
|
-
if (
|
|
17461
|
-
|
|
17781
|
+
if (err instanceof GatewayUnauthorizedError) {
|
|
17782
|
+
if (json) {
|
|
17783
|
+
logOut(
|
|
17784
|
+
JSON.stringify({
|
|
17785
|
+
ok: false,
|
|
17786
|
+
reachable: true,
|
|
17787
|
+
reason: "unauthorized",
|
|
17788
|
+
message
|
|
17789
|
+
})
|
|
17790
|
+
);
|
|
17791
|
+
} else {
|
|
17792
|
+
logError(`gateway: unauthorized \u2014 ${message}`);
|
|
17793
|
+
}
|
|
17794
|
+
return 1;
|
|
17462
17795
|
}
|
|
17463
|
-
if (
|
|
17464
|
-
|
|
17796
|
+
if (json) {
|
|
17797
|
+
logOut(JSON.stringify({ ok: false, reason: "error", message }));
|
|
17798
|
+
} else {
|
|
17799
|
+
logError(`gateway: ${message}`);
|
|
17465
17800
|
}
|
|
17466
|
-
return
|
|
17801
|
+
return 1;
|
|
17467
17802
|
}
|
|
17468
|
-
function
|
|
17469
|
-
if (
|
|
17470
|
-
const
|
|
17471
|
-
const
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17803
|
+
function formatRuntimeSummary(r) {
|
|
17804
|
+
if (!r) return " runtime=<none>";
|
|
17805
|
+
const lastRebindAt = r.binding.state !== "none" ? r.binding.lastRebindAt : void 0;
|
|
17806
|
+
const rebind = lastRebindAt !== void 0 ? ` rebound=${formatElapsed(Date.now() - lastRebindAt)}` : "";
|
|
17807
|
+
return ` runtime=${r.runtimeId} binding=${r.binding.state} pid=${r.pid}${rebind}`;
|
|
17808
|
+
}
|
|
17809
|
+
function formatChannelReloadResult(result) {
|
|
17810
|
+
const suffix = result.reason ? `: ${result.reason}` : "";
|
|
17811
|
+
return ` ${result.id} ${result.action}${suffix}`;
|
|
17812
|
+
}
|
|
17813
|
+
function formatChannelSummary(channels) {
|
|
17814
|
+
if (channels.length === 0) return " channels=<none>";
|
|
17815
|
+
const parts = channels.map((channel) => {
|
|
17816
|
+
const note = channel.note ? `(${channel.note})` : "";
|
|
17817
|
+
return `${channel.id}:${channel.state}${note}`;
|
|
17818
|
+
});
|
|
17819
|
+
return ` channels=${parts.join(",")}`;
|
|
17820
|
+
}
|
|
17821
|
+
function formatListenerSummary(listener) {
|
|
17822
|
+
if (listener.kind === "uds") {
|
|
17823
|
+
return `listener=uds:${listener.socketPath}`;
|
|
17824
|
+
}
|
|
17825
|
+
const flags = [];
|
|
17826
|
+
if (listener.tls) flags.push("tls");
|
|
17827
|
+
if (listener.insecure) flags.push("insecure");
|
|
17828
|
+
const suffix = flags.length > 0 ? ` (${flags.join(",")})` : "";
|
|
17829
|
+
return `listener=${listener.url}${suffix}`;
|
|
17479
17830
|
}
|
|
17480
17831
|
|
|
17481
17832
|
// src/app/entry/doctorCommand.ts
|
|
@@ -18175,7 +18526,7 @@ var cli = meow(
|
|
|
18175
18526
|
workflow <sub> Manage workflows (install, list, search, remove, upgrade, use)
|
|
18176
18527
|
marketplace <sub> Manage marketplace sources (add, remove, list)
|
|
18177
18528
|
channel <sub> Manage external channels
|
|
18178
|
-
dashboard <sub> Manage dashboard
|
|
18529
|
+
dashboard <sub> Manage dashboard pairing and runtime daemon (pair, status, daemon, unpair)
|
|
18179
18530
|
telemetry [action] Manage anonymous telemetry (enable/disable/status)
|
|
18180
18531
|
doctor Diagnose Claude headless setup (use with --harness=claude)
|
|
18181
18532
|
|
|
@@ -18200,15 +18551,8 @@ var cli = meow(
|
|
|
18200
18551
|
--bot-token Telegram bot token (channel telegram configure)
|
|
18201
18552
|
--user-id Telegram allowed user id (channel telegram configure)
|
|
18202
18553
|
--chat-id Telegram destination chat id (defaults to --user-id)
|
|
18203
|
-
--token Gateway link token (gateway link)
|
|
18204
18554
|
--url Dashboard origin (dashboard pair)
|
|
18205
18555
|
--name Friendly machine name (dashboard pair)
|
|
18206
|
-
--tls-ca Gateway custom CA path (gateway link)
|
|
18207
|
-
--tls-cert Gateway TLS certificate path (gateway start)
|
|
18208
|
-
--tls-key Gateway TLS private key path (gateway start)
|
|
18209
|
-
--bind Gateway listen address host:port (gateway start)
|
|
18210
|
-
--insecure Allow plain WS on non-loopback trusted tunnels (gateway start)
|
|
18211
|
-
--grace-period-ms Gateway reconnect grace period in milliseconds (gateway start)
|
|
18212
18556
|
--dry-run Print resolved bootstrap (workflow, isolation, plugins, harness) and exit (exec mode)
|
|
18213
18557
|
--project Scope workflow command to project config (workflow use)
|
|
18214
18558
|
--global Scope workflow command to global config (workflow use, default)
|
|
@@ -18368,6 +18712,9 @@ var cli = meow(
|
|
|
18368
18712
|
},
|
|
18369
18713
|
apiKey: {
|
|
18370
18714
|
type: "string"
|
|
18715
|
+
},
|
|
18716
|
+
attachmentId: {
|
|
18717
|
+
type: "string"
|
|
18371
18718
|
}
|
|
18372
18719
|
}
|
|
18373
18720
|
}
|
|
@@ -18417,7 +18764,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
18417
18764
|
await exitWith(1);
|
|
18418
18765
|
return;
|
|
18419
18766
|
}
|
|
18420
|
-
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-
|
|
18767
|
+
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-X754ND4V.js");
|
|
18421
18768
|
const { waitUntilExit } = render(
|
|
18422
18769
|
/* @__PURE__ */ jsx25(
|
|
18423
18770
|
WorkflowInstallWizard,
|
|
@@ -18680,6 +19027,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
18680
19027
|
isolationPreset,
|
|
18681
19028
|
ascii: cli.flags.ascii,
|
|
18682
19029
|
showSetup,
|
|
19030
|
+
...cli.flags.attachmentId !== void 0 ? { attachmentId: cli.flags.attachmentId } : {},
|
|
18683
19031
|
initialTelemetryDiagnosticsConsent: globalConfig.telemetryDiagnostics
|
|
18684
19032
|
}
|
|
18685
19033
|
),
|