@agentconnect.md/daemon 1.0.0-rc.243 → 1.0.0-rc.245
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/index.js +2 -2
- package/dist/{login-BKn49aay.js → login-BT6-XZ6A.js} +2 -2
- package/dist/{login-BKn49aay.js.map → login-BT6-XZ6A.js.map} +1 -1
- package/dist/{run-foreground-CLPgcMuB.js → run-foreground-BfsQhznN.js} +116 -10
- package/dist/{run-foreground-CLPgcMuB.js.map → run-foreground-BfsQhznN.js.map} +1 -1
- package/dist/run-foreground-Dji3CYuN.js +2 -0
- package/package.json +1 -1
- package/dist/run-foreground-V9zo7E1r.js +0 -2
package/dist/index.js
CHANGED
|
@@ -68493,8 +68493,8 @@ const [{ Command }, { fileURLToPath }, { runChat }, { runLogin }, { runForegroun
|
|
|
68493
68493
|
import("./commander-B5QVEFzH.js"),
|
|
68494
68494
|
import("node:url"),
|
|
68495
68495
|
import("./chat-hWjORIJV.js"),
|
|
68496
|
-
import("./login-
|
|
68497
|
-
import("./run-foreground-
|
|
68496
|
+
import("./login-BT6-XZ6A.js"),
|
|
68497
|
+
import("./run-foreground-Dji3CYuN.js"),
|
|
68498
68498
|
import("./service-6w0WKhdx.js"),
|
|
68499
68499
|
import("./lock-CA9Bm8_s.js"),
|
|
68500
68500
|
import("./paths-CcRUjFjy.js")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
2
|
-
import { i as ClientTransport, t as runForeground } from "./run-foreground-
|
|
2
|
+
import { i as ClientTransport, t as runForeground } from "./run-foreground-BfsQhznN.js";
|
|
3
3
|
import { c as ConfigSchema } from "./load-agents-Dt8ZYMsH.js";
|
|
4
4
|
import { l as resolveRoot, t as configPath } from "./paths-DEcUO8lC.js";
|
|
5
5
|
import { E as encode, T as decodeEnvelope, v as isFrame, w as buildEnvelope } from "./acp-host-iKdfasdN.js";
|
|
@@ -229,4 +229,4 @@ async function runLogin(opts, partial = {}) {
|
|
|
229
229
|
//#endregion
|
|
230
230
|
export { buildInstallOpts, persistCredentials, runLogin };
|
|
231
231
|
|
|
232
|
-
//# sourceMappingURL=login-
|
|
232
|
+
//# sourceMappingURL=login-BT6-XZ6A.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-BKn49aay.js","names":["realRunForeground"],"sources":["../src/cp/auth-probe.ts","../src/cli/login.ts"],"sourcesContent":["/**\n * `probeAuth` — a one-shot CP auth check used by `agentconnect login`. It dials\n * the CP, sends ONLY the `auth` frame (no `register`), and resolves with the\n * authoritative daemonId from `auth/ok`, or a human-readable failure reason on\n * a `4401` close, a correlated `error`, a dial failure, or a timeout. It always\n * closes the socket and never leaves a connection open.\n */\nimport { buildEnvelope, decodeEnvelope, encode, isFrame } from '@agentconnect.md/protocol'\nimport { ClientTransport, type Transport } from '@agentconnect.md/connection'\nimport { CP_SUBPROTOCOL, CP_WS_PATH } from './client.js'\n\nexport interface ProbeResult {\n ok: boolean\n daemonId?: string\n reason?: string\n}\n\nexport interface ProbeOpts {\n url: string\n token: string\n daemonId?: string\n agentVersion?: string\n timeoutMs?: number\n /** Injectable for tests; defaults to dialing the real WebSocket. */\n connect?: () => Promise<Transport>\n}\n\nexport async function probeAuth(opts: ProbeOpts): Promise<ProbeResult> {\n const timeoutMs = opts.timeoutMs ?? 10_000\n const dial = opts.connect ?? (() => ClientTransport.dial(opts.url, { subprotocol: CP_SUBPROTOCOL, path: CP_WS_PATH }))\n\n let transport: Transport\n try {\n transport = await dial()\n } catch (err) {\n return { ok: false, reason: `cannot reach ${opts.url}: ${(err as Error).message}` }\n }\n\n const authPayload: Record<string, unknown> = {\n apiKey: opts.token,\n agentVersion: opts.agentVersion ?? 'probe'\n }\n if (opts.daemonId) authPayload.daemonId = opts.daemonId\n const frame = buildEnvelope('auth', authPayload)\n\n return await new Promise<ProbeResult>((resolve) => {\n let settled = false\n const done = (r: ProbeResult): void => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n try {\n transport.close(1000, 'probe done')\n } catch {\n // socket may already be closed\n }\n resolve(r)\n }\n\n const timer = setTimeout(\n () => done({ ok: false, reason: `timed out after ${timeoutMs}ms waiting for auth/ok` }),\n timeoutMs\n )\n\n transport.onMessage((text) => {\n const decoded = decodeEnvelope(text)\n if (!decoded.ok) return\n const f = decoded.frame\n if (f.corr !== frame.id) return\n if (f.type === 'error') {\n const e = f.payload as { code: string; message: string }\n done({ ok: false, reason: `${e.code}: ${e.message}` })\n return\n }\n if (isFrame('auth/ok')(f)) {\n const ok = f.payload as { daemonId: string }\n done({ ok: true, daemonId: ok.daemonId })\n }\n })\n\n transport.onClose((code) => {\n if (code === 4401) done({ ok: false, reason: 'authentication failed (4401) — check the daemon token' })\n else done({ ok: false, reason: `connection closed (${code}) before auth/ok` })\n })\n\n transport.send(encode(frame))\n })\n}\n","/**\n * `agentconnect login` — interactive onboarding. It tests the Control Plane auth\n * flow, persists credentials only on success, then offers to install the OS\n * service. If installed it starts the service (`up`) and returns; otherwise it\n * runs the daemon in the foreground (the same path as `agentconnect run`).\n *\n * `persistCredentials` is the pure config-merge half (token-only onboarding:\n * `daemonId` is omitted unless given — the first connect adopts it from the\n * token's `sub`). `runLogin` is the orchestrator, with injectable deps for tests.\n */\nimport { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { createInterface } from 'node:readline'\nimport { fileURLToPath } from 'node:url'\nimport { ConfigSchema } from '../config/config-schema.js'\nimport { resolveRoot, configPath } from '../paths.js'\nimport { DAEMON_VERSION } from '../version.js'\nimport { probeAuth, type ProbeResult } from '../cp/auth-probe.js'\nimport { resolveController, type InstallOpts } from '../service/index.js'\nimport { runForeground as realRunForeground } from './run-foreground.js'\n\nexport interface PersistCredsOpts {\n cpUrl: string\n cpKey: string\n daemonId?: string\n root?: string\n configPath?: string\n}\n\n/** Merge url/token into config.json (validated), returning the written path. */\nexport function persistCredentials(opts: PersistCredsOpts): string {\n const root = resolveRoot(opts.root)\n const file = opts.configPath ?? configPath(root)\n const raw: Record<string, unknown> = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { version: 1 }\n\n const cp = (raw.controlPlane as Record<string, unknown> | undefined) ?? {}\n raw.controlPlane = { ...cp, url: opts.cpUrl, key: opts.cpKey, enabled: true }\n if (opts.daemonId) raw.daemonId = opts.daemonId\n\n ConfigSchema.parse(raw) // fail before writing if the merge is invalid\n\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n return file\n}\n\nexport interface RunLoginOpts {\n cpUrl?: string\n cpKey?: string\n daemonId?: string\n root?: string\n configPath?: string\n /** The resolved CLI entry path. Must be supplied by the CLI entry point\n * (index.ts) so the correct bundle path is used rather than inferring it\n * from login.ts's own import.meta.url (which is the same bundle but the\n * `../` path calculation would be wrong). Falls back to import.meta.url\n * for the pure test path where login.ts is loaded standalone. */\n cliEntry?: string\n}\n\n/**\n * Build the InstallOpts for the OS service installer.\n *\n * Exported as a pure helper so it can be unit-tested independently of the\n * full `runLogin` orchestration. The caller (index.ts) passes `cliEntry` so\n * the correct bundle path always flows through; if omitted we fall back to\n * `import.meta.url` (correct only when login.ts is the entry, e.g. in tests).\n */\nexport function buildInstallOpts(opts: { root?: string; cliEntry?: string }): InstallOpts {\n return {\n execPath: process.execPath,\n cliEntry: opts.cliEntry ?? fileURLToPath(import.meta.url),\n includeRootEnv: resolveRoot(opts.root) !== resolveRoot(undefined)\n }\n}\n\nexport interface LoginDeps {\n probe: (o: { url: string; token: string }) => Promise<ProbeResult>\n installService: () => Promise<void>\n runForeground: () => Promise<void>\n out: NodeJS.WritableStream\n input: NodeJS.ReadableStream\n isTTY: boolean\n}\n\nfunction realDeps(opts: RunLoginOpts): LoginDeps {\n return {\n probe: (o) => probeAuth({ url: o.url, token: o.token, agentVersion: DAEMON_VERSION }),\n installService: async () => {\n const controller = resolveController({ root: opts.root })\n await controller.install(buildInstallOpts(opts))\n await controller.up()\n },\n runForeground: () =>\n realRunForeground({\n root: opts.root,\n overrides: { cpUrl: opts.cpUrl, cpKey: opts.cpKey, daemonId: opts.daemonId }\n }),\n out: process.stdout,\n input: process.stdin,\n isTTY: Boolean(process.stdin.isTTY)\n }\n}\n\n/** Read the next line from a readline interface via async iterator. */\nasync function nextLine(\n iter: AsyncIterableIterator<string>,\n prompt: string,\n out: NodeJS.WritableStream\n): Promise<string> {\n out.write(prompt)\n const { value, done } = await iter.next()\n if (done) throw new Error(`unexpected end of input waiting for: ${prompt}`)\n return (value as string).trim()\n}\n\nexport async function runLogin(opts: RunLoginOpts, partial: Partial<LoginDeps> = {}): Promise<void> {\n const deps: LoginDeps = { ...realDeps(opts), ...partial }\n const { out } = deps\n\n // ── Non-interactive: flags required; probe; persist. No prompts, no handoff. ──\n if (!deps.isTTY) {\n if (!opts.cpUrl) throw new Error('login requires --cp-url <url>')\n if (!opts.cpKey) throw new Error('login requires --cp-key <key>')\n const r = await deps.probe({ url: opts.cpUrl, token: opts.cpKey })\n if (!r.ok) throw new Error(`authentication failed: ${r.reason}`)\n persistCredentials({ ...opts, cpUrl: opts.cpUrl, cpKey: opts.cpKey })\n out.write(`✓ authenticated${r.daemonId ? ` (daemon ${r.daemonId})` : ''} — credentials saved\\n`)\n return\n }\n\n // Both interactive handoffs read `<root>/config.json`: the installed service\n // runs `… run` (no `--config`), and the foreground `Daemon` loads by root. A\n // custom `--config` would persist credentials the daemon never reads, so the\n // install/run branch would look successful but start without them. Reject it;\n // `--root` is the knob that keeps persist + service + foreground in sync.\n if (opts.configPath) {\n throw new Error(\n 'interactive login does not support --config — use --root <dir> so the daemon and the installed service read the same config'\n )\n }\n\n // ── Interactive ──\n const rl = createInterface({ input: deps.input, output: out })\n const iter = rl[Symbol.asyncIterator]()\n let url = opts.cpUrl\n let token = opts.cpKey\n try {\n if (!url) url = await nextLine(iter, 'Control Plane URL: ', out)\n\n for (let attempt = 0; ; attempt++) {\n if (!token) token = await nextLine(iter, 'Daemon API key: ', out)\n out.write(`Testing connection to ${url}…\\n`)\n const r = await deps.probe({ url: url!, token: token! })\n if (r.ok) {\n out.write(`✓ authenticated${r.daemonId ? ` (daemon ${r.daemonId})` : ''}\\n`)\n break\n }\n out.write(`✗ ${r.reason}\\n`)\n if (attempt >= 1) throw new Error('authentication failed')\n token = undefined // re-prompt on retry\n }\n\n persistCredentials({ ...opts, cpUrl: url!, cpKey: token! })\n\n const ans = (await nextLine(iter, 'Install AgentConnect as a background service? (y/N) ', out)).toLowerCase()\n if (ans === 'y' || ans === 'yes') {\n await deps.installService()\n out.write('Service installed and started. Manage it with `agentconnect up` / `down` / `status`.\\n')\n return\n }\n out.write('Starting in the foreground (Ctrl-C to stop)…\\n')\n } finally {\n rl.close()\n }\n\n await deps.runForeground()\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"login-BT6-XZ6A.js","names":["realRunForeground"],"sources":["../src/cp/auth-probe.ts","../src/cli/login.ts"],"sourcesContent":["/**\n * `probeAuth` — a one-shot CP auth check used by `agentconnect login`. It dials\n * the CP, sends ONLY the `auth` frame (no `register`), and resolves with the\n * authoritative daemonId from `auth/ok`, or a human-readable failure reason on\n * a `4401` close, a correlated `error`, a dial failure, or a timeout. It always\n * closes the socket and never leaves a connection open.\n */\nimport { buildEnvelope, decodeEnvelope, encode, isFrame } from '@agentconnect.md/protocol'\nimport { ClientTransport, type Transport } from '@agentconnect.md/connection'\nimport { CP_SUBPROTOCOL, CP_WS_PATH } from './client.js'\n\nexport interface ProbeResult {\n ok: boolean\n daemonId?: string\n reason?: string\n}\n\nexport interface ProbeOpts {\n url: string\n token: string\n daemonId?: string\n agentVersion?: string\n timeoutMs?: number\n /** Injectable for tests; defaults to dialing the real WebSocket. */\n connect?: () => Promise<Transport>\n}\n\nexport async function probeAuth(opts: ProbeOpts): Promise<ProbeResult> {\n const timeoutMs = opts.timeoutMs ?? 10_000\n const dial = opts.connect ?? (() => ClientTransport.dial(opts.url, { subprotocol: CP_SUBPROTOCOL, path: CP_WS_PATH }))\n\n let transport: Transport\n try {\n transport = await dial()\n } catch (err) {\n return { ok: false, reason: `cannot reach ${opts.url}: ${(err as Error).message}` }\n }\n\n const authPayload: Record<string, unknown> = {\n apiKey: opts.token,\n agentVersion: opts.agentVersion ?? 'probe'\n }\n if (opts.daemonId) authPayload.daemonId = opts.daemonId\n const frame = buildEnvelope('auth', authPayload)\n\n return await new Promise<ProbeResult>((resolve) => {\n let settled = false\n const done = (r: ProbeResult): void => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n try {\n transport.close(1000, 'probe done')\n } catch {\n // socket may already be closed\n }\n resolve(r)\n }\n\n const timer = setTimeout(\n () => done({ ok: false, reason: `timed out after ${timeoutMs}ms waiting for auth/ok` }),\n timeoutMs\n )\n\n transport.onMessage((text) => {\n const decoded = decodeEnvelope(text)\n if (!decoded.ok) return\n const f = decoded.frame\n if (f.corr !== frame.id) return\n if (f.type === 'error') {\n const e = f.payload as { code: string; message: string }\n done({ ok: false, reason: `${e.code}: ${e.message}` })\n return\n }\n if (isFrame('auth/ok')(f)) {\n const ok = f.payload as { daemonId: string }\n done({ ok: true, daemonId: ok.daemonId })\n }\n })\n\n transport.onClose((code) => {\n if (code === 4401) done({ ok: false, reason: 'authentication failed (4401) — check the daemon token' })\n else done({ ok: false, reason: `connection closed (${code}) before auth/ok` })\n })\n\n transport.send(encode(frame))\n })\n}\n","/**\n * `agentconnect login` — interactive onboarding. It tests the Control Plane auth\n * flow, persists credentials only on success, then offers to install the OS\n * service. If installed it starts the service (`up`) and returns; otherwise it\n * runs the daemon in the foreground (the same path as `agentconnect run`).\n *\n * `persistCredentials` is the pure config-merge half (token-only onboarding:\n * `daemonId` is omitted unless given — the first connect adopts it from the\n * token's `sub`). `runLogin` is the orchestrator, with injectable deps for tests.\n */\nimport { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { createInterface } from 'node:readline'\nimport { fileURLToPath } from 'node:url'\nimport { ConfigSchema } from '../config/config-schema.js'\nimport { resolveRoot, configPath } from '../paths.js'\nimport { DAEMON_VERSION } from '../version.js'\nimport { probeAuth, type ProbeResult } from '../cp/auth-probe.js'\nimport { resolveController, type InstallOpts } from '../service/index.js'\nimport { runForeground as realRunForeground } from './run-foreground.js'\n\nexport interface PersistCredsOpts {\n cpUrl: string\n cpKey: string\n daemonId?: string\n root?: string\n configPath?: string\n}\n\n/** Merge url/token into config.json (validated), returning the written path. */\nexport function persistCredentials(opts: PersistCredsOpts): string {\n const root = resolveRoot(opts.root)\n const file = opts.configPath ?? configPath(root)\n const raw: Record<string, unknown> = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { version: 1 }\n\n const cp = (raw.controlPlane as Record<string, unknown> | undefined) ?? {}\n raw.controlPlane = { ...cp, url: opts.cpUrl, key: opts.cpKey, enabled: true }\n if (opts.daemonId) raw.daemonId = opts.daemonId\n\n ConfigSchema.parse(raw) // fail before writing if the merge is invalid\n\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n return file\n}\n\nexport interface RunLoginOpts {\n cpUrl?: string\n cpKey?: string\n daemonId?: string\n root?: string\n configPath?: string\n /** The resolved CLI entry path. Must be supplied by the CLI entry point\n * (index.ts) so the correct bundle path is used rather than inferring it\n * from login.ts's own import.meta.url (which is the same bundle but the\n * `../` path calculation would be wrong). Falls back to import.meta.url\n * for the pure test path where login.ts is loaded standalone. */\n cliEntry?: string\n}\n\n/**\n * Build the InstallOpts for the OS service installer.\n *\n * Exported as a pure helper so it can be unit-tested independently of the\n * full `runLogin` orchestration. The caller (index.ts) passes `cliEntry` so\n * the correct bundle path always flows through; if omitted we fall back to\n * `import.meta.url` (correct only when login.ts is the entry, e.g. in tests).\n */\nexport function buildInstallOpts(opts: { root?: string; cliEntry?: string }): InstallOpts {\n return {\n execPath: process.execPath,\n cliEntry: opts.cliEntry ?? fileURLToPath(import.meta.url),\n includeRootEnv: resolveRoot(opts.root) !== resolveRoot(undefined)\n }\n}\n\nexport interface LoginDeps {\n probe: (o: { url: string; token: string }) => Promise<ProbeResult>\n installService: () => Promise<void>\n runForeground: () => Promise<void>\n out: NodeJS.WritableStream\n input: NodeJS.ReadableStream\n isTTY: boolean\n}\n\nfunction realDeps(opts: RunLoginOpts): LoginDeps {\n return {\n probe: (o) => probeAuth({ url: o.url, token: o.token, agentVersion: DAEMON_VERSION }),\n installService: async () => {\n const controller = resolveController({ root: opts.root })\n await controller.install(buildInstallOpts(opts))\n await controller.up()\n },\n runForeground: () =>\n realRunForeground({\n root: opts.root,\n overrides: { cpUrl: opts.cpUrl, cpKey: opts.cpKey, daemonId: opts.daemonId }\n }),\n out: process.stdout,\n input: process.stdin,\n isTTY: Boolean(process.stdin.isTTY)\n }\n}\n\n/** Read the next line from a readline interface via async iterator. */\nasync function nextLine(\n iter: AsyncIterableIterator<string>,\n prompt: string,\n out: NodeJS.WritableStream\n): Promise<string> {\n out.write(prompt)\n const { value, done } = await iter.next()\n if (done) throw new Error(`unexpected end of input waiting for: ${prompt}`)\n return (value as string).trim()\n}\n\nexport async function runLogin(opts: RunLoginOpts, partial: Partial<LoginDeps> = {}): Promise<void> {\n const deps: LoginDeps = { ...realDeps(opts), ...partial }\n const { out } = deps\n\n // ── Non-interactive: flags required; probe; persist. No prompts, no handoff. ──\n if (!deps.isTTY) {\n if (!opts.cpUrl) throw new Error('login requires --cp-url <url>')\n if (!opts.cpKey) throw new Error('login requires --cp-key <key>')\n const r = await deps.probe({ url: opts.cpUrl, token: opts.cpKey })\n if (!r.ok) throw new Error(`authentication failed: ${r.reason}`)\n persistCredentials({ ...opts, cpUrl: opts.cpUrl, cpKey: opts.cpKey })\n out.write(`✓ authenticated${r.daemonId ? ` (daemon ${r.daemonId})` : ''} — credentials saved\\n`)\n return\n }\n\n // Both interactive handoffs read `<root>/config.json`: the installed service\n // runs `… run` (no `--config`), and the foreground `Daemon` loads by root. A\n // custom `--config` would persist credentials the daemon never reads, so the\n // install/run branch would look successful but start without them. Reject it;\n // `--root` is the knob that keeps persist + service + foreground in sync.\n if (opts.configPath) {\n throw new Error(\n 'interactive login does not support --config — use --root <dir> so the daemon and the installed service read the same config'\n )\n }\n\n // ── Interactive ──\n const rl = createInterface({ input: deps.input, output: out })\n const iter = rl[Symbol.asyncIterator]()\n let url = opts.cpUrl\n let token = opts.cpKey\n try {\n if (!url) url = await nextLine(iter, 'Control Plane URL: ', out)\n\n for (let attempt = 0; ; attempt++) {\n if (!token) token = await nextLine(iter, 'Daemon API key: ', out)\n out.write(`Testing connection to ${url}…\\n`)\n const r = await deps.probe({ url: url!, token: token! })\n if (r.ok) {\n out.write(`✓ authenticated${r.daemonId ? ` (daemon ${r.daemonId})` : ''}\\n`)\n break\n }\n out.write(`✗ ${r.reason}\\n`)\n if (attempt >= 1) throw new Error('authentication failed')\n token = undefined // re-prompt on retry\n }\n\n persistCredentials({ ...opts, cpUrl: url!, cpKey: token! })\n\n const ans = (await nextLine(iter, 'Install AgentConnect as a background service? (y/N) ', out)).toLowerCase()\n if (ans === 'y' || ans === 'yes') {\n await deps.installService()\n out.write('Service installed and started. Manage it with `agentconnect up` / `down` / `status`.\\n')\n return\n }\n out.write('Starting in the foreground (Ctrl-C to stop)…\\n')\n } finally {\n rl.close()\n }\n\n await deps.runForeground()\n}\n"],"mappings":"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"}
|
|
@@ -195430,9 +195430,9 @@ function substituteUserMentions(text, names) {
|
|
|
195430
195430
|
* - Names persist in SQLite, so a daemon restart keeps stale-but-useful names
|
|
195431
195431
|
* while the cache warms back up (rename lag is bounded by OK_TTL_MS).
|
|
195432
195432
|
*/
|
|
195433
|
-
const OK_TTL_MS = 360 * 60 * 1e3;
|
|
195434
|
-
const FAIL_TTL_MS = 600 * 1e3;
|
|
195435
|
-
const MAX_TRACKED_IDS = 5e3;
|
|
195433
|
+
const OK_TTL_MS$1 = 360 * 60 * 1e3;
|
|
195434
|
+
const FAIL_TTL_MS$1 = 600 * 1e3;
|
|
195435
|
+
const MAX_TRACKED_IDS$1 = 5e3;
|
|
195436
195436
|
var SlackNameResolver = class {
|
|
195437
195437
|
save;
|
|
195438
195438
|
log;
|
|
@@ -195457,9 +195457,9 @@ var SlackNameResolver = class {
|
|
|
195457
195457
|
const at = this.nextAttemptAt.get(id);
|
|
195458
195458
|
if (at !== void 0 && this.now() < at) return false;
|
|
195459
195459
|
this.nextAttemptAt.delete(id);
|
|
195460
|
-
this.nextAttemptAt.set(id, this.now() + OK_TTL_MS);
|
|
195460
|
+
this.nextAttemptAt.set(id, this.now() + OK_TTL_MS$1);
|
|
195461
195461
|
for (const key of this.nextAttemptAt.keys()) {
|
|
195462
|
-
if (this.nextAttemptAt.size <= MAX_TRACKED_IDS) break;
|
|
195462
|
+
if (this.nextAttemptAt.size <= MAX_TRACKED_IDS$1) break;
|
|
195463
195463
|
this.nextAttemptAt.delete(key);
|
|
195464
195464
|
}
|
|
195465
195465
|
return true;
|
|
@@ -195475,7 +195475,7 @@ var SlackNameResolver = class {
|
|
|
195475
195475
|
if (name) this.save(channel, `@${name}`);
|
|
195476
195476
|
}
|
|
195477
195477
|
} catch (err) {
|
|
195478
|
-
this.nextAttemptAt.set(channel, this.now() + FAIL_TTL_MS);
|
|
195478
|
+
this.nextAttemptAt.set(channel, this.now() + FAIL_TTL_MS$1);
|
|
195479
195479
|
this.log?.debug(`slack: name lookup failed for channel ${channel}: ${err.message}`);
|
|
195480
195480
|
}
|
|
195481
195481
|
}
|
|
@@ -195486,12 +195486,85 @@ var SlackNameResolver = class {
|
|
|
195486
195486
|
const name = p.realName || p.name;
|
|
195487
195487
|
if (name) this.save(user, name);
|
|
195488
195488
|
} catch (err) {
|
|
195489
|
-
this.nextAttemptAt.set(user, this.now() + FAIL_TTL_MS);
|
|
195489
|
+
this.nextAttemptAt.set(user, this.now() + FAIL_TTL_MS$1);
|
|
195490
195490
|
this.log?.debug(`slack: name lookup failed for user ${user}: ${err.message}`);
|
|
195491
195491
|
}
|
|
195492
195492
|
}
|
|
195493
195493
|
};
|
|
195494
195494
|
//#endregion
|
|
195495
|
+
//#region src/messages/channel-name-resolver.ts
|
|
195496
|
+
/**
|
|
195497
|
+
* Resolves a channel id to a human display name off the inbound-message path for the
|
|
195498
|
+
* platforms whose normalized messages don't already carry one (Discord channel/thread
|
|
195499
|
+
* ids, Telegram chat ids) and caches the result into the LocalStore `display_names`
|
|
195500
|
+
* table, so session read-back (`session/list` / `session/history`) can label the
|
|
195501
|
+
* channel without a live platform call at read time. The Slack analog is
|
|
195502
|
+
* SlackNameResolver — Slack instead learns names in bulk from its channel-membership
|
|
195503
|
+
* snapshot (daemon.refreshChannels), which Discord/Telegram have no cheap equivalent of.
|
|
195504
|
+
*
|
|
195505
|
+
* Design constraints (same as SlackNameResolver):
|
|
195506
|
+
* - Fire-and-forget: never on the dispatch hot path, never throws into it.
|
|
195507
|
+
* - Rate-friendly: an in-memory attempt cache means each id hits the platform API at
|
|
195508
|
+
* most once per TTL (short after a failure, long after success), whatever the volume.
|
|
195509
|
+
* - Names persist in SQLite, so a restart keeps stale-but-useful names while the cache
|
|
195510
|
+
* warms back up (rename lag is bounded by OK_TTL_MS).
|
|
195511
|
+
*
|
|
195512
|
+
* A group/channel title is saved bare (the console prefixes "#"); an IM/DM is saved
|
|
195513
|
+
* "@name" so the console renders it as a DM — matching the Slack convention.
|
|
195514
|
+
*/
|
|
195515
|
+
const OK_TTL_MS = 360 * 60 * 1e3;
|
|
195516
|
+
const FAIL_TTL_MS = 600 * 1e3;
|
|
195517
|
+
const MAX_TRACKED_IDS = 5e3;
|
|
195518
|
+
var ChannelNameResolver = class {
|
|
195519
|
+
save;
|
|
195520
|
+
log;
|
|
195521
|
+
now;
|
|
195522
|
+
/** channel id → epoch ms until which we won't re-attempt a lookup. */
|
|
195523
|
+
nextAttemptAt = /* @__PURE__ */ new Map();
|
|
195524
|
+
constructor(save, log, now = Date.now) {
|
|
195525
|
+
this.save = save;
|
|
195526
|
+
this.log = log;
|
|
195527
|
+
this.now = now;
|
|
195528
|
+
}
|
|
195529
|
+
/**
|
|
195530
|
+
* Kick off (cached) resolution for one channel id against its owning connection.
|
|
195531
|
+
* `triggeredBy` is the id of the human who opened the session — used only to label a
|
|
195532
|
+
* DM that has no name of its own (the person on the other side), mirroring Slack.
|
|
195533
|
+
*/
|
|
195534
|
+
noteChannel(src, channel, triggeredBy) {
|
|
195535
|
+
this.resolveChannel(src, channel, triggeredBy);
|
|
195536
|
+
}
|
|
195537
|
+
claim(id) {
|
|
195538
|
+
const at = this.nextAttemptAt.get(id);
|
|
195539
|
+
if (at !== void 0 && this.now() < at) return false;
|
|
195540
|
+
this.nextAttemptAt.delete(id);
|
|
195541
|
+
this.nextAttemptAt.set(id, this.now() + OK_TTL_MS);
|
|
195542
|
+
for (const key of this.nextAttemptAt.keys()) {
|
|
195543
|
+
if (this.nextAttemptAt.size <= MAX_TRACKED_IDS) break;
|
|
195544
|
+
this.nextAttemptAt.delete(key);
|
|
195545
|
+
}
|
|
195546
|
+
return true;
|
|
195547
|
+
}
|
|
195548
|
+
async resolveChannel(src, channel, triggeredBy) {
|
|
195549
|
+
if (!this.claim(channel)) return;
|
|
195550
|
+
try {
|
|
195551
|
+
const info = await src.getChannelInfo(channel);
|
|
195552
|
+
if (info.name) {
|
|
195553
|
+
this.save(channel, info.isIm ? `@${info.name}` : info.name);
|
|
195554
|
+
return;
|
|
195555
|
+
}
|
|
195556
|
+
if (info.isIm && triggeredBy) {
|
|
195557
|
+
const p = await src.getUserProfile(triggeredBy);
|
|
195558
|
+
const name = p.realName || p.name;
|
|
195559
|
+
if (name) this.save(channel, `@${name}`);
|
|
195560
|
+
}
|
|
195561
|
+
} catch (err) {
|
|
195562
|
+
this.nextAttemptAt.set(channel, this.now() + FAIL_TTL_MS);
|
|
195563
|
+
this.log?.debug(`name lookup failed for channel ${channel}: ${err.message}`);
|
|
195564
|
+
}
|
|
195565
|
+
}
|
|
195566
|
+
};
|
|
195567
|
+
//#endregion
|
|
195495
195568
|
//#region src/telegram/render.ts
|
|
195496
195569
|
/** Telegram single-message hard cap (4096 chars) — vs Slack's 12000-char block. */
|
|
195497
195570
|
const TELEGRAM_MESSAGE_LIMIT = 4096;
|
|
@@ -213389,6 +213462,7 @@ var Daemon = class Daemon {
|
|
|
213389
213462
|
telegramConns = [];
|
|
213390
213463
|
discordConns = [];
|
|
213391
213464
|
nameResolver;
|
|
213465
|
+
channelNameResolver;
|
|
213392
213466
|
scheduler;
|
|
213393
213467
|
sessions;
|
|
213394
213468
|
connByIntegration = /* @__PURE__ */ new Map();
|
|
@@ -213495,6 +213569,10 @@ var Daemon = class Daemon {
|
|
|
213495
213569
|
this.store.setDisplayName(id, name, Date.now());
|
|
213496
213570
|
this.emitSessionMetadataSnapshotsForDisplayName(id);
|
|
213497
213571
|
}, this.log);
|
|
213572
|
+
this.channelNameResolver = new ChannelNameResolver((id, name) => {
|
|
213573
|
+
this.store.setDisplayName(id, name, Date.now());
|
|
213574
|
+
this.emitSessionMetadataSnapshotsForDisplayName(id);
|
|
213575
|
+
}, this.log);
|
|
213498
213576
|
this.cpRouting = new CpRoutingLayer({
|
|
213499
213577
|
load: () => {
|
|
213500
213578
|
const row = this.store.getCpRouting();
|
|
@@ -213836,7 +213914,10 @@ var Daemon = class Daemon {
|
|
|
213836
213914
|
const conn = new TelegramConnection({
|
|
213837
213915
|
group,
|
|
213838
213916
|
newTraceId: () => randomUUID(),
|
|
213839
|
-
onMessage: (msg) =>
|
|
213917
|
+
onMessage: (msg) => {
|
|
213918
|
+
this.channelNameResolver?.noteChannel(conn, msg.channel, msg.sender.id);
|
|
213919
|
+
this.onInbound(msg);
|
|
213920
|
+
},
|
|
213840
213921
|
onCallback: (cb) => this.handleTelegramCallback(cb, conn),
|
|
213841
213922
|
log: this.log
|
|
213842
213923
|
});
|
|
@@ -213854,6 +213935,7 @@ var Daemon = class Daemon {
|
|
|
213854
213935
|
this.log.error(`telegram: failed to open long-poll for a bot token — leaving others intact: ${formatErr(err)}`);
|
|
213855
213936
|
}
|
|
213856
213937
|
}
|
|
213938
|
+
this.backfillChannelNames();
|
|
213857
213939
|
}
|
|
213858
213940
|
/**
|
|
213859
213941
|
* Reconcile the connection-derived Discord state (`botUserIds`,
|
|
@@ -213879,7 +213961,10 @@ var Daemon = class Daemon {
|
|
|
213879
213961
|
const conn = new DiscordConnection({
|
|
213880
213962
|
group,
|
|
213881
213963
|
newTraceId: () => randomUUID(),
|
|
213882
|
-
onMessage: (msg) =>
|
|
213964
|
+
onMessage: (msg) => {
|
|
213965
|
+
this.channelNameResolver?.noteChannel(conn, msg.channel, msg.sender.id);
|
|
213966
|
+
this.onInbound(msg);
|
|
213967
|
+
},
|
|
213883
213968
|
onStatusAction: (a) => this.handleStatusAction(a),
|
|
213884
213969
|
onSelectAction: (a) => this.handleDiscordSelect(a),
|
|
213885
213970
|
log: this.log
|
|
@@ -213898,6 +213983,26 @@ var Daemon = class Daemon {
|
|
|
213898
213983
|
this.log.error(`discord: failed to open Gateway for a bot token — leaving others intact: ${formatErr(err)}`);
|
|
213899
213984
|
}
|
|
213900
213985
|
}
|
|
213986
|
+
this.backfillChannelNames();
|
|
213987
|
+
}
|
|
213988
|
+
/**
|
|
213989
|
+
* Resolve display names for the channels of already-stored Discord/Telegram sessions
|
|
213990
|
+
* so the console labels them without waiting for a new inbound message (the per-message
|
|
213991
|
+
* ChannelNameResolver only fires on fresh traffic). The Slack analog is refreshChannels'
|
|
213992
|
+
* bulk membership snapshot; Discord/Telegram have no cheap channel enumeration, so we
|
|
213993
|
+
* resolve each live session's channel individually via its bot connection. Best-effort +
|
|
213994
|
+
* TTL-guarded by the resolver, so calling it on every reconcile is cheap.
|
|
213995
|
+
*/
|
|
213996
|
+
backfillChannelNames() {
|
|
213997
|
+
const resolver = this.channelNameResolver;
|
|
213998
|
+
if (!resolver) return;
|
|
213999
|
+
for (const row of this.store.listSessions()) {
|
|
214000
|
+
if (row.platform !== "discord" && row.platform !== "telegram") continue;
|
|
214001
|
+
const integrationId = this.agents.get(row.agentId)?.integrations.find((i) => i.platform === row.platform)?.id;
|
|
214002
|
+
if (!integrationId) continue;
|
|
214003
|
+
const conn = row.platform === "discord" ? this.dcConnByIntegration.get(integrationId) : this.tgConnByIntegration.get(integrationId);
|
|
214004
|
+
if (conn) resolver.noteChannel(conn, row.channel, row.triggeredBy ?? void 0);
|
|
214005
|
+
}
|
|
213901
214006
|
}
|
|
213902
214007
|
/**
|
|
213903
214008
|
* Re-list the channels this connection's bot is a member of and report the
|
|
@@ -214126,6 +214231,7 @@ var Daemon = class Daemon {
|
|
|
214126
214231
|
this.log.info(`discord: opened thread ${threadId} for ch=${msg.channel} msg=${messageId}`);
|
|
214127
214232
|
msg.channel = threadId;
|
|
214128
214233
|
msg.thread = threadId;
|
|
214234
|
+
if (conn) this.channelNameResolver?.noteChannel(conn, threadId);
|
|
214129
214235
|
} else this.log.debug(`discord: no thread opened for ch=${msg.channel} — replying in channel`);
|
|
214130
214236
|
await this.dispatch(agentId, msg, integrationId);
|
|
214131
214237
|
}
|
|
@@ -216108,4 +216214,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
216108
216214
|
//#endregion
|
|
216109
216215
|
export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
|
|
216110
216216
|
|
|
216111
|
-
//# sourceMappingURL=run-foreground-
|
|
216217
|
+
//# sourceMappingURL=run-foreground-BfsQhznN.js.map
|