@drisp/cli 0.4.5 → 0.5.0
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-4CRZXLIP.js → chunk-BTY7MYYT.js} +135 -135
- package/dist/{chunk-PJUDHH4R.js → chunk-K53YMYTG.js} +1049 -812
- package/dist/chunk-MRAM6EYI.js +76 -0
- package/dist/chunk-SHLHZL5F.js +4124 -0
- package/dist/chunk-ZVOGOZNT.js +395 -0
- package/dist/cli.js +1131 -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,
|
|
@@ -47,15 +45,13 @@ import {
|
|
|
47
45
|
lookupCredential,
|
|
48
46
|
makeSkippedProbe,
|
|
49
47
|
messageGlyphs,
|
|
48
|
+
normalizeHarnessOverride,
|
|
50
49
|
probeSkipReason,
|
|
51
50
|
processRegistry,
|
|
52
51
|
progressGlyphs,
|
|
53
|
-
readAttachmentMirror,
|
|
54
52
|
readGatewayClientConfig,
|
|
55
|
-
readPidLock,
|
|
56
53
|
register,
|
|
57
54
|
registerCleanupOnExit,
|
|
58
|
-
removeAttachmentMirror,
|
|
59
55
|
resolveClaudeBinary,
|
|
60
56
|
resolveClaudeSettingsSurfacePaths,
|
|
61
57
|
resolveHarnessAdapter,
|
|
@@ -70,13 +66,21 @@ import {
|
|
|
70
66
|
startSessionBridge,
|
|
71
67
|
supportsSessionApproval,
|
|
72
68
|
todoGlyphSet,
|
|
73
|
-
writeAttachmentMirror,
|
|
74
69
|
writeGatewayClientConfig,
|
|
75
70
|
wsClientOptionsForEndpoint
|
|
76
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-K53YMYTG.js";
|
|
77
72
|
import {
|
|
78
73
|
generateId as generateId2
|
|
79
74
|
} from "./chunk-BTKQ67RE.js";
|
|
75
|
+
import {
|
|
76
|
+
rotateGatewayToken
|
|
77
|
+
} from "./chunk-MRAM6EYI.js";
|
|
78
|
+
import {
|
|
79
|
+
readAttachmentMirror,
|
|
80
|
+
readPidLock,
|
|
81
|
+
removeAttachmentMirror,
|
|
82
|
+
writeAttachmentMirror
|
|
83
|
+
} from "./chunk-ZVOGOZNT.js";
|
|
80
84
|
import {
|
|
81
85
|
dashboardClientConfigPath,
|
|
82
86
|
disableTelemetry,
|
|
@@ -97,7 +101,7 @@ import {
|
|
|
97
101
|
trackTelemetryOptedOut,
|
|
98
102
|
writeDashboardClientConfig,
|
|
99
103
|
writeGatewayTrace
|
|
100
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-BTY7MYYT.js";
|
|
101
105
|
import {
|
|
102
106
|
McpOptionsStep,
|
|
103
107
|
StepSelector,
|
|
@@ -132,7 +136,7 @@ import {
|
|
|
132
136
|
useWorkflowSessionController,
|
|
133
137
|
writeGlobalConfig,
|
|
134
138
|
writeProjectConfig
|
|
135
|
-
} from "./chunk-
|
|
139
|
+
} from "./chunk-A54HGVML.js";
|
|
136
140
|
|
|
137
141
|
// src/app/entry/cli.tsx
|
|
138
142
|
import { render } from "ink";
|
|
@@ -1742,14 +1746,65 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1742
1746
|
const abortRef = useRef(new AbortController());
|
|
1743
1747
|
const feedEventsRef = useRef([]);
|
|
1744
1748
|
const notifiedRuntimeErrorRef = useRef(null);
|
|
1749
|
+
const dashboardFeedPublisher = useMemo3(
|
|
1750
|
+
() => options?.dashboardFeedPublisher ?? createDashboardFeedPublisher(),
|
|
1751
|
+
[options?.dashboardFeedPublisher]
|
|
1752
|
+
);
|
|
1753
|
+
const dashboardDecisionInboxRef = useRef(
|
|
1754
|
+
options?.dashboardDecisionInbox ?? null
|
|
1755
|
+
);
|
|
1745
1756
|
rulesRef.current = rules;
|
|
1746
1757
|
feedEventsRef.current = feedEvents;
|
|
1747
1758
|
sessionStoreRef.current = sessionStore;
|
|
1759
|
+
if (options?.dashboardDecisionInbox) {
|
|
1760
|
+
dashboardDecisionInboxRef.current = options.dashboardDecisionInbox;
|
|
1761
|
+
}
|
|
1762
|
+
const resolveAthenaSessionId = useCallback5(
|
|
1763
|
+
(fallback) => {
|
|
1764
|
+
if (options?.athenaSessionId) return options.athenaSessionId;
|
|
1765
|
+
try {
|
|
1766
|
+
const sessionId = sessionStoreRef.current?.getAthenaSession().id;
|
|
1767
|
+
if (sessionId) return sessionId;
|
|
1768
|
+
} catch {
|
|
1769
|
+
}
|
|
1770
|
+
return fallback;
|
|
1771
|
+
},
|
|
1772
|
+
[options?.athenaSessionId]
|
|
1773
|
+
);
|
|
1748
1774
|
useEffect(() => {
|
|
1749
1775
|
return () => {
|
|
1750
1776
|
sessionStoreRef.current = void 0;
|
|
1751
1777
|
};
|
|
1752
1778
|
}, []);
|
|
1779
|
+
useEffect(() => {
|
|
1780
|
+
const athenaSessionId = resolveAthenaSessionId();
|
|
1781
|
+
if (!athenaSessionId) return;
|
|
1782
|
+
if (!dashboardDecisionInboxRef.current) {
|
|
1783
|
+
if (!readDashboardClientConfig()) return;
|
|
1784
|
+
dashboardDecisionInboxRef.current = createDashboardDecisionInbox();
|
|
1785
|
+
}
|
|
1786
|
+
const inbox = dashboardDecisionInboxRef.current;
|
|
1787
|
+
const applyPending = () => {
|
|
1788
|
+
const rows = inbox.pendingForSession({ athenaSessionId, limit: 25 });
|
|
1789
|
+
for (const row of rows) {
|
|
1790
|
+
runtime.sendDecision(row.requestId, row.decision);
|
|
1791
|
+
inbox.markConsumed({ id: row.id });
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
applyPending();
|
|
1795
|
+
const interval = setInterval(
|
|
1796
|
+
applyPending,
|
|
1797
|
+
options?.dashboardDecisionPollIntervalMs ?? 1e3
|
|
1798
|
+
);
|
|
1799
|
+
return () => {
|
|
1800
|
+
clearInterval(interval);
|
|
1801
|
+
};
|
|
1802
|
+
}, [
|
|
1803
|
+
runtime,
|
|
1804
|
+
options?.dashboardDecisionInbox,
|
|
1805
|
+
options?.dashboardDecisionPollIntervalMs,
|
|
1806
|
+
resolveAthenaSessionId
|
|
1807
|
+
]);
|
|
1753
1808
|
const resetSession = useCallback5(() => {
|
|
1754
1809
|
const newMapper = createFeedMapper();
|
|
1755
1810
|
mapperRef.current = newMapper;
|
|
@@ -1860,18 +1915,37 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1860
1915
|
},
|
|
1861
1916
|
[runtime, dequeueQuestion]
|
|
1862
1917
|
);
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1918
|
+
const publishDashboardFeedEvents = useCallback5(
|
|
1919
|
+
(feedEventsToPublish, runtimeEvent) => {
|
|
1920
|
+
if (feedEventsToPublish.length === 0) return;
|
|
1921
|
+
const athenaSessionId = resolveAthenaSessionId(
|
|
1922
|
+
feedEventsToPublish[0]?.session_id ?? runtimeEvent?.sessionId
|
|
1923
|
+
);
|
|
1924
|
+
if (!athenaSessionId) return;
|
|
1925
|
+
dashboardFeedPublisher.publish({
|
|
1926
|
+
origin: options?.dashboardOrigin ?? "local",
|
|
1927
|
+
athenaSessionId,
|
|
1928
|
+
feedEvents: feedEventsToPublish
|
|
1929
|
+
});
|
|
1930
|
+
},
|
|
1931
|
+
[dashboardFeedPublisher, options?.dashboardOrigin, resolveAthenaSessionId]
|
|
1932
|
+
);
|
|
1933
|
+
const emitNotification = useCallback5(
|
|
1934
|
+
(message, title) => {
|
|
1935
|
+
const mapper = mapperRef.current;
|
|
1936
|
+
const syntheticRuntime = buildSyntheticNotificationEvent(
|
|
1937
|
+
mapper,
|
|
1938
|
+
message,
|
|
1939
|
+
title
|
|
1940
|
+
);
|
|
1941
|
+
const newEvents = mapper.mapEvent(syntheticRuntime);
|
|
1942
|
+
if (!abortRef.current.signal.aborted) {
|
|
1943
|
+
feedStoreRef.current.pushEvents(newEvents);
|
|
1944
|
+
publishDashboardFeedEvents(newEvents, syntheticRuntime);
|
|
1945
|
+
}
|
|
1946
|
+
},
|
|
1947
|
+
[publishDashboardFeedEvents]
|
|
1948
|
+
);
|
|
1875
1949
|
const refreshRuntimeStatus = useCallback5(
|
|
1876
1950
|
(notify = false) => {
|
|
1877
1951
|
const nextIsServerRunning = runtime.getStatus() === "running";
|
|
@@ -1956,6 +2030,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1956
2030
|
}
|
|
1957
2031
|
}
|
|
1958
2032
|
feedStoreRef.current.pushEvents(newFeedEvents);
|
|
2033
|
+
publishDashboardFeedEvents(newFeedEvents, runtimeEvent);
|
|
1959
2034
|
}
|
|
1960
2035
|
} finally {
|
|
1961
2036
|
doneCause();
|
|
@@ -1991,6 +2066,7 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
1991
2066
|
});
|
|
1992
2067
|
if (feedEvent) {
|
|
1993
2068
|
feedStoreRef.current.pushEvents([feedEvent]);
|
|
2069
|
+
publishDashboardFeedEvents([feedEvent]);
|
|
1994
2070
|
if (feedEvent.kind === "permission.decision" && feedEvent.cause?.hook_request_id) {
|
|
1995
2071
|
dequeuePermission(feedEvent.cause.hook_request_id);
|
|
1996
2072
|
}
|
|
@@ -2014,7 +2090,8 @@ function useFeed(runtime, messages = [], initialAllowedTools, sessionStore, opti
|
|
|
2014
2090
|
dequeueQuestion,
|
|
2015
2091
|
refreshRuntimeStatus,
|
|
2016
2092
|
options?.relayPermission,
|
|
2017
|
-
options?.relayQuestion
|
|
2093
|
+
options?.relayQuestion,
|
|
2094
|
+
publishDashboardFeedEvents
|
|
2018
2095
|
]);
|
|
2019
2096
|
const items = useMemo3(() => {
|
|
2020
2097
|
const done = startPerfStage("state.derive", {
|
|
@@ -2111,6 +2188,7 @@ function HookProvider({
|
|
|
2111
2188
|
runtimeFactory = createRuntime,
|
|
2112
2189
|
allowedTools,
|
|
2113
2190
|
athenaSessionId,
|
|
2191
|
+
attachmentId,
|
|
2114
2192
|
children
|
|
2115
2193
|
}) {
|
|
2116
2194
|
const runtime = useMemo4(
|
|
@@ -2158,6 +2236,7 @@ function HookProvider({
|
|
|
2158
2236
|
void startSessionBridge({
|
|
2159
2237
|
runtimeId: athenaSessionId,
|
|
2160
2238
|
defaultAgentId: "main",
|
|
2239
|
+
...attachmentId !== void 0 ? { attachmentId } : {},
|
|
2161
2240
|
signal: controller.signal
|
|
2162
2241
|
}).then((bridge) => {
|
|
2163
2242
|
if (!bridge) return;
|
|
@@ -2174,7 +2253,7 @@ function HookProvider({
|
|
|
2174
2253
|
return null;
|
|
2175
2254
|
});
|
|
2176
2255
|
};
|
|
2177
|
-
}, [athenaSessionId]);
|
|
2256
|
+
}, [athenaSessionId, attachmentId]);
|
|
2178
2257
|
useEffect2(() => {
|
|
2179
2258
|
return () => {
|
|
2180
2259
|
sessionStore.close();
|
|
@@ -10951,6 +11030,352 @@ function executeCommand(command, args, ctx) {
|
|
|
10951
11030
|
}
|
|
10952
11031
|
}
|
|
10953
11032
|
|
|
11033
|
+
// src/app/runner/assignmentHandler.ts
|
|
11034
|
+
function parseRunSpec(value) {
|
|
11035
|
+
if (typeof value !== "object" || value === null) return null;
|
|
11036
|
+
const obj = value;
|
|
11037
|
+
const prompt = obj["prompt"];
|
|
11038
|
+
if (typeof prompt !== "string" || prompt.trim().length === 0) return null;
|
|
11039
|
+
const env2 = obj["env"];
|
|
11040
|
+
const workflow = obj["workflow"];
|
|
11041
|
+
return {
|
|
11042
|
+
prompt,
|
|
11043
|
+
sessionId: typeof obj["sessionId"] === "string" && obj["sessionId"].length > 0 ? obj["sessionId"] : void 0,
|
|
11044
|
+
projectDir: typeof obj["projectDir"] === "string" && obj["projectDir"].length > 0 ? obj["projectDir"] : void 0,
|
|
11045
|
+
workflow: typeof workflow === "object" && workflow !== null && typeof workflow["ref"] === "string" ? { ref: workflow["ref"] } : void 0,
|
|
11046
|
+
harness: normalizeHarnessOverride(obj["harness"]),
|
|
11047
|
+
env: typeof env2 === "object" && env2 !== null ? Object.fromEntries(
|
|
11048
|
+
Object.entries(env2).filter(
|
|
11049
|
+
(entry) => typeof entry[1] === "string"
|
|
11050
|
+
)
|
|
11051
|
+
) : void 0,
|
|
11052
|
+
timeoutSec: typeof obj["timeoutSec"] === "number" && Number.isFinite(obj["timeoutSec"]) ? obj["timeoutSec"] : void 0
|
|
11053
|
+
};
|
|
11054
|
+
}
|
|
11055
|
+
function workflowNameFromRef(ref) {
|
|
11056
|
+
if (!ref) return void 0;
|
|
11057
|
+
const [name] = ref.split("@", 1);
|
|
11058
|
+
return name && name.length > 0 ? name : void 0;
|
|
11059
|
+
}
|
|
11060
|
+
function eventKindOf(event) {
|
|
11061
|
+
if (event.type === "exec.completed") {
|
|
11062
|
+
const data = event.data;
|
|
11063
|
+
return data?.success === false ? "error" : "completion";
|
|
11064
|
+
}
|
|
11065
|
+
return typeof event.type === "string" && event.type.length > 0 ? event.type : "progress";
|
|
11066
|
+
}
|
|
11067
|
+
function eventPayloadOf(event) {
|
|
11068
|
+
if (event.type === "exec.completed") {
|
|
11069
|
+
const data = event.data;
|
|
11070
|
+
if (data?.success === false) {
|
|
11071
|
+
return {
|
|
11072
|
+
...typeof event.data === "object" && event.data !== null ? event.data : {},
|
|
11073
|
+
message: typeof data.failure === "object" && data.failure !== null && typeof data.failure.message === "string" ? data.failure.message : "remote execution failed"
|
|
11074
|
+
};
|
|
11075
|
+
}
|
|
11076
|
+
}
|
|
11077
|
+
return event.data ?? null;
|
|
11078
|
+
}
|
|
11079
|
+
function withEnv(env2, fn) {
|
|
11080
|
+
if (!env2 || Object.keys(env2).length === 0) return fn();
|
|
11081
|
+
const previous = /* @__PURE__ */ new Map();
|
|
11082
|
+
for (const [key, value] of Object.entries(env2)) {
|
|
11083
|
+
previous.set(key, process.env[key]);
|
|
11084
|
+
process.env[key] = value;
|
|
11085
|
+
}
|
|
11086
|
+
return fn().finally(() => {
|
|
11087
|
+
for (const [key, value] of previous) {
|
|
11088
|
+
if (value === void 0) {
|
|
11089
|
+
delete process.env[key];
|
|
11090
|
+
} else {
|
|
11091
|
+
process.env[key] = value;
|
|
11092
|
+
}
|
|
11093
|
+
}
|
|
11094
|
+
});
|
|
11095
|
+
}
|
|
11096
|
+
async function executeAssignment(input) {
|
|
11097
|
+
const {
|
|
11098
|
+
envelope,
|
|
11099
|
+
bridge,
|
|
11100
|
+
dispatchId,
|
|
11101
|
+
location,
|
|
11102
|
+
projectDir: fallbackProjectDir = process.cwd(),
|
|
11103
|
+
runExecFn = runExec,
|
|
11104
|
+
bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
|
|
11105
|
+
now = Date.now,
|
|
11106
|
+
abortSignal
|
|
11107
|
+
} = input;
|
|
11108
|
+
const runId = envelope.runId;
|
|
11109
|
+
let seq = 0;
|
|
11110
|
+
let terminalSent = false;
|
|
11111
|
+
let deferredFailedCompletion = null;
|
|
11112
|
+
let lastTerminalFailureMessage = null;
|
|
11113
|
+
const nextSeq = () => {
|
|
11114
|
+
seq += 1;
|
|
11115
|
+
return seq;
|
|
11116
|
+
};
|
|
11117
|
+
const sendProgress = async (kind, payload, ts = now()) => {
|
|
11118
|
+
await bridge.sendRunEvent({
|
|
11119
|
+
location,
|
|
11120
|
+
runId,
|
|
11121
|
+
seq: nextSeq(),
|
|
11122
|
+
ts,
|
|
11123
|
+
kind,
|
|
11124
|
+
payload
|
|
11125
|
+
});
|
|
11126
|
+
};
|
|
11127
|
+
const sendTerminal = async (eventKind, payload, ts = now()) => {
|
|
11128
|
+
if (terminalSent) return;
|
|
11129
|
+
terminalSent = true;
|
|
11130
|
+
const envelopeText = JSON.stringify({
|
|
11131
|
+
kind: "run_event",
|
|
11132
|
+
runId,
|
|
11133
|
+
seq: nextSeq(),
|
|
11134
|
+
ts,
|
|
11135
|
+
eventKind,
|
|
11136
|
+
payload
|
|
11137
|
+
});
|
|
11138
|
+
await bridge.completeTurn({
|
|
11139
|
+
dispatchId,
|
|
11140
|
+
location,
|
|
11141
|
+
text: envelopeText,
|
|
11142
|
+
idempotencyKey: `run_event:${runId}:terminal`
|
|
11143
|
+
});
|
|
11144
|
+
};
|
|
11145
|
+
await sendProgress("progress", { message: "assignment received" });
|
|
11146
|
+
const spec = parseRunSpec(envelope.runSpec);
|
|
11147
|
+
if (!spec) {
|
|
11148
|
+
await sendTerminal("error", { message: "remote assignment missing prompt" });
|
|
11149
|
+
return;
|
|
11150
|
+
}
|
|
11151
|
+
const projectDir = spec.projectDir ?? fallbackProjectDir;
|
|
11152
|
+
let runtimeConfig;
|
|
11153
|
+
try {
|
|
11154
|
+
runtimeConfig = bootstrapRuntimeConfigFn({
|
|
11155
|
+
projectDir,
|
|
11156
|
+
showSetup: false,
|
|
11157
|
+
isolationPreset: "minimal",
|
|
11158
|
+
harnessOverride: spec.harness,
|
|
11159
|
+
workflowOverride: workflowNameFromRef(spec.workflow?.ref)
|
|
11160
|
+
});
|
|
11161
|
+
} catch (err) {
|
|
11162
|
+
await sendTerminal("error", {
|
|
11163
|
+
message: err instanceof Error ? err.message : String(err)
|
|
11164
|
+
});
|
|
11165
|
+
return;
|
|
11166
|
+
}
|
|
11167
|
+
for (const warning of runtimeConfig.warnings) {
|
|
11168
|
+
await sendProgress("warning", { message: warning });
|
|
11169
|
+
}
|
|
11170
|
+
let buffered = "";
|
|
11171
|
+
const pendingProgress = [];
|
|
11172
|
+
const stdout = {
|
|
11173
|
+
write(chunk) {
|
|
11174
|
+
buffered += chunk;
|
|
11175
|
+
let newline = buffered.indexOf("\n");
|
|
11176
|
+
while (newline >= 0) {
|
|
11177
|
+
const line = buffered.slice(0, newline).trim();
|
|
11178
|
+
buffered = buffered.slice(newline + 1);
|
|
11179
|
+
if (line.length > 0) {
|
|
11180
|
+
try {
|
|
11181
|
+
const event = JSON.parse(line);
|
|
11182
|
+
const data = event.data;
|
|
11183
|
+
if (event.type === "exec.completed" && data?.success === false) {
|
|
11184
|
+
deferredFailedCompletion = event;
|
|
11185
|
+
} else if (event.type === "exec.completed") {
|
|
11186
|
+
deferredFailedCompletion = event;
|
|
11187
|
+
} else {
|
|
11188
|
+
pendingProgress.push(
|
|
11189
|
+
sendProgress(eventKindOf(event), eventPayloadOf(event), now())
|
|
11190
|
+
);
|
|
11191
|
+
}
|
|
11192
|
+
} catch {
|
|
11193
|
+
pendingProgress.push(sendProgress("progress", { line }));
|
|
11194
|
+
}
|
|
11195
|
+
}
|
|
11196
|
+
newline = buffered.indexOf("\n");
|
|
11197
|
+
}
|
|
11198
|
+
return true;
|
|
11199
|
+
}
|
|
11200
|
+
};
|
|
11201
|
+
const stderr = {
|
|
11202
|
+
write(chunk) {
|
|
11203
|
+
const text = chunk.trim();
|
|
11204
|
+
if (text.length > 0) pendingProgress.push(sendProgress("stderr", { text }));
|
|
11205
|
+
return true;
|
|
11206
|
+
}
|
|
11207
|
+
};
|
|
11208
|
+
try {
|
|
11209
|
+
await withEnv(spec.env, async () => {
|
|
11210
|
+
const result = await runExecFn({
|
|
11211
|
+
prompt: spec.prompt,
|
|
11212
|
+
projectDir,
|
|
11213
|
+
harness: runtimeConfig.harness,
|
|
11214
|
+
athenaSessionId: spec.sessionId ?? `athena-${runId}`,
|
|
11215
|
+
isolationConfig: runtimeConfig.isolationConfig,
|
|
11216
|
+
pluginMcpConfig: runtimeConfig.pluginMcpConfig,
|
|
11217
|
+
workflow: runtimeConfig.workflow,
|
|
11218
|
+
workflowPlan: runtimeConfig.workflowPlan,
|
|
11219
|
+
json: true,
|
|
11220
|
+
verbose: false,
|
|
11221
|
+
ephemeral: false,
|
|
11222
|
+
timeoutMs: spec.timeoutSec ? spec.timeoutSec * 1e3 : void 0,
|
|
11223
|
+
signal: abortSignal,
|
|
11224
|
+
stdout,
|
|
11225
|
+
stderr
|
|
11226
|
+
});
|
|
11227
|
+
await Promise.all(pendingProgress);
|
|
11228
|
+
if (deferredFailedCompletion) {
|
|
11229
|
+
const data = typeof deferredFailedCompletion.data === "object" && deferredFailedCompletion.data !== null ? deferredFailedCompletion.data : {};
|
|
11230
|
+
const ts = typeof deferredFailedCompletion.ts === "number" ? deferredFailedCompletion.ts : now();
|
|
11231
|
+
const success = data.success !== false;
|
|
11232
|
+
const eventKind = success ? "completion" : "error";
|
|
11233
|
+
const message = !success ? result.failure?.message ?? eventPayloadOf(deferredFailedCompletion).message ?? "remote execution failed" : void 0;
|
|
11234
|
+
if (message) lastTerminalFailureMessage = message;
|
|
11235
|
+
await sendTerminal(
|
|
11236
|
+
eventKind,
|
|
11237
|
+
success ? {
|
|
11238
|
+
...data,
|
|
11239
|
+
exitCode: result.exitCode,
|
|
11240
|
+
athenaSessionId: result.athenaSessionId,
|
|
11241
|
+
adapterSessionId: result.adapterSessionId,
|
|
11242
|
+
finalMessage: result.finalMessage,
|
|
11243
|
+
tokens: result.tokens,
|
|
11244
|
+
durationMs: result.durationMs,
|
|
11245
|
+
success: true
|
|
11246
|
+
} : {
|
|
11247
|
+
...data,
|
|
11248
|
+
success: result.success,
|
|
11249
|
+
exitCode: result.exitCode,
|
|
11250
|
+
athenaSessionId: result.athenaSessionId,
|
|
11251
|
+
adapterSessionId: result.adapterSessionId,
|
|
11252
|
+
finalMessage: result.finalMessage,
|
|
11253
|
+
tokens: result.tokens,
|
|
11254
|
+
durationMs: result.durationMs,
|
|
11255
|
+
message
|
|
11256
|
+
},
|
|
11257
|
+
ts
|
|
11258
|
+
);
|
|
11259
|
+
return;
|
|
11260
|
+
}
|
|
11261
|
+
if (result.failure && result.failure.message !== lastTerminalFailureMessage) {
|
|
11262
|
+
await sendTerminal("error", {
|
|
11263
|
+
success: result.success,
|
|
11264
|
+
exitCode: result.exitCode,
|
|
11265
|
+
athenaSessionId: result.athenaSessionId,
|
|
11266
|
+
adapterSessionId: result.adapterSessionId,
|
|
11267
|
+
finalMessage: result.finalMessage,
|
|
11268
|
+
tokens: result.tokens,
|
|
11269
|
+
durationMs: result.durationMs,
|
|
11270
|
+
message: result.failure.message
|
|
11271
|
+
});
|
|
11272
|
+
return;
|
|
11273
|
+
}
|
|
11274
|
+
await sendTerminal("completion", {
|
|
11275
|
+
success: result.success,
|
|
11276
|
+
exitCode: result.exitCode,
|
|
11277
|
+
athenaSessionId: result.athenaSessionId,
|
|
11278
|
+
adapterSessionId: result.adapterSessionId,
|
|
11279
|
+
finalMessage: result.finalMessage,
|
|
11280
|
+
tokens: result.tokens,
|
|
11281
|
+
durationMs: result.durationMs
|
|
11282
|
+
});
|
|
11283
|
+
});
|
|
11284
|
+
} catch (err) {
|
|
11285
|
+
await Promise.all(pendingProgress);
|
|
11286
|
+
await sendTerminal("error", {
|
|
11287
|
+
message: err instanceof Error ? err.message : String(err)
|
|
11288
|
+
});
|
|
11289
|
+
}
|
|
11290
|
+
}
|
|
11291
|
+
|
|
11292
|
+
// src/app/runner/envelope.ts
|
|
11293
|
+
function parseRunnerEnvelope(text) {
|
|
11294
|
+
let value;
|
|
11295
|
+
try {
|
|
11296
|
+
value = JSON.parse(text);
|
|
11297
|
+
} catch {
|
|
11298
|
+
return null;
|
|
11299
|
+
}
|
|
11300
|
+
if (typeof value !== "object" || value === null) return null;
|
|
11301
|
+
const obj = value;
|
|
11302
|
+
const runId = obj["runId"];
|
|
11303
|
+
if (typeof runId !== "string" || runId.length === 0) return null;
|
|
11304
|
+
const kind = obj["kind"];
|
|
11305
|
+
if (kind === "job_assignment") {
|
|
11306
|
+
return { kind, runId, runSpec: obj["runSpec"] };
|
|
11307
|
+
}
|
|
11308
|
+
if (kind === "cancel") {
|
|
11309
|
+
return { kind, runId };
|
|
11310
|
+
}
|
|
11311
|
+
return null;
|
|
11312
|
+
}
|
|
11313
|
+
|
|
11314
|
+
// src/app/runner/runnerSession.ts
|
|
11315
|
+
function createRunnerSession(opts) {
|
|
11316
|
+
const {
|
|
11317
|
+
bridge,
|
|
11318
|
+
projectDir,
|
|
11319
|
+
runExecFn = runExec,
|
|
11320
|
+
bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
|
|
11321
|
+
now = Date.now
|
|
11322
|
+
} = opts;
|
|
11323
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
11324
|
+
function startAssignment(envelope, dispatchId, location) {
|
|
11325
|
+
const controller = new AbortController();
|
|
11326
|
+
inflight.set(envelope.runId, controller);
|
|
11327
|
+
return executeAssignment({
|
|
11328
|
+
envelope,
|
|
11329
|
+
bridge,
|
|
11330
|
+
dispatchId,
|
|
11331
|
+
location,
|
|
11332
|
+
projectDir,
|
|
11333
|
+
runExecFn,
|
|
11334
|
+
bootstrapRuntimeConfigFn,
|
|
11335
|
+
now,
|
|
11336
|
+
abortSignal: controller.signal
|
|
11337
|
+
}).finally(() => {
|
|
11338
|
+
if (inflight.get(envelope.runId) === controller) {
|
|
11339
|
+
inflight.delete(envelope.runId);
|
|
11340
|
+
}
|
|
11341
|
+
});
|
|
11342
|
+
}
|
|
11343
|
+
return {
|
|
11344
|
+
handleDispatch(input) {
|
|
11345
|
+
const envelope = parseRunnerEnvelope(input.text);
|
|
11346
|
+
if (!envelope) {
|
|
11347
|
+
return { recognised: false, completed: Promise.resolve() };
|
|
11348
|
+
}
|
|
11349
|
+
if (envelope.kind === "job_assignment") {
|
|
11350
|
+
const completed = startAssignment(
|
|
11351
|
+
envelope,
|
|
11352
|
+
input.dispatchId,
|
|
11353
|
+
input.location
|
|
11354
|
+
);
|
|
11355
|
+
return { recognised: true, completed };
|
|
11356
|
+
}
|
|
11357
|
+
const controller = inflight.get(envelope.runId);
|
|
11358
|
+
if (controller) controller.abort();
|
|
11359
|
+
return { recognised: true, completed: Promise.resolve() };
|
|
11360
|
+
}
|
|
11361
|
+
};
|
|
11362
|
+
}
|
|
11363
|
+
|
|
11364
|
+
// src/app/runner/dispatchRouter.ts
|
|
11365
|
+
function makeDispatchRouter(opts) {
|
|
11366
|
+
return (payload) => {
|
|
11367
|
+
if (opts.runnerSession) {
|
|
11368
|
+
const result = opts.runnerSession.handleDispatch({
|
|
11369
|
+
text: payload.inbound.text,
|
|
11370
|
+
dispatchId: payload.dispatchId,
|
|
11371
|
+
location: payload.inbound.location
|
|
11372
|
+
});
|
|
11373
|
+
if (result.recognised) return;
|
|
11374
|
+
}
|
|
11375
|
+
opts.fallback(payload);
|
|
11376
|
+
};
|
|
11377
|
+
}
|
|
11378
|
+
|
|
10954
11379
|
// src/ui/components/SessionPicker.tsx
|
|
10955
11380
|
import { useState as useState11 } from "react";
|
|
10956
11381
|
import { Box as Box11, Text as Text17, useInput as useInput12, useStdout as useStdout5 } from "ink";
|
|
@@ -13321,17 +13746,25 @@ function AppContent({
|
|
|
13321
13746
|
[addMessage, currentSessionId, feedEvents.length, spawnHarness]
|
|
13322
13747
|
);
|
|
13323
13748
|
submitDispatchAsTurnRef.current = submitDispatchAsTurn;
|
|
13749
|
+
const runnerSession = useMemo17(
|
|
13750
|
+
() => sessionBridge ? createRunnerSession({ bridge: sessionBridge, projectDir }) : null,
|
|
13751
|
+
[sessionBridge, projectDir]
|
|
13752
|
+
);
|
|
13324
13753
|
useEffect14(() => {
|
|
13325
13754
|
if (!sessionBridge) return;
|
|
13326
|
-
const
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
13755
|
+
const router = makeDispatchRouter({
|
|
13756
|
+
runnerSession,
|
|
13757
|
+
fallback: (payload) => {
|
|
13758
|
+
if (isHarnessRunningRef.current || pendingDispatchRef.current) {
|
|
13759
|
+
queuedDispatchRef.current = payload;
|
|
13760
|
+
return;
|
|
13761
|
+
}
|
|
13762
|
+
submitDispatchAsTurnRef.current?.(payload);
|
|
13330
13763
|
}
|
|
13331
|
-
submitDispatchAsTurnRef.current?.(payload);
|
|
13332
13764
|
});
|
|
13765
|
+
const off = sessionBridge.onTurnDispatch(router);
|
|
13333
13766
|
return off;
|
|
13334
|
-
}, [sessionBridge]);
|
|
13767
|
+
}, [sessionBridge, runnerSession]);
|
|
13335
13768
|
useEffect14(() => {
|
|
13336
13769
|
if (isHarnessRunning) return;
|
|
13337
13770
|
if (pendingDispatchRef.current) return;
|
|
@@ -14366,6 +14799,7 @@ function App({
|
|
|
14366
14799
|
isolationPreset,
|
|
14367
14800
|
ascii,
|
|
14368
14801
|
athenaSessionId: initialAthenaSessionId,
|
|
14802
|
+
attachmentId,
|
|
14369
14803
|
initialTelemetryDiagnosticsConsent
|
|
14370
14804
|
}) {
|
|
14371
14805
|
const [clearCount, setClearCount] = useState18(0);
|
|
@@ -14581,6 +15015,7 @@ function App({
|
|
|
14581
15015
|
runtimeState.isolation?.allowedTools
|
|
14582
15016
|
),
|
|
14583
15017
|
athenaSessionId,
|
|
15018
|
+
...attachmentId !== void 0 ? { attachmentId } : {},
|
|
14584
15019
|
children: /* @__PURE__ */ jsx24(
|
|
14585
15020
|
AppContent,
|
|
14586
15021
|
{
|
|
@@ -15417,446 +15852,74 @@ function runChannelCommand(input, deps = {}) {
|
|
|
15417
15852
|
|
|
15418
15853
|
// src/app/entry/dashboardCommand.ts
|
|
15419
15854
|
import crypto3 from "crypto";
|
|
15420
|
-
import { spawn
|
|
15421
|
-
import
|
|
15855
|
+
import { spawn } from "child_process";
|
|
15856
|
+
import fs6 from "fs";
|
|
15422
15857
|
import { createRequire } from "module";
|
|
15423
15858
|
import os5 from "os";
|
|
15424
|
-
import
|
|
15425
|
-
import { fileURLToPath
|
|
15859
|
+
import path6 from "path";
|
|
15860
|
+
import { fileURLToPath } from "url";
|
|
15426
15861
|
|
|
15427
|
-
// src/
|
|
15428
|
-
import { spawn } from "child_process";
|
|
15862
|
+
// src/infra/daemon/serviceUnit.ts
|
|
15429
15863
|
import fs5 from "fs";
|
|
15864
|
+
import os4 from "os";
|
|
15430
15865
|
import path5 from "path";
|
|
15431
|
-
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15866
|
+
function installServiceUnit(options) {
|
|
15867
|
+
const platform = options.platform ?? process.platform;
|
|
15868
|
+
const env2 = options.env ?? process.env;
|
|
15869
|
+
const home = env2["HOME"] ?? os4.homedir();
|
|
15870
|
+
if (platform === "darwin") {
|
|
15871
|
+
const target = options.targetPath ?? path5.join(home, "Library", "LaunchAgents", "ai.drisp.daemon.plist");
|
|
15872
|
+
const paths = daemonStatePaths(env2);
|
|
15873
|
+
const plist = renderLaunchdPlist({
|
|
15874
|
+
label: "ai.drisp.daemon",
|
|
15875
|
+
nodeBinary: options.nodeBinary,
|
|
15876
|
+
daemonEntry: options.daemonEntry,
|
|
15877
|
+
workingDirectory: home,
|
|
15878
|
+
stdoutPath: paths.logPath,
|
|
15879
|
+
stderrPath: paths.logPath
|
|
15880
|
+
});
|
|
15881
|
+
writeIfChanged(target, plist);
|
|
15882
|
+
return {
|
|
15883
|
+
ok: true,
|
|
15884
|
+
platform: "darwin",
|
|
15885
|
+
path: target,
|
|
15886
|
+
loadCommand: `launchctl load -w ${target}`,
|
|
15887
|
+
startCommand: "launchctl start ai.drisp.daemon"
|
|
15888
|
+
};
|
|
15889
|
+
}
|
|
15890
|
+
if (platform === "linux") {
|
|
15891
|
+
const target = options.targetPath ?? path5.join(home, ".config", "systemd", "user", "drisp-daemon.service");
|
|
15892
|
+
const unit = renderSystemdUnit({
|
|
15893
|
+
description: "Drisp dashboard runtime daemon",
|
|
15894
|
+
nodeBinary: options.nodeBinary,
|
|
15895
|
+
daemonEntry: options.daemonEntry
|
|
15896
|
+
});
|
|
15897
|
+
writeIfChanged(target, unit);
|
|
15898
|
+
return {
|
|
15899
|
+
ok: true,
|
|
15900
|
+
platform: "linux",
|
|
15901
|
+
path: target,
|
|
15902
|
+
loadCommand: "systemctl --user daemon-reload",
|
|
15903
|
+
startCommand: "systemctl --user enable --now drisp-daemon.service"
|
|
15904
|
+
};
|
|
15905
|
+
}
|
|
15906
|
+
return {
|
|
15907
|
+
ok: false,
|
|
15908
|
+
platform: "unsupported",
|
|
15909
|
+
message: `service install not supported on ${platform}`
|
|
15910
|
+
};
|
|
15451
15911
|
}
|
|
15452
|
-
function
|
|
15912
|
+
function writeIfChanged(target, content) {
|
|
15913
|
+
const dir = path5.dirname(target);
|
|
15914
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
15915
|
+
let existing = null;
|
|
15453
15916
|
try {
|
|
15454
|
-
|
|
15917
|
+
existing = fs5.readFileSync(target, "utf-8");
|
|
15455
15918
|
} 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;
|
|
15919
|
+
if (err.code !== "ENOENT") throw err;
|
|
15463
15920
|
}
|
|
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 });
|
|
15921
|
+
if (existing === content) return;
|
|
15922
|
+
fs5.writeFileSync(target, content, { mode: 384 });
|
|
15860
15923
|
}
|
|
15861
15924
|
function renderLaunchdPlist(input) {
|
|
15862
15925
|
const argv = [input.nodeBinary, input.daemonEntry].map((s) => ` <string>${escapeXml(s)}</string>`).join("\n");
|
|
@@ -15912,7 +15975,7 @@ function escapeXml(input) {
|
|
|
15912
15975
|
}
|
|
15913
15976
|
|
|
15914
15977
|
// src/app/entry/dashboardCommand.ts
|
|
15915
|
-
var
|
|
15978
|
+
var USAGE4 = `Usage: athena dashboard <subcommand> [options]
|
|
15916
15979
|
|
|
15917
15980
|
Subcommands:
|
|
15918
15981
|
pair <token> --url <dashboard-origin> [--name <machine-name>]
|
|
@@ -15938,20 +16001,13 @@ Subcommands:
|
|
|
15938
16001
|
runner is bound to this instance.
|
|
15939
16002
|
list Print the local attachment mirror (the runners the dashboard
|
|
15940
16003
|
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
16004
|
connect Deprecated alias for "daemon foreground".
|
|
15948
16005
|
unpair Stop the daemon, revoke the refresh token, and remove the
|
|
15949
16006
|
local config.
|
|
15950
16007
|
|
|
15951
16008
|
Options:
|
|
15952
16009
|
--url <origin> Dashboard origin (required for pair)
|
|
15953
|
-
--runner <id> Runner id (
|
|
15954
|
-
link", optional for "doctor")
|
|
16010
|
+
--runner <id> Runner id (optional for "doctor")
|
|
15955
16011
|
--name <name> Friendly machine name (optional, defaults to hostname)
|
|
15956
16012
|
--tail N Number of trailing log lines (default 20)
|
|
15957
16013
|
--follow Stream new log lines until interrupted
|
|
@@ -15964,7 +16020,7 @@ var cachedVersion = null;
|
|
|
15964
16020
|
function readPackageVersion() {
|
|
15965
16021
|
if (cachedVersion !== null) return cachedVersion;
|
|
15966
16022
|
try {
|
|
15967
|
-
const injected = "0.
|
|
16023
|
+
const injected = "0.5.0";
|
|
15968
16024
|
if (typeof injected === "string" && injected.length > 0) {
|
|
15969
16025
|
cachedVersion = injected;
|
|
15970
16026
|
return cachedVersion;
|
|
@@ -16013,14 +16069,14 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
16013
16069
|
const packageVersion = deps.packageVersion ?? readPackageVersion();
|
|
16014
16070
|
const { subcommand, subcommandArgs, flags } = input;
|
|
16015
16071
|
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
16016
|
-
logOut(
|
|
16072
|
+
logOut(USAGE4);
|
|
16017
16073
|
return 0;
|
|
16018
16074
|
}
|
|
16019
16075
|
if (subcommand === "pair") {
|
|
16020
16076
|
const token = subcommandArgs[0];
|
|
16021
16077
|
if (!token) {
|
|
16022
16078
|
logError("dashboard pair: missing pairing token");
|
|
16023
|
-
logError(
|
|
16079
|
+
logError(USAGE4);
|
|
16024
16080
|
return 2;
|
|
16025
16081
|
}
|
|
16026
16082
|
if (subcommandArgs.length > 1) {
|
|
@@ -16047,7 +16103,6 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
16047
16103
|
hostInfo: (deps.hostInfo ?? (() => defaultHostInfo(flags.name)))(),
|
|
16048
16104
|
capabilities: {
|
|
16049
16105
|
instanceSocket: true,
|
|
16050
|
-
consoleAdapter: true,
|
|
16051
16106
|
runtimeDaemon: true,
|
|
16052
16107
|
cliVersion: packageVersion,
|
|
16053
16108
|
// Legacy field — older dashboards read `version`. Drop in a
|
|
@@ -16762,166 +16817,13 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
16762
16817
|
return reportDoctor2(runnerOk);
|
|
16763
16818
|
}
|
|
16764
16819
|
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;
|
|
16820
|
+
const message = "dashboard console is deprecated; paired dashboard feed sync now routes dashboard UI and channel decisions.";
|
|
16821
|
+
if (flags.json) {
|
|
16822
|
+
logOut(JSON.stringify({ ok: false, deprecated: true, message }));
|
|
16823
|
+
} else {
|
|
16824
|
+
logError(message);
|
|
16920
16825
|
}
|
|
16921
|
-
|
|
16922
|
-
`dashboard console: unknown subcommand ${sub ? sub : "(missing)"}. Expected "enable <runnerId>" or "link <runnerId>".`
|
|
16923
|
-
);
|
|
16924
|
-
return 2;
|
|
16826
|
+
return 1;
|
|
16925
16827
|
}
|
|
16926
16828
|
if (subcommand === "list") {
|
|
16927
16829
|
if (subcommandArgs.length > 0) {
|
|
@@ -17080,7 +16982,7 @@ async function runDashboardCommand(input, deps = {}) {
|
|
|
17080
16982
|
return 0;
|
|
17081
16983
|
}
|
|
17082
16984
|
logError(`Unknown dashboard subcommand: ${subcommand}`);
|
|
17083
|
-
logError(
|
|
16985
|
+
logError(USAGE4);
|
|
17084
16986
|
return 2;
|
|
17085
16987
|
}
|
|
17086
16988
|
function defaultWaitForShutdown() {
|
|
@@ -17092,36 +16994,21 @@ function defaultWaitForShutdown() {
|
|
|
17092
16994
|
};
|
|
17093
16995
|
process.once("SIGINT", onSignal);
|
|
17094
16996
|
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
|
-
};
|
|
16997
|
+
});
|
|
17111
16998
|
}
|
|
17112
16999
|
function resolveDaemonEntry() {
|
|
17113
17000
|
let here;
|
|
17114
17001
|
try {
|
|
17115
|
-
here =
|
|
17002
|
+
here = fileURLToPath(import.meta.url);
|
|
17116
17003
|
} catch {
|
|
17117
17004
|
return null;
|
|
17118
17005
|
}
|
|
17119
17006
|
const candidates = [
|
|
17120
|
-
|
|
17007
|
+
path6.join(path6.dirname(here), "dashboard-daemon.js"),
|
|
17121
17008
|
// When invoked via `npm run start`, `here` may be the unbundled source
|
|
17122
17009
|
// path. Walk up until we hit a `dist/` sibling.
|
|
17123
|
-
|
|
17124
|
-
|
|
17010
|
+
path6.join(
|
|
17011
|
+
path6.dirname(here),
|
|
17125
17012
|
"..",
|
|
17126
17013
|
"..",
|
|
17127
17014
|
"..",
|
|
@@ -17131,7 +17018,7 @@ function resolveDaemonEntry() {
|
|
|
17131
17018
|
];
|
|
17132
17019
|
for (const candidate of candidates) {
|
|
17133
17020
|
try {
|
|
17134
|
-
|
|
17021
|
+
fs6.accessSync(candidate, fs6.constants.R_OK);
|
|
17135
17022
|
return candidate;
|
|
17136
17023
|
} catch {
|
|
17137
17024
|
}
|
|
@@ -17189,7 +17076,7 @@ async function defaultStartRuntimeDaemon(opts) {
|
|
|
17189
17076
|
}
|
|
17190
17077
|
let child;
|
|
17191
17078
|
try {
|
|
17192
|
-
child =
|
|
17079
|
+
child = spawn(process.execPath, [entry], {
|
|
17193
17080
|
detached: true,
|
|
17194
17081
|
stdio: "ignore",
|
|
17195
17082
|
env: buildDaemonEnv(process.env)
|
|
@@ -17316,14 +17203,14 @@ async function defaultTailDaemonLog(opts) {
|
|
|
17316
17203
|
let stream2 = null;
|
|
17317
17204
|
let watcher = null;
|
|
17318
17205
|
try {
|
|
17319
|
-
const stat =
|
|
17206
|
+
const stat = fs6.statSync(paths.logPath);
|
|
17320
17207
|
const size = stat.size;
|
|
17321
17208
|
const buf = Buffer.alloc(Math.min(size, opts.tail * 1024));
|
|
17322
|
-
const fd =
|
|
17209
|
+
const fd = fs6.openSync(paths.logPath, "r");
|
|
17323
17210
|
try {
|
|
17324
|
-
|
|
17211
|
+
fs6.readSync(fd, buf, 0, buf.length, Math.max(0, size - buf.length));
|
|
17325
17212
|
} finally {
|
|
17326
|
-
|
|
17213
|
+
fs6.closeSync(fd);
|
|
17327
17214
|
}
|
|
17328
17215
|
const lines = buf.toString("utf-8").split("\n").filter((l) => l.length > 0);
|
|
17329
17216
|
const tail = lines.slice(-opts.tail);
|
|
@@ -17337,253 +17224,612 @@ async function defaultTailDaemonLog(opts) {
|
|
|
17337
17224
|
`dashboard logs: log file ${paths.logPath} does not exist yet
|
|
17338
17225
|
`
|
|
17339
17226
|
);
|
|
17340
|
-
return opts.follow ? 0 : 1;
|
|
17227
|
+
return opts.follow ? 0 : 1;
|
|
17228
|
+
}
|
|
17229
|
+
throw err;
|
|
17230
|
+
}
|
|
17231
|
+
let position = fs6.statSync(paths.logPath).size;
|
|
17232
|
+
return await new Promise((resolve) => {
|
|
17233
|
+
let pollTimer = null;
|
|
17234
|
+
const drain = () => {
|
|
17235
|
+
try {
|
|
17236
|
+
const stat = fs6.statSync(paths.logPath);
|
|
17237
|
+
if (stat.size < position) {
|
|
17238
|
+
position = 0;
|
|
17239
|
+
}
|
|
17240
|
+
if (stat.size > position) {
|
|
17241
|
+
const fd = fs6.openSync(paths.logPath, "r");
|
|
17242
|
+
const buf = Buffer.alloc(stat.size - position);
|
|
17243
|
+
try {
|
|
17244
|
+
fs6.readSync(fd, buf, 0, buf.length, position);
|
|
17245
|
+
} finally {
|
|
17246
|
+
fs6.closeSync(fd);
|
|
17247
|
+
}
|
|
17248
|
+
position = stat.size;
|
|
17249
|
+
process.stdout.write(buf);
|
|
17250
|
+
}
|
|
17251
|
+
} catch (err) {
|
|
17252
|
+
if (err.code !== "ENOENT") {
|
|
17253
|
+
process.stderr.write(
|
|
17254
|
+
`dashboard logs: tail error: ${err instanceof Error ? err.message : String(err)}
|
|
17255
|
+
`
|
|
17256
|
+
);
|
|
17257
|
+
}
|
|
17258
|
+
}
|
|
17259
|
+
};
|
|
17260
|
+
try {
|
|
17261
|
+
watcher = fs6.watch(paths.logPath, { persistent: true }, () => {
|
|
17262
|
+
drain();
|
|
17263
|
+
});
|
|
17264
|
+
} catch {
|
|
17265
|
+
pollTimer = setInterval(drain, 500);
|
|
17266
|
+
pollTimer.unref();
|
|
17267
|
+
}
|
|
17268
|
+
const onSignal = () => {
|
|
17269
|
+
if (watcher) {
|
|
17270
|
+
watcher.close();
|
|
17271
|
+
watcher = null;
|
|
17272
|
+
}
|
|
17273
|
+
if (pollTimer) {
|
|
17274
|
+
clearInterval(pollTimer);
|
|
17275
|
+
pollTimer = null;
|
|
17276
|
+
}
|
|
17277
|
+
if (stream2) {
|
|
17278
|
+
stream2.close();
|
|
17279
|
+
stream2 = null;
|
|
17280
|
+
}
|
|
17281
|
+
resolve(0);
|
|
17282
|
+
};
|
|
17283
|
+
process.once("SIGINT", onSignal);
|
|
17284
|
+
process.once("SIGTERM", onSignal);
|
|
17285
|
+
});
|
|
17286
|
+
}
|
|
17287
|
+
function formatDuration2(seconds) {
|
|
17288
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "?";
|
|
17289
|
+
if (seconds < 60) return `${seconds}s`;
|
|
17290
|
+
if (seconds < 3600) {
|
|
17291
|
+
const m = Math.floor(seconds / 60);
|
|
17292
|
+
const s = seconds % 60;
|
|
17293
|
+
return s > 0 ? `${m}m${s}s` : `${m}m`;
|
|
17294
|
+
}
|
|
17295
|
+
if (seconds < 86400) {
|
|
17296
|
+
const h2 = Math.floor(seconds / 3600);
|
|
17297
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
17298
|
+
return m > 0 ? `${h2}h${m}m` : `${h2}h`;
|
|
17299
|
+
}
|
|
17300
|
+
const d = Math.floor(seconds / 86400);
|
|
17301
|
+
const h = Math.floor(seconds % 86400 / 3600);
|
|
17302
|
+
return h > 0 ? `${d}d${h}h` : `${d}d`;
|
|
17303
|
+
}
|
|
17304
|
+
async function fetchRunnerHealth(fetchImpl, dashboardUrl, runnerId, token) {
|
|
17305
|
+
const url = new URL(
|
|
17306
|
+
`/api/runners/${encodeURIComponent(runnerId)}`,
|
|
17307
|
+
dashboardUrl
|
|
17308
|
+
).toString();
|
|
17309
|
+
let response;
|
|
17310
|
+
try {
|
|
17311
|
+
response = await fetchImpl(url, {
|
|
17312
|
+
method: "GET",
|
|
17313
|
+
headers: {
|
|
17314
|
+
authorization: `Bearer ${token.accessToken}`,
|
|
17315
|
+
accept: "application/json"
|
|
17316
|
+
}
|
|
17317
|
+
});
|
|
17318
|
+
} catch (err) {
|
|
17319
|
+
return {
|
|
17320
|
+
id: runnerId,
|
|
17321
|
+
matches: false,
|
|
17322
|
+
error: `request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17323
|
+
};
|
|
17324
|
+
}
|
|
17325
|
+
if (!response.ok) {
|
|
17326
|
+
return {
|
|
17327
|
+
id: runnerId,
|
|
17328
|
+
matches: false,
|
|
17329
|
+
error: `dashboard returned ${response.status}`
|
|
17330
|
+
};
|
|
17331
|
+
}
|
|
17332
|
+
let body;
|
|
17333
|
+
try {
|
|
17334
|
+
body = await response.json();
|
|
17335
|
+
} catch (err) {
|
|
17336
|
+
return {
|
|
17337
|
+
id: runnerId,
|
|
17338
|
+
matches: false,
|
|
17339
|
+
error: `invalid response body: ${err instanceof Error ? err.message : String(err)}`
|
|
17340
|
+
};
|
|
17341
|
+
}
|
|
17342
|
+
const obj = typeof body === "object" && body !== null ? body : {};
|
|
17343
|
+
const executionTarget = typeof obj["executionTarget"] === "string" ? obj["executionTarget"] : void 0;
|
|
17344
|
+
const remoteInstanceId = typeof obj["remoteInstanceId"] === "string" ? obj["remoteInstanceId"] : void 0;
|
|
17345
|
+
const matches = executionTarget === "remote" && remoteInstanceId === token.instanceId;
|
|
17346
|
+
const reasons = [];
|
|
17347
|
+
if (executionTarget !== "remote") {
|
|
17348
|
+
reasons.push(
|
|
17349
|
+
`executionTarget=${executionTarget ?? "unset"} (expected "remote")`
|
|
17350
|
+
);
|
|
17351
|
+
}
|
|
17352
|
+
if (remoteInstanceId !== token.instanceId) {
|
|
17353
|
+
reasons.push(
|
|
17354
|
+
`remoteInstanceId=${remoteInstanceId ?? "unset"} (expected "${token.instanceId}")`
|
|
17355
|
+
);
|
|
17356
|
+
}
|
|
17357
|
+
return {
|
|
17358
|
+
id: runnerId,
|
|
17359
|
+
matches,
|
|
17360
|
+
...executionTarget !== void 0 ? { executionTarget } : {},
|
|
17361
|
+
...remoteInstanceId !== void 0 ? { remoteInstanceId } : {},
|
|
17362
|
+
...reasons.length > 0 ? { error: reasons.join("; ") } : {}
|
|
17363
|
+
};
|
|
17364
|
+
}
|
|
17365
|
+
function defaultInstallServiceUnit() {
|
|
17366
|
+
const entry = resolveDaemonEntry();
|
|
17367
|
+
if (!entry) {
|
|
17368
|
+
return {
|
|
17369
|
+
ok: false,
|
|
17370
|
+
platform: "unsupported",
|
|
17371
|
+
message: "cannot resolve dashboard-daemon.js entry path"
|
|
17372
|
+
};
|
|
17373
|
+
}
|
|
17374
|
+
return installServiceUnit({
|
|
17375
|
+
daemonEntry: entry,
|
|
17376
|
+
nodeBinary: process.execPath
|
|
17377
|
+
});
|
|
17378
|
+
}
|
|
17379
|
+
function compareSemver(a, b) {
|
|
17380
|
+
const parseN = (s) => {
|
|
17381
|
+
const parts = s.replace(/^v/, "").split("-")[0].split(".");
|
|
17382
|
+
return [
|
|
17383
|
+
Number.parseInt(parts[0] ?? "0", 10) || 0,
|
|
17384
|
+
Number.parseInt(parts[1] ?? "0", 10) || 0,
|
|
17385
|
+
Number.parseInt(parts[2] ?? "0", 10) || 0
|
|
17386
|
+
];
|
|
17387
|
+
};
|
|
17388
|
+
const av = parseN(a);
|
|
17389
|
+
const bv = parseN(b);
|
|
17390
|
+
for (let i = 0; i < 3; i += 1) {
|
|
17391
|
+
if (av[i] < bv[i]) return -1;
|
|
17392
|
+
if (av[i] > bv[i]) return 1;
|
|
17393
|
+
}
|
|
17394
|
+
return 0;
|
|
17395
|
+
}
|
|
17396
|
+
async function safeReadError(response) {
|
|
17397
|
+
try {
|
|
17398
|
+
const text = await response.text();
|
|
17399
|
+
if (text.length === 0) return "";
|
|
17400
|
+
try {
|
|
17401
|
+
const parsed = JSON.parse(text);
|
|
17402
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed["error"] === "string") {
|
|
17403
|
+
return parsed["error"];
|
|
17404
|
+
}
|
|
17405
|
+
} catch {
|
|
17406
|
+
}
|
|
17407
|
+
return text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
|
|
17408
|
+
} catch {
|
|
17409
|
+
return "";
|
|
17410
|
+
}
|
|
17411
|
+
}
|
|
17412
|
+
function parsePairResponse(raw) {
|
|
17413
|
+
if (typeof raw !== "object" || raw === null) {
|
|
17414
|
+
throw new Error("expected object");
|
|
17415
|
+
}
|
|
17416
|
+
const obj = raw;
|
|
17417
|
+
const instanceId = obj["instanceId"];
|
|
17418
|
+
const refreshToken = obj["refreshToken"];
|
|
17419
|
+
if (typeof instanceId !== "string" || instanceId.length === 0) {
|
|
17420
|
+
throw new Error("missing instanceId");
|
|
17421
|
+
}
|
|
17422
|
+
if (typeof refreshToken !== "string" || refreshToken.length === 0) {
|
|
17423
|
+
throw new Error("missing refreshToken");
|
|
17424
|
+
}
|
|
17425
|
+
return {
|
|
17426
|
+
instanceId,
|
|
17427
|
+
refreshToken,
|
|
17428
|
+
...typeof obj["jti"] === "string" ? { jti: obj["jti"] } : {},
|
|
17429
|
+
...typeof obj["accessToken"] === "string" ? { accessToken: obj["accessToken"] } : {},
|
|
17430
|
+
...typeof obj["expiresInSec"] === "number" ? { expiresInSec: obj["expiresInSec"] } : {},
|
|
17431
|
+
...Array.isArray(obj["runners"]) ? {
|
|
17432
|
+
runners: obj["runners"].map(parsePairedRunner).filter((runner) => runner !== null)
|
|
17433
|
+
} : {},
|
|
17434
|
+
...typeof obj["requiredCliVersion"] === "string" ? { requiredCliVersion: obj["requiredCliVersion"] } : {},
|
|
17435
|
+
...typeof obj["capabilityAck"] === "object" && obj["capabilityAck"] !== null ? { capabilityAck: parseCapabilityAck(obj["capabilityAck"]) } : {}
|
|
17436
|
+
};
|
|
17437
|
+
}
|
|
17438
|
+
function parseCapabilityAck(raw) {
|
|
17439
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
17440
|
+
const obj = raw;
|
|
17441
|
+
const ack = {};
|
|
17442
|
+
if (typeof obj["runtimeDaemon"] === "boolean") {
|
|
17443
|
+
ack.runtimeDaemon = obj["runtimeDaemon"];
|
|
17444
|
+
}
|
|
17445
|
+
if (typeof obj["instanceSocket"] === "boolean") {
|
|
17446
|
+
ack.instanceSocket = obj["instanceSocket"];
|
|
17447
|
+
}
|
|
17448
|
+
return ack;
|
|
17449
|
+
}
|
|
17450
|
+
function parsePairedRunner(raw) {
|
|
17451
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
17452
|
+
const obj = raw;
|
|
17453
|
+
const runnerId = obj["runnerId"];
|
|
17454
|
+
if (typeof runnerId !== "string" || runnerId.length === 0) return null;
|
|
17455
|
+
return {
|
|
17456
|
+
runnerId,
|
|
17457
|
+
...typeof obj["name"] === "string" ? { name: obj["name"] } : {},
|
|
17458
|
+
...typeof obj["executionTarget"] === "string" ? { executionTarget: obj["executionTarget"] } : {},
|
|
17459
|
+
...typeof obj["remoteInstanceId"] === "string" ? { remoteInstanceId: obj["remoteInstanceId"] } : {}
|
|
17460
|
+
};
|
|
17461
|
+
}
|
|
17462
|
+
|
|
17463
|
+
// src/app/entry/gatewayCommand.ts
|
|
17464
|
+
import { spawn as spawn2 } from "child_process";
|
|
17465
|
+
import fs7 from "fs";
|
|
17466
|
+
import path7 from "path";
|
|
17467
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17468
|
+
var USAGE5 = `Usage: athena-flow gateway <subcommand> [--json]
|
|
17469
|
+
|
|
17470
|
+
Subcommands:
|
|
17471
|
+
start Run the gateway daemon in foreground (only mode in this build).
|
|
17472
|
+
Options: [--bind <host:port>] [--insecure]
|
|
17473
|
+
[--tls-cert <path>] [--tls-key <path>]
|
|
17474
|
+
[--grace-period-ms <n>]
|
|
17475
|
+
status Print daemon pid, uptime, and version.
|
|
17476
|
+
probe Send a ping RPC and report reachability + latency.
|
|
17477
|
+
link Store a remote WS/WSS gateway endpoint for this user.
|
|
17478
|
+
unlink Restore local UDS gateway mode for this user.
|
|
17479
|
+
rotate-token Regenerate the gateway token file (server-side).
|
|
17480
|
+
Restart the daemon to drop existing connections; clients
|
|
17481
|
+
must re-run "athena gateway link --token <new>".
|
|
17482
|
+
reload-channels Reload channel sidecars without restarting the daemon.
|
|
17483
|
+
`;
|
|
17484
|
+
function defaultResolveDaemonEntry() {
|
|
17485
|
+
const here = path7.dirname(fileURLToPath2(import.meta.url));
|
|
17486
|
+
return path7.resolve(here, "athena-gateway.js");
|
|
17487
|
+
}
|
|
17488
|
+
function readToken(tokenPath) {
|
|
17489
|
+
try {
|
|
17490
|
+
return fs7.readFileSync(tokenPath, "utf-8").trim();
|
|
17491
|
+
} catch (err) {
|
|
17492
|
+
const code = err.code;
|
|
17493
|
+
if (code === "ENOENT") {
|
|
17494
|
+
throw new Error(
|
|
17495
|
+
`gateway token missing at ${tokenPath}. Start the daemon with "athena gateway start" first.`
|
|
17496
|
+
);
|
|
17341
17497
|
}
|
|
17342
17498
|
throw err;
|
|
17343
17499
|
}
|
|
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
17500
|
}
|
|
17400
|
-
function
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
|
|
17405
|
-
|
|
17406
|
-
|
|
17501
|
+
function flagJson(args) {
|
|
17502
|
+
return args.includes("--json");
|
|
17503
|
+
}
|
|
17504
|
+
async function runGatewayCommand(input, deps = {}) {
|
|
17505
|
+
const logOut = deps.logOut ?? ((m) => process.stdout.write(m + "\n"));
|
|
17506
|
+
const logError = deps.logError ?? ((m) => process.stderr.write(m + "\n"));
|
|
17507
|
+
const resolveDaemonEntry2 = deps.resolveDaemonEntry ?? defaultResolveDaemonEntry;
|
|
17508
|
+
const resolveSocketPath = deps.resolveSocketPath ?? (() => resolveGatewayPaths().socketPath);
|
|
17509
|
+
const resolveTokenPath = deps.resolveTokenPath ?? (() => resolveGatewayPaths().tokenPath);
|
|
17510
|
+
const readClientConfig = deps.readClientConfig ?? readGatewayClientConfig;
|
|
17511
|
+
const writeClientConfig = deps.writeClientConfig ?? ((config) => writeGatewayClientConfig(config));
|
|
17512
|
+
const connectGateway = deps.connectGateway ?? defaultConnectGateway;
|
|
17513
|
+
const spawnDaemon = deps.spawnDaemon ?? ((entry, args) => spawn2(process.execPath, [entry, ...args], { stdio: "inherit" }));
|
|
17514
|
+
const { subcommand, subcommandArgs } = input;
|
|
17515
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
17516
|
+
logOut(USAGE5);
|
|
17517
|
+
return 0;
|
|
17407
17518
|
}
|
|
17408
|
-
if (
|
|
17409
|
-
const
|
|
17410
|
-
const
|
|
17411
|
-
return
|
|
17519
|
+
if (subcommand === "start") {
|
|
17520
|
+
const entry = resolveDaemonEntry2();
|
|
17521
|
+
const child = spawnDaemon(entry, subcommandArgs);
|
|
17522
|
+
return await new Promise((resolve) => {
|
|
17523
|
+
child.once("exit", (code) => resolve(code ?? 0));
|
|
17524
|
+
child.once("error", (err) => {
|
|
17525
|
+
logError(`gateway start: failed to spawn daemon: ${err.message}`);
|
|
17526
|
+
resolve(1);
|
|
17527
|
+
});
|
|
17528
|
+
});
|
|
17412
17529
|
}
|
|
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
|
-
}
|
|
17530
|
+
if (subcommand === "link") {
|
|
17531
|
+
const parsed = parseLinkArgs(subcommandArgs);
|
|
17532
|
+
if (!parsed.ok) {
|
|
17533
|
+
logError(parsed.message);
|
|
17534
|
+
return 2;
|
|
17535
|
+
}
|
|
17536
|
+
writeClientConfig({
|
|
17537
|
+
mode: "remote",
|
|
17538
|
+
url: parsed.url,
|
|
17539
|
+
token: parsed.token,
|
|
17540
|
+
...parsed.tlsCaPath !== void 0 ? { tlsCaPath: parsed.tlsCaPath } : {}
|
|
17430
17541
|
});
|
|
17431
|
-
|
|
17432
|
-
return
|
|
17433
|
-
id: runnerId,
|
|
17434
|
-
matches: false,
|
|
17435
|
-
error: `request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17436
|
-
};
|
|
17542
|
+
logOut(`gateway: linked remote endpoint ${parsed.url}`);
|
|
17543
|
+
return 0;
|
|
17437
17544
|
}
|
|
17438
|
-
if (
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17545
|
+
if (subcommand === "rotate-token") {
|
|
17546
|
+
const json = flagJson(subcommandArgs);
|
|
17547
|
+
const extras = subcommandArgs.filter((a) => a !== "--json");
|
|
17548
|
+
if (extras.length > 0) {
|
|
17549
|
+
logError(`gateway rotate-token: unexpected argument ${extras[0]}`);
|
|
17550
|
+
return 2;
|
|
17551
|
+
}
|
|
17552
|
+
const tokenPath = resolveTokenPath();
|
|
17553
|
+
try {
|
|
17554
|
+
const newToken = rotateGatewayToken(tokenPath);
|
|
17555
|
+
if (json) {
|
|
17556
|
+
logOut(JSON.stringify({ ok: true, token: newToken, tokenPath }));
|
|
17557
|
+
} else {
|
|
17558
|
+
logOut(newToken);
|
|
17559
|
+
logOut(
|
|
17560
|
+
`gateway: rotated token at ${tokenPath}. Restart the daemon to drop existing connections, then re-run "athena gateway link --token <new>" on each client.`
|
|
17561
|
+
);
|
|
17562
|
+
}
|
|
17563
|
+
return 0;
|
|
17564
|
+
} catch (err) {
|
|
17565
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17566
|
+
if (json) {
|
|
17567
|
+
logOut(JSON.stringify({ ok: false, message }));
|
|
17568
|
+
} else {
|
|
17569
|
+
logError(`gateway rotate-token: ${message}`);
|
|
17570
|
+
}
|
|
17571
|
+
return 1;
|
|
17572
|
+
}
|
|
17444
17573
|
}
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
};
|
|
17574
|
+
if (subcommand === "unlink") {
|
|
17575
|
+
if (subcommandArgs.length > 0) {
|
|
17576
|
+
logError("gateway unlink does not accept arguments");
|
|
17577
|
+
return 2;
|
|
17578
|
+
}
|
|
17579
|
+
writeClientConfig({ mode: "local" });
|
|
17580
|
+
logOut("gateway: using local gateway endpoint");
|
|
17581
|
+
return 0;
|
|
17454
17582
|
}
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17583
|
+
if (subcommand === "probe") {
|
|
17584
|
+
const json = flagJson(subcommandArgs);
|
|
17585
|
+
const socketPath = resolveSocketPath();
|
|
17586
|
+
const tokenPath = resolveTokenPath();
|
|
17587
|
+
const endpoint = readClientConfig();
|
|
17588
|
+
const startedAt = Date.now();
|
|
17589
|
+
try {
|
|
17590
|
+
const client = await connectGateway({
|
|
17591
|
+
endpoint,
|
|
17592
|
+
socketPath,
|
|
17593
|
+
tokenPath,
|
|
17594
|
+
timeoutMs: 3e3
|
|
17595
|
+
});
|
|
17596
|
+
const res = await client.request("ping", {});
|
|
17597
|
+
client.close();
|
|
17598
|
+
const latencyMs = Date.now() - startedAt;
|
|
17599
|
+
if (json) {
|
|
17600
|
+
logOut(
|
|
17601
|
+
JSON.stringify({
|
|
17602
|
+
ok: true,
|
|
17603
|
+
reachable: true,
|
|
17604
|
+
latency_ms: latencyMs,
|
|
17605
|
+
daemon_pid: res.daemonPid,
|
|
17606
|
+
daemon_uptime_ms: res.uptimeMs
|
|
17607
|
+
})
|
|
17608
|
+
);
|
|
17609
|
+
} else {
|
|
17610
|
+
logOut(
|
|
17611
|
+
`gateway: reachable pid=${res.daemonPid} uptime=${res.uptimeMs}ms latency=${latencyMs}ms`
|
|
17612
|
+
);
|
|
17613
|
+
}
|
|
17614
|
+
return 0;
|
|
17615
|
+
} catch (err) {
|
|
17616
|
+
return reportProbeFailure(err, json, logOut, logError);
|
|
17617
|
+
}
|
|
17464
17618
|
}
|
|
17465
|
-
if (
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
)
|
|
17619
|
+
if (subcommand === "reload-channels") {
|
|
17620
|
+
const json = flagJson(subcommandArgs);
|
|
17621
|
+
const extras = subcommandArgs.filter((a) => a !== "--json");
|
|
17622
|
+
if (extras.length > 0) {
|
|
17623
|
+
logError(`gateway reload-channels: unexpected argument ${extras[0]}`);
|
|
17624
|
+
return 2;
|
|
17625
|
+
}
|
|
17626
|
+
const socketPath = resolveSocketPath();
|
|
17627
|
+
const tokenPath = resolveTokenPath();
|
|
17628
|
+
const endpoint = readClientConfig();
|
|
17629
|
+
try {
|
|
17630
|
+
const client = await connectGateway({
|
|
17631
|
+
endpoint,
|
|
17632
|
+
socketPath,
|
|
17633
|
+
tokenPath,
|
|
17634
|
+
timeoutMs: 3e3
|
|
17635
|
+
});
|
|
17636
|
+
const res = await client.request("channels.reload", {});
|
|
17637
|
+
client.close();
|
|
17638
|
+
if (json) {
|
|
17639
|
+
logOut(JSON.stringify({ ok: true, ...res }));
|
|
17640
|
+
} else if (res.results.length === 0) {
|
|
17641
|
+
logOut("gateway: channels reloaded (no channel sidecars found)");
|
|
17642
|
+
} else {
|
|
17643
|
+
logOut("gateway: channels reloaded");
|
|
17644
|
+
for (const result of res.results) {
|
|
17645
|
+
logOut(formatChannelReloadResult(result));
|
|
17646
|
+
}
|
|
17647
|
+
}
|
|
17648
|
+
return 0;
|
|
17649
|
+
} catch (err) {
|
|
17650
|
+
return reportProbeFailure(err, json, logOut, logError);
|
|
17651
|
+
}
|
|
17469
17652
|
}
|
|
17470
|
-
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
|
|
17482
|
-
|
|
17483
|
-
|
|
17484
|
-
|
|
17485
|
-
|
|
17653
|
+
if (subcommand === "status") {
|
|
17654
|
+
const json = flagJson(subcommandArgs);
|
|
17655
|
+
const socketPath = resolveSocketPath();
|
|
17656
|
+
const tokenPath = resolveTokenPath();
|
|
17657
|
+
const endpoint = readClientConfig();
|
|
17658
|
+
try {
|
|
17659
|
+
const client = await connectGateway({
|
|
17660
|
+
endpoint,
|
|
17661
|
+
socketPath,
|
|
17662
|
+
tokenPath,
|
|
17663
|
+
timeoutMs: 3e3
|
|
17664
|
+
});
|
|
17665
|
+
const res = await client.request("status", {});
|
|
17666
|
+
client.close();
|
|
17667
|
+
if (json) {
|
|
17668
|
+
logOut(JSON.stringify(res));
|
|
17669
|
+
} else {
|
|
17670
|
+
const listenerSummary = formatListenerSummary(res.listener);
|
|
17671
|
+
const channelSummary = formatChannelSummary(res.channels);
|
|
17672
|
+
const runtimeSummary = formatRuntimeSummary(res.runtimes[0]);
|
|
17673
|
+
logOut(
|
|
17674
|
+
`gateway: running pid=${res.daemonPid} uptime=${res.uptimeMs}ms version=${res.version} ${listenerSummary}${channelSummary}${runtimeSummary}`
|
|
17675
|
+
);
|
|
17676
|
+
}
|
|
17677
|
+
return 0;
|
|
17678
|
+
} catch (err) {
|
|
17679
|
+
return reportProbeFailure(err, json, logOut, logError);
|
|
17680
|
+
}
|
|
17486
17681
|
}
|
|
17487
|
-
|
|
17488
|
-
|
|
17489
|
-
|
|
17490
|
-
});
|
|
17682
|
+
logError(`Unknown gateway subcommand: ${subcommand}`);
|
|
17683
|
+
logError(USAGE5);
|
|
17684
|
+
return 2;
|
|
17491
17685
|
}
|
|
17492
|
-
function
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
|
|
17504
|
-
|
|
17505
|
-
|
|
17686
|
+
async function defaultConnectGateway(opts) {
|
|
17687
|
+
if (opts.endpoint.mode === "remote") {
|
|
17688
|
+
return connect({
|
|
17689
|
+
socketPath: opts.socketPath,
|
|
17690
|
+
token: opts.endpoint.token,
|
|
17691
|
+
timeoutMs: opts.timeoutMs,
|
|
17692
|
+
transport: createWsClientTransport(
|
|
17693
|
+
wsClientOptionsForEndpoint({
|
|
17694
|
+
url: opts.endpoint.url,
|
|
17695
|
+
timeoutMs: opts.timeoutMs,
|
|
17696
|
+
tlsCaPath: opts.endpoint.tlsCaPath
|
|
17697
|
+
})
|
|
17698
|
+
)
|
|
17699
|
+
});
|
|
17506
17700
|
}
|
|
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();
|
|
17701
|
+
return connect({
|
|
17702
|
+
socketPath: opts.socketPath,
|
|
17703
|
+
token: readToken(opts.tokenPath),
|
|
17704
|
+
timeoutMs: opts.timeoutMs
|
|
17705
|
+
});
|
|
17518
17706
|
}
|
|
17519
|
-
|
|
17520
|
-
|
|
17521
|
-
|
|
17522
|
-
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
17526
|
-
|
|
17707
|
+
function parseLinkArgs(args) {
|
|
17708
|
+
const positional = [];
|
|
17709
|
+
let token;
|
|
17710
|
+
let tlsCaPath;
|
|
17711
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
17712
|
+
const arg = args[i];
|
|
17713
|
+
if (arg === "--token") {
|
|
17714
|
+
const v = args[i + 1];
|
|
17715
|
+
if (!v || v.startsWith("--")) {
|
|
17716
|
+
return { ok: false, message: "gateway link --token requires a value" };
|
|
17527
17717
|
}
|
|
17528
|
-
|
|
17718
|
+
token = v;
|
|
17719
|
+
i += 1;
|
|
17720
|
+
continue;
|
|
17529
17721
|
}
|
|
17530
|
-
|
|
17531
|
-
|
|
17532
|
-
|
|
17722
|
+
if (arg === "--tls-ca") {
|
|
17723
|
+
const v = args[i + 1];
|
|
17724
|
+
if (!v || v.startsWith("--")) {
|
|
17725
|
+
return { ok: false, message: "gateway link --tls-ca requires a path" };
|
|
17726
|
+
}
|
|
17727
|
+
tlsCaPath = v;
|
|
17728
|
+
i += 1;
|
|
17729
|
+
continue;
|
|
17730
|
+
}
|
|
17731
|
+
if (arg.startsWith("--token=")) {
|
|
17732
|
+
token = arg.slice("--token=".length);
|
|
17733
|
+
continue;
|
|
17734
|
+
}
|
|
17735
|
+
if (arg.startsWith("--tls-ca=")) {
|
|
17736
|
+
tlsCaPath = arg.slice("--tls-ca=".length);
|
|
17737
|
+
continue;
|
|
17738
|
+
}
|
|
17739
|
+
if (arg.startsWith("--")) {
|
|
17740
|
+
return { ok: false, message: `gateway link: unknown option ${arg}` };
|
|
17741
|
+
}
|
|
17742
|
+
positional.push(arg);
|
|
17533
17743
|
}
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
throw new Error("expected object");
|
|
17744
|
+
const url = positional[0];
|
|
17745
|
+
if (!url) {
|
|
17746
|
+
return { ok: false, message: "gateway link requires a ws:// or wss:// URL" };
|
|
17538
17747
|
}
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
const refreshToken = obj["refreshToken"];
|
|
17542
|
-
if (typeof instanceId !== "string" || instanceId.length === 0) {
|
|
17543
|
-
throw new Error("missing instanceId");
|
|
17748
|
+
if (positional.length > 1) {
|
|
17749
|
+
return { ok: false, message: "gateway link accepts exactly one URL" };
|
|
17544
17750
|
}
|
|
17545
|
-
if (
|
|
17546
|
-
|
|
17751
|
+
if (!isSupportedGatewayUrl(url)) {
|
|
17752
|
+
return { ok: false, message: "gateway link URL must use ws:// or wss://" };
|
|
17753
|
+
}
|
|
17754
|
+
if (!token) {
|
|
17755
|
+
return { ok: false, message: "gateway link requires --token <token>" };
|
|
17756
|
+
}
|
|
17757
|
+
if (tlsCaPath !== void 0 && tlsCaPath.length === 0) {
|
|
17758
|
+
return { ok: false, message: "gateway link --tls-ca requires a path" };
|
|
17547
17759
|
}
|
|
17548
17760
|
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"]) } : {}
|
|
17761
|
+
ok: true,
|
|
17762
|
+
url,
|
|
17763
|
+
token,
|
|
17764
|
+
...tlsCaPath !== void 0 ? { tlsCaPath } : {}
|
|
17559
17765
|
};
|
|
17560
17766
|
}
|
|
17561
|
-
function
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
|
|
17767
|
+
function reportProbeFailure(err, json, logOut, logError) {
|
|
17768
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17769
|
+
if (err instanceof GatewayUnreachableError) {
|
|
17770
|
+
if (json) {
|
|
17771
|
+
logOut(
|
|
17772
|
+
JSON.stringify({
|
|
17773
|
+
ok: false,
|
|
17774
|
+
reachable: false,
|
|
17775
|
+
reason: "unreachable",
|
|
17776
|
+
message
|
|
17777
|
+
})
|
|
17778
|
+
);
|
|
17779
|
+
} else {
|
|
17780
|
+
logError(`gateway: not reachable \u2014 ${message}`);
|
|
17781
|
+
}
|
|
17782
|
+
return 1;
|
|
17567
17783
|
}
|
|
17568
|
-
if (
|
|
17569
|
-
|
|
17784
|
+
if (err instanceof GatewayUnauthorizedError) {
|
|
17785
|
+
if (json) {
|
|
17786
|
+
logOut(
|
|
17787
|
+
JSON.stringify({
|
|
17788
|
+
ok: false,
|
|
17789
|
+
reachable: true,
|
|
17790
|
+
reason: "unauthorized",
|
|
17791
|
+
message
|
|
17792
|
+
})
|
|
17793
|
+
);
|
|
17794
|
+
} else {
|
|
17795
|
+
logError(`gateway: unauthorized \u2014 ${message}`);
|
|
17796
|
+
}
|
|
17797
|
+
return 1;
|
|
17570
17798
|
}
|
|
17571
|
-
if (
|
|
17572
|
-
|
|
17799
|
+
if (json) {
|
|
17800
|
+
logOut(JSON.stringify({ ok: false, reason: "error", message }));
|
|
17801
|
+
} else {
|
|
17802
|
+
logError(`gateway: ${message}`);
|
|
17573
17803
|
}
|
|
17574
|
-
return
|
|
17804
|
+
return 1;
|
|
17575
17805
|
}
|
|
17576
|
-
function
|
|
17577
|
-
if (
|
|
17578
|
-
const
|
|
17579
|
-
const
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
17806
|
+
function formatRuntimeSummary(r) {
|
|
17807
|
+
if (!r) return " runtime=<none>";
|
|
17808
|
+
const lastRebindAt = r.binding.state !== "none" ? r.binding.lastRebindAt : void 0;
|
|
17809
|
+
const rebind = lastRebindAt !== void 0 ? ` rebound=${formatElapsed(Date.now() - lastRebindAt)}` : "";
|
|
17810
|
+
return ` runtime=${r.runtimeId} binding=${r.binding.state} pid=${r.pid}${rebind}`;
|
|
17811
|
+
}
|
|
17812
|
+
function formatChannelReloadResult(result) {
|
|
17813
|
+
const suffix = result.reason ? `: ${result.reason}` : "";
|
|
17814
|
+
return ` ${result.id} ${result.action}${suffix}`;
|
|
17815
|
+
}
|
|
17816
|
+
function formatChannelSummary(channels) {
|
|
17817
|
+
if (channels.length === 0) return " channels=<none>";
|
|
17818
|
+
const parts = channels.map((channel) => {
|
|
17819
|
+
const note = channel.note ? `(${channel.note})` : "";
|
|
17820
|
+
return `${channel.id}:${channel.state}${note}`;
|
|
17821
|
+
});
|
|
17822
|
+
return ` channels=${parts.join(",")}`;
|
|
17823
|
+
}
|
|
17824
|
+
function formatListenerSummary(listener) {
|
|
17825
|
+
if (listener.kind === "uds") {
|
|
17826
|
+
return `listener=uds:${listener.socketPath}`;
|
|
17827
|
+
}
|
|
17828
|
+
const flags = [];
|
|
17829
|
+
if (listener.tls) flags.push("tls");
|
|
17830
|
+
if (listener.insecure) flags.push("insecure");
|
|
17831
|
+
const suffix = flags.length > 0 ? ` (${flags.join(",")})` : "";
|
|
17832
|
+
return `listener=${listener.url}${suffix}`;
|
|
17587
17833
|
}
|
|
17588
17834
|
|
|
17589
17835
|
// src/app/entry/doctorCommand.ts
|
|
@@ -18283,7 +18529,7 @@ var cli = meow(
|
|
|
18283
18529
|
workflow <sub> Manage workflows (install, list, search, remove, upgrade, use)
|
|
18284
18530
|
marketplace <sub> Manage marketplace sources (add, remove, list)
|
|
18285
18531
|
channel <sub> Manage external channels
|
|
18286
|
-
dashboard <sub> Manage dashboard
|
|
18532
|
+
dashboard <sub> Manage dashboard pairing and runtime daemon (pair, status, daemon, unpair)
|
|
18287
18533
|
telemetry [action] Manage anonymous telemetry (enable/disable/status)
|
|
18288
18534
|
doctor Diagnose Claude headless setup (use with --harness=claude)
|
|
18289
18535
|
|
|
@@ -18308,15 +18554,8 @@ var cli = meow(
|
|
|
18308
18554
|
--bot-token Telegram bot token (channel telegram configure)
|
|
18309
18555
|
--user-id Telegram allowed user id (channel telegram configure)
|
|
18310
18556
|
--chat-id Telegram destination chat id (defaults to --user-id)
|
|
18311
|
-
--token Gateway link token (gateway link)
|
|
18312
18557
|
--url Dashboard origin (dashboard pair)
|
|
18313
18558
|
--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
18559
|
--dry-run Print resolved bootstrap (workflow, isolation, plugins, harness) and exit (exec mode)
|
|
18321
18560
|
--project Scope workflow command to project config (workflow use)
|
|
18322
18561
|
--global Scope workflow command to global config (workflow use, default)
|
|
@@ -18476,6 +18715,9 @@ var cli = meow(
|
|
|
18476
18715
|
},
|
|
18477
18716
|
apiKey: {
|
|
18478
18717
|
type: "string"
|
|
18718
|
+
},
|
|
18719
|
+
attachmentId: {
|
|
18720
|
+
type: "string"
|
|
18479
18721
|
}
|
|
18480
18722
|
}
|
|
18481
18723
|
}
|
|
@@ -18525,7 +18767,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
18525
18767
|
await exitWith(1);
|
|
18526
18768
|
return;
|
|
18527
18769
|
}
|
|
18528
|
-
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-
|
|
18770
|
+
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-X754ND4V.js");
|
|
18529
18771
|
const { waitUntilExit } = render(
|
|
18530
18772
|
/* @__PURE__ */ jsx25(
|
|
18531
18773
|
WorkflowInstallWizard,
|
|
@@ -18788,6 +19030,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
18788
19030
|
isolationPreset,
|
|
18789
19031
|
ascii: cli.flags.ascii,
|
|
18790
19032
|
showSetup,
|
|
19033
|
+
...cli.flags.attachmentId !== void 0 ? { attachmentId: cli.flags.attachmentId } : {},
|
|
18791
19034
|
initialTelemetryDiagnosticsConsent: globalConfig.telemetryDiagnostics
|
|
18792
19035
|
}
|
|
18793
19036
|
),
|