@botcord/daemon 0.1.1
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/activity-tracker.d.ts +43 -0
- package/dist/activity-tracker.js +110 -0
- package/dist/adapters/runtimes.d.ts +14 -0
- package/dist/adapters/runtimes.js +18 -0
- package/dist/agent-discovery.d.ts +81 -0
- package/dist/agent-discovery.js +181 -0
- package/dist/agent-workspace.d.ts +31 -0
- package/dist/agent-workspace.js +221 -0
- package/dist/config.d.ts +116 -0
- package/dist/config.js +180 -0
- package/dist/control-channel.d.ts +99 -0
- package/dist/control-channel.js +388 -0
- package/dist/cross-room.d.ts +23 -0
- package/dist/cross-room.js +55 -0
- package/dist/daemon-config-map.d.ts +61 -0
- package/dist/daemon-config-map.js +153 -0
- package/dist/daemon.d.ts +123 -0
- package/dist/daemon.js +349 -0
- package/dist/doctor.d.ts +89 -0
- package/dist/doctor.js +191 -0
- package/dist/gateway/channel-manager.d.ts +54 -0
- package/dist/gateway/channel-manager.js +292 -0
- package/dist/gateway/channels/botcord.d.ts +93 -0
- package/dist/gateway/channels/botcord.js +510 -0
- package/dist/gateway/channels/index.d.ts +2 -0
- package/dist/gateway/channels/index.js +1 -0
- package/dist/gateway/channels/sanitize.d.ts +20 -0
- package/dist/gateway/channels/sanitize.js +56 -0
- package/dist/gateway/dispatcher.d.ts +73 -0
- package/dist/gateway/dispatcher.js +431 -0
- package/dist/gateway/gateway.d.ts +87 -0
- package/dist/gateway/gateway.js +158 -0
- package/dist/gateway/index.d.ts +15 -0
- package/dist/gateway/index.js +15 -0
- package/dist/gateway/log.d.ts +9 -0
- package/dist/gateway/log.js +20 -0
- package/dist/gateway/router.d.ts +10 -0
- package/dist/gateway/router.js +48 -0
- package/dist/gateway/runtimes/claude-code.d.ts +30 -0
- package/dist/gateway/runtimes/claude-code.js +162 -0
- package/dist/gateway/runtimes/codex.d.ts +83 -0
- package/dist/gateway/runtimes/codex.js +272 -0
- package/dist/gateway/runtimes/gemini.d.ts +15 -0
- package/dist/gateway/runtimes/gemini.js +29 -0
- package/dist/gateway/runtimes/ndjson-stream.d.ts +43 -0
- package/dist/gateway/runtimes/ndjson-stream.js +169 -0
- package/dist/gateway/runtimes/probe.d.ts +17 -0
- package/dist/gateway/runtimes/probe.js +54 -0
- package/dist/gateway/runtimes/registry.d.ts +59 -0
- package/dist/gateway/runtimes/registry.js +94 -0
- package/dist/gateway/session-store.d.ts +39 -0
- package/dist/gateway/session-store.js +133 -0
- package/dist/gateway/types.d.ts +265 -0
- package/dist/gateway/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +854 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +44 -0
- package/dist/provision.d.ts +88 -0
- package/dist/provision.js +749 -0
- package/dist/room-context-fetcher.d.ts +18 -0
- package/dist/room-context-fetcher.js +101 -0
- package/dist/room-context.d.ts +53 -0
- package/dist/room-context.js +112 -0
- package/dist/sender-classify.d.ts +30 -0
- package/dist/sender-classify.js +32 -0
- package/dist/snapshot-writer.d.ts +37 -0
- package/dist/snapshot-writer.js +84 -0
- package/dist/status-render.d.ts +28 -0
- package/dist/status-render.js +97 -0
- package/dist/system-context.d.ts +57 -0
- package/dist/system-context.js +91 -0
- package/dist/turn-text.d.ts +36 -0
- package/dist/turn-text.js +57 -0
- package/dist/user-auth.d.ts +75 -0
- package/dist/user-auth.js +245 -0
- package/dist/working-memory.d.ts +46 -0
- package/dist/working-memory.js +274 -0
- package/package.json +39 -0
- package/src/__tests__/activity-tracker.test.ts +130 -0
- package/src/__tests__/agent-discovery.test.ts +191 -0
- package/src/__tests__/agent-workspace.test.ts +147 -0
- package/src/__tests__/control-channel.test.ts +327 -0
- package/src/__tests__/cross-room.test.ts +116 -0
- package/src/__tests__/daemon-config-map.test.ts +416 -0
- package/src/__tests__/daemon.test.ts +300 -0
- package/src/__tests__/device-code.test.ts +152 -0
- package/src/__tests__/doctor.test.ts +218 -0
- package/src/__tests__/protocol-core-reexport.test.ts +24 -0
- package/src/__tests__/provision.test.ts +922 -0
- package/src/__tests__/room-context.test.ts +233 -0
- package/src/__tests__/runtime-discovery.test.ts +173 -0
- package/src/__tests__/snapshot-writer.test.ts +141 -0
- package/src/__tests__/status-render.test.ts +137 -0
- package/src/__tests__/system-context.test.ts +315 -0
- package/src/__tests__/turn-text.test.ts +116 -0
- package/src/__tests__/user-auth.test.ts +125 -0
- package/src/__tests__/working-memory.test.ts +240 -0
- package/src/activity-tracker.ts +140 -0
- package/src/adapters/runtimes.ts +30 -0
- package/src/agent-discovery.ts +262 -0
- package/src/agent-workspace.ts +247 -0
- package/src/config.ts +290 -0
- package/src/control-channel.ts +455 -0
- package/src/cross-room.ts +89 -0
- package/src/daemon-config-map.ts +200 -0
- package/src/daemon.ts +478 -0
- package/src/doctor.ts +282 -0
- package/src/gateway/__tests__/.gitkeep +0 -0
- package/src/gateway/__tests__/botcord-channel.test.ts +480 -0
- package/src/gateway/__tests__/channel-manager.test.ts +475 -0
- package/src/gateway/__tests__/claude-code-adapter.test.ts +318 -0
- package/src/gateway/__tests__/codex-adapter.test.ts +350 -0
- package/src/gateway/__tests__/dispatcher.test.ts +1159 -0
- package/src/gateway/__tests__/gateway-add-channel.test.ts +180 -0
- package/src/gateway/__tests__/gateway-managed-routes.test.ts +181 -0
- package/src/gateway/__tests__/gateway.test.ts +222 -0
- package/src/gateway/__tests__/router.test.ts +247 -0
- package/src/gateway/__tests__/sanitize.test.ts +193 -0
- package/src/gateway/__tests__/session-store.test.ts +235 -0
- package/src/gateway/channel-manager.ts +349 -0
- package/src/gateway/channels/botcord.ts +605 -0
- package/src/gateway/channels/index.ts +6 -0
- package/src/gateway/channels/sanitize.ts +68 -0
- package/src/gateway/dispatcher.ts +554 -0
- package/src/gateway/gateway.ts +211 -0
- package/src/gateway/index.ts +29 -0
- package/src/gateway/log.ts +30 -0
- package/src/gateway/router.ts +60 -0
- package/src/gateway/runtimes/claude-code.ts +180 -0
- package/src/gateway/runtimes/codex.ts +312 -0
- package/src/gateway/runtimes/gemini.ts +43 -0
- package/src/gateway/runtimes/ndjson-stream.ts +225 -0
- package/src/gateway/runtimes/probe.ts +73 -0
- package/src/gateway/runtimes/registry.ts +143 -0
- package/src/gateway/session-store.ts +157 -0
- package/src/gateway/types.ts +325 -0
- package/src/index.ts +961 -0
- package/src/log.ts +47 -0
- package/src/provision.ts +879 -0
- package/src/room-context-fetcher.ts +124 -0
- package/src/room-context.ts +167 -0
- package/src/sender-classify.ts +46 -0
- package/src/snapshot-writer.ts +103 -0
- package/src/status-render.ts +132 -0
- package/src/system-context.ts +162 -0
- package/src/turn-text.ts +93 -0
- package/src/user-auth.ts +295 -0
- package/src/working-memory.ts +352 -0
package/src/doctor.ts
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import type { RuntimeProbeEntry } from "./adapters/runtimes.js";
|
|
2
|
+
import type { DaemonConfig } from "./config.js";
|
|
3
|
+
import { resolveBootAgents } from "./agent-discovery.js";
|
|
4
|
+
|
|
5
|
+
/** Summary of a single channel's readiness, printable by the doctor command. */
|
|
6
|
+
export interface ChannelProbeResult {
|
|
7
|
+
id: string;
|
|
8
|
+
type: string;
|
|
9
|
+
accountId: string;
|
|
10
|
+
credentialsOk: boolean;
|
|
11
|
+
credentialsMessage: string;
|
|
12
|
+
hubUrl: string | null;
|
|
13
|
+
hubOk: boolean;
|
|
14
|
+
hubMessage: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Minimal filesystem surface needed by {@link probeChannel}; injectable for tests. */
|
|
18
|
+
export interface DoctorFileReader {
|
|
19
|
+
readFile(path: string): string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** HTTP GET surface needed by {@link probeChannel}; injectable for tests. */
|
|
23
|
+
export interface DoctorHttpFetcher {
|
|
24
|
+
(url: string, timeoutMs: number): Promise<DoctorHttpResult>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Response shape returned by {@link DoctorHttpFetcher}. */
|
|
28
|
+
export interface DoctorHttpResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
status?: number;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Input for the rendered doctor output. */
|
|
35
|
+
export interface DoctorInput {
|
|
36
|
+
runtimes: RuntimeProbeEntry[];
|
|
37
|
+
channels: ChannelProbeResult[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Per-channel config entry accepted by {@link probeChannel}. */
|
|
41
|
+
export interface ChannelProbeConfig {
|
|
42
|
+
id: string;
|
|
43
|
+
type: string;
|
|
44
|
+
accountId: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional explicit credential file path. When set, wins over the
|
|
47
|
+
* default `~/.botcord/credentials/<accountId>.json` used by
|
|
48
|
+
* `probeChannel`'s fallback. Populated by discovery to surface the
|
|
49
|
+
* exact file that would be loaded at start.
|
|
50
|
+
*/
|
|
51
|
+
credentialsFile?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Top-level options for {@link probeChannels}. */
|
|
55
|
+
export interface ProbeChannelsOptions {
|
|
56
|
+
channels: ChannelProbeConfig[];
|
|
57
|
+
credentialsPath: (accountId: string) => string;
|
|
58
|
+
fileReader: DoctorFileReader;
|
|
59
|
+
fetcher: DoctorHttpFetcher;
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build the implicit channel list for a daemon config. One channel per
|
|
65
|
+
* configured or discovered agent, keyed by agentId (matches
|
|
66
|
+
* `toGatewayConfig`). Mirrors the daemon's boot-agent resolution so
|
|
67
|
+
* `doctor` reports channels even when the config file omits `agents`.
|
|
68
|
+
*/
|
|
69
|
+
export function channelsFromDaemonConfig(cfg: DaemonConfig): ChannelProbeConfig[] {
|
|
70
|
+
let boot;
|
|
71
|
+
try {
|
|
72
|
+
boot = resolveBootAgents(cfg);
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
return boot.agents.map((a) => {
|
|
77
|
+
const entry: ChannelProbeConfig = {
|
|
78
|
+
id: a.agentId,
|
|
79
|
+
type: "botcord",
|
|
80
|
+
accountId: a.agentId,
|
|
81
|
+
};
|
|
82
|
+
if (a.credentialsFile) entry.credentialsFile = a.credentialsFile;
|
|
83
|
+
return entry;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Inspect credentials + Hub reachability for one channel. Pure modulo the
|
|
89
|
+
* injected file reader and fetcher.
|
|
90
|
+
*/
|
|
91
|
+
export async function probeChannel(
|
|
92
|
+
ch: ChannelProbeConfig,
|
|
93
|
+
opts: {
|
|
94
|
+
credentialsPath: (accountId: string) => string;
|
|
95
|
+
fileReader: DoctorFileReader;
|
|
96
|
+
fetcher: DoctorHttpFetcher;
|
|
97
|
+
timeoutMs: number;
|
|
98
|
+
},
|
|
99
|
+
): Promise<ChannelProbeResult> {
|
|
100
|
+
const result: ChannelProbeResult = {
|
|
101
|
+
id: ch.id,
|
|
102
|
+
type: ch.type,
|
|
103
|
+
accountId: ch.accountId,
|
|
104
|
+
credentialsOk: false,
|
|
105
|
+
credentialsMessage: "",
|
|
106
|
+
hubUrl: null,
|
|
107
|
+
hubOk: false,
|
|
108
|
+
hubMessage: "",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (ch.type !== "botcord") {
|
|
112
|
+
result.credentialsMessage = `unsupported channel type "${ch.type}" (no credentials check)`;
|
|
113
|
+
result.hubMessage = "skipped";
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const credFile = ch.credentialsFile ?? opts.credentialsPath(ch.accountId);
|
|
118
|
+
const raw = opts.fileReader.readFile(credFile);
|
|
119
|
+
if (raw === null) {
|
|
120
|
+
result.credentialsMessage = `missing at ${credFile}`;
|
|
121
|
+
} else {
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
124
|
+
const token = parsed.token ?? parsed["token"];
|
|
125
|
+
const hubUrlRaw = parsed.hubUrl ?? parsed["hub_url"] ?? parsed["hub"];
|
|
126
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
127
|
+
result.credentialsMessage = "token missing or empty";
|
|
128
|
+
} else if (typeof hubUrlRaw !== "string" || hubUrlRaw.length === 0) {
|
|
129
|
+
result.credentialsMessage = "hubUrl missing";
|
|
130
|
+
} else {
|
|
131
|
+
result.credentialsOk = true;
|
|
132
|
+
result.credentialsMessage = `loaded (${credFile})`;
|
|
133
|
+
result.hubUrl = hubUrlRaw;
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
result.credentialsMessage = `invalid JSON: ${(err as Error).message}`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!result.hubUrl) {
|
|
141
|
+
result.hubMessage = "skipped (no hub URL)";
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Probe `/` — the hub is ASGI and responds 2xx/3xx/404 which is fine for
|
|
146
|
+
// "reachable". We treat any response as reachable; network errors fall
|
|
147
|
+
// through to hubOk=false.
|
|
148
|
+
const probeUrl = `${result.hubUrl.replace(/\/+$/, "")}/`;
|
|
149
|
+
const http = await opts.fetcher(probeUrl, opts.timeoutMs);
|
|
150
|
+
if (http.ok) {
|
|
151
|
+
result.hubOk = true;
|
|
152
|
+
result.hubMessage = `reachable (HTTP ${http.status})`;
|
|
153
|
+
} else if (http.status !== undefined) {
|
|
154
|
+
result.hubMessage = `HTTP ${http.status}`;
|
|
155
|
+
} else {
|
|
156
|
+
result.hubMessage = http.error ?? "unreachable";
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Probe a list of channels sequentially. Sequential keeps output stable. */
|
|
162
|
+
export async function probeChannels(
|
|
163
|
+
opts: ProbeChannelsOptions,
|
|
164
|
+
): Promise<ChannelProbeResult[]> {
|
|
165
|
+
const timeoutMs = opts.timeoutMs ?? 5_000;
|
|
166
|
+
const out: ChannelProbeResult[] = [];
|
|
167
|
+
for (const ch of opts.channels) {
|
|
168
|
+
out.push(
|
|
169
|
+
await probeChannel(ch, {
|
|
170
|
+
credentialsPath: opts.credentialsPath,
|
|
171
|
+
fileReader: opts.fileReader,
|
|
172
|
+
fetcher: opts.fetcher,
|
|
173
|
+
timeoutMs,
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Default HTTP fetcher using `fetch` + `AbortController` timeout. */
|
|
181
|
+
export const defaultHttpFetcher: DoctorHttpFetcher = async (url, timeoutMs) => {
|
|
182
|
+
const controller = new AbortController();
|
|
183
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
184
|
+
try {
|
|
185
|
+
const resp = await fetch(url, {
|
|
186
|
+
method: "GET",
|
|
187
|
+
signal: controller.signal,
|
|
188
|
+
redirect: "manual",
|
|
189
|
+
});
|
|
190
|
+
const ok = resp.status >= 200 && resp.status < 400;
|
|
191
|
+
return { ok, status: resp.status };
|
|
192
|
+
} catch (err) {
|
|
193
|
+
const e = err as Error & { name?: string };
|
|
194
|
+
if (e.name === "AbortError") return { ok: false, error: "timeout" };
|
|
195
|
+
return { ok: false, error: e.message };
|
|
196
|
+
} finally {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
function pad(s: string, n: number): string {
|
|
202
|
+
return s + " ".repeat(Math.max(0, n - s.length));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Render runtime + channel probe output. Pure — all IO happened already.
|
|
207
|
+
* Used by the CLI `doctor` command and by unit tests.
|
|
208
|
+
*/
|
|
209
|
+
export function renderDoctor(input: DoctorInput): string {
|
|
210
|
+
const lines: string[] = [];
|
|
211
|
+
|
|
212
|
+
// Runtimes table (matches existing doctor layout).
|
|
213
|
+
const rows = input.runtimes.map((e) => ({
|
|
214
|
+
runtime: e.id,
|
|
215
|
+
name: e.displayName,
|
|
216
|
+
status: e.result.available ? "ok" : "missing",
|
|
217
|
+
version: e.result.version ?? "—",
|
|
218
|
+
path: e.result.path ?? "—",
|
|
219
|
+
}));
|
|
220
|
+
const widths = {
|
|
221
|
+
runtime: Math.max(7, ...rows.map((r) => r.runtime.length)),
|
|
222
|
+
name: Math.max(4, ...rows.map((r) => r.name.length)),
|
|
223
|
+
status: Math.max(6, ...rows.map((r) => r.status.length)),
|
|
224
|
+
version: Math.max(7, ...rows.map((r) => r.version.length)),
|
|
225
|
+
};
|
|
226
|
+
lines.push(
|
|
227
|
+
`${pad("RUNTIME", widths.runtime)} ${pad("NAME", widths.name)} ${pad("STATUS", widths.status)} ${pad("VERSION", widths.version)} PATH`,
|
|
228
|
+
);
|
|
229
|
+
for (const r of rows) {
|
|
230
|
+
lines.push(
|
|
231
|
+
`${pad(r.runtime, widths.runtime)} ${pad(r.name, widths.name)} ${pad(r.status, widths.status)} ${pad(r.version, widths.version)} ${r.path}`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const available = input.runtimes.filter((e) => e.result.available).length;
|
|
235
|
+
lines.push(`\n${available}/${input.runtimes.length} runtimes available`);
|
|
236
|
+
|
|
237
|
+
lines.push("");
|
|
238
|
+
lines.push("Channels:");
|
|
239
|
+
if (input.channels.length === 0) {
|
|
240
|
+
lines.push(" No channels configured.");
|
|
241
|
+
return lines.join("\n");
|
|
242
|
+
}
|
|
243
|
+
const cw = {
|
|
244
|
+
id: Math.max(2, ...input.channels.map((c) => c.id.length)),
|
|
245
|
+
type: Math.max(4, ...input.channels.map((c) => c.type.length)),
|
|
246
|
+
};
|
|
247
|
+
lines.push(
|
|
248
|
+
` ${pad("ID", cw.id)} ${pad("TYPE", cw.type)} CREDENTIALS HUB`,
|
|
249
|
+
);
|
|
250
|
+
for (const c of input.channels) {
|
|
251
|
+
const credMark = c.credentialsOk ? "✓" : "✗";
|
|
252
|
+
const hubMark = c.hubOk ? "✓" : "✗";
|
|
253
|
+
lines.push(
|
|
254
|
+
` ${pad(c.id, cw.id)} ${pad(c.type, cw.type)} ${credMark} ${pad(c.credentialsMessage, 16)} ${hubMark} ${c.hubMessage}`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return lines.join("\n");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Thin orchestrator: runs runtime + channel probes and returns the rendered
|
|
262
|
+
* text. Keeps `index.ts` free of probe wiring.
|
|
263
|
+
*/
|
|
264
|
+
export async function runDoctor(
|
|
265
|
+
runtimes: RuntimeProbeEntry[],
|
|
266
|
+
channels: ChannelProbeConfig[],
|
|
267
|
+
opts: {
|
|
268
|
+
credentialsPath: (accountId: string) => string;
|
|
269
|
+
fileReader: DoctorFileReader;
|
|
270
|
+
fetcher: DoctorHttpFetcher;
|
|
271
|
+
timeoutMs?: number;
|
|
272
|
+
},
|
|
273
|
+
): Promise<DoctorInput> {
|
|
274
|
+
const channelResults = await probeChannels({
|
|
275
|
+
channels,
|
|
276
|
+
credentialsPath: opts.credentialsPath,
|
|
277
|
+
fileReader: opts.fileReader,
|
|
278
|
+
fetcher: opts.fetcher,
|
|
279
|
+
timeoutMs: opts.timeoutMs,
|
|
280
|
+
});
|
|
281
|
+
return { runtimes, channels: channelResults };
|
|
282
|
+
}
|
|
File without changes
|