@agentconnect.md/daemon 1.0.0-rc.230 → 1.0.0-rc.232
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-8pfSiMc2.js → login-BCxHYEeG.js} +2 -2
- package/dist/{login-8pfSiMc2.js.map → login-BCxHYEeG.js.map} +1 -1
- package/dist/run-foreground-BQl2a8y9.js +2 -0
- package/dist/{run-foreground-Cg2ewYjn.js → run-foreground-DN1BSygk.js} +306 -9
- package/dist/run-foreground-DN1BSygk.js.map +1 -0
- package/package.json +1 -1
- package/dist/run-foreground-Cg2ewYjn.js.map +0 -1
- package/dist/run-foreground-DxSBKNXU.js +0 -2
package/dist/index.js
CHANGED
|
@@ -68502,8 +68502,8 @@ const [{ Command }, { fileURLToPath: fileURLToPath$1 }, { runChat }, { runLogin
|
|
|
68502
68502
|
import("./commander-B5QVEFzH.js"),
|
|
68503
68503
|
import("node:url"),
|
|
68504
68504
|
import("./chat-Di5QFvjw.js"),
|
|
68505
|
-
import("./login-
|
|
68506
|
-
import("./run-foreground-
|
|
68505
|
+
import("./login-BCxHYEeG.js"),
|
|
68506
|
+
import("./run-foreground-BQl2a8y9.js"),
|
|
68507
68507
|
import("./service-6w0WKhdx.js"),
|
|
68508
68508
|
import("./lock-CA9Bm8_s.js"),
|
|
68509
68509
|
import("./paths-CcRUjFjy.js")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
2
|
-
import { n as ClientTransport, t as runForeground } from "./run-foreground-
|
|
2
|
+
import { n as ClientTransport, t as runForeground } from "./run-foreground-DN1BSygk.js";
|
|
3
3
|
import { c as ConfigSchema } from "./load-agents-tb8Rcwan.js";
|
|
4
4
|
import { l as resolveRoot, t as configPath } from "./paths-DEcUO8lC.js";
|
|
5
5
|
import { f as buildEnvelope, l as isFrame, m as encode, p as decodeEnvelope } from "./acp-host-CXUQ91Gh.js";
|
|
@@ -226,4 +226,4 @@ async function runLogin(opts, partial = {}) {
|
|
|
226
226
|
//#endregion
|
|
227
227
|
export { buildInstallOpts, persistCredentials, runLogin };
|
|
228
228
|
|
|
229
|
-
//# sourceMappingURL=login-
|
|
229
|
+
//# sourceMappingURL=login-BCxHYEeG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-8pfSiMc2.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 './transport.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))\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-BCxHYEeG.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 './transport.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))\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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"}
|
|
@@ -5517,6 +5517,19 @@ var LocalStore = class {
|
|
|
5517
5517
|
if (agentId !== void 0) return this.db.prepare("SELECT * FROM sessions WHERE acpSessionId IS NOT NULL AND agentId = ? ORDER BY updatedAt DESC").all(agentId);
|
|
5518
5518
|
return this.db.prepare("SELECT * FROM sessions WHERE acpSessionId IS NOT NULL ORDER BY updatedAt DESC").all();
|
|
5519
5519
|
}
|
|
5520
|
+
/** The most recently active addressable session (has an ACP id) for an agent in a
|
|
5521
|
+
* channel, or undefined. Backs `/status` when the command message itself doesn't fall
|
|
5522
|
+
* in a session's thread (a bare Telegram `/status` keys to its own reply thread) — we
|
|
5523
|
+
* then report the channel's latest session rather than "nothing here". */
|
|
5524
|
+
latestSession(agentId, channel) {
|
|
5525
|
+
return this.db.prepare("SELECT * FROM sessions WHERE agentId = ? AND channel = ? AND acpSessionId IS NOT NULL ORDER BY updatedAt DESC LIMIT 1").get(agentId, channel);
|
|
5526
|
+
}
|
|
5527
|
+
/** The most recently active addressable session (has an ACP id) in a channel, across
|
|
5528
|
+
* ALL agents — or undefined. Used to resolve which agent a bare command targets when
|
|
5529
|
+
* routing can't (e.g. a group `/status@bot` with no mention entity / thread). */
|
|
5530
|
+
latestSessionInChannel(channel) {
|
|
5531
|
+
return this.db.prepare("SELECT * FROM sessions WHERE channel = ? AND acpSessionId IS NOT NULL ORDER BY updatedAt DESC LIMIT 1").get(channel);
|
|
5532
|
+
}
|
|
5520
5533
|
/** Lookup by ACP session id (the protocol-facing `sessionId`), or undefined. */
|
|
5521
5534
|
getSessionByAcpId(acpSessionId) {
|
|
5522
5535
|
return this.db.prepare("SELECT * FROM sessions WHERE acpSessionId = ?").get(acpSessionId);
|
|
@@ -10169,6 +10182,13 @@ const CANCEL_WORDS = /* @__PURE__ */ new Set(["cancel"]);
|
|
|
10169
10182
|
const QUEUE_WORDS = /* @__PURE__ */ new Set(["queue"]);
|
|
10170
10183
|
const STATUS_WORDS = /* @__PURE__ */ new Set(["status"]);
|
|
10171
10184
|
const FAST_WORDS = /* @__PURE__ */ new Set(["fast"]);
|
|
10185
|
+
const MODEL_WORDS = /* @__PURE__ */ new Set(["model", "models"]);
|
|
10186
|
+
const EFFORT_WORDS = /* @__PURE__ */ new Set(["effort"]);
|
|
10187
|
+
const PERMISSION_WORDS = /* @__PURE__ */ new Set([
|
|
10188
|
+
"permission",
|
|
10189
|
+
"permissions",
|
|
10190
|
+
"perm"
|
|
10191
|
+
]);
|
|
10172
10192
|
/**
|
|
10173
10193
|
* Parse a leading control command from a message's text. Returns `null` when the
|
|
10174
10194
|
* text is not a recognized command (so it flows to the agent unchanged). The
|
|
@@ -10199,6 +10219,18 @@ function parseCommand(raw) {
|
|
|
10199
10219
|
enable: a === "on" ? true : a === "off" ? false : null
|
|
10200
10220
|
};
|
|
10201
10221
|
}
|
|
10222
|
+
if (MODEL_WORDS.has(word)) return {
|
|
10223
|
+
kind: "model",
|
|
10224
|
+
value: arg || null
|
|
10225
|
+
};
|
|
10226
|
+
if (EFFORT_WORDS.has(word)) return {
|
|
10227
|
+
kind: "effort",
|
|
10228
|
+
value: arg || null
|
|
10229
|
+
};
|
|
10230
|
+
if (PERMISSION_WORDS.has(word)) return {
|
|
10231
|
+
kind: "permission",
|
|
10232
|
+
value: arg || null
|
|
10233
|
+
};
|
|
10202
10234
|
return null;
|
|
10203
10235
|
}
|
|
10204
10236
|
//#endregion
|
|
@@ -117446,6 +117478,18 @@ const BOT_COMMANDS = [
|
|
|
117446
117478
|
command: "fast",
|
|
117447
117479
|
description: "Toggle fast mode — /fast on | off"
|
|
117448
117480
|
},
|
|
117481
|
+
{
|
|
117482
|
+
command: "models",
|
|
117483
|
+
description: "Choose the model — /models [name] (bare = list)"
|
|
117484
|
+
},
|
|
117485
|
+
{
|
|
117486
|
+
command: "effort",
|
|
117487
|
+
description: "Choose reasoning effort — /effort [level] (bare = list)"
|
|
117488
|
+
},
|
|
117489
|
+
{
|
|
117490
|
+
command: "permission",
|
|
117491
|
+
description: "Choose permission mode — /permission [mode] (bare = list)"
|
|
117492
|
+
},
|
|
117449
117493
|
{
|
|
117450
117494
|
command: "queue",
|
|
117451
117495
|
description: "Queue a message to run when idle — /queue <text>"
|
|
@@ -117485,12 +117529,22 @@ function defaultFactory(token) {
|
|
|
117485
117529
|
onMessage(handler) {
|
|
117486
117530
|
bot.on("message", (ctx) => handler(ctx.message));
|
|
117487
117531
|
},
|
|
117532
|
+
onCallbackQuery(handler) {
|
|
117533
|
+
bot.on("callback_query:data", (ctx) => handler(ctx.callbackQuery));
|
|
117534
|
+
},
|
|
117488
117535
|
start(onStart) {
|
|
117489
117536
|
bot.start(onStart ? { onStart: () => onStart() } : void 0);
|
|
117490
117537
|
},
|
|
117491
117538
|
stop: () => bot.stop()
|
|
117492
117539
|
};
|
|
117493
117540
|
}
|
|
117541
|
+
/** Map our button rows to grammY's `InlineKeyboardMarkup` wire shape. */
|
|
117542
|
+
function toInlineKeyboard(buttons) {
|
|
117543
|
+
return { inline_keyboard: buttons.map((row) => row.map((b) => ({
|
|
117544
|
+
text: b.text,
|
|
117545
|
+
callback_data: b.callbackData
|
|
117546
|
+
}))) };
|
|
117547
|
+
}
|
|
117494
117548
|
const DEFAULT_MAX_ATTACHMENT_BYTES = 8 * 1024 * 1024;
|
|
117495
117549
|
/** Cap on admins enriched per listMembers call (bounds the response). */
|
|
117496
117550
|
const MEMBER_CAP = 50;
|
|
@@ -117526,6 +117580,19 @@ var TelegramConnection = class {
|
|
|
117526
117580
|
log?.debug(`telegram: inbound ch=${msg.channel} user=${msg.sender.id} isBot=${msg.sender.isBot} isDm=${msg.isDm} topic=${msg.telegramTopicId ?? "-"} root=${msg.telegramThreadRoot ?? "-"} replyTo=${msg.replyTo ?? "-"} mentions=[${msg.mentionedBots.join(",")}] text=${JSON.stringify(msg.text.slice(0, 80))}`);
|
|
117527
117581
|
this.deps.onMessage(msg);
|
|
117528
117582
|
});
|
|
117583
|
+
this.bot.onCallbackQuery((q) => {
|
|
117584
|
+
if (!q.message || q.data == null) return;
|
|
117585
|
+
const cb = {
|
|
117586
|
+
id: q.id,
|
|
117587
|
+
data: q.data,
|
|
117588
|
+
channel: String(q.message.chat.id),
|
|
117589
|
+
messageId: q.message.message_id,
|
|
117590
|
+
userId: q.from ? String(q.from.id) : "unknown",
|
|
117591
|
+
...q.message.message_thread_id != null ? { topicId: String(q.message.message_thread_id) } : {}
|
|
117592
|
+
};
|
|
117593
|
+
log?.debug(`telegram: callback ch=${cb.channel} user=${cb.userId} data=${JSON.stringify(cb.data)}`);
|
|
117594
|
+
this.deps.onCallback?.(cb);
|
|
117595
|
+
});
|
|
117529
117596
|
try {
|
|
117530
117597
|
await this.bot.api.setMyCommands(BOT_COMMANDS);
|
|
117531
117598
|
log?.debug("telegram: setMyCommands ok");
|
|
@@ -117581,6 +117648,53 @@ var TelegramConnection = class {
|
|
|
117581
117648
|
}
|
|
117582
117649
|
});
|
|
117583
117650
|
}
|
|
117651
|
+
/**
|
|
117652
|
+
* Post an interactive "card": a message carrying an inline keyboard (rows of tappable
|
|
117653
|
+
* buttons) — the session-control pickers (/models, /effort, /permission). Best-effort;
|
|
117654
|
+
* returns the new message id so the card can be edited in place after a tap.
|
|
117655
|
+
*/
|
|
117656
|
+
async postCard(channel, text, buttons, opts = {}) {
|
|
117657
|
+
const thread = opts.threadTs != null && /^\d+$/.test(opts.threadTs) ? Number(opts.threadTs) : void 0;
|
|
117658
|
+
return this.queue.enqueue(async () => {
|
|
117659
|
+
try {
|
|
117660
|
+
const res = await this.bot.api.sendMessage(channel, text, {
|
|
117661
|
+
...thread !== void 0 ? { message_thread_id: thread } : {},
|
|
117662
|
+
...opts.parseMode ? { parse_mode: opts.parseMode } : {},
|
|
117663
|
+
...opts.replyTo !== void 0 ? { reply_parameters: {
|
|
117664
|
+
message_id: opts.replyTo,
|
|
117665
|
+
allow_sending_without_reply: true
|
|
117666
|
+
} } : {},
|
|
117667
|
+
reply_markup: toInlineKeyboard(buttons)
|
|
117668
|
+
});
|
|
117669
|
+
return res?.message_id != null ? String(res.message_id) : void 0;
|
|
117670
|
+
} catch (err) {
|
|
117671
|
+
this.deps.log?.debug(`telegram: sendMessage (card) failed (ch=${channel}): ${err.message}`);
|
|
117672
|
+
return;
|
|
117673
|
+
}
|
|
117674
|
+
});
|
|
117675
|
+
}
|
|
117676
|
+
/** Re-render a card in place (new text + keyboard) after a tap. Best-effort. */
|
|
117677
|
+
async editCard(channel, messageId, text, buttons, opts = {}) {
|
|
117678
|
+
await this.queue.enqueue(async () => {
|
|
117679
|
+
try {
|
|
117680
|
+
await this.bot.api.editMessageText(channel, messageId, text, {
|
|
117681
|
+
...opts.parseMode ? { parse_mode: opts.parseMode } : {},
|
|
117682
|
+
reply_markup: toInlineKeyboard(buttons)
|
|
117683
|
+
});
|
|
117684
|
+
} catch (err) {
|
|
117685
|
+
this.deps.log?.debug(`telegram: editMessageText (card) failed (ch=${channel} id=${messageId}): ${err.message}`);
|
|
117686
|
+
}
|
|
117687
|
+
});
|
|
117688
|
+
}
|
|
117689
|
+
/** Acknowledge an inline-keyboard tap (clears the client's loading spinner; the
|
|
117690
|
+
* optional `text` shows as a transient toast). Not queued — a quick standalone ack. */
|
|
117691
|
+
async answerCallback(callbackId, text) {
|
|
117692
|
+
try {
|
|
117693
|
+
await this.bot.api.answerCallbackQuery(callbackId, text ? { text } : void 0);
|
|
117694
|
+
} catch (err) {
|
|
117695
|
+
this.deps.log?.debug(`telegram: answerCallbackQuery failed: ${err.message}`);
|
|
117696
|
+
}
|
|
117697
|
+
}
|
|
117584
117698
|
/** Edit a previously-posted message in place (editMessageText). Best-effort. */
|
|
117585
117699
|
async updateMessage(channel, messageId, text, opts = {}) {
|
|
117586
117700
|
await this.queue.enqueue(async () => {
|
|
@@ -136307,6 +136421,16 @@ function formatErr(err) {
|
|
|
136307
136421
|
return e?.stack ?? String(err);
|
|
136308
136422
|
}
|
|
136309
136423
|
const MAX_QUEUED_PER_SESSION = 10;
|
|
136424
|
+
const SELECT_KIND_CODE = {
|
|
136425
|
+
model: "m",
|
|
136426
|
+
effort: "e",
|
|
136427
|
+
permission: "p"
|
|
136428
|
+
};
|
|
136429
|
+
const SELECT_CODE_KIND = {
|
|
136430
|
+
m: "model",
|
|
136431
|
+
e: "effort",
|
|
136432
|
+
p: "permission"
|
|
136433
|
+
};
|
|
136310
136434
|
const WEBCHAT_CHUNK_BYTES = 32 * 1024;
|
|
136311
136435
|
/** Split `text` into pieces whose UTF-8 byte length each stays under WEBCHAT_CHUNK_BYTES
|
|
136312
136436
|
* so no single webchat/output frame exceeds the 256 KiB cap. Splits on byte budget by
|
|
@@ -136801,6 +136925,7 @@ var Daemon = class Daemon {
|
|
|
136801
136925
|
group,
|
|
136802
136926
|
newTraceId: () => randomUUID(),
|
|
136803
136927
|
onMessage: (msg) => this.onInbound(msg),
|
|
136928
|
+
onCallback: (cb) => this.handleTelegramCallback(cb, conn),
|
|
136804
136929
|
log: this.log
|
|
136805
136930
|
});
|
|
136806
136931
|
try {
|
|
@@ -137280,28 +137405,59 @@ var Daemon = class Daemon {
|
|
|
137280
137405
|
}
|
|
137281
137406
|
queued = /* @__PURE__ */ new Map();
|
|
137282
137407
|
/**
|
|
137408
|
+
* Resolve a command's target from the channel's latest session when the routing ladder
|
|
137409
|
+
* couldn't (no mention entity / thread / dm rule matched — e.g. a group `/status@bot`).
|
|
137410
|
+
* Picks the agent that owns the most-recent session in the channel and its integration
|
|
137411
|
+
* for this platform, enforcing that integration's `allowedUserIds` so a command can't
|
|
137412
|
+
* bypass the authz the routing rules would have applied. Null when there's no session,
|
|
137413
|
+
* no matching integration, or the sender isn't allowed.
|
|
137414
|
+
*/
|
|
137415
|
+
resolveCommandTargetFromLatest(msg) {
|
|
137416
|
+
const latest = this.store.latestSessionInChannel(msg.channel);
|
|
137417
|
+
if (!latest) return null;
|
|
137418
|
+
const integ = this.agents.get(latest.agentId)?.integrations.find((i) => i.platform === msg.platform);
|
|
137419
|
+
if (!integ) return null;
|
|
137420
|
+
const allowed = integ.platform === "telegram" ? integ.telegram.allowedUserIds : integ.slack.allowedUserIds;
|
|
137421
|
+
if (allowed.length > 0 && !allowed.includes(msg.sender.id)) return null;
|
|
137422
|
+
return {
|
|
137423
|
+
agentId: latest.agentId,
|
|
137424
|
+
integrationId: integ.id,
|
|
137425
|
+
via: "thread"
|
|
137426
|
+
};
|
|
137427
|
+
}
|
|
137428
|
+
/**
|
|
137283
137429
|
* Handle an in-conversation control command. Resolves the target agent via the
|
|
137284
137430
|
* same routing ladder as a normal message (so thread-affinity + per-integration
|
|
137285
137431
|
* `allowedUserIds` authz apply), then acts on that agent's session in this
|
|
137286
137432
|
* (channel, thread).
|
|
137287
137433
|
*/
|
|
137288
137434
|
handleCommand(command, msg) {
|
|
137289
|
-
|
|
137435
|
+
let target = routeRules(msg, this.mergedRules(), (c, t) => this.sessions.threadOwner(c, t));
|
|
137436
|
+
if (!target) target = this.resolveCommandTargetFromLatest(msg);
|
|
137290
137437
|
if (!target) {
|
|
137291
137438
|
this.log.debug(`command: '${command.kind}' in ch=${msg.channel} — no agent resolved, ignoring`);
|
|
137292
137439
|
return;
|
|
137293
137440
|
}
|
|
137294
137441
|
const conn = this.replyConnFor(target.agentId, target.integrationId);
|
|
137295
|
-
const
|
|
137296
|
-
|
|
137297
|
-
|
|
137442
|
+
const replyThread = msg.thread ?? msg.msgId;
|
|
137443
|
+
let thread = replyThread;
|
|
137444
|
+
let key = sessionKey(msg.platform, msg.channel, thread, target.agentId);
|
|
137445
|
+
let rec = this.store.getSession(key);
|
|
137446
|
+
if (!rec) {
|
|
137447
|
+
const latest = this.store.latestSession(target.agentId, msg.channel);
|
|
137448
|
+
if (latest) {
|
|
137449
|
+
rec = latest;
|
|
137450
|
+
key = latest.key;
|
|
137451
|
+
thread = latest.thread;
|
|
137452
|
+
}
|
|
137453
|
+
}
|
|
137298
137454
|
const acpSessionId = rec?.acpSessionId;
|
|
137299
137455
|
const inflight = !!(acpSessionId && this.pending.has(acpSessionId));
|
|
137300
137456
|
const tgReplyTo = this.telegramReplyTarget(msg);
|
|
137301
137457
|
const reply = (text) => {
|
|
137302
137458
|
if (!conn) return;
|
|
137303
|
-
if (msg.platform === "telegram") conn.postMessage(msg.channel, text,
|
|
137304
|
-
else conn.postMessage(msg.channel, text,
|
|
137459
|
+
if (msg.platform === "telegram") conn.postMessage(msg.channel, text, replyThread, tgReplyTo);
|
|
137460
|
+
else conn.postMessage(msg.channel, text, replyThread);
|
|
137305
137461
|
};
|
|
137306
137462
|
if (command.kind === "stop") {
|
|
137307
137463
|
if (rec) this.store.setSessionMuted(key, true);
|
|
@@ -137332,7 +137488,7 @@ var Daemon = class Daemon {
|
|
|
137332
137488
|
const link = acpSessionId ? this.sessionLink(acpSessionId) : void 0;
|
|
137333
137489
|
if (msg.platform === "telegram") conn?.postChrome(msg.channel, renderStatusReply(info, link), {
|
|
137334
137490
|
parseMode: "HTML",
|
|
137335
|
-
threadTs:
|
|
137491
|
+
threadTs: replyThread,
|
|
137336
137492
|
replyTo: tgReplyTo
|
|
137337
137493
|
});
|
|
137338
137494
|
else reply(link ? `${renderStatusBar(info)} · <${link}|View session>` : renderStatusBar(info));
|
|
@@ -137351,13 +137507,26 @@ var Daemon = class Daemon {
|
|
|
137351
137507
|
reply(command.enable ? "⚡ Fast mode on." : "🐢 Fast mode off.");
|
|
137352
137508
|
return;
|
|
137353
137509
|
}
|
|
137510
|
+
if (command.kind === "model" || command.kind === "effort" || command.kind === "permission") {
|
|
137511
|
+
if (!rec) {
|
|
137512
|
+
reply("No active session here to configure.");
|
|
137513
|
+
return;
|
|
137514
|
+
}
|
|
137515
|
+
const card = msg.platform === "telegram" && conn ? (text, buttons) => void conn.postCard(msg.channel, text, buttons, {
|
|
137516
|
+
threadTs: replyThread,
|
|
137517
|
+
replyTo: tgReplyTo
|
|
137518
|
+
}) : void 0;
|
|
137519
|
+
this.handleSelectCommand(command.kind, command.value, target.agentId, key, acpSessionId ?? void 0, reply, card);
|
|
137520
|
+
return;
|
|
137521
|
+
}
|
|
137354
137522
|
if (!command.text) {
|
|
137355
137523
|
reply("Usage: `!queue <message>` — runs when the current turn finishes.");
|
|
137356
137524
|
return;
|
|
137357
137525
|
}
|
|
137358
137526
|
const payload = {
|
|
137359
137527
|
...msg,
|
|
137360
|
-
text: command.text
|
|
137528
|
+
text: command.text,
|
|
137529
|
+
thread
|
|
137361
137530
|
};
|
|
137362
137531
|
if (!inflight) {
|
|
137363
137532
|
this.log.info(`command: queue → agent "${target.agentId}" idle, dispatching now`);
|
|
@@ -137375,6 +137544,134 @@ var Daemon = class Daemon {
|
|
|
137375
137544
|
this.log.info(`command: queue → agent "${target.agentId}" session ${acpSessionId} (depth ${q.length})`);
|
|
137376
137545
|
reply(`📥 Queued (#${q.length}) — will run when the current turn finishes.`);
|
|
137377
137546
|
}
|
|
137547
|
+
selectLabel(kind) {
|
|
137548
|
+
return kind === "model" ? "Model" : kind === "effort" ? "Reasoning effort" : "Permission mode";
|
|
137549
|
+
}
|
|
137550
|
+
/** Current value + selectable options for a select kind, from a status snapshot. */
|
|
137551
|
+
selectOptions(kind, info) {
|
|
137552
|
+
if (kind === "model") return {
|
|
137553
|
+
current: info.model,
|
|
137554
|
+
options: info.models ?? []
|
|
137555
|
+
};
|
|
137556
|
+
if (kind === "effort") return {
|
|
137557
|
+
current: info.effort,
|
|
137558
|
+
options: info.efforts ?? []
|
|
137559
|
+
};
|
|
137560
|
+
return {
|
|
137561
|
+
current: info.permissionMode,
|
|
137562
|
+
options: info.permissionModes ?? []
|
|
137563
|
+
};
|
|
137564
|
+
}
|
|
137565
|
+
/** Apply a resolved select value to a session key via the matching sticky-override setter. */
|
|
137566
|
+
applySelect(kind, key, value) {
|
|
137567
|
+
if (kind === "model") this.setModelByKey(key, value);
|
|
137568
|
+
else if (kind === "effort") this.setEffortByKey(key, value);
|
|
137569
|
+
else this.setPermissionModeByKey(key, value);
|
|
137570
|
+
}
|
|
137571
|
+
/** Build a session-control card: a header line + one tappable button per option (the
|
|
137572
|
+
* current one flagged), with `callback_data` = `<kindCode>:<optionIndex>`. */
|
|
137573
|
+
buildSelectCard(kind, current, options) {
|
|
137574
|
+
const code = SELECT_KIND_CODE[kind];
|
|
137575
|
+
const buttons = options.map((o, i) => [{
|
|
137576
|
+
text: `${o === current ? "✅ " : ""}${o}`,
|
|
137577
|
+
callbackData: `${code}:${i}`
|
|
137578
|
+
}]);
|
|
137579
|
+
return {
|
|
137580
|
+
text: `${this.selectLabel(kind)} — tap to switch (current: ${current ?? "default"}):`,
|
|
137581
|
+
buttons
|
|
137582
|
+
};
|
|
137583
|
+
}
|
|
137584
|
+
/**
|
|
137585
|
+
* Back the `/models` `/effort` `/permission` commands. A bare command lists the current
|
|
137586
|
+
* selectable values — as a tappable inline-keyboard card when `card` is provided
|
|
137587
|
+
* (Telegram), else a numbered text list. An argument applies a choice, matched by exact
|
|
137588
|
+
* id, unique case-insensitive substring, or 1-based list index. Options come from the
|
|
137589
|
+
* live host's config selectors (statusInfoFrom); when the host is cold a given value is
|
|
137590
|
+
* accepted optimistically and takes effect on the next turn.
|
|
137591
|
+
*/
|
|
137592
|
+
handleSelectCommand(kind, value, agentId, key, acpSessionId, reply, card) {
|
|
137593
|
+
const label = this.selectLabel(kind);
|
|
137594
|
+
const info = this.statusInfoFrom(agentId, key, acpSessionId);
|
|
137595
|
+
const { current, options } = this.selectOptions(kind, info);
|
|
137596
|
+
const cmd = kind === "model" ? "models" : kind;
|
|
137597
|
+
if (value === null) {
|
|
137598
|
+
if (options.length === 0) {
|
|
137599
|
+
reply(current ? `${label}: ${current} (no other options offered${kind === "effort" ? " — the current model may not support effort" : ""}).` : `No ${label.toLowerCase()} options available yet — send me a message first, then try /${cmd} again.`);
|
|
137600
|
+
return;
|
|
137601
|
+
}
|
|
137602
|
+
if (card) {
|
|
137603
|
+
const { text, buttons } = this.buildSelectCard(kind, current, options);
|
|
137604
|
+
card(text, buttons);
|
|
137605
|
+
return;
|
|
137606
|
+
}
|
|
137607
|
+
reply(`${label} — reply \`/${cmd} <name or number>\`:\n${options.map((o, i) => `${i + 1}. ${o}${o === current ? " ✓ (current)" : ""}`).join("\n")}`);
|
|
137608
|
+
return;
|
|
137609
|
+
}
|
|
137610
|
+
let resolved;
|
|
137611
|
+
if (options.length === 0) resolved = value.trim();
|
|
137612
|
+
else {
|
|
137613
|
+
const v = value.trim();
|
|
137614
|
+
const idx = Number(v);
|
|
137615
|
+
if (Number.isInteger(idx) && idx >= 1 && idx <= options.length) resolved = options[idx - 1];
|
|
137616
|
+
else {
|
|
137617
|
+
const lc = v.toLowerCase();
|
|
137618
|
+
resolved = options.find((o) => o.toLowerCase() === lc) ?? (options.filter((o) => o.toLowerCase().includes(lc)).length === 1 ? options.find((o) => o.toLowerCase().includes(lc)) : void 0);
|
|
137619
|
+
}
|
|
137620
|
+
}
|
|
137621
|
+
if (!resolved) {
|
|
137622
|
+
reply(`Unknown ${label.toLowerCase()} "${value.trim()}".${options.length ? ` Options: ${options.join(", ")}` : ""}`);
|
|
137623
|
+
return;
|
|
137624
|
+
}
|
|
137625
|
+
this.applySelect(kind, key, resolved);
|
|
137626
|
+
reply(options.length === 0 ? `${label} set to ${resolved} — applies on your next message.` : `✅ ${label} set to ${resolved}.`);
|
|
137627
|
+
}
|
|
137628
|
+
/**
|
|
137629
|
+
* Handle a tapped session-control card button (Telegram inline keyboard). Decodes
|
|
137630
|
+
* `<kindCode>:<optionIndex>`, resolves the channel's latest session (with the same
|
|
137631
|
+
* allowedUserIds authz as a command), applies the picked value, acks the tap, and
|
|
137632
|
+
* re-renders the card with the new current marked. Best-effort throughout — a tap
|
|
137633
|
+
* must never throw out of the update pump.
|
|
137634
|
+
*/
|
|
137635
|
+
handleTelegramCallback(cb, conn) {
|
|
137636
|
+
const m = /^([mep]):(\d+)$/.exec(cb.data);
|
|
137637
|
+
if (!m) {
|
|
137638
|
+
conn.answerCallback(cb.id);
|
|
137639
|
+
return;
|
|
137640
|
+
}
|
|
137641
|
+
const kind = SELECT_CODE_KIND[m[1]];
|
|
137642
|
+
const idx = Number(m[2]);
|
|
137643
|
+
const session = this.commandSessionForLatest(cb.channel, cb.userId);
|
|
137644
|
+
if (!session) {
|
|
137645
|
+
conn.answerCallback(cb.id, "No active session here.");
|
|
137646
|
+
return;
|
|
137647
|
+
}
|
|
137648
|
+
const info = this.statusInfoFrom(session.agentId, session.key, session.acpSessionId);
|
|
137649
|
+
const { options } = this.selectOptions(kind, info);
|
|
137650
|
+
const value = options[idx];
|
|
137651
|
+
if (value === void 0) {
|
|
137652
|
+
conn.answerCallback(cb.id, "Options changed — reopen the menu.");
|
|
137653
|
+
return;
|
|
137654
|
+
}
|
|
137655
|
+
this.applySelect(kind, session.key, value);
|
|
137656
|
+
conn.answerCallback(cb.id, `${this.selectLabel(kind)} → ${value}`);
|
|
137657
|
+
const { text, buttons } = this.buildSelectCard(kind, value, options);
|
|
137658
|
+
conn.editCard(cb.channel, cb.messageId, text, buttons);
|
|
137659
|
+
}
|
|
137660
|
+
/** The channel's latest session for a Telegram command/callback, gated by the agent's
|
|
137661
|
+
* telegram integration allowedUserIds. Null when there's no session, no telegram
|
|
137662
|
+
* integration, or the user isn't allowed. */
|
|
137663
|
+
commandSessionForLatest(channel, userId) {
|
|
137664
|
+
const latest = this.store.latestSessionInChannel(channel);
|
|
137665
|
+
if (!latest) return null;
|
|
137666
|
+
const integ = this.agents.get(latest.agentId)?.integrations.find((i) => i.platform === "telegram");
|
|
137667
|
+
if (!integ || integ.platform !== "telegram") return null;
|
|
137668
|
+
if (integ.telegram.allowedUserIds.length > 0 && !integ.telegram.allowedUserIds.includes(userId)) return null;
|
|
137669
|
+
return {
|
|
137670
|
+
agentId: latest.agentId,
|
|
137671
|
+
key: latest.key,
|
|
137672
|
+
acpSessionId: latest.acpSessionId ?? void 0
|
|
137673
|
+
};
|
|
137674
|
+
}
|
|
137378
137675
|
/** Drain one buffered message for a session whose turn just finished (FIFO). */
|
|
137379
137676
|
flushQueued(agentId, sessionId, integrationId) {
|
|
137380
137677
|
const q = this.queued.get(sessionId);
|
|
@@ -138637,4 +138934,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
138637
138934
|
//#endregion
|
|
138638
138935
|
export { ClientTransport as n, runForeground as t };
|
|
138639
138936
|
|
|
138640
|
-
//# sourceMappingURL=run-foreground-
|
|
138937
|
+
//# sourceMappingURL=run-foreground-DN1BSygk.js.map
|