@agentconnect.md/daemon 1.0.0-rc.271 → 1.0.0-rc.273
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/{acp-host-Da9dMauw.js → acp-host-BfCc6AlK.js} +24 -6
- package/dist/{acp-host-Da9dMauw.js.map → acp-host-BfCc6AlK.js.map} +1 -1
- package/dist/{chat-C5exDWtC.js → chat-BfgzfiG5.js} +2 -2
- package/dist/{chat-C5exDWtC.js.map → chat-BfgzfiG5.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/{login-FfGeGEde.js → login-BvyLgncV.js} +2 -2
- package/dist/{login-FfGeGEde.js.map → login-BvyLgncV.js.map} +1 -1
- package/dist/run-foreground-CXCTj8h-.js +2 -0
- package/dist/{run-foreground-C4qribod.js → run-foreground-CcV3IOs2.js} +342 -2
- package/dist/{run-foreground-C4qribod.js.map → run-foreground-CcV3IOs2.js.map} +1 -1
- package/package.json +1 -1
- package/dist/run-foreground-B-RDdyh7.js +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as agentChildEnv, i as selectAgent, o as loadConfig } from "./load-agents-Amic2B8b.js";
|
|
2
2
|
import { l as resolveRoot } from "./paths-DEcUO8lC.js";
|
|
3
|
-
import { S as resolveRuntimes, d as memoryProviderFor, i as prepareWorkspace, t as AcpHost } from "./acp-host-
|
|
3
|
+
import { S as resolveRuntimes, d as memoryProviderFor, i as prepareWorkspace, t as AcpHost } from "./acp-host-BfCc6AlK.js";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
//#region src/cli/chat.ts
|
|
6
6
|
function renderUpdate(out) {
|
|
@@ -79,4 +79,4 @@ async function runChat(opts) {
|
|
|
79
79
|
//#endregion
|
|
80
80
|
export { renderUpdate, runChat };
|
|
81
81
|
|
|
82
|
-
//# sourceMappingURL=chat-
|
|
82
|
+
//# sourceMappingURL=chat-BfgzfiG5.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-
|
|
1
|
+
{"version":3,"file":"chat-BfgzfiG5.js","names":[],"sources":["../src/cli/chat.ts"],"sourcesContent":["import { createInterface } from 'node:readline'\nimport type { ContentBlock, SessionUpdate } from '@agentclientprotocol/sdk'\nimport { loadConfig } from '../config/load-config.js'\nimport { resolveRuntimes } from '../runtimes/registry.js'\nimport { selectAgent } from '../agents/load-agents.js'\nimport { agentChildEnv } from '../agents/agent-env.js'\nimport { memoryProviderFor } from '../agents/memory-provider.js'\nimport { prepareWorkspace } from '../workspace/workspace-manager.js'\nimport { AcpHost } from '../acp/acp-host.js'\nimport { resolveRoot } from '../paths.js'\n\nexport function renderUpdate(out: NodeJS.WritableStream) {\n return (_sessionId: string, update: SessionUpdate): void => {\n if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {\n out.write(update.content.text)\n } else if (update.sessionUpdate === 'tool_call') {\n const title = ('title' in update && update.title) || update.toolCallId || 'tool'\n out.write(`\\n[tool] ${title}\\n`)\n }\n }\n}\n\nexport interface RunChatOpts {\n agentsDir?: string\n agentName?: string\n message?: string\n configPath?: string\n root?: string\n out?: NodeJS.WritableStream\n input?: NodeJS.ReadableStream\n}\n\nexport async function runChat(opts: RunChatOpts): Promise<void> {\n const out = opts.out ?? process.stdout\n const root = resolveRoot(opts.root)\n const cfg = loadConfig({\n root: opts.root,\n configPath: opts.configPath,\n optional: true,\n overrides: { agentsDir: opts.agentsDir }\n })\n const agent = selectAgent(cfg.agentsDir!, opts.agentName)\n\n const runtimes = await resolveRuntimes(cfg, root, { neededRuntimes: [agent.runtime], mode: 'cache-first' })\n const runtime = runtimes[agent.runtime]\n if (!runtime) {\n const available = Object.keys(runtimes).sort().join(', ') || '(none)'\n throw new Error(`runtime \"${agent.runtime}\" not found. Available: ${available}`)\n }\n\n const host = new AcpHost(runtime, {\n onUpdate: renderUpdate(out),\n // Memory-backend env for parity with the daemon (managed disables the runtime's\n // own memory; native redirects it under the agent root).\n env: { ...agentChildEnv(agent), ...memoryProviderFor(agent, runtime).runtimeEnv() },\n configPrefs: {\n model: agent.runtimeOverrides?.model,\n permissionMode: agent.permissionMode,\n reasoningEffort: agent.reasoningEffort,\n fastMode: agent.fastMode\n }\n })\n await host.start()\n try {\n const cwd = await prepareWorkspace(agent)\n const sessionId = await host.newSession(cwd)\n\n const send = async (text: string) => {\n const blocks: ContentBlock[] = [{ type: 'text', text }]\n await host.prompt(sessionId, blocks)\n out.write('\\n')\n }\n\n if (typeof opts.message === 'string' && opts.message.length > 0) {\n await send(opts.message)\n return\n }\n\n const rl = createInterface({ input: opts.input ?? process.stdin })\n out.write(`Chatting with ${agent.name} (${agent.id}). Type .exit to quit.\\n> `)\n try {\n for await (const line of rl) {\n const text = line.trim()\n if (text === '.exit' || text === '.quit') break\n if (text.length > 0) await send(text)\n out.write('> ')\n }\n } finally {\n rl.close()\n }\n } finally {\n await host.stop()\n }\n}\n"],"mappings":";;;;;AAWA,SAAgB,aAAa,KAA4B;CACvD,QAAQ,YAAoB,WAAgC;EAC1D,IAAI,OAAO,kBAAkB,yBAAyB,OAAO,QAAQ,SAAS,QAC5E,IAAI,MAAM,OAAO,QAAQ,IAAI;OACxB,IAAI,OAAO,kBAAkB,aAAa;GAC/C,MAAM,QAAS,WAAW,UAAU,OAAO,SAAU,OAAO,cAAc;GAC1E,IAAI,MAAM,YAAY,MAAM,GAAG;EACjC;CACF;AACF;AAYA,eAAsB,QAAQ,MAAkC;CAC9D,MAAM,MAAM,KAAK,OAAO,QAAQ;CAChC,MAAM,OAAO,YAAY,KAAK,IAAI;CAClC,MAAM,MAAM,WAAW;EACrB,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,UAAU;EACV,WAAW,EAAE,WAAW,KAAK,UAAU;CACzC,CAAC;CACD,MAAM,QAAQ,YAAY,IAAI,WAAY,KAAK,SAAS;CAExD,MAAM,WAAW,MAAM,gBAAgB,KAAK,MAAM;EAAE,gBAAgB,CAAC,MAAM,OAAO;EAAG,MAAM;CAAc,CAAC;CAC1G,MAAM,UAAU,SAAS,MAAM;CAC/B,IAAI,CAAC,SAAS;EACZ,MAAM,YAAY,OAAO,KAAK,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,KAAK;EAC7D,MAAM,IAAI,MAAM,YAAY,MAAM,QAAQ,0BAA0B,WAAW;CACjF;CAEA,MAAM,OAAO,IAAI,QAAQ,SAAS;EAChC,UAAU,aAAa,GAAG;EAG1B,KAAK;GAAE,GAAG,cAAc,KAAK;GAAG,GAAG,kBAAkB,OAAO,OAAO,CAAC,CAAC,WAAW;EAAE;EAClF,aAAa;GACX,OAAO,MAAM,kBAAkB;GAC/B,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACvB,UAAU,MAAM;EAClB;CACF,CAAC;CACD,MAAM,KAAK,MAAM;CACjB,IAAI;EACF,MAAM,MAAM,MAAM,iBAAiB,KAAK;EACxC,MAAM,YAAY,MAAM,KAAK,WAAW,GAAG;EAE3C,MAAM,OAAO,OAAO,SAAiB;GACnC,MAAM,SAAyB,CAAC;IAAE,MAAM;IAAQ;GAAK,CAAC;GACtD,MAAM,KAAK,OAAO,WAAW,MAAM;GACnC,IAAI,MAAM,IAAI;EAChB;EAEA,IAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,SAAS,GAAG;GAC/D,MAAM,KAAK,KAAK,OAAO;GACvB;EACF;EAEA,MAAM,KAAK,gBAAgB,EAAE,OAAO,KAAK,SAAS,QAAQ,MAAM,CAAC;EACjE,IAAI,MAAM,iBAAiB,MAAM,KAAK,IAAI,MAAM,GAAG,2BAA2B;EAC9E,IAAI;GACF,WAAW,MAAM,QAAQ,IAAI;IAC3B,MAAM,OAAO,KAAK,KAAK;IACvB,IAAI,SAAS,WAAW,SAAS,SAAS;IAC1C,IAAI,KAAK,SAAS,GAAG,MAAM,KAAK,IAAI;IACpC,IAAI,MAAM,IAAI;GAChB;EACF,UAAU;GACR,GAAG,MAAM;EACX;CACF,UAAU;EACR,MAAM,KAAK,KAAK;CAClB;AACF"}
|
package/dist/index.js
CHANGED
|
@@ -68492,9 +68492,9 @@ const telemetry = startDaemonOpenTelemetry({ serviceVersion: DAEMON_VERSION });
|
|
|
68492
68492
|
const [{ Command }, { fileURLToPath }, { runChat }, { runLogin }, { runForeground }, { resolveController }, { acquireSingletonLock }, { resolveRoot }] = await Promise.all([
|
|
68493
68493
|
import("./commander-B5QVEFzH.js"),
|
|
68494
68494
|
import("node:url"),
|
|
68495
|
-
import("./chat-
|
|
68496
|
-
import("./login-
|
|
68497
|
-
import("./run-foreground-
|
|
68495
|
+
import("./chat-BfgzfiG5.js"),
|
|
68496
|
+
import("./login-BvyLgncV.js"),
|
|
68497
|
+
import("./run-foreground-CXCTj8h-.js"),
|
|
68498
68498
|
import("./service-6w0WKhdx.js"),
|
|
68499
68499
|
import("./lock-CA9Bm8_s.js"),
|
|
68500
68500
|
import("./paths-CcRUjFjy.js")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
2
2
|
import { _ as buildEnvelope, l as ConfigSchema, u as isFrame, v as decodeEnvelope, y as encode } from "./load-agents-Amic2B8b.js";
|
|
3
|
-
import { i as ClientTransport, t as runForeground } from "./run-foreground-
|
|
3
|
+
import { i as ClientTransport, t as runForeground } from "./run-foreground-CcV3IOs2.js";
|
|
4
4
|
import { l as resolveRoot, t as configPath } from "./paths-DEcUO8lC.js";
|
|
5
5
|
import { n as resolveController } from "./service-BQVw7wbz.js";
|
|
6
6
|
import { dirname } from "node:path";
|
|
@@ -228,4 +228,4 @@ async function runLogin(opts, partial = {}) {
|
|
|
228
228
|
//#endregion
|
|
229
229
|
export { buildInstallOpts, persistCredentials, runLogin };
|
|
230
230
|
|
|
231
|
-
//# sourceMappingURL=login-
|
|
231
|
+
//# sourceMappingURL=login-BvyLgncV.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-FfGeGEde.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-BvyLgncV.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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"}
|
|
@@ -5,7 +5,7 @@ import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
|
5
5
|
import { C as _coercedNumber, T as NEVER, a as array, d as literal, f as looseObject, i as any, m as object, o as boolean, p as number$1, t as ZodNumber, v as string, x as url$4 } from "./schemas-CQkO9hC5.js";
|
|
6
6
|
import { C as ZodIssueCode, S as RESERVED_MCP_SERVER_NAME, _ as buildEnvelope, a as agentChildEnv, b as MAX_FRAME_BYTES, c as persistRelays, d as normalizeGitUrl, f as RELAY_DAEMON_SUBPROTOCOL, g as SHARED_CONFIG_ACTION_ID, h as decodeRelayDaemonFrame, i as selectAgent, m as buildRelayDaemonFrame, n as findAgentFiles, o as loadConfig, p as RELAY_DAEMON_WS_PATH, r as loadAgents, s as persistDaemonId, v as decodeEnvelope, y as encode } from "./load-agents-Amic2B8b.js";
|
|
7
7
|
import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
|
|
8
|
-
import { S as resolveRuntimes, _ as writeMemoryFile, a as initGitInjection, b as cachedRuntimeNames, c as simpleGit, d as memoryProviderFor, f as MemoryConflictError, g as resolveInMemoryDir, h as listMemory, i as prepareWorkspace, l as createMemoryProvider, m as MemoryTooLargeError, n as installedRuntimes, o as probeGitVersion, p as MemoryPathError, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as memoryKindOf, v as MEMORY_TOOL_NAMES, x as cachedRuntimeVersions, y as toolsForIntegrations } from "./acp-host-
|
|
8
|
+
import { S as resolveRuntimes, _ as writeMemoryFile, a as initGitInjection, b as cachedRuntimeNames, c as simpleGit, d as memoryProviderFor, f as MemoryConflictError, g as resolveInMemoryDir, h as listMemory, i as prepareWorkspace, l as createMemoryProvider, m as MemoryTooLargeError, n as installedRuntimes, o as probeGitVersion, p as MemoryPathError, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as memoryKindOf, v as MEMORY_TOOL_NAMES, x as cachedRuntimeVersions, y as toolsForIntegrations } from "./acp-host-BfCc6AlK.js";
|
|
9
9
|
import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-CMC80O_1.js";
|
|
10
10
|
import { B as ToolListChangedNotificationSchema, D as ListPromptsResultSchema, E as ListChangedOptionsBaseSchema, F as PromptListChangedNotificationSchema, G as isZ4Schema, H as isJSONRPCRequest, I as ReadResourceResultSchema, J as encodeFrame, K as safeParse, L as ResourceListChangedNotificationSchema, M as ListToolsResultSchema, O as ListResourceTemplatesResultSchema, P as McpError, R as SUPPORTED_PROTOCOL_VERSIONS, S as InitializeResultSchema, T as LATEST_PROTOCOL_VERSION, U as isJSONRPCResultResponse, V as isInitializedNotification, W as getObjectShape, _ as ElicitResultSchema, a as AjvJsonSchemaValidator, b as GetPromptResultSchema, c as mergeCapabilities, d as CompleteResultSchema, f as CreateMessageRequestSchema, g as ElicitRequestSchema, h as CreateTaskResultSchema, i as assertToolsCallTaskCapability, k as ListResourcesResultSchema, m as CreateMessageResultWithToolsSchema, n as serializeMessage, o as require_fast_deep_equal, p as CreateMessageResultSchema, q as decodeFrames, r as assertClientRequestTaskCapability, s as Protocol, t as ReadBuffer, u as CallToolResultSchema, v as EmptyResultSchema, w as JSONRPCMessageSchema, y as ErrorCode } from "./stdio-DnS8WcXi.js";
|
|
11
11
|
import { URL as URL$1 } from "url";
|
|
@@ -67443,6 +67443,189 @@ const STATUS_ACTION = {
|
|
|
67443
67443
|
/** The modal view's callback_id — used if we ever handle a submit (controls apply on
|
|
67444
67444
|
* interaction today, so there's no submit). */
|
|
67445
67445
|
const STATUS_MODAL_CALLBACK = "ac_status_modal";
|
|
67446
|
+
/** action_id prefix for the interactive permission card's option buttons. Each button is
|
|
67447
|
+
* `ac_perm:<index>` (action_ids must be unique within a message); the connection matches
|
|
67448
|
+
* them with a RegExp and reads the choice from the button `value`. */
|
|
67449
|
+
const PERMISSION_ACTION_PREFIX = "ac_perm";
|
|
67450
|
+
/** Encode a permission button `value` as `<requestId>|<optionId>`. `requestId` is
|
|
67451
|
+
* daemon-generated and `|`-free, so {@link decodePermValue} splits on the FIRST `|` —
|
|
67452
|
+
* the agent-owned `optionId` may contain anything. */
|
|
67453
|
+
function encodePermValue(requestId, optionId) {
|
|
67454
|
+
return `${requestId}|${optionId}`;
|
|
67455
|
+
}
|
|
67456
|
+
function decodePermValue(value) {
|
|
67457
|
+
const i = value.indexOf("|");
|
|
67458
|
+
if (i < 0) return null;
|
|
67459
|
+
return {
|
|
67460
|
+
requestId: value.slice(0, i),
|
|
67461
|
+
optionId: value.slice(i + 1)
|
|
67462
|
+
};
|
|
67463
|
+
}
|
|
67464
|
+
/** Human label for the tool a permission request is about. ACP's `toolCall.title` is the
|
|
67465
|
+
* intended display string, but some runtimes (e.g. codex) omit it at request time — fall
|
|
67466
|
+
* back to the tool `kind`, then the `toolCallId`, then a generic phrase. */
|
|
67467
|
+
function permToolLabel(params) {
|
|
67468
|
+
const tc = params.toolCall;
|
|
67469
|
+
return clampTo$2(tc?.title?.trim() || tc?.kind?.trim() || tc?.toolCallId?.trim() || "a tool call", 200);
|
|
67470
|
+
}
|
|
67471
|
+
/** Slack button color for a permission option: green for allow, red for reject, default
|
|
67472
|
+
* (gray) for anything else the runtime might send. */
|
|
67473
|
+
function permOptionStyle(kind) {
|
|
67474
|
+
if (kind === "allow_once" || kind === "allow_always") return "primary";
|
|
67475
|
+
if (kind === "reject_once" || kind === "reject_always") return "danger";
|
|
67476
|
+
}
|
|
67477
|
+
/**
|
|
67478
|
+
* Build the interactive permission-request card: a header naming the tool the agent
|
|
67479
|
+
* wants to run, and an actions row of buttons — one per ACP PermissionOption, green for
|
|
67480
|
+
* allow / red for reject. The choice rides each button `value` (`<requestId>|<optionId>`);
|
|
67481
|
+
* `requestId` ties the click back to the pending ACP request. Options are capped at 5
|
|
67482
|
+
* (Slack renders at most 5 buttons cleanly on one row). Pure — safe to unit-test.
|
|
67483
|
+
*/
|
|
67484
|
+
function buildPermissionCard(requestId, params) {
|
|
67485
|
+
const header = `:lock: *Permission requested* — ${permToolLabel(params)}`;
|
|
67486
|
+
const buttons = params.options.slice(0, 5).map((o, i) => {
|
|
67487
|
+
const style = permOptionStyle(o.kind);
|
|
67488
|
+
return {
|
|
67489
|
+
type: "button",
|
|
67490
|
+
action_id: `${PERMISSION_ACTION_PREFIX}:${i}`,
|
|
67491
|
+
text: {
|
|
67492
|
+
type: "plain_text",
|
|
67493
|
+
text: clampTo$2(o.name, 75),
|
|
67494
|
+
emoji: true
|
|
67495
|
+
},
|
|
67496
|
+
value: encodePermValue(requestId, o.optionId),
|
|
67497
|
+
...style ? { style } : {}
|
|
67498
|
+
};
|
|
67499
|
+
});
|
|
67500
|
+
return [{
|
|
67501
|
+
type: "section",
|
|
67502
|
+
text: {
|
|
67503
|
+
type: "mrkdwn",
|
|
67504
|
+
text: header
|
|
67505
|
+
}
|
|
67506
|
+
}, {
|
|
67507
|
+
type: "actions",
|
|
67508
|
+
elements: buttons
|
|
67509
|
+
}];
|
|
67510
|
+
}
|
|
67511
|
+
/**
|
|
67512
|
+
* Build the RESOLVED permission card (buttons removed) that replaces {@link
|
|
67513
|
+
* buildPermissionCard} in place once a choice is made — or when the turn is cancelled.
|
|
67514
|
+
* `decision` is the human label of what happened; `allowed` picks the icon (undefined ⇒
|
|
67515
|
+
* a neutral ⌛ for cancelled/expired). Pure.
|
|
67516
|
+
*/
|
|
67517
|
+
function buildPermissionResolvedCard(params, decision, allowed) {
|
|
67518
|
+
const icon = allowed === void 0 ? ":hourglass:" : allowed ? ":white_check_mark:" : ":no_entry_sign:";
|
|
67519
|
+
return [{
|
|
67520
|
+
type: "section",
|
|
67521
|
+
text: {
|
|
67522
|
+
type: "mrkdwn",
|
|
67523
|
+
text: `:lock: *Permission* — ${permToolLabel(params)}\n${icon} ${decision}`
|
|
67524
|
+
}
|
|
67525
|
+
}];
|
|
67526
|
+
}
|
|
67527
|
+
/** action_id prefix for an elicitation card's option buttons (`ac_elicit:<index>`),
|
|
67528
|
+
* matched by the connection with a RegExp; the choice rides the button `value`
|
|
67529
|
+
* (`<requestId>|<optionValue>`, same codec as the permission card). */
|
|
67530
|
+
const ELICIT_ACTION_PREFIX = "ac_elicit";
|
|
67531
|
+
/** action_id of an elicitation card's Dismiss button — its `value` is the bare
|
|
67532
|
+
* requestId (no option), decline-ing the elicitation. */
|
|
67533
|
+
const ELICIT_DISMISS_ACTION = "ac_elicit_dismiss";
|
|
67534
|
+
/**
|
|
67535
|
+
* Resolve the single form field an elicitation card renders (v1): the FIRST string-enum
|
|
67536
|
+
* (`oneOf` titled options or bare `enum`) or boolean property in the requested schema.
|
|
67537
|
+
* Returns null for URL-mode, an empty/absent schema, or a form whose fields we can't
|
|
67538
|
+
* render inline (free text, numbers, multi-field) — the daemon then declines. Pure, and
|
|
67539
|
+
* shared by {@link buildElicitationCard} and the daemon's click handler so the button
|
|
67540
|
+
* values and their interpretation can't drift.
|
|
67541
|
+
*/
|
|
67542
|
+
function elicitTarget(params) {
|
|
67543
|
+
const p = params;
|
|
67544
|
+
if (p.mode !== "form") return null;
|
|
67545
|
+
const props = p.requestedSchema?.properties ?? {};
|
|
67546
|
+
for (const [name, prop] of Object.entries(props)) {
|
|
67547
|
+
if (prop?.type === "string") {
|
|
67548
|
+
const oneOf = prop.oneOf;
|
|
67549
|
+
const en = prop.enum;
|
|
67550
|
+
const options = Array.isArray(oneOf) ? oneOf.map((o) => ({
|
|
67551
|
+
value: String(o.const),
|
|
67552
|
+
label: clampTo$2(String(o.title ?? o.const), 75)
|
|
67553
|
+
})) : Array.isArray(en) ? en.map((v) => ({
|
|
67554
|
+
value: String(v),
|
|
67555
|
+
label: clampTo$2(String(v), 75)
|
|
67556
|
+
})) : [];
|
|
67557
|
+
if (options.length) return {
|
|
67558
|
+
propName: name,
|
|
67559
|
+
kind: "enum",
|
|
67560
|
+
options
|
|
67561
|
+
};
|
|
67562
|
+
}
|
|
67563
|
+
if (prop?.type === "boolean") return {
|
|
67564
|
+
propName: name,
|
|
67565
|
+
kind: "boolean",
|
|
67566
|
+
options: [{
|
|
67567
|
+
value: "true",
|
|
67568
|
+
label: "Yes"
|
|
67569
|
+
}, {
|
|
67570
|
+
value: "false",
|
|
67571
|
+
label: "No"
|
|
67572
|
+
}]
|
|
67573
|
+
};
|
|
67574
|
+
}
|
|
67575
|
+
return null;
|
|
67576
|
+
}
|
|
67577
|
+
/**
|
|
67578
|
+
* Build the interactive elicitation card: the agent's `message`, an optional field title,
|
|
67579
|
+
* and an actions row of option buttons (one per {@link elicitTarget} option, capped at 5)
|
|
67580
|
+
* plus a Dismiss button. The choice rides each button `value` (`<requestId>|<optionValue>`).
|
|
67581
|
+
* Returns null when the form can't be rendered inline (caller declines). Pure.
|
|
67582
|
+
*/
|
|
67583
|
+
function buildElicitationCard(requestId, params) {
|
|
67584
|
+
const target = elicitTarget(params);
|
|
67585
|
+
if (!target) return null;
|
|
67586
|
+
const message = params.message?.trim() || "The agent needs your input";
|
|
67587
|
+
const buttons = target.options.slice(0, 5).map((o, i) => ({
|
|
67588
|
+
type: "button",
|
|
67589
|
+
action_id: `${ELICIT_ACTION_PREFIX}:${i}`,
|
|
67590
|
+
text: {
|
|
67591
|
+
type: "plain_text",
|
|
67592
|
+
text: o.label,
|
|
67593
|
+
emoji: true
|
|
67594
|
+
},
|
|
67595
|
+
value: encodePermValue(requestId, o.value)
|
|
67596
|
+
}));
|
|
67597
|
+
buttons.push({
|
|
67598
|
+
type: "button",
|
|
67599
|
+
action_id: ELICIT_DISMISS_ACTION,
|
|
67600
|
+
text: {
|
|
67601
|
+
type: "plain_text",
|
|
67602
|
+
text: "Dismiss",
|
|
67603
|
+
emoji: true
|
|
67604
|
+
},
|
|
67605
|
+
value: requestId
|
|
67606
|
+
});
|
|
67607
|
+
return [{
|
|
67608
|
+
type: "section",
|
|
67609
|
+
text: {
|
|
67610
|
+
type: "mrkdwn",
|
|
67611
|
+
text: `:speech_balloon: ${clampTo$2(message, 400)}`
|
|
67612
|
+
}
|
|
67613
|
+
}, {
|
|
67614
|
+
type: "actions",
|
|
67615
|
+
elements: buttons
|
|
67616
|
+
}];
|
|
67617
|
+
}
|
|
67618
|
+
/** Build the RESOLVED elicitation card (buttons removed) that replaces {@link
|
|
67619
|
+
* buildElicitationCard} once answered, dismissed, or cancelled. Pure. */
|
|
67620
|
+
function buildElicitationResolvedCard(params, decision) {
|
|
67621
|
+
return [{
|
|
67622
|
+
type: "section",
|
|
67623
|
+
text: {
|
|
67624
|
+
type: "mrkdwn",
|
|
67625
|
+
text: `:speech_balloon: ${clampTo$2(params.message?.trim() || "The agent needs your input", 400)}\n${decision}`
|
|
67626
|
+
}
|
|
67627
|
+
}];
|
|
67628
|
+
}
|
|
67446
67629
|
/**
|
|
67447
67630
|
* Build the COMPACT in-thread status message as ONE row: a `section` whose mrkdwn text is
|
|
67448
67631
|
* the live status line (model · ctx · tokens · cost, updated via chat.update) with a
|
|
@@ -68105,6 +68288,26 @@ var SlackConnection = class {
|
|
|
68105
68288
|
this.app.action(STATUS_ACTION.view, async ({ ack }) => {
|
|
68106
68289
|
await ack();
|
|
68107
68290
|
});
|
|
68291
|
+
this.app.action(/^ac_perm:/, async ({ ack, action }) => {
|
|
68292
|
+
await ack();
|
|
68293
|
+
const decoded = action.value ? decodePermValue(action.value) : null;
|
|
68294
|
+
if (decoded) this.deps.onPermissionChoice?.(decoded);
|
|
68295
|
+
});
|
|
68296
|
+
this.app.action(new RegExp(`^${ELICIT_ACTION_PREFIX}:`), async ({ ack, action }) => {
|
|
68297
|
+
await ack();
|
|
68298
|
+
const decoded = action.value ? decodePermValue(action.value) : null;
|
|
68299
|
+
if (decoded) this.deps.onElicitChoice?.({
|
|
68300
|
+
requestId: decoded.requestId,
|
|
68301
|
+
value: decoded.optionId
|
|
68302
|
+
});
|
|
68303
|
+
});
|
|
68304
|
+
this.app.action(ELICIT_DISMISS_ACTION, async ({ ack, action }) => {
|
|
68305
|
+
await ack();
|
|
68306
|
+
if (action.value) this.deps.onElicitChoice?.({
|
|
68307
|
+
requestId: action.value,
|
|
68308
|
+
value: null
|
|
68309
|
+
});
|
|
68310
|
+
});
|
|
68108
68311
|
log?.debug("slack: app.start → opening Socket Mode WebSocket (wss://…slack.com)…");
|
|
68109
68312
|
await this.app.start();
|
|
68110
68313
|
log?.debug("slack: app.start resolved → socket established");
|
|
@@ -213950,6 +214153,8 @@ var Daemon = class Daemon {
|
|
|
213950
214153
|
onChannelsChanged: () => void this.refreshChannels(conn),
|
|
213951
214154
|
onStatusAction: (a) => this.handleStatusAction(a),
|
|
213952
214155
|
onStatusInfo: (key) => this.statusInfoForKey(key),
|
|
214156
|
+
onPermissionChoice: (a) => this.handlePermissionChoice(a),
|
|
214157
|
+
onElicitChoice: (a) => this.handleElicitChoice(a),
|
|
213953
214158
|
log: this.log,
|
|
213954
214159
|
boltDebug: cfg.logging.level === "debug" || cfg.logging.level === "trace"
|
|
213955
214160
|
});
|
|
@@ -214148,6 +214353,8 @@ var Daemon = class Daemon {
|
|
|
214148
214353
|
onChannelsChanged: () => void this.refreshChannels(conn),
|
|
214149
214354
|
onStatusAction: (a) => this.handleStatusAction(a),
|
|
214150
214355
|
onStatusInfo: (key) => this.statusInfoForKey(key),
|
|
214356
|
+
onPermissionChoice: (a) => this.handlePermissionChoice(a),
|
|
214357
|
+
onElicitChoice: (a) => this.handleElicitChoice(a),
|
|
214151
214358
|
log: this.log,
|
|
214152
214359
|
boltDebug: this.cfg.logging.level === "debug" || this.cfg.logging.level === "trace"
|
|
214153
214360
|
});
|
|
@@ -214199,6 +214406,8 @@ var Daemon = class Daemon {
|
|
|
214199
214406
|
onMessage: () => {},
|
|
214200
214407
|
onStatusAction: (a) => this.handleStatusAction(a),
|
|
214201
214408
|
onStatusInfo: (key) => this.statusInfoForKey(key),
|
|
214409
|
+
onPermissionChoice: (a) => this.handlePermissionChoice(a),
|
|
214410
|
+
onElicitChoice: (a) => this.handleElicitChoice(a),
|
|
214202
214411
|
log: this.log
|
|
214203
214412
|
});
|
|
214204
214413
|
try {
|
|
@@ -214388,6 +214597,8 @@ var Daemon = class Daemon {
|
|
|
214388
214597
|
onChannelsChanged: () => void this.refreshChannels(conn),
|
|
214389
214598
|
onStatusAction: (a) => this.handleStatusAction(a),
|
|
214390
214599
|
onStatusInfo: (key) => this.statusInfoForKey(key),
|
|
214600
|
+
onPermissionChoice: (a) => this.handlePermissionChoice(a),
|
|
214601
|
+
onElicitChoice: (a) => this.handleElicitChoice(a),
|
|
214391
214602
|
log: this.log,
|
|
214392
214603
|
boltDebug: this.cfg.logging.level === "debug" || this.cfg.logging.level === "trace"
|
|
214393
214604
|
});
|
|
@@ -214422,6 +214633,8 @@ var Daemon = class Daemon {
|
|
|
214422
214633
|
if (!runtime) throw new Error(`runtime "${agent.runtime}" not available: not installed on this host, or absent from config.runtimes / the ACP registry`);
|
|
214423
214634
|
host = new AcpHost(runtime, {
|
|
214424
214635
|
onUpdate,
|
|
214636
|
+
onPermission: (sid, params) => this.onAcpPermission(agentId, sid, params),
|
|
214637
|
+
onElicit: (sid, params) => this.onAcpElicit(agentId, sid, params),
|
|
214425
214638
|
env: {
|
|
214426
214639
|
...ghToken ? { GH_TOKEN: ghToken } : {},
|
|
214427
214640
|
...agentChildEnv(agent),
|
|
@@ -215571,6 +215784,8 @@ var Daemon = class Daemon {
|
|
|
215571
215784
|
throw err;
|
|
215572
215785
|
} finally {
|
|
215573
215786
|
this.clearIdle(p);
|
|
215787
|
+
this.releasePermissions(sessionId);
|
|
215788
|
+
this.releaseElicits(sessionId);
|
|
215574
215789
|
this.pending.delete(sessionId);
|
|
215575
215790
|
this.store.setSessionState(key, "idle", this.clock.now());
|
|
215576
215791
|
this.emitSessionMetadataSnapshot({
|
|
@@ -215593,6 +215808,8 @@ var Daemon = class Daemon {
|
|
|
215593
215808
|
* `!stop` mutes, `!cancel`/webchat-cancel do not. `reason` is just for the log line. */
|
|
215594
215809
|
interruptTurn(agentId, acpSessionId, key, reason) {
|
|
215595
215810
|
this.queued.delete(acpSessionId);
|
|
215811
|
+
this.releasePermissions(acpSessionId);
|
|
215812
|
+
this.releaseElicits(acpSessionId);
|
|
215596
215813
|
this.log.info(`command: ${reason} → agent "${agentId}" session ${acpSessionId}`);
|
|
215597
215814
|
this.store.setSessionState(key, "cancelling", this.clock.now());
|
|
215598
215815
|
this.hosts.get(agentId)?.cancel(acpSessionId).catch((err) => this.log.error(`command ${reason}: cancel failed: ${err.message}`));
|
|
@@ -216048,6 +216265,129 @@ var Daemon = class Daemon {
|
|
|
216048
216265
|
p.idleTimer = void 0;
|
|
216049
216266
|
}
|
|
216050
216267
|
pending = /* @__PURE__ */ new Map();
|
|
216268
|
+
permSeq = 0;
|
|
216269
|
+
pendingPerms = /* @__PURE__ */ new Map();
|
|
216270
|
+
/**
|
|
216271
|
+
* ACP `session/request_permission` policy (wired as AcpHost.onPermission). Renders the
|
|
216272
|
+
* options as an interactive Slack card and resolves with the user's choice. Returns
|
|
216273
|
+
* `undefined` — so the host falls back to auto-allow — when there is no live turn
|
|
216274
|
+
* context, the turn isn't on Slack, or the request carries no options. The returned
|
|
216275
|
+
* promise stays pending until the user taps a button (handlePermissionChoice) or the
|
|
216276
|
+
* turn is cancelled/ends (releasePermissions).
|
|
216277
|
+
*/
|
|
216278
|
+
async onAcpPermission(_agentId, sessionId, params) {
|
|
216279
|
+
const p = this.pending.get(sessionId);
|
|
216280
|
+
if (!p) return void 0;
|
|
216281
|
+
const conn = p.conn;
|
|
216282
|
+
if (p.platform !== "slack" || !(conn instanceof SlackConnection)) return void 0;
|
|
216283
|
+
if (!params.options?.length) return void 0;
|
|
216284
|
+
const requestId = `perm-${++this.permSeq}`;
|
|
216285
|
+
const fallback = `Permission requested: ${params.toolCall?.title ?? "a tool call"}`;
|
|
216286
|
+
const ts = await conn.postBlocks(p.channel, buildPermissionCard(requestId, params), fallback, p.statusThread).catch(() => void 0);
|
|
216287
|
+
return await new Promise((resolve) => {
|
|
216288
|
+
this.pendingPerms.set(requestId, {
|
|
216289
|
+
sessionId,
|
|
216290
|
+
params,
|
|
216291
|
+
conn,
|
|
216292
|
+
channel: p.channel,
|
|
216293
|
+
ts,
|
|
216294
|
+
resolve
|
|
216295
|
+
});
|
|
216296
|
+
});
|
|
216297
|
+
}
|
|
216298
|
+
/** A tapped permission-card button (SlackDeps.onPermissionChoice): resolve the pending
|
|
216299
|
+
* ACP request with the chosen option and edit the card in place to its resolved state
|
|
216300
|
+
* (buttons removed). No-op if the request is already gone (double-tap / cancelled). */
|
|
216301
|
+
handlePermissionChoice(a) {
|
|
216302
|
+
const rec = this.pendingPerms.get(a.requestId);
|
|
216303
|
+
if (!rec) return;
|
|
216304
|
+
this.pendingPerms.delete(a.requestId);
|
|
216305
|
+
const opt = rec.params.options.find((o) => o.optionId === a.optionId);
|
|
216306
|
+
const allowed = opt?.kind === "allow_once" || opt?.kind === "allow_always";
|
|
216307
|
+
if (rec.ts) rec.conn.updateBlocks(rec.channel, rec.ts, buildPermissionResolvedCard(rec.params, opt?.name ?? a.optionId, allowed), "Permission resolved").catch(() => {});
|
|
216308
|
+
rec.resolve({ outcome: {
|
|
216309
|
+
outcome: "selected",
|
|
216310
|
+
optionId: a.optionId
|
|
216311
|
+
} });
|
|
216312
|
+
}
|
|
216313
|
+
/** Resolve every outstanding permission request for a session as `cancelled` — ACP
|
|
216314
|
+
* requires this when a prompt turn is cancelled, and it also unblocks a turn whose
|
|
216315
|
+
* card the user abandoned. Edits each card to a neutral cancelled state. */
|
|
216316
|
+
releasePermissions(sessionId) {
|
|
216317
|
+
for (const [id, rec] of this.pendingPerms) {
|
|
216318
|
+
if (rec.sessionId !== sessionId) continue;
|
|
216319
|
+
this.pendingPerms.delete(id);
|
|
216320
|
+
if (rec.ts) rec.conn.updateBlocks(rec.channel, rec.ts, buildPermissionResolvedCard(rec.params, "Cancelled", void 0), "Permission cancelled").catch(() => {});
|
|
216321
|
+
rec.resolve({ outcome: { outcome: "cancelled" } });
|
|
216322
|
+
}
|
|
216323
|
+
}
|
|
216324
|
+
elicitSeq = 0;
|
|
216325
|
+
pendingElicits = /* @__PURE__ */ new Map();
|
|
216326
|
+
/**
|
|
216327
|
+
* ACP `elicitation/create` policy (wired as AcpHost.onElicit). Renders the form's first
|
|
216328
|
+
* choice/boolean field as a Slack card and resolves with the user's pick. Returns
|
|
216329
|
+
* `undefined` — so the host declines — when there's no live turn, the turn isn't on
|
|
216330
|
+
* Slack, or the form has no field we can render inline. Stays pending until the user
|
|
216331
|
+
* taps a button (handleElicitChoice) or the turn ends/cancels (releaseElicits).
|
|
216332
|
+
*/
|
|
216333
|
+
async onAcpElicit(_agentId, sessionId, params) {
|
|
216334
|
+
const p = this.pending.get(sessionId);
|
|
216335
|
+
if (!p) return void 0;
|
|
216336
|
+
const conn = p.conn;
|
|
216337
|
+
if (p.platform !== "slack" || !(conn instanceof SlackConnection)) return void 0;
|
|
216338
|
+
const target = elicitTarget(params);
|
|
216339
|
+
if (!target) return void 0;
|
|
216340
|
+
const requestId = `elicit-${++this.elicitSeq}`;
|
|
216341
|
+
const blocks = buildElicitationCard(requestId, params);
|
|
216342
|
+
if (!blocks) return void 0;
|
|
216343
|
+
const fallback = params.message ?? "The agent needs your input";
|
|
216344
|
+
const ts = await conn.postBlocks(p.channel, blocks, fallback, p.statusThread).catch(() => void 0);
|
|
216345
|
+
return await new Promise((resolve) => {
|
|
216346
|
+
this.pendingElicits.set(requestId, {
|
|
216347
|
+
sessionId,
|
|
216348
|
+
params,
|
|
216349
|
+
propName: target.propName,
|
|
216350
|
+
kind: target.kind,
|
|
216351
|
+
conn,
|
|
216352
|
+
channel: p.channel,
|
|
216353
|
+
ts,
|
|
216354
|
+
resolve
|
|
216355
|
+
});
|
|
216356
|
+
});
|
|
216357
|
+
}
|
|
216358
|
+
/** A tapped elicitation-card button (SlackDeps.onElicitChoice): resolve the pending ACP
|
|
216359
|
+
* request — `accept` with the chosen value (under the field name), or `decline` for the
|
|
216360
|
+
* Dismiss button (value === null) — and edit the card in place. No-op if already gone. */
|
|
216361
|
+
handleElicitChoice(a) {
|
|
216362
|
+
const rec = this.pendingElicits.get(a.requestId);
|
|
216363
|
+
if (!rec) return;
|
|
216364
|
+
this.pendingElicits.delete(a.requestId);
|
|
216365
|
+
let res;
|
|
216366
|
+
let decision;
|
|
216367
|
+
if (a.value === null) {
|
|
216368
|
+
res = { action: "decline" };
|
|
216369
|
+
decision = ":no_entry_sign: Dismissed";
|
|
216370
|
+
} else {
|
|
216371
|
+
const value = rec.kind === "boolean" ? a.value === "true" : a.value;
|
|
216372
|
+
res = {
|
|
216373
|
+
action: "accept",
|
|
216374
|
+
content: { [rec.propName]: value }
|
|
216375
|
+
};
|
|
216376
|
+
decision = `:white_check_mark: ${rec.kind === "boolean" ? value ? "Yes" : "No" : a.value}`;
|
|
216377
|
+
}
|
|
216378
|
+
if (rec.ts) rec.conn.updateBlocks(rec.channel, rec.ts, buildElicitationResolvedCard(rec.params, decision), "Input received").catch(() => {});
|
|
216379
|
+
rec.resolve(res);
|
|
216380
|
+
}
|
|
216381
|
+
/** Resolve every outstanding elicitation for a session as `cancel` — ACP's cancellation
|
|
216382
|
+
* contract, and it unblocks a turn whose card the user abandoned. */
|
|
216383
|
+
releaseElicits(sessionId) {
|
|
216384
|
+
for (const [id, rec] of this.pendingElicits) {
|
|
216385
|
+
if (rec.sessionId !== sessionId) continue;
|
|
216386
|
+
this.pendingElicits.delete(id);
|
|
216387
|
+
if (rec.ts) rec.conn.updateBlocks(rec.channel, rec.ts, buildElicitationResolvedCard(rec.params, ":hourglass: Cancelled"), "Cancelled").catch(() => {});
|
|
216388
|
+
rec.resolve({ action: "cancel" });
|
|
216389
|
+
}
|
|
216390
|
+
}
|
|
216051
216391
|
onAcpUpdate(_agentId, sessionId, update) {
|
|
216052
216392
|
const p = this.pending.get(sessionId);
|
|
216053
216393
|
if (!p) return;
|
|
@@ -216788,4 +217128,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
216788
217128
|
//#endregion
|
|
216789
217129
|
export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
|
|
216790
217130
|
|
|
216791
|
-
//# sourceMappingURL=run-foreground-
|
|
217131
|
+
//# sourceMappingURL=run-foreground-CcV3IOs2.js.map
|