@drisp/cli 0.4.5 → 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 -3888
- 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-PJUDHH4R.js → chunk-TQKNZNDP.js} +1049 -831
- package/dist/{chunk-4CRZXLIP.js → chunk-WRHKXH5M.js} +136 -136
- package/dist/chunk-ZVOGOZNT.js +395 -0
- package/dist/cli.js +1128 -888
- package/dist/dashboard-daemon.js +9 -107
- package/dist/supervisor.js +692 -0
- package/package.json +1 -1
- package/dist/chunk-M44KEGM7.js +0 -173
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-M44KEGM7.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,
|
|
@@ -50,12 +48,9 @@ import {
|
|
|
50
48
|
probeSkipReason,
|
|
51
49
|
processRegistry,
|
|
52
50
|
progressGlyphs,
|
|
53
|
-
readAttachmentMirror,
|
|
54
51
|
readGatewayClientConfig,
|
|
55
|
-
readPidLock,
|
|
56
52
|
register,
|
|
57
53
|
registerCleanupOnExit,
|
|
58
|
-
removeAttachmentMirror,
|
|
59
54
|
resolveClaudeBinary,
|
|
60
55
|
resolveClaudeSettingsSurfacePaths,
|
|
61
56
|
resolveHarnessAdapter,
|
|
@@ -70,13 +65,21 @@ import {
|
|
|
70
65
|
startSessionBridge,
|
|
71
66
|
supportsSessionApproval,
|
|
72
67
|
todoGlyphSet,
|
|
73
|
-
writeAttachmentMirror,
|
|
74
68
|
writeGatewayClientConfig,
|
|
75
69
|
wsClientOptionsForEndpoint
|
|
76
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-TQKNZNDP.js";
|
|
77
71
|
import {
|
|
78
72
|
generateId as generateId2
|
|
79
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";
|
|
80
83
|
import {
|
|
81
84
|
dashboardClientConfigPath,
|
|
82
85
|
disableTelemetry,
|
|
@@ -97,7 +100,7 @@ import {
|
|
|
97
100
|
trackTelemetryOptedOut,
|
|
98
101
|
writeDashboardClientConfig,
|
|
99
102
|
writeGatewayTrace
|
|
100
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-WRHKXH5M.js";
|
|
101
104
|
import {
|
|
102
105
|
McpOptionsStep,
|
|
103
106
|
StepSelector,
|
|
@@ -132,7 +135,7 @@ import {
|
|
|
132
135
|
useWorkflowSessionController,
|
|
133
136
|
writeGlobalConfig,
|
|
134
137
|
writeProjectConfig
|
|
135
|
-
} from "./chunk-
|
|
138
|
+
} from "./chunk-A54HGVML.js";
|
|
136
139
|
|
|
137
140
|
// src/app/entry/cli.tsx
|
|
138
141
|
import { render } from "ink";
|
|
@@ -1742,14 +1745,65 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1742
1745
|
const abortRef = useRef(new AbortController());
|
|
1743
1746
|
const feedEventsRef = useRef([]);
|
|
1744
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
|
+
);
|
|
1745
1755
|
rulesRef.current = rules;
|
|
1746
1756
|
feedEventsRef.current = feedEvents;
|
|
1747
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
|
+
);
|
|
1748
1773
|
useEffect(() => {
|
|
1749
1774
|
return () => {
|
|
1750
1775
|
sessionStoreRef.current = void 0;
|
|
1751
1776
|
};
|
|
1752
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
|
+
]);
|
|
1753
1807
|
const resetSession = useCallback5(() => {
|
|
1754
1808
|
const newMapper = createFeedMapper();
|
|
1755
1809
|
mapperRef.current = newMapper;
|
|
@@ -1860,18 +1914,37 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1860
1914
|
},
|
|
1861
1915
|
[runtime, dequeueQuestion]
|
|
1862
1916
|
);
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
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
|
+
);
|
|
1875
1948
|
const refreshRuntimeStatus = useCallback5(
|
|
1876
1949
|
(notify = false) => {
|
|
1877
1950
|
const nextIsServerRunning = runtime.getStatus() === "running";
|
|
@@ -1956,6 +2029,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1956
2029
|
}
|
|
1957
2030
|
}
|
|
1958
2031
|
feedStoreRef.current.pushEvents(newFeedEvents);
|
|
2032
|
+
publishDashboardFeedEvents(newFeedEvents, runtimeEvent);
|
|
1959
2033
|
}
|
|
1960
2034
|
} finally {
|
|
1961
2035
|
doneCause();
|
|
@@ -1991,6 +2065,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1991
2065
|
});
|
|
1992
2066
|
if (feedEvent) {
|
|
1993
2067
|
feedStoreRef.current.pushEvents([feedEvent]);
|
|
2068
|
+
publishDashboardFeedEvents([feedEvent]);
|
|
1994
2069
|
if (feedEvent.kind === "permission.decision" && feedEvent.cause?.hook_request_id) {
|
|
1995
2070
|
dequeuePermission(feedEvent.cause.hook_request_id);
|
|
1996
2071
|
}
|
|
@@ -2014,7 +2089,8 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
2014
2089
|
dequeueQuestion,
|
|
2015
2090
|
refreshRuntimeStatus,
|
|
2016
2091
|
options?.relayPermission,
|
|
2017
|
-
options?.relayQuestion
|
|
2092
|
+
options?.relayQuestion,
|
|
2093
|
+
publishDashboardFeedEvents
|
|
2018
2094
|
]);
|
|
2019
2095
|
const items = useMemo3(() => {
|
|
2020
2096
|
const done = startPerfStage("state.derive", {
|
|
@@ -2111,6 +2187,7 @@ function HookProvider({
|
|
|
2111
2187
|
runtimeFactory = createRuntime,
|
|
2112
2188
|
allowedTools,
|
|
2113
2189
|
athenaSessionId,
|
|
2190
|
+
attachmentId,
|
|
2114
2191
|
children
|
|
2115
2192
|
}) {
|
|
2116
2193
|
const runtime = useMemo4(
|
|
@@ -2158,6 +2235,7 @@ function HookProvider({
|
|
|
2158
2235
|
void startSessionBridge({
|
|
2159
2236
|
runtimeId: athenaSessionId,
|
|
2160
2237
|
defaultAgentId: "main",
|
|
2238
|
+
...attachmentId !== void 0 ? { attachmentId } : {},
|
|
2161
2239
|
signal: controller.signal
|
|
2162
2240
|
}).then((bridge) => {
|
|
2163
2241
|
if (!bridge) return;
|
|
@@ -2174,7 +2252,7 @@ function HookProvider({
|
|
|
2174
2252
|
return null;
|
|
2175
2253
|
});
|
|
2176
2254
|
};
|
|
2177
|
-
}, [athenaSessionId]);
|
|
2255
|
+
}, [athenaSessionId, attachmentId]);
|
|
2178
2256
|
useEffect2(() => {
|
|
2179
2257
|
return () => {
|
|
2180
2258
|
sessionStore.close();
|
|
@@ -10951,6 +11029,350 @@ function executeCommand(command, args, ctx) {
|
|
|
10951
11029
|
}
|
|
10952
11030
|
}
|
|
10953
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
|
+
|
|
10954
11376
|
// src/ui/components/SessionPicker.tsx
|
|
10955
11377
|
import { useState as useState11 } from "react";
|
|
10956
11378
|
import { Box as Box11, Text as Text17, useInput as useInput12, useStdout as useStdout5 } from "ink";
|
|
@@ -13321,17 +13743,25 @@ function AppContent({
|
|
|
13321
13743
|
[addMessage, currentSessionId, feedEvents.length, spawnHarness]
|
|
13322
13744
|
);
|
|
13323
13745
|
submitDispatchAsTurnRef.current = submitDispatchAsTurn;
|
|
13746
|
+
const runnerSession = useMemo17(
|
|
13747
|
+
() => sessionBridge ? createRunnerSession({ bridge: sessionBridge, projectDir }) : null,
|
|
13748
|
+
[sessionBridge, projectDir]
|
|
13749
|
+
);
|
|
13324
13750
|
useEffect14(() => {
|
|
13325
13751
|
if (!sessionBridge) return;
|
|
13326
|
-
const
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
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);
|
|
13330
13760
|
}
|
|
13331
|
-
submitDispatchAsTurnRef.current?.(payload);
|
|
13332
13761
|
});
|
|
13762
|
+
const off = sessionBridge.onTurnDispatch(router);
|
|
13333
13763
|
return off;
|
|
13334
|
-
}, [sessionBridge]);
|
|
13764
|
+
}, [sessionBridge, runnerSession]);
|
|
13335
13765
|
useEffect14(() => {
|
|
13336
13766
|
if (isHarnessRunning) return;
|
|
13337
13767
|
if (pendingDispatchRef.current) return;
|
|
@@ -14366,6 +14796,7 @@ function App({
|
|
|
14366
14796
|
isolationPreset,
|
|
14367
14797
|
ascii,
|
|
14368
14798
|
athenaSessionId: initialAthenaSessionId,
|
|
14799
|
+
attachmentId,
|
|
14369
14800
|
initialTelemetryDiagnosticsConsent
|
|
14370
14801
|
}) {
|
|
14371
14802
|
const [clearCount, setClearCount] = useState18(0);
|
|
@@ -14581,6 +15012,7 @@ function App({
|
|
|
14581
15012
|
runtimeState.isolation?.allowedTools
|
|
14582
15013
|
),
|
|
14583
15014
|
athenaSessionId,
|
|
15015
|
+
...attachmentId !== void 0 ? { attachmentId } : {},
|
|
14584
15016
|
children: /* @__PURE__ */ jsx24(
|
|
14585
15017
|
AppContent,
|
|
14586
15018
|
{
|
|
@@ -15417,446 +15849,74 @@ function runChannelCommand(input, deps = {}) {
|
|
|
15417
15849
|
|
|
15418
15850
|
// src/app/entry/dashboardCommand.ts
|
|
15419
15851
|
import crypto3 from "crypto";
|
|
15420
|
-
import { spawn
|
|
15421
|
-
import
|
|
15852
|
+
import { spawn } from "child_process";
|
|
15853
|
+
import fs6 from "fs";
|
|
15422
15854
|
import { createRequire } from "module";
|
|
15423
15855
|
import os5 from "os";
|
|
15424
|
-
import
|
|
15425
|
-
import { fileURLToPath
|
|
15856
|
+
import path6 from "path";
|
|
15857
|
+
import { fileURLToPath } from "url";
|
|
15426
15858
|
|
|
15427
|
-
// src/
|
|
15428
|
-
import { spawn } from "child_process";
|
|
15859
|
+
// src/infra/daemon/serviceUnit.ts
|
|
15429
15860
|
import fs5 from "fs";
|
|
15861
|
+
import os4 from "os";
|
|
15430
15862
|
import path5 from "path";
|
|
15431
|
-
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
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
|
+
};
|
|
15451
15908
|
}
|
|
15452
|
-
function
|
|
15909
|
+
function writeIfChanged(target, content) {
|
|
15910
|
+
const dir = path5.dirname(target);
|
|
15911
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
15912
|
+
let existing = null;
|
|
15453
15913
|
try {
|
|
15454
|
-
|
|
15914
|
+
existing = fs5.readFileSync(target, "utf-8");
|
|
15455
15915
|
} catch (err) {
|
|
15456
|
-
|
|
15457
|
-
if (code === "ENOENT") {
|
|
15458
|
-
throw new Error(
|
|
15459
|
-
`gateway token missing at ${tokenPath}. Start the daemon with "athena gateway start" first.`
|
|
15460
|
-
);
|
|
15461
|
-
}
|
|
15462
|
-
throw err;
|
|
15916
|
+
if (err.code !== "ENOENT") throw err;
|
|
15463
15917
|
}
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
return args.includes("--json");
|
|
15467
|
-
}
|
|
15468
|
-
async function runGatewayCommand(input, deps = {}) {
|
|
15469
|
-
const logOut = deps.logOut ?? ((m) => process.stdout.write(m + "\n"));
|
|
15470
|
-
const logError = deps.logError ?? ((m) => process.stderr.write(m + "\n"));
|
|
15471
|
-
const resolveDaemonEntry2 = deps.resolveDaemonEntry ?? defaultResolveDaemonEntry;
|
|
15472
|
-
const resolveSocketPath = deps.resolveSocketPath ?? (() => resolveGatewayPaths().socketPath);
|
|
15473
|
-
const resolveTokenPath = deps.resolveTokenPath ?? (() => resolveGatewayPaths().tokenPath);
|
|
15474
|
-
const readClientConfig = deps.readClientConfig ?? readGatewayClientConfig;
|
|
15475
|
-
const writeClientConfig = deps.writeClientConfig ?? ((config) => writeGatewayClientConfig(config));
|
|
15476
|
-
const connectGateway = deps.connectGateway ?? defaultConnectGateway;
|
|
15477
|
-
const spawnDaemon = deps.spawnDaemon ?? ((entry, args) => spawn(process.execPath, [entry, ...args], { stdio: "inherit" }));
|
|
15478
|
-
const { subcommand, subcommandArgs } = input;
|
|
15479
|
-
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
15480
|
-
logOut(USAGE4);
|
|
15481
|
-
return 0;
|
|
15482
|
-
}
|
|
15483
|
-
if (subcommand === "start") {
|
|
15484
|
-
const entry = resolveDaemonEntry2();
|
|
15485
|
-
const child = spawnDaemon(entry, subcommandArgs);
|
|
15486
|
-
return await new Promise((resolve) => {
|
|
15487
|
-
child.once("exit", (code) => resolve(code ?? 0));
|
|
15488
|
-
child.once("error", (err) => {
|
|
15489
|
-
logError(`gateway start: failed to spawn daemon: ${err.message}`);
|
|
15490
|
-
resolve(1);
|
|
15491
|
-
});
|
|
15492
|
-
});
|
|
15493
|
-
}
|
|
15494
|
-
if (subcommand === "link") {
|
|
15495
|
-
const parsed = parseLinkArgs(subcommandArgs);
|
|
15496
|
-
if (!parsed.ok) {
|
|
15497
|
-
logError(parsed.message);
|
|
15498
|
-
return 2;
|
|
15499
|
-
}
|
|
15500
|
-
writeClientConfig({
|
|
15501
|
-
mode: "remote",
|
|
15502
|
-
url: parsed.url,
|
|
15503
|
-
token: parsed.token,
|
|
15504
|
-
...parsed.tlsCaPath !== void 0 ? { tlsCaPath: parsed.tlsCaPath } : {}
|
|
15505
|
-
});
|
|
15506
|
-
logOut(`gateway: linked remote endpoint ${parsed.url}`);
|
|
15507
|
-
return 0;
|
|
15508
|
-
}
|
|
15509
|
-
if (subcommand === "rotate-token") {
|
|
15510
|
-
const json = flagJson(subcommandArgs);
|
|
15511
|
-
const extras = subcommandArgs.filter((a) => a !== "--json");
|
|
15512
|
-
if (extras.length > 0) {
|
|
15513
|
-
logError(`gateway rotate-token: unexpected argument ${extras[0]}`);
|
|
15514
|
-
return 2;
|
|
15515
|
-
}
|
|
15516
|
-
const tokenPath = resolveTokenPath();
|
|
15517
|
-
try {
|
|
15518
|
-
const newToken = rotateGatewayToken(tokenPath);
|
|
15519
|
-
if (json) {
|
|
15520
|
-
logOut(JSON.stringify({ ok: true, token: newToken, tokenPath }));
|
|
15521
|
-
} else {
|
|
15522
|
-
logOut(newToken);
|
|
15523
|
-
logOut(
|
|
15524
|
-
`gateway: rotated token at ${tokenPath}. Restart the daemon to drop existing connections, then re-run "athena gateway link --token <new>" on each client.`
|
|
15525
|
-
);
|
|
15526
|
-
}
|
|
15527
|
-
return 0;
|
|
15528
|
-
} catch (err) {
|
|
15529
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
15530
|
-
if (json) {
|
|
15531
|
-
logOut(JSON.stringify({ ok: false, message }));
|
|
15532
|
-
} else {
|
|
15533
|
-
logError(`gateway rotate-token: ${message}`);
|
|
15534
|
-
}
|
|
15535
|
-
return 1;
|
|
15536
|
-
}
|
|
15537
|
-
}
|
|
15538
|
-
if (subcommand === "unlink") {
|
|
15539
|
-
if (subcommandArgs.length > 0) {
|
|
15540
|
-
logError("gateway unlink does not accept arguments");
|
|
15541
|
-
return 2;
|
|
15542
|
-
}
|
|
15543
|
-
writeClientConfig({ mode: "local" });
|
|
15544
|
-
logOut("gateway: using local gateway endpoint");
|
|
15545
|
-
return 0;
|
|
15546
|
-
}
|
|
15547
|
-
if (subcommand === "probe") {
|
|
15548
|
-
const json = flagJson(subcommandArgs);
|
|
15549
|
-
const socketPath = resolveSocketPath();
|
|
15550
|
-
const tokenPath = resolveTokenPath();
|
|
15551
|
-
const endpoint = readClientConfig();
|
|
15552
|
-
const startedAt = Date.now();
|
|
15553
|
-
try {
|
|
15554
|
-
const client = await connectGateway({
|
|
15555
|
-
endpoint,
|
|
15556
|
-
socketPath,
|
|
15557
|
-
tokenPath,
|
|
15558
|
-
timeoutMs: 3e3
|
|
15559
|
-
});
|
|
15560
|
-
const res = await client.request("ping", {});
|
|
15561
|
-
client.close();
|
|
15562
|
-
const latencyMs = Date.now() - startedAt;
|
|
15563
|
-
if (json) {
|
|
15564
|
-
logOut(
|
|
15565
|
-
JSON.stringify({
|
|
15566
|
-
ok: true,
|
|
15567
|
-
reachable: true,
|
|
15568
|
-
latency_ms: latencyMs,
|
|
15569
|
-
daemon_pid: res.daemonPid,
|
|
15570
|
-
daemon_uptime_ms: res.uptimeMs
|
|
15571
|
-
})
|
|
15572
|
-
);
|
|
15573
|
-
} else {
|
|
15574
|
-
logOut(
|
|
15575
|
-
`gateway: reachable pid=${res.daemonPid} uptime=${res.uptimeMs}ms latency=${latencyMs}ms`
|
|
15576
|
-
);
|
|
15577
|
-
}
|
|
15578
|
-
return 0;
|
|
15579
|
-
} catch (err) {
|
|
15580
|
-
return reportProbeFailure(err, json, logOut, logError);
|
|
15581
|
-
}
|
|
15582
|
-
}
|
|
15583
|
-
if (subcommand === "reload-channels") {
|
|
15584
|
-
const json = flagJson(subcommandArgs);
|
|
15585
|
-
const extras = subcommandArgs.filter((a) => a !== "--json");
|
|
15586
|
-
if (extras.length > 0) {
|
|
15587
|
-
logError(`gateway reload-channels: unexpected argument ${extras[0]}`);
|
|
15588
|
-
return 2;
|
|
15589
|
-
}
|
|
15590
|
-
const socketPath = resolveSocketPath();
|
|
15591
|
-
const tokenPath = resolveTokenPath();
|
|
15592
|
-
const endpoint = readClientConfig();
|
|
15593
|
-
try {
|
|
15594
|
-
const client = await connectGateway({
|
|
15595
|
-
endpoint,
|
|
15596
|
-
socketPath,
|
|
15597
|
-
tokenPath,
|
|
15598
|
-
timeoutMs: 3e3
|
|
15599
|
-
});
|
|
15600
|
-
const res = await client.request("channels.reload", {});
|
|
15601
|
-
client.close();
|
|
15602
|
-
if (json) {
|
|
15603
|
-
logOut(JSON.stringify({ ok: true, ...res }));
|
|
15604
|
-
} else if (res.results.length === 0) {
|
|
15605
|
-
logOut("gateway: channels reloaded (no channel sidecars found)");
|
|
15606
|
-
} else {
|
|
15607
|
-
logOut("gateway: channels reloaded");
|
|
15608
|
-
for (const result of res.results) {
|
|
15609
|
-
logOut(formatChannelReloadResult(result));
|
|
15610
|
-
}
|
|
15611
|
-
}
|
|
15612
|
-
return 0;
|
|
15613
|
-
} catch (err) {
|
|
15614
|
-
return reportProbeFailure(err, json, logOut, logError);
|
|
15615
|
-
}
|
|
15616
|
-
}
|
|
15617
|
-
if (subcommand === "status") {
|
|
15618
|
-
const json = flagJson(subcommandArgs);
|
|
15619
|
-
const socketPath = resolveSocketPath();
|
|
15620
|
-
const tokenPath = resolveTokenPath();
|
|
15621
|
-
const endpoint = readClientConfig();
|
|
15622
|
-
try {
|
|
15623
|
-
const client = await connectGateway({
|
|
15624
|
-
endpoint,
|
|
15625
|
-
socketPath,
|
|
15626
|
-
tokenPath,
|
|
15627
|
-
timeoutMs: 3e3
|
|
15628
|
-
});
|
|
15629
|
-
const res = await client.request("status", {});
|
|
15630
|
-
client.close();
|
|
15631
|
-
if (json) {
|
|
15632
|
-
logOut(JSON.stringify(res));
|
|
15633
|
-
} else {
|
|
15634
|
-
const listenerSummary = formatListenerSummary(res.listener);
|
|
15635
|
-
const channelSummary = formatChannelSummary(res.channels);
|
|
15636
|
-
const runtimeSummary = formatRuntimeSummary(res.runtimes[0]);
|
|
15637
|
-
logOut(
|
|
15638
|
-
`gateway: running pid=${res.daemonPid} uptime=${res.uptimeMs}ms version=${res.version} ${listenerSummary}${channelSummary}${runtimeSummary}`
|
|
15639
|
-
);
|
|
15640
|
-
}
|
|
15641
|
-
return 0;
|
|
15642
|
-
} catch (err) {
|
|
15643
|
-
return reportProbeFailure(err, json, logOut, logError);
|
|
15644
|
-
}
|
|
15645
|
-
}
|
|
15646
|
-
logError(`Unknown gateway subcommand: ${subcommand}`);
|
|
15647
|
-
logError(USAGE4);
|
|
15648
|
-
return 2;
|
|
15649
|
-
}
|
|
15650
|
-
async function defaultConnectGateway(opts) {
|
|
15651
|
-
if (opts.endpoint.mode === "remote") {
|
|
15652
|
-
return connect({
|
|
15653
|
-
socketPath: opts.socketPath,
|
|
15654
|
-
token: opts.endpoint.token,
|
|
15655
|
-
timeoutMs: opts.timeoutMs,
|
|
15656
|
-
transport: createWsClientTransport(
|
|
15657
|
-
wsClientOptionsForEndpoint({
|
|
15658
|
-
url: opts.endpoint.url,
|
|
15659
|
-
timeoutMs: opts.timeoutMs,
|
|
15660
|
-
tlsCaPath: opts.endpoint.tlsCaPath
|
|
15661
|
-
})
|
|
15662
|
-
)
|
|
15663
|
-
});
|
|
15664
|
-
}
|
|
15665
|
-
return connect({
|
|
15666
|
-
socketPath: opts.socketPath,
|
|
15667
|
-
token: readToken(opts.tokenPath),
|
|
15668
|
-
timeoutMs: opts.timeoutMs
|
|
15669
|
-
});
|
|
15670
|
-
}
|
|
15671
|
-
function parseLinkArgs(args) {
|
|
15672
|
-
const positional = [];
|
|
15673
|
-
let token;
|
|
15674
|
-
let tlsCaPath;
|
|
15675
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
15676
|
-
const arg = args[i];
|
|
15677
|
-
if (arg === "--token") {
|
|
15678
|
-
const v = args[i + 1];
|
|
15679
|
-
if (!v || v.startsWith("--")) {
|
|
15680
|
-
return { ok: false, message: "gateway link --token requires a value" };
|
|
15681
|
-
}
|
|
15682
|
-
token = v;
|
|
15683
|
-
i += 1;
|
|
15684
|
-
continue;
|
|
15685
|
-
}
|
|
15686
|
-
if (arg === "--tls-ca") {
|
|
15687
|
-
const v = args[i + 1];
|
|
15688
|
-
if (!v || v.startsWith("--")) {
|
|
15689
|
-
return { ok: false, message: "gateway link --tls-ca requires a path" };
|
|
15690
|
-
}
|
|
15691
|
-
tlsCaPath = v;
|
|
15692
|
-
i += 1;
|
|
15693
|
-
continue;
|
|
15694
|
-
}
|
|
15695
|
-
if (arg.startsWith("--token=")) {
|
|
15696
|
-
token = arg.slice("--token=".length);
|
|
15697
|
-
continue;
|
|
15698
|
-
}
|
|
15699
|
-
if (arg.startsWith("--tls-ca=")) {
|
|
15700
|
-
tlsCaPath = arg.slice("--tls-ca=".length);
|
|
15701
|
-
continue;
|
|
15702
|
-
}
|
|
15703
|
-
if (arg.startsWith("--")) {
|
|
15704
|
-
return { ok: false, message: `gateway link: unknown option ${arg}` };
|
|
15705
|
-
}
|
|
15706
|
-
positional.push(arg);
|
|
15707
|
-
}
|
|
15708
|
-
const url = positional[0];
|
|
15709
|
-
if (!url) {
|
|
15710
|
-
return { ok: false, message: "gateway link requires a ws:// or wss:// URL" };
|
|
15711
|
-
}
|
|
15712
|
-
if (positional.length > 1) {
|
|
15713
|
-
return { ok: false, message: "gateway link accepts exactly one URL" };
|
|
15714
|
-
}
|
|
15715
|
-
if (!isSupportedGatewayUrl(url)) {
|
|
15716
|
-
return { ok: false, message: "gateway link URL must use ws:// or wss://" };
|
|
15717
|
-
}
|
|
15718
|
-
if (!token) {
|
|
15719
|
-
return { ok: false, message: "gateway link requires --token <token>" };
|
|
15720
|
-
}
|
|
15721
|
-
if (tlsCaPath !== void 0 && tlsCaPath.length === 0) {
|
|
15722
|
-
return { ok: false, message: "gateway link --tls-ca requires a path" };
|
|
15723
|
-
}
|
|
15724
|
-
return {
|
|
15725
|
-
ok: true,
|
|
15726
|
-
url,
|
|
15727
|
-
token,
|
|
15728
|
-
...tlsCaPath !== void 0 ? { tlsCaPath } : {}
|
|
15729
|
-
};
|
|
15730
|
-
}
|
|
15731
|
-
function reportProbeFailure(err, json, logOut, logError) {
|
|
15732
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
15733
|
-
if (err instanceof GatewayUnreachableError) {
|
|
15734
|
-
if (json) {
|
|
15735
|
-
logOut(
|
|
15736
|
-
JSON.stringify({
|
|
15737
|
-
ok: false,
|
|
15738
|
-
reachable: false,
|
|
15739
|
-
reason: "unreachable",
|
|
15740
|
-
message
|
|
15741
|
-
})
|
|
15742
|
-
);
|
|
15743
|
-
} else {
|
|
15744
|
-
logError(`gateway: not reachable \u2014 ${message}`);
|
|
15745
|
-
}
|
|
15746
|
-
return 1;
|
|
15747
|
-
}
|
|
15748
|
-
if (err instanceof GatewayUnauthorizedError) {
|
|
15749
|
-
if (json) {
|
|
15750
|
-
logOut(
|
|
15751
|
-
JSON.stringify({
|
|
15752
|
-
ok: false,
|
|
15753
|
-
reachable: true,
|
|
15754
|
-
reason: "unauthorized",
|
|
15755
|
-
message
|
|
15756
|
-
})
|
|
15757
|
-
);
|
|
15758
|
-
} else {
|
|
15759
|
-
logError(`gateway: unauthorized \u2014 ${message}`);
|
|
15760
|
-
}
|
|
15761
|
-
return 1;
|
|
15762
|
-
}
|
|
15763
|
-
if (json) {
|
|
15764
|
-
logOut(JSON.stringify({ ok: false, reason: "error", message }));
|
|
15765
|
-
} else {
|
|
15766
|
-
logError(`gateway: ${message}`);
|
|
15767
|
-
}
|
|
15768
|
-
return 1;
|
|
15769
|
-
}
|
|
15770
|
-
function formatRuntimeSummary(r) {
|
|
15771
|
-
if (!r) return " runtime=<none>";
|
|
15772
|
-
const lastRebindAt = r.binding.state !== "none" ? r.binding.lastRebindAt : void 0;
|
|
15773
|
-
const rebind = lastRebindAt !== void 0 ? ` rebound=${formatElapsed(Date.now() - lastRebindAt)}` : "";
|
|
15774
|
-
return ` runtime=${r.runtimeId} binding=${r.binding.state} pid=${r.pid}${rebind}`;
|
|
15775
|
-
}
|
|
15776
|
-
function formatChannelReloadResult(result) {
|
|
15777
|
-
const suffix = result.reason ? `: ${result.reason}` : "";
|
|
15778
|
-
return ` ${result.id} ${result.action}${suffix}`;
|
|
15779
|
-
}
|
|
15780
|
-
function formatChannelSummary(channels) {
|
|
15781
|
-
if (channels.length === 0) return " channels=<none>";
|
|
15782
|
-
const parts = channels.map((channel) => {
|
|
15783
|
-
const note = channel.note ? `(${channel.note})` : "";
|
|
15784
|
-
return `${channel.id}:${channel.state}${note}`;
|
|
15785
|
-
});
|
|
15786
|
-
return ` channels=${parts.join(",")}`;
|
|
15787
|
-
}
|
|
15788
|
-
function formatListenerSummary(listener) {
|
|
15789
|
-
if (listener.kind === "uds") {
|
|
15790
|
-
return `listener=uds:${listener.socketPath}`;
|
|
15791
|
-
}
|
|
15792
|
-
const flags = [];
|
|
15793
|
-
if (listener.tls) flags.push("tls");
|
|
15794
|
-
if (listener.insecure) flags.push("insecure");
|
|
15795
|
-
const suffix = flags.length > 0 ? ` (${flags.join(",")})` : "";
|
|
15796
|
-
return `listener=${listener.url}${suffix}`;
|
|
15797
|
-
}
|
|
15798
|
-
|
|
15799
|
-
// src/infra/daemon/serviceUnit.ts
|
|
15800
|
-
import fs6 from "fs";
|
|
15801
|
-
import os4 from "os";
|
|
15802
|
-
import path6 from "path";
|
|
15803
|
-
function installServiceUnit(options) {
|
|
15804
|
-
const platform = options.platform ?? process.platform;
|
|
15805
|
-
const env2 = options.env ?? process.env;
|
|
15806
|
-
const home = env2["HOME"] ?? os4.homedir();
|
|
15807
|
-
if (platform === "darwin") {
|
|
15808
|
-
const target = options.targetPath ?? path6.join(home, "Library", "LaunchAgents", "ai.drisp.daemon.plist");
|
|
15809
|
-
const paths = daemonStatePaths(env2);
|
|
15810
|
-
const plist = renderLaunchdPlist({
|
|
15811
|
-
label: "ai.drisp.daemon",
|
|
15812
|
-
nodeBinary: options.nodeBinary,
|
|
15813
|
-
daemonEntry: options.daemonEntry,
|
|
15814
|
-
workingDirectory: home,
|
|
15815
|
-
stdoutPath: paths.logPath,
|
|
15816
|
-
stderrPath: paths.logPath
|
|
15817
|
-
});
|
|
15818
|
-
writeIfChanged(target, plist);
|
|
15819
|
-
return {
|
|
15820
|
-
ok: true,
|
|
15821
|
-
platform: "darwin",
|
|
15822
|
-
path: target,
|
|
15823
|
-
loadCommand: `launchctl load -w ${target}`,
|
|
15824
|
-
startCommand: "launchctl start ai.drisp.daemon"
|
|
15825
|
-
};
|
|
15826
|
-
}
|
|
15827
|
-
if (platform === "linux") {
|
|
15828
|
-
const target = options.targetPath ?? path6.join(home, ".config", "systemd", "user", "drisp-daemon.service");
|
|
15829
|
-
const unit = renderSystemdUnit({
|
|
15830
|
-
description: "Drisp dashboard runtime daemon",
|
|
15831
|
-
nodeBinary: options.nodeBinary,
|
|
15832
|
-
daemonEntry: options.daemonEntry
|
|
15833
|
-
});
|
|
15834
|
-
writeIfChanged(target, unit);
|
|
15835
|
-
return {
|
|
15836
|
-
ok: true,
|
|
15837
|
-
platform: "linux",
|
|
15838
|
-
path: target,
|
|
15839
|
-
loadCommand: "systemctl --user daemon-reload",
|
|
15840
|
-
startCommand: "systemctl --user enable --now drisp-daemon.service"
|
|
15841
|
-
};
|
|
15842
|
-
}
|
|
15843
|
-
return {
|
|
15844
|
-
ok: false,
|
|
15845
|
-
platform: "unsupported",
|
|
15846
|
-
message: `service install not supported on ${platform}`
|
|
15847
|
-
};
|
|
15848
|
-
}
|
|
15849
|
-
function writeIfChanged(target, content) {
|
|
15850
|
-
const dir = path6.dirname(target);
|
|
15851
|
-
fs6.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
15852
|
-
let existing = null;
|
|
15853
|
-
try {
|
|
15854
|
-
existing = fs6.readFileSync(target, "utf-8");
|
|
15855
|
-
} catch (err) {
|
|
15856
|
-
if (err.code !== "ENOENT") throw err;
|
|
15857
|
-
}
|
|
15858
|
-
if (existing === content) return;
|
|
15859
|
-
fs6.writeFileSync(target, content, { mode: 384 });
|
|
15918
|
+
if (existing === content) return;
|
|
15919
|
+
fs5.writeFileSync(target, content, { mode: 384 });
|
|
15860
15920
|
}
|
|
15861
15921
|
function renderLaunchdPlist(input) {
|
|
15862
15922
|
const argv = [input.nodeBinary, input.daemonEntry].map((s) => ` <string>${escapeXml(s)}</string>`).join("\n");
|
|
@@ -15912,7 +15972,7 @@ function escapeXml(input) {
|
|
|
15912
15972
|
}
|
|
15913
15973
|
|
|
15914
15974
|
// src/app/entry/dashboardCommand.ts
|
|
15915
|
-
var
|
|
15975
|
+
var USAGE4 = `Usage: athena dashboard <subcommand> [options]
|
|
15916
15976
|
|
|
15917
15977
|
Subcommands:
|
|
15918
15978
|
pair <token> --url <dashboard-origin> [--name <machine-name>]
|
|
@@ -15938,20 +15998,13 @@ Subcommands:
|
|
|
15938
15998
|
runner is bound to this instance.
|
|
15939
15999
|
list Print the local attachment mirror (the runners the dashboard
|
|
15940
16000
|
reported as bound to this instance at the last pair/refresh).
|
|
15941
|
-
console enable <runnerId>
|
|
15942
|
-
Configure the console channel for a runner (opinionated;
|
|
15943
|
-
writes the sidecar and reloads the gateway).
|
|
15944
|
-
console link <runnerId>
|
|
15945
|
-
Primitive: write ~/.config/athena/channels/console.json for
|
|
15946
|
-
the given runner. Prefer "console enable".
|
|
15947
16001
|
connect Deprecated alias for "daemon foreground".
|
|
15948
16002
|
unpair Stop the daemon, revoke the refresh token, and remove the
|
|
15949
16003
|
local config.
|
|
15950
16004
|
|
|
15951
16005
|
Options:
|
|
15952
16006
|
--url <origin> Dashboard origin (required for pair)
|
|
15953
|
-
--runner <id> Runner id (
|
|
15954
|
-
link", optional for "doctor")
|
|
16007
|
+
--runner <id> Runner id (optional for "doctor")
|
|
15955
16008
|
--name <name> Friendly machine name (optional, defaults to hostname)
|
|
15956
16009
|
--tail N Number of trailing log lines (default 20)
|
|
15957
16010
|
--follow Stream new log lines until interrupted
|
|
@@ -15964,7 +16017,7 @@ var cachedVersion = null;
|
|
|
15964
16017
|
function readPackageVersion() {
|
|
15965
16018
|
if (cachedVersion !== null) return cachedVersion;
|
|
15966
16019
|
try {
|
|
15967
|
-
const injected = "0.4.
|
|
16020
|
+
const injected = "0.4.7";
|
|
15968
16021
|
if (typeof injected === "string" && injected.length > 0) {
|
|
15969
16022
|
cachedVersion = injected;
|
|
15970
16023
|
return cachedVersion;
|
|
@@ -16013,14 +16066,14 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
16013
16066
|
const packageVersion = deps.packageVersion ?? readPackageVersion();
|
|
16014
16067
|
const { subcommand, subcommandArgs, flags } = input;
|
|
16015
16068
|
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
16016
|
-
logOut(
|
|
16069
|
+
logOut(USAGE4);
|
|
16017
16070
|
return 0;
|
|
16018
16071
|
}
|
|
16019
16072
|
if (subcommand === "pair") {
|
|
16020
16073
|
const token = subcommandArgs[0];
|
|
16021
16074
|
if (!token) {
|
|
16022
16075
|
logError("dashboard pair: missing pairing token");
|
|
16023
|
-
logError(
|
|
16076
|
+
logError(USAGE4);
|
|
16024
16077
|
return 2;
|
|
16025
16078
|
}
|
|
16026
16079
|
if (subcommandArgs.length > 1) {
|
|
@@ -16047,7 +16100,6 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
16047
16100
|
hostInfo: (deps.hostInfo ?? (() => defaultHostInfo(flags.name)))(),
|
|
16048
16101
|
capabilities: {
|
|
16049
16102
|
instanceSocket: true,
|
|
16050
|
-
consoleAdapter: true,
|
|
16051
16103
|
runtimeDaemon: true,
|
|
16052
16104
|
cliVersion: packageVersion,
|
|
16053
16105
|
// Legacy field — older dashboards read `version`. Drop in a
|
|
@@ -16762,166 +16814,13 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
16762
16814
|
return reportDoctor2(runnerOk);
|
|
16763
16815
|
}
|
|
16764
16816
|
if (subcommand === "console") {
|
|
16765
|
-
const
|
|
16766
|
-
if (
|
|
16767
|
-
|
|
16768
|
-
|
|
16769
|
-
|
|
16770
|
-
return 2;
|
|
16771
|
-
}
|
|
16772
|
-
if (subcommandArgs.length > 2) {
|
|
16773
|
-
logError(
|
|
16774
|
-
`dashboard console enable: unexpected argument ${subcommandArgs[2]}`
|
|
16775
|
-
);
|
|
16776
|
-
return 2;
|
|
16777
|
-
}
|
|
16778
|
-
const config = readConfig2();
|
|
16779
|
-
if (!config) {
|
|
16780
|
-
logError(
|
|
16781
|
-
'dashboard console enable: not paired. Run "drisp dashboard pair" first.'
|
|
16782
|
-
);
|
|
16783
|
-
return 1;
|
|
16784
|
-
}
|
|
16785
|
-
const refreshResult = await tryRefresh("refresh");
|
|
16786
|
-
if (!refreshResult.ok) return refreshResult.code;
|
|
16787
|
-
const health = await fetchRunnerHealth(
|
|
16788
|
-
fetchImpl,
|
|
16789
|
-
config.dashboardUrl,
|
|
16790
|
-
runnerId,
|
|
16791
|
-
refreshResult.token
|
|
16792
|
-
);
|
|
16793
|
-
if (!health.matches) {
|
|
16794
|
-
logError(
|
|
16795
|
-
`dashboard console enable: runner ${runnerId} is not bound to this instance${health.error ? ` (${health.error})` : ""}`
|
|
16796
|
-
);
|
|
16797
|
-
return 1;
|
|
16798
|
-
}
|
|
16799
|
-
return await runDashboardCommand(
|
|
16800
|
-
{
|
|
16801
|
-
subcommand: "console",
|
|
16802
|
-
subcommandArgs: ["link", runnerId],
|
|
16803
|
-
flags: input.flags
|
|
16804
|
-
},
|
|
16805
|
-
deps
|
|
16806
|
-
);
|
|
16807
|
-
}
|
|
16808
|
-
if (sub === "link") {
|
|
16809
|
-
const runnerId = subcommandArgs[1];
|
|
16810
|
-
if (!runnerId) {
|
|
16811
|
-
logError("dashboard console link: missing <runnerId>");
|
|
16812
|
-
return 2;
|
|
16813
|
-
}
|
|
16814
|
-
if (subcommandArgs.length > 2) {
|
|
16815
|
-
logError(
|
|
16816
|
-
`dashboard console link: unexpected argument ${subcommandArgs[2]}`
|
|
16817
|
-
);
|
|
16818
|
-
return 2;
|
|
16819
|
-
}
|
|
16820
|
-
const config = readConfig2();
|
|
16821
|
-
if (!config) {
|
|
16822
|
-
logError(
|
|
16823
|
-
'dashboard console link: not paired. Run "drisp dashboard pair" first.'
|
|
16824
|
-
);
|
|
16825
|
-
return 1;
|
|
16826
|
-
}
|
|
16827
|
-
let brokerUrl;
|
|
16828
|
-
try {
|
|
16829
|
-
brokerUrl = consoleBrokerUrl(config.dashboardUrl, runnerId);
|
|
16830
|
-
} catch (err) {
|
|
16831
|
-
logError(
|
|
16832
|
-
`dashboard console link: ${err instanceof Error ? err.message : String(err)}`
|
|
16833
|
-
);
|
|
16834
|
-
return 1;
|
|
16835
|
-
}
|
|
16836
|
-
const dir = (deps.channelDir ?? channelSidecarDir)();
|
|
16837
|
-
const target = path7.join(dir, `console-${runnerId}.json`);
|
|
16838
|
-
const legacyTarget = path7.join(dir, "console.json");
|
|
16839
|
-
let previousBroker;
|
|
16840
|
-
try {
|
|
16841
|
-
const existing = JSON.parse(fs7.readFileSync(target, "utf-8"));
|
|
16842
|
-
if (typeof existing.broker_url === "string") {
|
|
16843
|
-
previousBroker = existing.broker_url;
|
|
16844
|
-
}
|
|
16845
|
-
} catch {
|
|
16846
|
-
try {
|
|
16847
|
-
const legacy = JSON.parse(fs7.readFileSync(legacyTarget, "utf-8"));
|
|
16848
|
-
if (typeof legacy.broker_url === "string" && legacy.runner_id === runnerId) {
|
|
16849
|
-
previousBroker = legacy.broker_url;
|
|
16850
|
-
}
|
|
16851
|
-
} catch {
|
|
16852
|
-
}
|
|
16853
|
-
}
|
|
16854
|
-
try {
|
|
16855
|
-
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
16856
|
-
} catch (err) {
|
|
16857
|
-
logError(
|
|
16858
|
-
`dashboard console link: failed to create ${dir}: ${err instanceof Error ? err.message : String(err)}`
|
|
16859
|
-
);
|
|
16860
|
-
return 1;
|
|
16861
|
-
}
|
|
16862
|
-
const payload = {
|
|
16863
|
-
kind: "console",
|
|
16864
|
-
instance_id: `console:${runnerId}`,
|
|
16865
|
-
broker_url: brokerUrl,
|
|
16866
|
-
runner_id: runnerId,
|
|
16867
|
-
dashboard_config: true
|
|
16868
|
-
};
|
|
16869
|
-
const tmp = `${target}.tmp`;
|
|
16870
|
-
try {
|
|
16871
|
-
fs7.writeFileSync(tmp, JSON.stringify(payload, null, 2) + "\n", {
|
|
16872
|
-
mode: 384
|
|
16873
|
-
});
|
|
16874
|
-
fs7.renameSync(tmp, target);
|
|
16875
|
-
} catch (err) {
|
|
16876
|
-
try {
|
|
16877
|
-
fs7.unlinkSync(tmp);
|
|
16878
|
-
} catch {
|
|
16879
|
-
}
|
|
16880
|
-
logError(
|
|
16881
|
-
`dashboard console link: failed to write ${target}: ${err instanceof Error ? err.message : String(err)}`
|
|
16882
|
-
);
|
|
16883
|
-
return 1;
|
|
16884
|
-
}
|
|
16885
|
-
try {
|
|
16886
|
-
const legacy = JSON.parse(fs7.readFileSync(legacyTarget, "utf-8"));
|
|
16887
|
-
if (legacy.runner_id === runnerId) {
|
|
16888
|
-
fs7.unlinkSync(legacyTarget);
|
|
16889
|
-
}
|
|
16890
|
-
} catch {
|
|
16891
|
-
}
|
|
16892
|
-
const reload = await (deps.reloadGatewayChannels ?? defaultReloadGatewayChannels)();
|
|
16893
|
-
if (flags.json) {
|
|
16894
|
-
logOut(
|
|
16895
|
-
JSON.stringify({
|
|
16896
|
-
ok: true,
|
|
16897
|
-
runnerId,
|
|
16898
|
-
brokerUrl,
|
|
16899
|
-
path: target,
|
|
16900
|
-
...previousBroker ? { previousBrokerUrl: previousBroker } : {},
|
|
16901
|
-
gatewayReload: reload
|
|
16902
|
-
})
|
|
16903
|
-
);
|
|
16904
|
-
} else {
|
|
16905
|
-
if (previousBroker && previousBroker !== brokerUrl) {
|
|
16906
|
-
logOut(`console: replaced existing config (was: ${previousBroker})`);
|
|
16907
|
-
}
|
|
16908
|
-
logOut(`console: linked runner ${runnerId} at ${brokerUrl}`);
|
|
16909
|
-
logOut(`console: wrote ${target}`);
|
|
16910
|
-
if (reload.ok) {
|
|
16911
|
-
logOut(`console: gateway channels reloaded (${reload.message})`);
|
|
16912
|
-
} else {
|
|
16913
|
-
logError(`console: gateway reload skipped: ${reload.message}`);
|
|
16914
|
-
logOut(
|
|
16915
|
-
"console: start or reload the gateway before using the Console tab."
|
|
16916
|
-
);
|
|
16917
|
-
}
|
|
16918
|
-
}
|
|
16919
|
-
return 0;
|
|
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);
|
|
16920
16822
|
}
|
|
16921
|
-
|
|
16922
|
-
`dashboard console: unknown subcommand ${sub ? sub : "(missing)"}. Expected "enable <runnerId>" or "link <runnerId>".`
|
|
16923
|
-
);
|
|
16924
|
-
return 2;
|
|
16823
|
+
return 1;
|
|
16925
16824
|
}
|
|
16926
16825
|
if (subcommand === "list") {
|
|
16927
16826
|
if (subcommandArgs.length > 0) {
|
|
@@ -17080,7 +16979,7 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
17080
16979
|
return 0;
|
|
17081
16980
|
}
|
|
17082
16981
|
logError(`Unknown dashboard subcommand: ${subcommand}`);
|
|
17083
|
-
logError(
|
|
16982
|
+
logError(USAGE4);
|
|
17084
16983
|
return 2;
|
|
17085
16984
|
}
|
|
17086
16985
|
function defaultWaitForShutdown() {
|
|
@@ -17092,36 +16991,21 @@ function defaultWaitForShutdown() {
|
|
|
17092
16991
|
};
|
|
17093
16992
|
process.once("SIGINT", onSignal);
|
|
17094
16993
|
process.once("SIGTERM", onSignal);
|
|
17095
|
-
});
|
|
17096
|
-
}
|
|
17097
|
-
async function defaultReloadGatewayChannels() {
|
|
17098
|
-
const out = [];
|
|
17099
|
-
const err = [];
|
|
17100
|
-
const code = await runGatewayCommand(
|
|
17101
|
-
{ subcommand: "reload-channels", subcommandArgs: [] },
|
|
17102
|
-
{
|
|
17103
|
-
logOut: (m) => out.push(m),
|
|
17104
|
-
logError: (m) => err.push(m)
|
|
17105
|
-
}
|
|
17106
|
-
);
|
|
17107
|
-
return {
|
|
17108
|
-
ok: code === 0,
|
|
17109
|
-
message: (code === 0 ? out.join("\n") : err.join("\n")) || (code === 0 ? "gateway channels reloaded" : "gateway not reachable")
|
|
17110
|
-
};
|
|
16994
|
+
});
|
|
17111
16995
|
}
|
|
17112
16996
|
function resolveDaemonEntry() {
|
|
17113
16997
|
let here;
|
|
17114
16998
|
try {
|
|
17115
|
-
here =
|
|
16999
|
+
here = fileURLToPath(import.meta.url);
|
|
17116
17000
|
} catch {
|
|
17117
17001
|
return null;
|
|
17118
17002
|
}
|
|
17119
17003
|
const candidates = [
|
|
17120
|
-
|
|
17004
|
+
path6.join(path6.dirname(here), "dashboard-daemon.js"),
|
|
17121
17005
|
// When invoked via `npm run start`, `here` may be the unbundled source
|
|
17122
17006
|
// path. Walk up until we hit a `dist/` sibling.
|
|
17123
|
-
|
|
17124
|
-
|
|
17007
|
+
path6.join(
|
|
17008
|
+
path6.dirname(here),
|
|
17125
17009
|
"..",
|
|
17126
17010
|
"..",
|
|
17127
17011
|
"..",
|
|
@@ -17131,7 +17015,7 @@ function resolveDaemonEntry() {
|
|
|
17131
17015
|
];
|
|
17132
17016
|
for (const candidate of candidates) {
|
|
17133
17017
|
try {
|
|
17134
|
-
|
|
17018
|
+
fs6.accessSync(candidate, fs6.constants.R_OK);
|
|
17135
17019
|
return candidate;
|
|
17136
17020
|
} catch {
|
|
17137
17021
|
}
|
|
@@ -17189,7 +17073,7 @@ async function defaultStartRuntimeDaemon(opts) {
|
|
|
17189
17073
|
}
|
|
17190
17074
|
let child;
|
|
17191
17075
|
try {
|
|
17192
|
-
child =
|
|
17076
|
+
child = spawn(process.execPath, [entry], {
|
|
17193
17077
|
detached: true,
|
|
17194
17078
|
stdio: "ignore",
|
|
17195
17079
|
env: buildDaemonEnv(process.env)
|
|
@@ -17316,14 +17200,14 @@ async function defaultTailDaemonLog(opts) {
|
|
|
17316
17200
|
let stream2 = null;
|
|
17317
17201
|
let watcher = null;
|
|
17318
17202
|
try {
|
|
17319
|
-
const stat =
|
|
17203
|
+
const stat = fs6.statSync(paths.logPath);
|
|
17320
17204
|
const size = stat.size;
|
|
17321
17205
|
const buf = Buffer.alloc(Math.min(size, opts.tail * 1024));
|
|
17322
|
-
const fd =
|
|
17206
|
+
const fd = fs6.openSync(paths.logPath, "r");
|
|
17323
17207
|
try {
|
|
17324
|
-
|
|
17208
|
+
fs6.readSync(fd, buf, 0, buf.length, Math.max(0, size - buf.length));
|
|
17325
17209
|
} finally {
|
|
17326
|
-
|
|
17210
|
+
fs6.closeSync(fd);
|
|
17327
17211
|
}
|
|
17328
17212
|
const lines = buf.toString("utf-8").split("\n").filter((l) => l.length > 0);
|
|
17329
17213
|
const tail = lines.slice(-opts.tail);
|
|
@@ -17337,253 +17221,612 @@ async function defaultTailDaemonLog(opts) {
|
|
|
17337
17221
|
`dashboard logs: log file ${paths.logPath} does not exist yet
|
|
17338
17222
|
`
|
|
17339
17223
|
);
|
|
17340
|
-
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
|
+
);
|
|
17341
17494
|
}
|
|
17342
17495
|
throw err;
|
|
17343
17496
|
}
|
|
17344
|
-
let position = fs7.statSync(paths.logPath).size;
|
|
17345
|
-
return await new Promise((resolve) => {
|
|
17346
|
-
let pollTimer = null;
|
|
17347
|
-
const drain = () => {
|
|
17348
|
-
try {
|
|
17349
|
-
const stat = fs7.statSync(paths.logPath);
|
|
17350
|
-
if (stat.size < position) {
|
|
17351
|
-
position = 0;
|
|
17352
|
-
}
|
|
17353
|
-
if (stat.size > position) {
|
|
17354
|
-
const fd = fs7.openSync(paths.logPath, "r");
|
|
17355
|
-
const buf = Buffer.alloc(stat.size - position);
|
|
17356
|
-
try {
|
|
17357
|
-
fs7.readSync(fd, buf, 0, buf.length, position);
|
|
17358
|
-
} finally {
|
|
17359
|
-
fs7.closeSync(fd);
|
|
17360
|
-
}
|
|
17361
|
-
position = stat.size;
|
|
17362
|
-
process.stdout.write(buf);
|
|
17363
|
-
}
|
|
17364
|
-
} catch (err) {
|
|
17365
|
-
if (err.code !== "ENOENT") {
|
|
17366
|
-
process.stderr.write(
|
|
17367
|
-
`dashboard logs: tail error: ${err instanceof Error ? err.message : String(err)}
|
|
17368
|
-
`
|
|
17369
|
-
);
|
|
17370
|
-
}
|
|
17371
|
-
}
|
|
17372
|
-
};
|
|
17373
|
-
try {
|
|
17374
|
-
watcher = fs7.watch(paths.logPath, { persistent: true }, () => {
|
|
17375
|
-
drain();
|
|
17376
|
-
});
|
|
17377
|
-
} catch {
|
|
17378
|
-
pollTimer = setInterval(drain, 500);
|
|
17379
|
-
pollTimer.unref?.();
|
|
17380
|
-
}
|
|
17381
|
-
const onSignal = () => {
|
|
17382
|
-
if (watcher) {
|
|
17383
|
-
watcher.close();
|
|
17384
|
-
watcher = null;
|
|
17385
|
-
}
|
|
17386
|
-
if (pollTimer) {
|
|
17387
|
-
clearInterval(pollTimer);
|
|
17388
|
-
pollTimer = null;
|
|
17389
|
-
}
|
|
17390
|
-
if (stream2) {
|
|
17391
|
-
stream2.close();
|
|
17392
|
-
stream2 = null;
|
|
17393
|
-
}
|
|
17394
|
-
resolve(0);
|
|
17395
|
-
};
|
|
17396
|
-
process.once("SIGINT", onSignal);
|
|
17397
|
-
process.once("SIGTERM", onSignal);
|
|
17398
|
-
});
|
|
17399
17497
|
}
|
|
17400
|
-
function
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
|
|
17405
|
-
|
|
17406
|
-
|
|
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;
|
|
17407
17515
|
}
|
|
17408
|
-
if (
|
|
17409
|
-
const
|
|
17410
|
-
const
|
|
17411
|
-
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
|
+
});
|
|
17412
17526
|
}
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
response = await fetchImpl(url, {
|
|
17425
|
-
method: "GET",
|
|
17426
|
-
headers: {
|
|
17427
|
-
authorization: `Bearer ${token.accessToken}`,
|
|
17428
|
-
accept: "application/json"
|
|
17429
|
-
}
|
|
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 } : {}
|
|
17430
17538
|
});
|
|
17431
|
-
|
|
17432
|
-
return
|
|
17433
|
-
id: runnerId,
|
|
17434
|
-
matches: false,
|
|
17435
|
-
error: `request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17436
|
-
};
|
|
17539
|
+
logOut(`gateway: linked remote endpoint ${parsed.url}`);
|
|
17540
|
+
return 0;
|
|
17437
17541
|
}
|
|
17438
|
-
if (
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
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
|
+
}
|
|
17444
17570
|
}
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
};
|
|
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;
|
|
17454
17579
|
}
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
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
|
+
}
|
|
17464
17615
|
}
|
|
17465
|
-
if (
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
)
|
|
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
|
+
}
|
|
17469
17649
|
}
|
|
17470
|
-
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
|
|
17482
|
-
|
|
17483
|
-
|
|
17484
|
-
|
|
17485
|
-
|
|
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
|
+
}
|
|
17486
17678
|
}
|
|
17487
|
-
|
|
17488
|
-
|
|
17489
|
-
|
|
17490
|
-
});
|
|
17679
|
+
logError(`Unknown gateway subcommand: ${subcommand}`);
|
|
17680
|
+
logError(USAGE5);
|
|
17681
|
+
return 2;
|
|
17491
17682
|
}
|
|
17492
|
-
function
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
|
|
17504
|
-
|
|
17505
|
-
|
|
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
|
+
});
|
|
17506
17697
|
}
|
|
17507
|
-
return
|
|
17508
|
-
|
|
17509
|
-
|
|
17510
|
-
|
|
17511
|
-
|
|
17512
|
-
else if (url.protocol === "http:") url.protocol = "ws:";
|
|
17513
|
-
else throw new Error(`unsupported dashboard protocol: ${url.protocol}`);
|
|
17514
|
-
url.pathname = `/api/runners/${encodeURIComponent(runnerId)}/console/adapter`;
|
|
17515
|
-
url.search = "";
|
|
17516
|
-
url.hash = "";
|
|
17517
|
-
return url.toString();
|
|
17698
|
+
return connect({
|
|
17699
|
+
socketPath: opts.socketPath,
|
|
17700
|
+
token: readToken(opts.tokenPath),
|
|
17701
|
+
timeoutMs: opts.timeoutMs
|
|
17702
|
+
});
|
|
17518
17703
|
}
|
|
17519
|
-
|
|
17520
|
-
|
|
17521
|
-
|
|
17522
|
-
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
17526
|
-
|
|
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" };
|
|
17527
17714
|
}
|
|
17528
|
-
|
|
17715
|
+
token = v;
|
|
17716
|
+
i += 1;
|
|
17717
|
+
continue;
|
|
17529
17718
|
}
|
|
17530
|
-
|
|
17531
|
-
|
|
17532
|
-
|
|
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);
|
|
17533
17740
|
}
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
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" };
|
|
17538
17744
|
}
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
const refreshToken = obj["refreshToken"];
|
|
17542
|
-
if (typeof instanceId !== "string" || instanceId.length === 0) {
|
|
17543
|
-
throw new Error("missing instanceId");
|
|
17745
|
+
if (positional.length > 1) {
|
|
17746
|
+
return { ok: false, message: "gateway link accepts exactly one URL" };
|
|
17544
17747
|
}
|
|
17545
|
-
if (
|
|
17546
|
-
|
|
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" };
|
|
17547
17756
|
}
|
|
17548
17757
|
return {
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
...
|
|
17553
|
-
...typeof obj["expiresInSec"] === "number" ? { expiresInSec: obj["expiresInSec"] } : {},
|
|
17554
|
-
...Array.isArray(obj["runners"]) ? {
|
|
17555
|
-
runners: obj["runners"].map(parsePairedRunner).filter((runner) => runner !== null)
|
|
17556
|
-
} : {},
|
|
17557
|
-
...typeof obj["requiredCliVersion"] === "string" ? { requiredCliVersion: obj["requiredCliVersion"] } : {},
|
|
17558
|
-
...typeof obj["capabilityAck"] === "object" && obj["capabilityAck"] !== null ? { capabilityAck: parseCapabilityAck(obj["capabilityAck"]) } : {}
|
|
17758
|
+
ok: true,
|
|
17759
|
+
url,
|
|
17760
|
+
token,
|
|
17761
|
+
...tlsCaPath !== void 0 ? { tlsCaPath } : {}
|
|
17559
17762
|
};
|
|
17560
17763
|
}
|
|
17561
|
-
function
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
|
|
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;
|
|
17567
17780
|
}
|
|
17568
|
-
if (
|
|
17569
|
-
|
|
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;
|
|
17570
17795
|
}
|
|
17571
|
-
if (
|
|
17572
|
-
|
|
17796
|
+
if (json) {
|
|
17797
|
+
logOut(JSON.stringify({ ok: false, reason: "error", message }));
|
|
17798
|
+
} else {
|
|
17799
|
+
logError(`gateway: ${message}`);
|
|
17573
17800
|
}
|
|
17574
|
-
return
|
|
17801
|
+
return 1;
|
|
17575
17802
|
}
|
|
17576
|
-
function
|
|
17577
|
-
if (
|
|
17578
|
-
const
|
|
17579
|
-
const
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
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}`;
|
|
17587
17830
|
}
|
|
17588
17831
|
|
|
17589
17832
|
// src/app/entry/doctorCommand.ts
|
|
@@ -18283,7 +18526,7 @@ var cli = meow(
|
|
|
18283
18526
|
workflow <sub> Manage workflows (install, list, search, remove, upgrade, use)
|
|
18284
18527
|
marketplace <sub> Manage marketplace sources (add, remove, list)
|
|
18285
18528
|
channel <sub> Manage external channels
|
|
18286
|
-
dashboard <sub> Manage dashboard
|
|
18529
|
+
dashboard <sub> Manage dashboard pairing and runtime daemon (pair, status, daemon, unpair)
|
|
18287
18530
|
telemetry [action] Manage anonymous telemetry (enable/disable/status)
|
|
18288
18531
|
doctor Diagnose Claude headless setup (use with --harness=claude)
|
|
18289
18532
|
|
|
@@ -18308,15 +18551,8 @@ var cli = meow(
|
|
|
18308
18551
|
--bot-token Telegram bot token (channel telegram configure)
|
|
18309
18552
|
--user-id Telegram allowed user id (channel telegram configure)
|
|
18310
18553
|
--chat-id Telegram destination chat id (defaults to --user-id)
|
|
18311
|
-
--token Gateway link token (gateway link)
|
|
18312
18554
|
--url Dashboard origin (dashboard pair)
|
|
18313
18555
|
--name Friendly machine name (dashboard pair)
|
|
18314
|
-
--tls-ca Gateway custom CA path (gateway link)
|
|
18315
|
-
--tls-cert Gateway TLS certificate path (gateway start)
|
|
18316
|
-
--tls-key Gateway TLS private key path (gateway start)
|
|
18317
|
-
--bind Gateway listen address host:port (gateway start)
|
|
18318
|
-
--insecure Allow plain WS on non-loopback trusted tunnels (gateway start)
|
|
18319
|
-
--grace-period-ms Gateway reconnect grace period in milliseconds (gateway start)
|
|
18320
18556
|
--dry-run Print resolved bootstrap (workflow, isolation, plugins, harness) and exit (exec mode)
|
|
18321
18557
|
--project Scope workflow command to project config (workflow use)
|
|
18322
18558
|
--global Scope workflow command to global config (workflow use, default)
|
|
@@ -18476,6 +18712,9 @@ var cli = meow(
|
|
|
18476
18712
|
},
|
|
18477
18713
|
apiKey: {
|
|
18478
18714
|
type: "string"
|
|
18715
|
+
},
|
|
18716
|
+
attachmentId: {
|
|
18717
|
+
type: "string"
|
|
18479
18718
|
}
|
|
18480
18719
|
}
|
|
18481
18720
|
}
|
|
@@ -18525,7 +18764,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
18525
18764
|
await exitWith(1);
|
|
18526
18765
|
return;
|
|
18527
18766
|
}
|
|
18528
|
-
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-
|
|
18767
|
+
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-X754ND4V.js");
|
|
18529
18768
|
const { waitUntilExit } = render(
|
|
18530
18769
|
/* @__PURE__ */ jsx25(
|
|
18531
18770
|
WorkflowInstallWizard,
|
|
@@ -18788,6 +19027,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
18788
19027
|
isolationPreset,
|
|
18789
19028
|
ascii: cli.flags.ascii,
|
|
18790
19029
|
showSetup,
|
|
19030
|
+
...cli.flags.attachmentId !== void 0 ? { attachmentId: cli.flags.attachmentId } : {},
|
|
18791
19031
|
initialTelemetryDiagnosticsConsent: globalConfig.telemetryDiagnostics
|
|
18792
19032
|
}
|
|
18793
19033
|
),
|