@agentconnect.md/daemon 1.0.0-rc.357 → 1.0.0-rc.359
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-C7hCEgr4.js → acp-host-BfQlq8k_.js} +2 -2
- package/dist/{acp-host-C7hCEgr4.js.map → acp-host-BfQlq8k_.js.map} +1 -1
- package/dist/{agent-list-BG1--msq.js → agent-list-B0C-1hRo.js} +2 -2
- package/dist/{agent-list-BG1--msq.js.map → agent-list-B0C-1hRo.js.map} +1 -1
- package/dist/{chat-rvo6gEcw.js → chat-DE2LUJp_.js} +3 -3
- package/dist/{chat-rvo6gEcw.js.map → chat-DE2LUJp_.js.map} +1 -1
- package/dist/{dist-Dlv2_XNX.js → dist-FlOD_K8A.js} +7 -3
- package/dist/dist-FlOD_K8A.js.map +1 -0
- package/dist/{gh-token-gxbPV8lx.js → gh-token-DRdNjdPq.js} +2 -2
- package/dist/{gh-token-gxbPV8lx.js.map → gh-token-DRdNjdPq.js.map} +1 -1
- package/dist/index.js +5 -5
- package/dist/{load-agents-BvKpvUli.js → load-agents-CFlI_kHW.js} +2 -2
- package/dist/{load-agents-BvKpvUli.js.map → load-agents-CFlI_kHW.js.map} +1 -1
- package/dist/{login-BTedqIRg.js → login-B-TGMM_y.js} +4 -4
- package/dist/{login-BTedqIRg.js.map → login-B-TGMM_y.js.map} +1 -1
- package/dist/run-foreground-C0Xtqt1X.js +2 -0
- package/dist/{run-foreground-BLNtLVQ4.js → run-foreground-RQIJALDC.js} +42 -15
- package/dist/{run-foreground-BLNtLVQ4.js.map → run-foreground-RQIJALDC.js.map} +1 -1
- package/package.json +1 -1
- package/dist/dist-Dlv2_XNX.js.map +0 -1
- package/dist/run-foreground-BHL1L80w.js +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as record, a as array, c as discriminatedUnion, d as literal, m as object, n as _enum, o as boolean, p as number, v as string } from "./schemas-CQkO9hC5.js";
|
|
2
|
-
import { b as RelayRosterEntry } from "./dist-
|
|
2
|
+
import { b as RelayRosterEntry } from "./dist-FlOD_K8A.js";
|
|
3
3
|
import { l as resolveRoot, r as defaultAgentsDir, t as configPath } from "./paths-DEcUO8lC.js";
|
|
4
4
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
@@ -400,4 +400,4 @@ function selectAgent(agentsDir, name) {
|
|
|
400
400
|
//#endregion
|
|
401
401
|
export { agentChildEnv as a, persistRelays as c, selectAgent as i, ConfigSchema as l, findAgentFiles as n, loadConfig as o, loadAgents as r, persistDaemonId as s, discoverAgents as t };
|
|
402
402
|
|
|
403
|
-
//# sourceMappingURL=load-agents-
|
|
403
|
+
//# sourceMappingURL=load-agents-CFlI_kHW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-agents-BvKpvUli.js","names":["z.array","z.object","z.string","z\n .object","z.enum","z.literal","z\n .object","z.boolean","z.number","z.record","z\n .number","z.discriminatedUnion","z.object","z.literal","z.string","z.enum","z.array","z\n .object","z.boolean"],"sources":["../src/config/config-schema.ts","../src/config/load-config.ts","../src/agents/agent-schema.ts","../src/agents/agent-env.ts","../src/agents/load-agents.ts"],"sourcesContent":["import { z } from 'zod'\nimport { RelayRosterEntry } from '@agentconnect.md/protocol'\n\n/** The `{name, value}[]` shape shared by runtime env, MCP env, and MCP headers. */\nconst NameValueList = z.array(z.object({ name: z.string(), value: z.string() })).default([])\n\nexport const RuntimeDefSchema = z.object({\n command: z.string(),\n args: z.array(z.string()).default([]),\n env: NameValueList\n})\nexport type RuntimeDef = z.infer<typeof RuntimeDefSchema>\n\n// A daemon-configured MCP server, keyed by name in `config.mcpServers`. The name\n// is what an agent's `mcpServers` list references, and what the probe snapshot\n// reports to the CP — definitions (command/url/headers) never leave the daemon.\n// The name \"agentconnect\" is reserved for the daemon's own injected bridge entry.\nexport const McpServerDefSchema = z\n .object({\n transport: z.enum(['stdio', 'http', 'sse']).default('stdio'),\n // stdio transport: the executable to spawn (required for stdio).\n command: z.string().optional(),\n args: z.array(z.string()).default([]),\n env: NameValueList,\n // http/sse transports: the server endpoint (required for http/sse).\n url: z.string().optional(),\n headers: NameValueList\n })\n .superRefine((def, ctx) => {\n if (def.transport === 'stdio' && !def.command)\n ctx.addIssue({ code: 'custom', path: ['command'], message: 'a stdio MCP server requires \"command\"' })\n if (def.transport !== 'stdio' && !def.url)\n ctx.addIssue({ code: 'custom', path: ['url'], message: `a ${def.transport} MCP server requires \"url\"` })\n })\nexport type McpServerDef = z.infer<typeof McpServerDefSchema>\n\nexport const ConfigSchema = z.object({\n version: z.literal(1),\n daemonId: z.string().optional(),\n // Base URL of the Web App console; used to build the §9.1 \"details\" deep link\n // (`<webAppUrl>/sessions/<sessionId>`). Unset ⇒ the daemon adopts the URL the CP sends\n // down on `auth/ok` (the CP is authoritative for its own console origin); only truly\n // absent when neither is set. A local config value wins over the CP-provided one.\n webAppUrl: z.string().optional(),\n controlPlane: z\n .object({\n enabled: z.boolean().default(true),\n url: z.string().optional(),\n key: z.string().optional(), // CP API key (opaque); sent as `apiKey` on the auth frame\n heartbeatMs: z.number().int().default(15000)\n })\n .default({ enabled: false, heartbeatMs: 15000 }),\n agentsDir: z.string().optional(), // resolved against root if absent\n runtimes: z.record(z.string(), RuntimeDefSchema).optional(),\n // MCP servers this daemon can attach to agent sessions (health-probed + reported\n // to the CP as facts; agents opt in by name via their `mcpServers` list).\n mcpServers: z.record(z.string(), McpServerDefSchema).optional(),\n // Relay roster the CP last published (shared-bot-relay.md §5). Persisted whole so\n // the daemon can re-dial its relays at boot while the CP is unreachable (graceful\n // degradation); the CP's register/ok snapshot re-converges it authoritatively once\n // connected. CP-owned — overwritten on every roster converge, not hand-edited.\n relays: z.array(RelayRosterEntry).default([]),\n logging: z\n .object({ level: z.enum(['trace', 'debug', 'info', 'warn', 'error']).default('info') })\n .default({ level: 'info' }),\n limits: z\n .object({\n maxAgents: z.number().int().default(8),\n maxConcurrentSessions: z.number().int().default(32),\n agentIdleTimeoutMs: z.number().int().default(900_000),\n // How often the idle sweep runs: reaps idle ACP adapter children back to\n // `provisioned` (§7.2) and TTL-closes idle sessions (§7.3). Keep well below\n // agentIdleTimeoutMs so a host lingers at most one interval past its window.\n idleSweepMs: z.number().int().default(60_000),\n // SIGTERM/daemon-drain grace window: in-flight turns get this long to finish\n // before the daemon cancels stragglers and tears children down (§2.5/§5.3).\n shutdownDrainMs: z.number().int().default(25_000),\n // §7.3 force-cancel backstop: after `!stop` we send session/cancel and wait\n // this long; if the turn still hasn't yielded, we force-stop the host.\n cancelBackstopMs: z.number().int().default(30_000),\n // How many times to (re)try launching an agent's ACP host — spawn + the\n // `initialize` handshake — before giving up and surfacing the failure to the\n // session. Covers transient failures (a resource race, a slow cold start). A\n // deterministic failure (missing binary) just burns all attempts then reports.\n agentStartAttempts: z.number().int().min(1).default(3),\n // Fixed backoff between agent-start attempts.\n agentStartBackoffMs: z.number().int().min(0).default(500),\n // Cap (bytes) for inlining an inbound attachment into the ACP prompt.\n // Files larger than this are passed as a resource_link pointer, never\n // downloaded/base64'd — bounds daemon RSS and the prompt frame size.\n maxAttachmentBytes: z\n .number()\n .int()\n .default(8 * 1024 * 1024)\n })\n .default({\n maxAgents: 8,\n maxConcurrentSessions: 32,\n agentIdleTimeoutMs: 900_000,\n idleSweepMs: 60_000,\n shutdownDrainMs: 25_000,\n cancelBackstopMs: 30_000,\n agentStartAttempts: 3,\n agentStartBackoffMs: 500,\n maxAttachmentBytes: 8 * 1024 * 1024\n })\n})\nexport type Config = z.infer<typeof ConfigSchema>\n","import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport type { RelayRosterEntry } from '@agentconnect.md/protocol'\nimport { ConfigSchema, type Config } from './config-schema.js'\nimport { resolveRoot, configPath, defaultAgentsDir } from '../paths.js'\n\nexport interface FlatOverrides {\n cpUrl?: string\n cpKey?: string\n noCp?: boolean\n daemonId?: string\n logLevel?: Config['logging']['level']\n agentsDir?: string\n maxAgents?: number\n}\n\nexport function loadConfig(\n opts: { root?: string; configPath?: string; overrides?: FlatOverrides; optional?: boolean; autoCreate?: boolean } = {}\n): Config {\n const root = resolveRoot(opts.root)\n const file = opts.configPath ?? configPath(root)\n // `optional` (used by `chat`) lets the daemon run with zero config: a missing\n // config.json yields the schema defaults, and runtimes fall back to the ACP registry.\n // `autoCreate` (used by `run`) goes a step further and writes that empty config to\n // disk so the daemon runs fully local (control plane disabled by default) and the\n // user has a file to edit later — no `agentconnect login` required.\n let raw: unknown\n if (existsSync(file)) {\n raw = JSON.parse(readFileSync(file, 'utf8'))\n } else if (opts.autoCreate) {\n raw = { version: 1 }\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n } else if (opts.optional) {\n raw = { version: 1 }\n } else {\n throw new Error(`config not found: ${file} (create it, pass --config, or run \\`agentconnect login\\`)`)\n }\n const cfg = ConfigSchema.parse(raw) // throws on invalid\n\n const o = opts.overrides ?? {}\n if (o.daemonId) cfg.daemonId = o.daemonId\n if (o.logLevel) cfg.logging.level = o.logLevel\n if (o.maxAgents !== undefined) cfg.limits.maxAgents = o.maxAgents\n if (o.cpUrl) cfg.controlPlane.url = o.cpUrl\n if (o.cpKey) cfg.controlPlane.key = o.cpKey\n // Passing --cp-url/--cp-key implies \"connect to the CP\" (it defaults off),\n // so the one-line onboarding command works without a config edit. --no-cp wins.\n if (o.cpUrl || o.cpKey) cfg.controlPlane.enabled = true\n if (o.noCp) cfg.controlPlane.enabled = false\n\n cfg.agentsDir = o.agentsDir ?? cfg.agentsDir ?? defaultAgentsDir(root)\n return cfg\n}\n\n/**\n * Persist a (freshly-minted) `daemonId` back into config.json so it is stable\n * per install. Best-effort: a write failure is swallowed (the daemon still runs\n * with the in-memory id this session).\n */\nexport function persistDaemonId(root: string | undefined, daemonId: string): void {\n try {\n const file = configPath(resolveRoot(root))\n const raw = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { version: 1 }\n raw.daemonId = daemonId\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n } catch {\n // ignore — non-fatal\n }\n}\n\n/**\n * Persist the CP-published relay roster back into config.json so the daemon can\n * re-dial its relays at boot while the CP is unreachable (graceful degradation).\n * Whole-set (CP-owned): overwrites any prior value, so a swept relay is cleared.\n * Best-effort — a write failure is swallowed (the in-memory roster still drives\n * this session's dials).\n */\nexport function persistRelays(root: string | undefined, relays: RelayRosterEntry[]): void {\n try {\n const file = configPath(resolveRoot(root))\n const raw = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { version: 1 }\n raw.relays = relays\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n } catch {\n // ignore — non-fatal\n }\n}\n","import { z } from 'zod'\n\nexport const BindMatchSchema = z.discriminatedUnion('kind', [\n z.object({ kind: z.literal('mention') }),\n z.object({ kind: z.literal('dm') }),\n z.object({ kind: z.literal('keyword'), value: z.string() }),\n z.object({ kind: z.literal('auto') })\n])\nexport type BindMatch = z.infer<typeof BindMatchSchema>\n\nexport const BindRuleConfigSchema = z.object({\n channel: z.string().optional(), // absent = any channel\n thread: z.string().optional(),\n match: BindMatchSchema\n})\nexport type BindRuleConfig = z.infer<typeof BindRuleConfigSchema>\n\nexport const SlackConfigSchema = z.object({\n // 'direct' (default, and the shape of every pre-shared-bot agent.json): the daemon\n // opens the Socket Mode connection itself. 'shared': the bot's inbound lives on a\n // relay, so the daemon holds xoxb ONLY (send path) and opens no socket — routing is\n // arbitrated in the relay and delivered pre-addressed. See shared-bot-relay.md §7.3.\n mode: z.enum(['direct', 'shared']).default('direct'),\n botToken: z.string(),\n appToken: z.string().optional(), // direct only (Socket Mode); absent for shared\n signingSecret: z.string().optional(),\n botUserId: z.string().optional(), // filled at connect via auth.test if absent; provided by CP for shared\n allowedUserIds: z.array(z.string()).default([]),\n bindRules: z.array(BindRuleConfigSchema).default([])\n})\nexport type SlackConfig = z.infer<typeof SlackConfigSchema>\n\nexport const TelegramConfigSchema = z.object({\n botToken: z.string(), // BotFather \"123456:ABC…\" (single token; no app token / signing secret)\n botUserId: z.string().optional(), // numeric bot id, filled at connect via getMe if absent\n botUsername: z.string().optional(), // @username without the '@', for mention detection; filled via getMe\n allowedUserIds: z.array(z.string()).default([]),\n bindRules: z.array(BindRuleConfigSchema).default([])\n})\nexport type TelegramConfig = z.infer<typeof TelegramConfigSchema>\n\nexport const DiscordConfigSchema = z.object({\n botToken: z.string(), // Discord Gateway bot token (single token; no app token / signing secret)\n applicationId: z.string().optional(), // public client id for the invite URL (not used to connect)\n botUserId: z.string().optional(), // numeric bot user id, filled at connect via the ready event if absent\n allowedUserIds: z.array(z.string()).default([]),\n bindRules: z.array(BindRuleConfigSchema).default([])\n})\nexport type DiscordConfig = z.infer<typeof DiscordConfigSchema>\n\nexport const IntegrationSchema = z.discriminatedUnion('platform', [\n z.object({\n id: z.string(),\n platform: z.literal('slack'),\n slack: SlackConfigSchema\n }),\n z.object({\n id: z.string(),\n platform: z.literal('telegram'),\n telegram: TelegramConfigSchema\n }),\n z.object({\n id: z.string(),\n platform: z.literal('discord'),\n discord: DiscordConfigSchema\n })\n])\nexport type Integration = z.infer<typeof IntegrationSchema>\n\n/** A scheduled trigger for THIS agent: every `schedule` tick, prompt the agent\n * with `trigger`. `target` is optional output routing — when present the daemon\n * posts the trigger into that channel and the session replies in its thread;\n * absent ⇒ headless fire (no platform output). `origin:\"cp\"` marks CP-pushed\n * entries (written by cron/upsert, pruned by drop.crons); hand-authored entries\n * have no origin and are never touched by the CP. */\nexport const CronDefSchema = z.object({\n id: z.string(),\n schedule: z.string(),\n // integrationId picks which of the agent's integrations posts the anchor;\n // absent (legacy defs) ⇒ the agent's first integration.\n target: z\n .object({\n platform: z.enum(['slack', 'telegram', 'discord']),\n channel: z.string(),\n integrationId: z.string().optional()\n })\n .optional(),\n trigger: z.string(),\n enabled: z.boolean().default(true),\n origin: z.literal('cp').optional()\n})\nexport type CronDef = z.infer<typeof CronDefSchema>\n\nexport const AgentSchema = z.object({\n id: z.string(),\n name: z.string(),\n // Optional human-facing bot name. `name` remains the stable agent identifier;\n // the CP may set or clear this field independently via AgentSpec.displayName.\n displayName: z.string().optional(),\n // CP-resolved public avatar URL (its icon endpoint, or a user image URL). Used\n // as the Slack per-message `icon_url` (chat:write.customize), the sibling of\n // displayName→username. Set/cleared by the CP via AgentSpec.iconUrl.\n iconUrl: z.string().optional(),\n status: z.enum(['active', 'inactive', 'paused']).default('active'),\n // Operational message-processing toggle, orthogonal to `status` (which gates\n // whether the agent is loaded/placed at all). When true the agent still loads and\n // connects its platform bot, but the daemon skips ALL turn dispatch (platform,\n // webchat, cron) — see Daemon.dispatch. Turning it on also cancels in-flight\n // turns and drops their queued follow-ups (including their durable inbox rows, so\n // restart cannot resurrect them), without tearing down the warm host or ACP sessions.\n // Silent: skipped turns are dropped, not recorded. A flip remains\n // a soft-only reconcile change (no host/session teardown).\n pause: z.boolean().default(false),\n runtime: z.string(),\n // System-prompt seed + runtime knobs. Settable in agent.json and overlaid by\n // the CP spec (agent/upsert + register/ok roster) — see cp/cp-agent-registry.ts.\n description: z.string().optional(),\n reasoningEffort: z.string().optional(),\n executionMode: z.string().optional(),\n // Runtime fast mode (ACP `model_config` toggle, claude/codex). Absent ⇒ leave\n // the runtime's own default; the daemon only pushes an explicit on/off.\n fastMode: z.boolean().optional(),\n // Runtime permission/approval mode (ACP `mode` selector). The values are\n // runtime-owned strings: claude-acp uses default/acceptEdits/auto/dontAsk/plan,\n // codex-acp uses read-only/agent/agent-full-access.\n permissionMode: z.string().default('default'),\n runtimeOverrides: z\n .object({\n model: z.string().optional(),\n env: z.array(z.object({ name: z.string(), value: z.string() })).default([])\n })\n .optional(),\n // Names of daemon-configured MCP servers (daemon config `mcpServers`) to attach\n // at ACP session/new|load, after the daemon's own bridge entry. Empty ⇒ none;\n // unknown names are skipped with a warn (see mcp/resolve-servers.ts).\n mcpServers: z.array(z.string()).default([]),\n // Agent→agent call authorization (design §2.5), replicated from the CP so the\n // daemon enforces it LOCALLY when another agent uses `messageAgent` to wake this\n // one. `all` (the default) ⇒ any org peer may call; `selected` ⇒ only agents in\n // `allowedCallerAgentIds`. Absent callPolicy ⇒ treated as `all` (backward-compat;\n // see §6.5 for the fail-closed alternative — noted as a P1 gap).\n callPolicy: z.enum(['all', 'selected']).default('all'),\n allowedCallerAgentIds: z.array(z.string()).default([]),\n // Which memory backend this agent uses (see agents/memory-provider.ts). Absent ⇒\n // managed (the default): our <agent-root>/memory/ directory. `native` redirects\n // the runtime's own memory under the agent root; `external` is not yet built.\n memory: z.object({ provider: z.enum(['native', 'managed', 'external']).default('managed') }).optional(),\n workspace: z.object({\n mode: z.enum(['git-repo', 'from-scratch']),\n path: z.string(),\n gitRepo: z.string().optional(), // full cloneable address (e.g. https://github.com/acme/infra)\n gitBranch: z.string().default('main'),\n // Remote-git credential mode. Absent ⇒ anonymous (public repos). 'github-app' ⇒\n // clone/fetch/push authenticate via the local credential helper backed by\n // CP-minted short-lived installation tokens — nothing durable on this host.\n gitCredential: z.enum(['github-app']).optional(),\n pullOnNewSession: z.boolean().default(true),\n skills: z.array(z.string()).default([])\n }),\n integrations: z.array(IntegrationSchema).default([]),\n // zod 4: nested .default({}) does not apply inner field defaults — use explicit full literal\n output: z.object({ mode: z.enum(['low', 'medium', 'high']).default('low') }).default({ mode: 'low' }),\n permissions: z\n .object({ policy: z.enum(['ask', 'auto']).default('ask'), autoApprove: z.array(z.string()).default([]) })\n .default({ policy: 'ask', autoApprove: [] }),\n crons: z.array(CronDefSchema).default([])\n})\nexport type Agent = z.infer<typeof AgentSchema>\n","import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Agent } from './agent-schema.js'\n\n// Minimal .env parser: KEY=VALUE per line, `#` comments, blank lines,\n// optional `export ` prefix, matching single/double quotes stripped.\n// No variable expansion within the file. Last duplicate key wins.\nexport function parseDotenv(text: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const rawLine of text.split('\\n')) {\n const line = rawLine.replace(/\\r$/, '').trim()\n if (!line || line.startsWith('#')) continue\n const body = line.startsWith('export ') ? line.slice(7).trim() : line\n const eq = body.indexOf('=')\n if (eq === -1) continue\n const key = body.slice(0, eq).trim()\n if (!key) continue\n let value = body.slice(eq + 1).trim()\n const q = value[0]\n if (value.length >= 2 && (q === '\"' || q === \"'\") && value.at(-1) === q) {\n value = value.slice(1, -1)\n }\n out[key] = value\n }\n return out\n}\n\n// Parse `<dir>/.env`, or {} if the file does not exist.\nexport function loadDotenv(dir: string): Record<string, string> {\n const file = join(dir, '.env')\n if (!existsSync(file)) return {}\n return parseDotenv(readFileSync(file, 'utf8'))\n}\n\nconst VAR_RE = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g\n\nfunction interpolateString(\n value: string,\n source: Record<string, string | undefined>,\n path: string,\n ctx: { id: string; envPath: string }\n): string {\n return value.replace(VAR_RE, (_m, name: string) => {\n const resolved = source[name]\n if (resolved === undefined) {\n throw new Error(`agent \"${ctx.id}\": unresolved \\${${name}} in ${path} (.env: ${ctx.envPath})`)\n }\n return resolved\n })\n}\n\nfunction walk(\n node: unknown,\n source: Record<string, string | undefined>,\n path: string,\n ctx: { id: string; envPath: string }\n): unknown {\n if (typeof node === 'string') return interpolateString(node, source, path, ctx)\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) node[i] = walk(node[i], source, `${path}[${i}]`, ctx)\n return node\n }\n if (node && typeof node === 'object') {\n const obj = node as Record<string, unknown>\n for (const key of Object.keys(obj)) {\n obj[key] = walk(obj[key], source, path ? `${path}.${key}` : key, ctx)\n }\n return node\n }\n return node\n}\n\n// Rewrite every ${VAR} in every string of `agent` (in place). Throws on the\n// first unresolved variable.\nexport function interpolateAgent(\n agent: Agent,\n source: Record<string, string | undefined>,\n ctx: { id: string; envPath: string }\n): void {\n walk(agent, source, '', ctx)\n}\n\n// Per-agent env overlay applied on top of runtime.env when spawning the ACP\n// child. .env values, then runtimeOverrides.env (which wins).\nexport function agentChildEnv(agent: {\n env: Record<string, string>\n runtimeOverrides?: { env?: { name: string; value: string }[] }\n}): Record<string, string> {\n return {\n ...agent.env,\n ...Object.fromEntries((agent.runtimeOverrides?.env ?? []).map((e) => [e.name, e.value]))\n }\n}\n","import { readFileSync, readdirSync, existsSync } from 'node:fs'\nimport { join, resolve, isAbsolute, dirname } from 'node:path'\nimport { AgentSchema, type Agent } from './agent-schema.js'\nimport { loadDotenv, interpolateAgent } from './agent-env.js'\n\nconst IGNORED_DIRS = new Set(['node_modules', '.git'])\nconst MAX_DEPTH = 4\n\n// Agent plus loader-derived data: its directory and parsed .env.\nexport type LoadedAgent = Agent & { dir: string; env: Record<string, string> }\n\n// Parse one agent.json, interpolate ${VAR} from the sibling .env (+ process.env),\n// then resolve workspace.path relative to the agent dir.\nfunction parseAgentFile(file: string): LoadedAgent {\n const dir = dirname(file)\n const env = loadDotenv(dir)\n const agent = AgentSchema.parse(JSON.parse(readFileSync(file, 'utf8')))\n interpolateAgent(agent, { ...process.env, ...env }, { id: agent.id, envPath: join(dir, '.env') })\n if (!isAbsolute(agent.workspace.path)) {\n agent.workspace.path = resolve(dir, agent.workspace.path)\n }\n return { ...agent, dir, env }\n}\n\n// Bounded recursive walk: collect every agent.json under `dir`. A directory that\n// contains an agent.json is treated as a leaf (we do not recurse into it), so an\n// agent's own workspace checkout can't masquerade as nested agents.\nexport function findAgentFiles(dir: string, depth = 0): string[] {\n if (depth > MAX_DEPTH || !existsSync(dir)) return []\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return []\n }\n if (entries.some((e) => e.isFile() && e.name === 'agent.json')) {\n return [join(dir, 'agent.json')]\n }\n const out: string[] = []\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith('.')) continue\n out.push(...findAgentFiles(join(dir, entry.name), depth + 1))\n }\n return out\n}\n\n// All parsed agents under `agentsDir`, no status filter. `dir` is the directory\n// holding each agent.json.\nexport function discoverAgents(agentsDir: string): { agent: LoadedAgent; dir: string }[] {\n return findAgentFiles(agentsDir).map((file) => {\n try {\n return { agent: parseAgentFile(file), dir: dirname(file) }\n } catch (err) {\n throw new Error(`invalid agent.json at ${file}: ${(err as Error).message}`)\n }\n })\n}\n\n// Active agents only — the daemon's multi-agent path.\nexport function loadAgents(agentsDir: string): LoadedAgent[] {\n return discoverAgents(agentsDir)\n .map((d) => d.agent)\n .filter((a) => a.status === 'active')\n}\n\n// Resolve a single agent from `agentsDir`. With `name`, matches the agent `id`.\n// Without `name`: requires exactly one discovered agent. Used by `chat` and by\n// `run --agent`. Does NOT filter by status.\nexport function selectAgent(agentsDir: string, name?: string): LoadedAgent {\n const agents = discoverAgents(agentsDir).map((d) => d.agent)\n if (name) {\n const match = agents.find((a) => a.id === name)\n if (!match) {\n const available =\n agents\n .map((a) => a.id)\n .sort()\n .join(', ') || '(none)'\n throw new Error(`agent \"${name}\" not found in ${agentsDir}. Available: ${available}`)\n }\n return match\n }\n if (agents.length === 0) throw new Error(`no agent.json found in ${agentsDir}`)\n if (agents.length > 1) {\n const ids = agents\n .map((a) => a.id)\n .sort()\n .join(', ')\n throw new Error(`multiple agents found in ${agentsDir}: ${ids}; use --agent <name> to specify one`)\n }\n return agents[0]!\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"load-agents-CFlI_kHW.js","names":["z.array","z.object","z.string","z\n .object","z.enum","z.literal","z\n .object","z.boolean","z.number","z.record","z\n .number","z.discriminatedUnion","z.object","z.literal","z.string","z.enum","z.array","z\n .object","z.boolean"],"sources":["../src/config/config-schema.ts","../src/config/load-config.ts","../src/agents/agent-schema.ts","../src/agents/agent-env.ts","../src/agents/load-agents.ts"],"sourcesContent":["import { z } from 'zod'\nimport { RelayRosterEntry } from '@agentconnect.md/protocol'\n\n/** The `{name, value}[]` shape shared by runtime env, MCP env, and MCP headers. */\nconst NameValueList = z.array(z.object({ name: z.string(), value: z.string() })).default([])\n\nexport const RuntimeDefSchema = z.object({\n command: z.string(),\n args: z.array(z.string()).default([]),\n env: NameValueList\n})\nexport type RuntimeDef = z.infer<typeof RuntimeDefSchema>\n\n// A daemon-configured MCP server, keyed by name in `config.mcpServers`. The name\n// is what an agent's `mcpServers` list references, and what the probe snapshot\n// reports to the CP — definitions (command/url/headers) never leave the daemon.\n// The name \"agentconnect\" is reserved for the daemon's own injected bridge entry.\nexport const McpServerDefSchema = z\n .object({\n transport: z.enum(['stdio', 'http', 'sse']).default('stdio'),\n // stdio transport: the executable to spawn (required for stdio).\n command: z.string().optional(),\n args: z.array(z.string()).default([]),\n env: NameValueList,\n // http/sse transports: the server endpoint (required for http/sse).\n url: z.string().optional(),\n headers: NameValueList\n })\n .superRefine((def, ctx) => {\n if (def.transport === 'stdio' && !def.command)\n ctx.addIssue({ code: 'custom', path: ['command'], message: 'a stdio MCP server requires \"command\"' })\n if (def.transport !== 'stdio' && !def.url)\n ctx.addIssue({ code: 'custom', path: ['url'], message: `a ${def.transport} MCP server requires \"url\"` })\n })\nexport type McpServerDef = z.infer<typeof McpServerDefSchema>\n\nexport const ConfigSchema = z.object({\n version: z.literal(1),\n daemonId: z.string().optional(),\n // Base URL of the Web App console; used to build the §9.1 \"details\" deep link\n // (`<webAppUrl>/sessions/<sessionId>`). Unset ⇒ the daemon adopts the URL the CP sends\n // down on `auth/ok` (the CP is authoritative for its own console origin); only truly\n // absent when neither is set. A local config value wins over the CP-provided one.\n webAppUrl: z.string().optional(),\n controlPlane: z\n .object({\n enabled: z.boolean().default(true),\n url: z.string().optional(),\n key: z.string().optional(), // CP API key (opaque); sent as `apiKey` on the auth frame\n heartbeatMs: z.number().int().default(15000)\n })\n .default({ enabled: false, heartbeatMs: 15000 }),\n agentsDir: z.string().optional(), // resolved against root if absent\n runtimes: z.record(z.string(), RuntimeDefSchema).optional(),\n // MCP servers this daemon can attach to agent sessions (health-probed + reported\n // to the CP as facts; agents opt in by name via their `mcpServers` list).\n mcpServers: z.record(z.string(), McpServerDefSchema).optional(),\n // Relay roster the CP last published (shared-bot-relay.md §5). Persisted whole so\n // the daemon can re-dial its relays at boot while the CP is unreachable (graceful\n // degradation); the CP's register/ok snapshot re-converges it authoritatively once\n // connected. CP-owned — overwritten on every roster converge, not hand-edited.\n relays: z.array(RelayRosterEntry).default([]),\n logging: z\n .object({ level: z.enum(['trace', 'debug', 'info', 'warn', 'error']).default('info') })\n .default({ level: 'info' }),\n limits: z\n .object({\n maxAgents: z.number().int().default(8),\n maxConcurrentSessions: z.number().int().default(32),\n agentIdleTimeoutMs: z.number().int().default(900_000),\n // How often the idle sweep runs: reaps idle ACP adapter children back to\n // `provisioned` (§7.2) and TTL-closes idle sessions (§7.3). Keep well below\n // agentIdleTimeoutMs so a host lingers at most one interval past its window.\n idleSweepMs: z.number().int().default(60_000),\n // SIGTERM/daemon-drain grace window: in-flight turns get this long to finish\n // before the daemon cancels stragglers and tears children down (§2.5/§5.3).\n shutdownDrainMs: z.number().int().default(25_000),\n // §7.3 force-cancel backstop: after `!stop` we send session/cancel and wait\n // this long; if the turn still hasn't yielded, we force-stop the host.\n cancelBackstopMs: z.number().int().default(30_000),\n // How many times to (re)try launching an agent's ACP host — spawn + the\n // `initialize` handshake — before giving up and surfacing the failure to the\n // session. Covers transient failures (a resource race, a slow cold start). A\n // deterministic failure (missing binary) just burns all attempts then reports.\n agentStartAttempts: z.number().int().min(1).default(3),\n // Fixed backoff between agent-start attempts.\n agentStartBackoffMs: z.number().int().min(0).default(500),\n // Cap (bytes) for inlining an inbound attachment into the ACP prompt.\n // Files larger than this are passed as a resource_link pointer, never\n // downloaded/base64'd — bounds daemon RSS and the prompt frame size.\n maxAttachmentBytes: z\n .number()\n .int()\n .default(8 * 1024 * 1024)\n })\n .default({\n maxAgents: 8,\n maxConcurrentSessions: 32,\n agentIdleTimeoutMs: 900_000,\n idleSweepMs: 60_000,\n shutdownDrainMs: 25_000,\n cancelBackstopMs: 30_000,\n agentStartAttempts: 3,\n agentStartBackoffMs: 500,\n maxAttachmentBytes: 8 * 1024 * 1024\n })\n})\nexport type Config = z.infer<typeof ConfigSchema>\n","import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport type { RelayRosterEntry } from '@agentconnect.md/protocol'\nimport { ConfigSchema, type Config } from './config-schema.js'\nimport { resolveRoot, configPath, defaultAgentsDir } from '../paths.js'\n\nexport interface FlatOverrides {\n cpUrl?: string\n cpKey?: string\n noCp?: boolean\n daemonId?: string\n logLevel?: Config['logging']['level']\n agentsDir?: string\n maxAgents?: number\n}\n\nexport function loadConfig(\n opts: { root?: string; configPath?: string; overrides?: FlatOverrides; optional?: boolean; autoCreate?: boolean } = {}\n): Config {\n const root = resolveRoot(opts.root)\n const file = opts.configPath ?? configPath(root)\n // `optional` (used by `chat`) lets the daemon run with zero config: a missing\n // config.json yields the schema defaults, and runtimes fall back to the ACP registry.\n // `autoCreate` (used by `run`) goes a step further and writes that empty config to\n // disk so the daemon runs fully local (control plane disabled by default) and the\n // user has a file to edit later — no `agentconnect login` required.\n let raw: unknown\n if (existsSync(file)) {\n raw = JSON.parse(readFileSync(file, 'utf8'))\n } else if (opts.autoCreate) {\n raw = { version: 1 }\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n } else if (opts.optional) {\n raw = { version: 1 }\n } else {\n throw new Error(`config not found: ${file} (create it, pass --config, or run \\`agentconnect login\\`)`)\n }\n const cfg = ConfigSchema.parse(raw) // throws on invalid\n\n const o = opts.overrides ?? {}\n if (o.daemonId) cfg.daemonId = o.daemonId\n if (o.logLevel) cfg.logging.level = o.logLevel\n if (o.maxAgents !== undefined) cfg.limits.maxAgents = o.maxAgents\n if (o.cpUrl) cfg.controlPlane.url = o.cpUrl\n if (o.cpKey) cfg.controlPlane.key = o.cpKey\n // Passing --cp-url/--cp-key implies \"connect to the CP\" (it defaults off),\n // so the one-line onboarding command works without a config edit. --no-cp wins.\n if (o.cpUrl || o.cpKey) cfg.controlPlane.enabled = true\n if (o.noCp) cfg.controlPlane.enabled = false\n\n cfg.agentsDir = o.agentsDir ?? cfg.agentsDir ?? defaultAgentsDir(root)\n return cfg\n}\n\n/**\n * Persist a (freshly-minted) `daemonId` back into config.json so it is stable\n * per install. Best-effort: a write failure is swallowed (the daemon still runs\n * with the in-memory id this session).\n */\nexport function persistDaemonId(root: string | undefined, daemonId: string): void {\n try {\n const file = configPath(resolveRoot(root))\n const raw = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { version: 1 }\n raw.daemonId = daemonId\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n } catch {\n // ignore — non-fatal\n }\n}\n\n/**\n * Persist the CP-published relay roster back into config.json so the daemon can\n * re-dial its relays at boot while the CP is unreachable (graceful degradation).\n * Whole-set (CP-owned): overwrites any prior value, so a swept relay is cleared.\n * Best-effort — a write failure is swallowed (the in-memory roster still drives\n * this session's dials).\n */\nexport function persistRelays(root: string | undefined, relays: RelayRosterEntry[]): void {\n try {\n const file = configPath(resolveRoot(root))\n const raw = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { version: 1 }\n raw.relays = relays\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n } catch {\n // ignore — non-fatal\n }\n}\n","import { z } from 'zod'\n\nexport const BindMatchSchema = z.discriminatedUnion('kind', [\n z.object({ kind: z.literal('mention') }),\n z.object({ kind: z.literal('dm') }),\n z.object({ kind: z.literal('keyword'), value: z.string() }),\n z.object({ kind: z.literal('auto') })\n])\nexport type BindMatch = z.infer<typeof BindMatchSchema>\n\nexport const BindRuleConfigSchema = z.object({\n channel: z.string().optional(), // absent = any channel\n thread: z.string().optional(),\n match: BindMatchSchema\n})\nexport type BindRuleConfig = z.infer<typeof BindRuleConfigSchema>\n\nexport const SlackConfigSchema = z.object({\n // 'direct' (default, and the shape of every pre-shared-bot agent.json): the daemon\n // opens the Socket Mode connection itself. 'shared': the bot's inbound lives on a\n // relay, so the daemon holds xoxb ONLY (send path) and opens no socket — routing is\n // arbitrated in the relay and delivered pre-addressed. See shared-bot-relay.md §7.3.\n mode: z.enum(['direct', 'shared']).default('direct'),\n botToken: z.string(),\n appToken: z.string().optional(), // direct only (Socket Mode); absent for shared\n signingSecret: z.string().optional(),\n botUserId: z.string().optional(), // filled at connect via auth.test if absent; provided by CP for shared\n allowedUserIds: z.array(z.string()).default([]),\n bindRules: z.array(BindRuleConfigSchema).default([])\n})\nexport type SlackConfig = z.infer<typeof SlackConfigSchema>\n\nexport const TelegramConfigSchema = z.object({\n botToken: z.string(), // BotFather \"123456:ABC…\" (single token; no app token / signing secret)\n botUserId: z.string().optional(), // numeric bot id, filled at connect via getMe if absent\n botUsername: z.string().optional(), // @username without the '@', for mention detection; filled via getMe\n allowedUserIds: z.array(z.string()).default([]),\n bindRules: z.array(BindRuleConfigSchema).default([])\n})\nexport type TelegramConfig = z.infer<typeof TelegramConfigSchema>\n\nexport const DiscordConfigSchema = z.object({\n botToken: z.string(), // Discord Gateway bot token (single token; no app token / signing secret)\n applicationId: z.string().optional(), // public client id for the invite URL (not used to connect)\n botUserId: z.string().optional(), // numeric bot user id, filled at connect via the ready event if absent\n allowedUserIds: z.array(z.string()).default([]),\n bindRules: z.array(BindRuleConfigSchema).default([])\n})\nexport type DiscordConfig = z.infer<typeof DiscordConfigSchema>\n\nexport const IntegrationSchema = z.discriminatedUnion('platform', [\n z.object({\n id: z.string(),\n platform: z.literal('slack'),\n slack: SlackConfigSchema\n }),\n z.object({\n id: z.string(),\n platform: z.literal('telegram'),\n telegram: TelegramConfigSchema\n }),\n z.object({\n id: z.string(),\n platform: z.literal('discord'),\n discord: DiscordConfigSchema\n })\n])\nexport type Integration = z.infer<typeof IntegrationSchema>\n\n/** A scheduled trigger for THIS agent: every `schedule` tick, prompt the agent\n * with `trigger`. `target` is optional output routing — when present the daemon\n * posts the trigger into that channel and the session replies in its thread;\n * absent ⇒ headless fire (no platform output). `origin:\"cp\"` marks CP-pushed\n * entries (written by cron/upsert, pruned by drop.crons); hand-authored entries\n * have no origin and are never touched by the CP. */\nexport const CronDefSchema = z.object({\n id: z.string(),\n schedule: z.string(),\n // integrationId picks which of the agent's integrations posts the anchor;\n // absent (legacy defs) ⇒ the agent's first integration.\n target: z\n .object({\n platform: z.enum(['slack', 'telegram', 'discord']),\n channel: z.string(),\n integrationId: z.string().optional()\n })\n .optional(),\n trigger: z.string(),\n enabled: z.boolean().default(true),\n origin: z.literal('cp').optional()\n})\nexport type CronDef = z.infer<typeof CronDefSchema>\n\nexport const AgentSchema = z.object({\n id: z.string(),\n name: z.string(),\n // Optional human-facing bot name. `name` remains the stable agent identifier;\n // the CP may set or clear this field independently via AgentSpec.displayName.\n displayName: z.string().optional(),\n // CP-resolved public avatar URL (its icon endpoint, or a user image URL). Used\n // as the Slack per-message `icon_url` (chat:write.customize), the sibling of\n // displayName→username. Set/cleared by the CP via AgentSpec.iconUrl.\n iconUrl: z.string().optional(),\n status: z.enum(['active', 'inactive', 'paused']).default('active'),\n // Operational message-processing toggle, orthogonal to `status` (which gates\n // whether the agent is loaded/placed at all). When true the agent still loads and\n // connects its platform bot, but the daemon skips ALL turn dispatch (platform,\n // webchat, cron) — see Daemon.dispatch. Turning it on also cancels in-flight\n // turns and drops their queued follow-ups (including their durable inbox rows, so\n // restart cannot resurrect them), without tearing down the warm host or ACP sessions.\n // Silent: skipped turns are dropped, not recorded. A flip remains\n // a soft-only reconcile change (no host/session teardown).\n pause: z.boolean().default(false),\n runtime: z.string(),\n // System-prompt seed + runtime knobs. Settable in agent.json and overlaid by\n // the CP spec (agent/upsert + register/ok roster) — see cp/cp-agent-registry.ts.\n description: z.string().optional(),\n reasoningEffort: z.string().optional(),\n executionMode: z.string().optional(),\n // Runtime fast mode (ACP `model_config` toggle, claude/codex). Absent ⇒ leave\n // the runtime's own default; the daemon only pushes an explicit on/off.\n fastMode: z.boolean().optional(),\n // Runtime permission/approval mode (ACP `mode` selector). The values are\n // runtime-owned strings: claude-acp uses default/acceptEdits/auto/dontAsk/plan,\n // codex-acp uses read-only/agent/agent-full-access.\n permissionMode: z.string().default('default'),\n runtimeOverrides: z\n .object({\n model: z.string().optional(),\n env: z.array(z.object({ name: z.string(), value: z.string() })).default([])\n })\n .optional(),\n // Names of daemon-configured MCP servers (daemon config `mcpServers`) to attach\n // at ACP session/new|load, after the daemon's own bridge entry. Empty ⇒ none;\n // unknown names are skipped with a warn (see mcp/resolve-servers.ts).\n mcpServers: z.array(z.string()).default([]),\n // Agent→agent call authorization (design §2.5), replicated from the CP so the\n // daemon enforces it LOCALLY when another agent uses `messageAgent` to wake this\n // one. `all` (the default) ⇒ any org peer may call; `selected` ⇒ only agents in\n // `allowedCallerAgentIds`. Absent callPolicy ⇒ treated as `all` (backward-compat;\n // see §6.5 for the fail-closed alternative — noted as a P1 gap).\n callPolicy: z.enum(['all', 'selected']).default('all'),\n allowedCallerAgentIds: z.array(z.string()).default([]),\n // Which memory backend this agent uses (see agents/memory-provider.ts). Absent ⇒\n // managed (the default): our <agent-root>/memory/ directory. `native` redirects\n // the runtime's own memory under the agent root; `external` is not yet built.\n memory: z.object({ provider: z.enum(['native', 'managed', 'external']).default('managed') }).optional(),\n workspace: z.object({\n mode: z.enum(['git-repo', 'from-scratch']),\n path: z.string(),\n gitRepo: z.string().optional(), // full cloneable address (e.g. https://github.com/acme/infra)\n gitBranch: z.string().default('main'),\n // Remote-git credential mode. Absent ⇒ anonymous (public repos). 'github-app' ⇒\n // clone/fetch/push authenticate via the local credential helper backed by\n // CP-minted short-lived installation tokens — nothing durable on this host.\n gitCredential: z.enum(['github-app']).optional(),\n pullOnNewSession: z.boolean().default(true),\n skills: z.array(z.string()).default([])\n }),\n integrations: z.array(IntegrationSchema).default([]),\n // zod 4: nested .default({}) does not apply inner field defaults — use explicit full literal\n output: z.object({ mode: z.enum(['low', 'medium', 'high']).default('low') }).default({ mode: 'low' }),\n permissions: z\n .object({ policy: z.enum(['ask', 'auto']).default('ask'), autoApprove: z.array(z.string()).default([]) })\n .default({ policy: 'ask', autoApprove: [] }),\n crons: z.array(CronDefSchema).default([])\n})\nexport type Agent = z.infer<typeof AgentSchema>\n","import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Agent } from './agent-schema.js'\n\n// Minimal .env parser: KEY=VALUE per line, `#` comments, blank lines,\n// optional `export ` prefix, matching single/double quotes stripped.\n// No variable expansion within the file. Last duplicate key wins.\nexport function parseDotenv(text: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const rawLine of text.split('\\n')) {\n const line = rawLine.replace(/\\r$/, '').trim()\n if (!line || line.startsWith('#')) continue\n const body = line.startsWith('export ') ? line.slice(7).trim() : line\n const eq = body.indexOf('=')\n if (eq === -1) continue\n const key = body.slice(0, eq).trim()\n if (!key) continue\n let value = body.slice(eq + 1).trim()\n const q = value[0]\n if (value.length >= 2 && (q === '\"' || q === \"'\") && value.at(-1) === q) {\n value = value.slice(1, -1)\n }\n out[key] = value\n }\n return out\n}\n\n// Parse `<dir>/.env`, or {} if the file does not exist.\nexport function loadDotenv(dir: string): Record<string, string> {\n const file = join(dir, '.env')\n if (!existsSync(file)) return {}\n return parseDotenv(readFileSync(file, 'utf8'))\n}\n\nconst VAR_RE = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g\n\nfunction interpolateString(\n value: string,\n source: Record<string, string | undefined>,\n path: string,\n ctx: { id: string; envPath: string }\n): string {\n return value.replace(VAR_RE, (_m, name: string) => {\n const resolved = source[name]\n if (resolved === undefined) {\n throw new Error(`agent \"${ctx.id}\": unresolved \\${${name}} in ${path} (.env: ${ctx.envPath})`)\n }\n return resolved\n })\n}\n\nfunction walk(\n node: unknown,\n source: Record<string, string | undefined>,\n path: string,\n ctx: { id: string; envPath: string }\n): unknown {\n if (typeof node === 'string') return interpolateString(node, source, path, ctx)\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) node[i] = walk(node[i], source, `${path}[${i}]`, ctx)\n return node\n }\n if (node && typeof node === 'object') {\n const obj = node as Record<string, unknown>\n for (const key of Object.keys(obj)) {\n obj[key] = walk(obj[key], source, path ? `${path}.${key}` : key, ctx)\n }\n return node\n }\n return node\n}\n\n// Rewrite every ${VAR} in every string of `agent` (in place). Throws on the\n// first unresolved variable.\nexport function interpolateAgent(\n agent: Agent,\n source: Record<string, string | undefined>,\n ctx: { id: string; envPath: string }\n): void {\n walk(agent, source, '', ctx)\n}\n\n// Per-agent env overlay applied on top of runtime.env when spawning the ACP\n// child. .env values, then runtimeOverrides.env (which wins).\nexport function agentChildEnv(agent: {\n env: Record<string, string>\n runtimeOverrides?: { env?: { name: string; value: string }[] }\n}): Record<string, string> {\n return {\n ...agent.env,\n ...Object.fromEntries((agent.runtimeOverrides?.env ?? []).map((e) => [e.name, e.value]))\n }\n}\n","import { readFileSync, readdirSync, existsSync } from 'node:fs'\nimport { join, resolve, isAbsolute, dirname } from 'node:path'\nimport { AgentSchema, type Agent } from './agent-schema.js'\nimport { loadDotenv, interpolateAgent } from './agent-env.js'\n\nconst IGNORED_DIRS = new Set(['node_modules', '.git'])\nconst MAX_DEPTH = 4\n\n// Agent plus loader-derived data: its directory and parsed .env.\nexport type LoadedAgent = Agent & { dir: string; env: Record<string, string> }\n\n// Parse one agent.json, interpolate ${VAR} from the sibling .env (+ process.env),\n// then resolve workspace.path relative to the agent dir.\nfunction parseAgentFile(file: string): LoadedAgent {\n const dir = dirname(file)\n const env = loadDotenv(dir)\n const agent = AgentSchema.parse(JSON.parse(readFileSync(file, 'utf8')))\n interpolateAgent(agent, { ...process.env, ...env }, { id: agent.id, envPath: join(dir, '.env') })\n if (!isAbsolute(agent.workspace.path)) {\n agent.workspace.path = resolve(dir, agent.workspace.path)\n }\n return { ...agent, dir, env }\n}\n\n// Bounded recursive walk: collect every agent.json under `dir`. A directory that\n// contains an agent.json is treated as a leaf (we do not recurse into it), so an\n// agent's own workspace checkout can't masquerade as nested agents.\nexport function findAgentFiles(dir: string, depth = 0): string[] {\n if (depth > MAX_DEPTH || !existsSync(dir)) return []\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return []\n }\n if (entries.some((e) => e.isFile() && e.name === 'agent.json')) {\n return [join(dir, 'agent.json')]\n }\n const out: string[] = []\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith('.')) continue\n out.push(...findAgentFiles(join(dir, entry.name), depth + 1))\n }\n return out\n}\n\n// All parsed agents under `agentsDir`, no status filter. `dir` is the directory\n// holding each agent.json.\nexport function discoverAgents(agentsDir: string): { agent: LoadedAgent; dir: string }[] {\n return findAgentFiles(agentsDir).map((file) => {\n try {\n return { agent: parseAgentFile(file), dir: dirname(file) }\n } catch (err) {\n throw new Error(`invalid agent.json at ${file}: ${(err as Error).message}`)\n }\n })\n}\n\n// Active agents only — the daemon's multi-agent path.\nexport function loadAgents(agentsDir: string): LoadedAgent[] {\n return discoverAgents(agentsDir)\n .map((d) => d.agent)\n .filter((a) => a.status === 'active')\n}\n\n// Resolve a single agent from `agentsDir`. With `name`, matches the agent `id`.\n// Without `name`: requires exactly one discovered agent. Used by `chat` and by\n// `run --agent`. Does NOT filter by status.\nexport function selectAgent(agentsDir: string, name?: string): LoadedAgent {\n const agents = discoverAgents(agentsDir).map((d) => d.agent)\n if (name) {\n const match = agents.find((a) => a.id === name)\n if (!match) {\n const available =\n agents\n .map((a) => a.id)\n .sort()\n .join(', ') || '(none)'\n throw new Error(`agent \"${name}\" not found in ${agentsDir}. Available: ${available}`)\n }\n return match\n }\n if (agents.length === 0) throw new Error(`no agent.json found in ${agentsDir}`)\n if (agents.length > 1) {\n const ids = agents\n .map((a) => a.id)\n .sort()\n .join(', ')\n throw new Error(`multiple agents found in ${agentsDir}: ${ids}; use --agent <name> to specify one`)\n }\n return agents[0]!\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
2
|
-
import { f as buildEnvelope, m as encode, p as decodeEnvelope, t as isFrame } from "./dist-
|
|
3
|
-
import { i as ClientTransport, t as runForeground } from "./run-foreground-
|
|
4
|
-
import { l as ConfigSchema } from "./load-agents-
|
|
2
|
+
import { f as buildEnvelope, m as encode, p as decodeEnvelope, t as isFrame } from "./dist-FlOD_K8A.js";
|
|
3
|
+
import { i as ClientTransport, t as runForeground } from "./run-foreground-RQIJALDC.js";
|
|
4
|
+
import { l as ConfigSchema } from "./load-agents-CFlI_kHW.js";
|
|
5
5
|
import { l as resolveRoot, t as configPath } from "./paths-DEcUO8lC.js";
|
|
6
6
|
import { n as resolveController } from "./service-BQVw7wbz.js";
|
|
7
7
|
import { dirname } from "node:path";
|
|
@@ -229,4 +229,4 @@ async function runLogin(opts, partial = {}) {
|
|
|
229
229
|
//#endregion
|
|
230
230
|
export { buildInstallOpts, persistCredentials, runLogin };
|
|
231
231
|
|
|
232
|
-
//# sourceMappingURL=login-
|
|
232
|
+
//# sourceMappingURL=login-B-TGMM_y.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-BTedqIRg.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-B-TGMM_y.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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"}
|
|
@@ -3,10 +3,10 @@ import { n as require_ms, t as require_src$3 } from "./src-DDtlVutj.js";
|
|
|
3
3
|
import { n as init_esm_shims, t as __dirname } from "./esm-shims-DxGcm4Hw.js";
|
|
4
4
|
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
|
-
import { S as ZodIssueCode, a as RELAY_DAEMON_WS_PATH, c as SLACK_STATUS_ACTION, d as encodeSlackStatusOverflowValue, f as buildEnvelope, h as MAX_FRAME_BYTES, i as RELAY_DAEMON_SUBPROTOCOL, l as decodeSlackStatusOverflowValue, m as encode, n as gitRepoLabel, o as buildRelayDaemonFrame, p as decodeEnvelope, r as normalizeGitUrl, s as decodeRelayDaemonFrame, u as encodeSharedSlackStatusTarget, x as RESERVED_MCP_SERVER_NAME, y as HookReport } from "./dist-
|
|
7
|
-
import { a as agentChildEnv, c as persistRelays, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-
|
|
6
|
+
import { S as ZodIssueCode, a as RELAY_DAEMON_WS_PATH, c as SLACK_STATUS_ACTION, d as encodeSlackStatusOverflowValue, f as buildEnvelope, h as MAX_FRAME_BYTES, i as RELAY_DAEMON_SUBPROTOCOL, l as decodeSlackStatusOverflowValue, m as encode, n as gitRepoLabel, o as buildRelayDaemonFrame, p as decodeEnvelope, r as normalizeGitUrl, s as decodeRelayDaemonFrame, u as encodeSharedSlackStatusTarget, x as RESERVED_MCP_SERVER_NAME, y as HookReport } from "./dist-FlOD_K8A.js";
|
|
7
|
+
import { a as agentChildEnv, c as persistRelays, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-CFlI_kHW.js";
|
|
8
8
|
import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
|
|
9
|
-
import { C as toolsForIntegrations, E as resolveRuntimes, S as MEMORY_TOOL_NAMES, T as cachedRuntimeVersions, _ as listMemory, a as prefetchWorkspace, b as ALL_TOOL_NAMES, c as probeGitVersion, d as createMemoryProvider, f as memoryKindOf, g as MemoryTooLargeError, h as MemoryPathError, i as installedRuntimes, l as sessionGitEnv, m as MemoryConflictError, n as turnFailureCode, o as prepareWorkspace, p as memoryProviderFor, r as turnFailureReason, s as initGitInjection, t as AcpHost, u as simpleGit, v as resolveInMemoryDir, w as cachedRuntimeNames, x as GITHUB_REVIEW_TOOLS, y as writeMemoryFile } from "./acp-host-
|
|
9
|
+
import { C as toolsForIntegrations, E as resolveRuntimes, S as MEMORY_TOOL_NAMES, T as cachedRuntimeVersions, _ as listMemory, a as prefetchWorkspace, b as ALL_TOOL_NAMES, c as probeGitVersion, d as createMemoryProvider, f as memoryKindOf, g as MemoryTooLargeError, h as MemoryPathError, i as installedRuntimes, l as sessionGitEnv, m as MemoryConflictError, n as turnFailureCode, o as prepareWorkspace, p as memoryProviderFor, r as turnFailureReason, s as initGitInjection, t as AcpHost, u as simpleGit, v as resolveInMemoryDir, w as cachedRuntimeNames, x as GITHUB_REVIEW_TOOLS, y as writeMemoryFile } from "./acp-host-BfQlq8k_.js";
|
|
10
10
|
import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-Bu-Svl7L.js";
|
|
11
11
|
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";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
@@ -197880,25 +197880,36 @@ function githubSubjectLine(c) {
|
|
|
197880
197880
|
function neutralizeDelimiters(body) {
|
|
197881
197881
|
return body.split("\n").map((line) => line.trimStart().startsWith("----- ") ? `\\${line}` : line).join("\n");
|
|
197882
197882
|
}
|
|
197883
|
+
function trustedInlineReplyTarget(c, github) {
|
|
197884
|
+
if (c.event !== "pull_request_review_comment" || github?.subjectKind !== "pull_request" || github.pullNumber === void 0 || github.reviewThreadRootCommentId === void 0) return;
|
|
197885
|
+
return {
|
|
197886
|
+
repo: github.repoFullName,
|
|
197887
|
+
number: github.pullNumber
|
|
197888
|
+
};
|
|
197889
|
+
}
|
|
197883
197890
|
/** Trailing instruction for github fires on a NUMBERED thread (issue/PR): the
|
|
197884
197891
|
* daemon is the sole writer of the reply comment, so the agent must return its
|
|
197885
197892
|
* answer rather than mutate comments/reviews through CLI, MCP, connector, or API.
|
|
197886
197893
|
* Only emitted when the poster will actually run (a thread number exists); push
|
|
197887
197894
|
* fires have no thread, so they never see this. */
|
|
197888
|
-
function githubReplyHint(c) {
|
|
197889
|
-
|
|
197895
|
+
function githubReplyHint(c, github) {
|
|
197896
|
+
const inlineTarget = trustedInlineReplyTarget(c, github);
|
|
197897
|
+
const where = inlineTarget ? `${inlineTarget.repo}#${inlineTarget.number}` : `${c.repo ?? "this thread"}#${c.number}`;
|
|
197898
|
+
if (inlineTarget) return ["", `Return one self-contained final answer for the triggering inline review conversation. The daemon posts that final back to the existing review thread on ${where} automatically and exclusively owns the inline reply. Do NOT create, update, or delete GitHub comments or formal reviews through a tool, \`gh\`, another CLI, a connector, or a direct API call — those paths would race or double-post. Other GitHub tools are for READ-only inspection (thread, diff, files), then return the final answer for the daemon-owned inline reply.`].join("\n");
|
|
197899
|
+
if (c.event === "pull_request_review_comment") return ["", `Return one self-contained final answer for the triggering review conversation. This delivery does not carry trusted inline-thread metadata, so the daemon posts that final back to ${where} automatically as one ordinary GitHub comment. Formal GitHub reviews are unavailable for this review-comment event family, and the daemon exclusively owns the fallback comment. Do NOT create, update, or delete GitHub comments or formal reviews through a tool, \`gh\`, another CLI, a connector, or a direct API call — those paths would race or double-post. Other GitHub tools are for READ-only inspection (thread, diff, files), then return the final answer for the daemon-owned fallback comment.`].join("\n");
|
|
197900
|
+
return ["", `Your final reply is kept in the session transcript and is posted back to ${where} automatically as an ordinary GitHub comment only when no formal review was attempted or the current attempt definitively returns \`not_submitted\`. Keep it self-contained; the daemon exclusively owns that fallback reply comment. If this active PR hook permits a formal review, use only the structured \`submitGithubReview\` tool for COMMENT / REQUEST_CHANGES / APPROVE and inline review comments. Its \`body\` must be a complete, self-contained, non-empty public review summary (including for APPROVE), because a submitted, ambiguous, or otherwise unresolved formal attempt suppresses the ordinary comment. Do NOT create, update, or delete GitHub comments or formal reviews through \`gh\`, another CLI, a connector, or a direct API call — those paths would race or double-post. Other GitHub tools are for READ-only inspection (thread, diff, files), then return a self-contained final reply for the transcript and fallback path.`].join("\n");
|
|
197890
197901
|
}
|
|
197891
197902
|
/** The github-kind turn text: a trusted metadata header + the FENCED excerpt.
|
|
197892
197903
|
* The title rides the header (relay-sanitized to one capped line) — it is
|
|
197893
197904
|
* still attacker-authored, so keep it quoted and short, never instructional
|
|
197894
197905
|
* framing of our own. */
|
|
197895
|
-
function buildGithubHookText(c) {
|
|
197906
|
+
function buildGithubHookText(c, github) {
|
|
197896
197907
|
const head = [
|
|
197897
197908
|
`GitHub ${githubSubjectLine(c)}${c.title ? ` "${c.title}"` : ""}`,
|
|
197898
197909
|
`From: ${c.senderLogin ?? "unknown"}${c.authorAssociation ? ` (${c.authorAssociation})` : ""}${c.labels?.length ? ` · labels: ${c.labels.join(", ")}` : ""}`,
|
|
197899
197910
|
...c.htmlUrl ? [c.htmlUrl] : []
|
|
197900
197911
|
].join("\n");
|
|
197901
|
-
const tail = c.number !== void 0 ? githubReplyHint(c) : "";
|
|
197912
|
+
const tail = c.number !== void 0 || trustedInlineReplyTarget(c, github) !== void 0 ? githubReplyHint(c, github) : "";
|
|
197902
197913
|
if (!c.bodyExcerpt) return head + tail;
|
|
197903
197914
|
return [
|
|
197904
197915
|
head,
|
|
@@ -197911,7 +197922,7 @@ function buildGithubHookText(c) {
|
|
|
197911
197922
|
}
|
|
197912
197923
|
/** The turn text: the caller's payload-borne message (+ leftover fields as context). */
|
|
197913
197924
|
function buildHookText(msg) {
|
|
197914
|
-
if (msg.context?.source === "github") return buildGithubHookText(msg.context);
|
|
197925
|
+
if (msg.context?.source === "github") return buildGithubHookText(msg.context, msg.github);
|
|
197915
197926
|
const parts = [];
|
|
197916
197927
|
const body = msg.context?.body;
|
|
197917
197928
|
if (body) {
|
|
@@ -198180,16 +198191,18 @@ var GithubFinalPoster = class {
|
|
|
198180
198191
|
deps;
|
|
198181
198192
|
repo;
|
|
198182
198193
|
issueNumber;
|
|
198194
|
+
reviewThreadRootCommentId;
|
|
198183
198195
|
abandoned = false;
|
|
198184
198196
|
publishPromise;
|
|
198185
198197
|
abort = new AbortController();
|
|
198186
198198
|
sched;
|
|
198187
198199
|
finalizeTimeoutMs;
|
|
198188
198200
|
attribution;
|
|
198189
|
-
constructor(deps, repo, issueNumber, attribution) {
|
|
198201
|
+
constructor(deps, repo, issueNumber, attribution, reviewThreadRootCommentId) {
|
|
198190
198202
|
this.deps = deps;
|
|
198191
198203
|
this.repo = repo;
|
|
198192
198204
|
this.issueNumber = issueNumber;
|
|
198205
|
+
this.reviewThreadRootCommentId = reviewThreadRootCommentId;
|
|
198193
198206
|
this.sched = deps.scheduler ?? {
|
|
198194
198207
|
now: () => Date.now(),
|
|
198195
198208
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
@@ -198237,6 +198250,7 @@ var GithubFinalPoster = class {
|
|
|
198237
198250
|
const attribution = typeof this.attribution === "function" ? this.attribution() : this.attribution;
|
|
198238
198251
|
const body = this.render(text, githubAttributionFooter(attribution));
|
|
198239
198252
|
const doFetch = this.deps.fetchImpl ?? fetch;
|
|
198253
|
+
const commentPath = this.reviewThreadRootCommentId ? `/repos/${this.repo}/pulls/${this.issueNumber}/comments/${this.reviewThreadRootCommentId}/replies` : `/repos/${this.repo}/issues/${this.issueNumber}/comments`;
|
|
198240
198254
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
198241
198255
|
const token = await this.deps.token();
|
|
198242
198256
|
if (this.abandoned) return;
|
|
@@ -198244,7 +198258,7 @@ var GithubFinalPoster = class {
|
|
|
198244
198258
|
this.abandonTimedOut();
|
|
198245
198259
|
return;
|
|
198246
198260
|
}
|
|
198247
|
-
const res = await doFetch(`${this.deps.baseUrl ?? "https://api.github.com"}
|
|
198261
|
+
const res = await doFetch(`${this.deps.baseUrl ?? "https://api.github.com"}${commentPath}`, {
|
|
198248
198262
|
method: "POST",
|
|
198249
198263
|
headers: {
|
|
198250
198264
|
authorization: `Bearer ${token}`,
|
|
@@ -216067,6 +216081,12 @@ function githubReviewResultForCompletion(hook) {
|
|
|
216067
216081
|
result: hook.reviewReportResult
|
|
216068
216082
|
};
|
|
216069
216083
|
}
|
|
216084
|
+
/** Review-comment follow-ups already belong to one existing inline thread.
|
|
216085
|
+
* They may receive exactly one daemon-owned inline reply, but must never gain
|
|
216086
|
+
* authority to create a second, top-level formal PR review. */
|
|
216087
|
+
function isGithubReviewCommentHook(hook) {
|
|
216088
|
+
return hook.event?.split(":", 1)[0] === "pull_request_review_comment" || hook.github?.reviewThreadRootCommentId !== void 0;
|
|
216089
|
+
}
|
|
216070
216090
|
function authorizedReviewTargetMatches(active, attemptId, authorized) {
|
|
216071
216091
|
return authorized.attemptId === attemptId && authorized.repoId === active.repoId && authorized.repoFullName.toLowerCase() === active.repoFullName.toLowerCase() && authorized.pullNumber === active.pullNumber && authorized.expectedHeadSha === active.expectedHeadSha && authorized.expectedBaseSha === active.expectedBaseSha;
|
|
216072
216092
|
}
|
|
@@ -218078,11 +218098,17 @@ var Daemon = class Daemon {
|
|
|
218078
218098
|
return { accepted: true };
|
|
218079
218099
|
}
|
|
218080
218100
|
}
|
|
218081
|
-
const githubReply = c?.source === "github" &&
|
|
218101
|
+
const githubReply = (c?.source === "github" && msg.github?.subjectKind === "pull_request" && msg.github.pullNumber !== void 0 && msg.github.reviewThreadRootCommentId !== void 0 ? {
|
|
218102
|
+
hookId: msg.hookId,
|
|
218103
|
+
repo: msg.github.repoFullName,
|
|
218104
|
+
number: msg.github.pullNumber,
|
|
218105
|
+
...msg.github.reviewCommentId ? { reviewCommentId: msg.github.reviewCommentId } : {},
|
|
218106
|
+
reviewThreadRootCommentId: msg.github.reviewThreadRootCommentId
|
|
218107
|
+
} : void 0) ?? (c?.source === "github" && c.repo && c.number !== void 0 ? {
|
|
218082
218108
|
hookId: msg.hookId,
|
|
218083
218109
|
repo: c.repo,
|
|
218084
218110
|
number: c.number
|
|
218085
|
-
} : void 0;
|
|
218111
|
+
} : void 0);
|
|
218086
218112
|
if (githubReply) hookContext.githubReply = githubReply;
|
|
218087
218113
|
const anchored = await this.anchorTrigger(msg.agentId, nmsg, msg.target, hookAnchorText(msg), `hook "${msg.hookId}"`);
|
|
218088
218114
|
if (!anchored) return {
|
|
@@ -218158,7 +218184,7 @@ var Daemon = class Daemon {
|
|
|
218158
218184
|
}
|
|
218159
218185
|
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
|
|
218160
218186
|
}
|
|
218161
|
-
if (snapshot.reviewPolicy === "off") return void 0;
|
|
218187
|
+
if (snapshot.reviewPolicy === "off" || isGithubReviewCommentHook(hook)) return void 0;
|
|
218162
218188
|
const recoverableAttempt = hook.reviewAttemptId !== void 0 && hook.reviewRequestedEvent !== void 0 && hook.reviewRequestedVerdict !== void 0 && (hook.reviewResult === void 0 || hook.reviewResult.state === "ambiguous");
|
|
218163
218189
|
const active = {
|
|
218164
218190
|
entry,
|
|
@@ -218228,6 +218254,7 @@ var Daemon = class Daemon {
|
|
|
218228
218254
|
const key = sessionKey(req.platform, req.channel, req.thread, req.agentId);
|
|
218229
218255
|
const active = this.activeGithubTurnMeta.get(key);
|
|
218230
218256
|
if (!active || active.hook.agentId !== req.agentId) throw new Error("formal GitHub review is only available during the active PR hook turn");
|
|
218257
|
+
if (isGithubReviewCommentHook(active.hook)) throw new Error("formal GitHub review is unavailable for an inline review-comment reply turn");
|
|
218231
218258
|
if (!reviewPolicyAllows(active.snapshot.reviewPolicy, req.event)) throw new Error(`${req.event} exceeds this hook's ${active.snapshot.reviewPolicy} review policy`);
|
|
218232
218259
|
if (active.reviewState !== "idle") throw new Error("this PR hook turn already has a formal review attempt");
|
|
218233
218260
|
active.reviewState = "submitting";
|
|
@@ -221694,7 +221721,7 @@ var Daemon = class Daemon {
|
|
|
221694
221721
|
token: async () => (await this.gitCreds.getPostToken(agentId, ref.repo, ref.hookId)).token,
|
|
221695
221722
|
invalidateToken: (token) => this.gitCreds.invalidatePost(agentId, ref.repo, token),
|
|
221696
221723
|
log: { warn: (m) => this.log.warn(m) }
|
|
221697
|
-
}, ref.repo, ref.number, () => this.githubCommentAttribution(agentId, sessionId))
|
|
221724
|
+
}, ref.repo, ref.number, () => this.githubCommentAttribution(agentId, sessionId), ref.reviewThreadRootCommentId)
|
|
221698
221725
|
};
|
|
221699
221726
|
}
|
|
221700
221727
|
githubCommentAttribution(agentId, sessionId) {
|
|
@@ -222009,4 +222036,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
222009
222036
|
//#endregion
|
|
222010
222037
|
export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
|
|
222011
222038
|
|
|
222012
|
-
//# sourceMappingURL=run-foreground-
|
|
222039
|
+
//# sourceMappingURL=run-foreground-RQIJALDC.js.map
|