@agentconnect.md/daemon 1.0.0-rc.302 → 1.0.0-rc.304

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.
Files changed (26) hide show
  1. package/dist/{acp-host-DezWSmEG.js → acp-host-D_rGwxE5.js} +2 -2
  2. package/dist/{acp-host-DezWSmEG.js.map → acp-host-D_rGwxE5.js.map} +1 -1
  3. package/dist/{agent-list-Da9Osn4O.js → agent-list-Drju-EDG.js} +2 -2
  4. package/dist/{agent-list-Da9Osn4O.js.map → agent-list-Drju-EDG.js.map} +1 -1
  5. package/dist/{chat-D8QIIHor.js → chat-CsR5oJP2.js} +3 -3
  6. package/dist/{chat-D8QIIHor.js.map → chat-CsR5oJP2.js.map} +1 -1
  7. package/dist/{dist-Cq1Kimg_.js → dist-CO_RU4EO.js} +8 -4
  8. package/dist/dist-CO_RU4EO.js.map +1 -0
  9. package/dist/{gh-token-BfPlYQav.js → gh-token-CppI9sti.js} +3 -3
  10. package/dist/{gh-token-BfPlYQav.js.map → gh-token-CppI9sti.js.map} +1 -1
  11. package/dist/{git-credential-DhWn1IBJ.js → git-credential-BJUvnCyW.js} +2 -2
  12. package/dist/{git-credential-DhWn1IBJ.js.map → git-credential-BJUvnCyW.js.map} +1 -1
  13. package/dist/{gitcred-server-KXxLyYxc.js → gitcred-server-Bu-Svl7L.js} +29 -9
  14. package/dist/gitcred-server-Bu-Svl7L.js.map +1 -0
  15. package/dist/index.js +6 -6
  16. package/dist/{load-agents-gTqVkOwl.js → load-agents-BC4zZ0xL.js} +2 -2
  17. package/dist/{load-agents-gTqVkOwl.js.map → load-agents-BC4zZ0xL.js.map} +1 -1
  18. package/dist/{login-BqF1z9wE.js → login-D1KMrVDs.js} +4 -4
  19. package/dist/{login-BqF1z9wE.js.map → login-D1KMrVDs.js.map} +1 -1
  20. package/dist/{run-foreground-syksXj_b.js → run-foreground-CN_44eeH.js} +37 -26
  21. package/dist/{run-foreground-syksXj_b.js.map → run-foreground-CN_44eeH.js.map} +1 -1
  22. package/dist/run-foreground-PrZ6IyPn.js +2 -0
  23. package/package.json +1 -1
  24. package/dist/dist-Cq1Kimg_.js.map +0 -1
  25. package/dist/gitcred-server-KXxLyYxc.js.map +0 -1
  26. package/dist/run-foreground-DEmppQGW.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"gh-token-BfPlYQav.js","names":[],"sources":["../src/cli/gh-token.ts"],"sourcesContent":["/**\n * `agentconnect gh-token <agentId> [repo]` — the gh wrapper's token fetch\n * (agent-multi-repo-authorization.md decision 4, issue #457).\n *\n * Prints a fresh installation token for the named repo (or the agent's\n * workspace repo when none is given) to STDOUT — nothing else ever lands\n * there. Proxies gitcred.sock with `plane: 'gh'` (the widened GH_TOKEN\n * capability set); caching/coalescing/clamping all live daemon/CP-side.\n *\n * Exit codes (the wrapper's contract):\n * 0 — token on stdout\n * 1 — daemon refused / unreachable (actionable reason on stderr; the wrapper\n * falls back to the spawn-time env token)\n * 2 — \"not ours\": the repo argument names a non-github.com host — the\n * wrapper runs the real gh untouched\n */\nimport { createConnection } from 'node:net'\nimport { gitRepoLabel } from '@agentconnect.md/protocol'\nimport { resolveRoot } from '../paths.js'\nimport { gitcredSocketPath } from '../cp/gitcred-server.js'\n\nexport async function runGhToken(agentId: string, repoRaw?: string): Promise<void> {\n const target = normalizeRepoArg(repoRaw)\n if (target.defer) {\n process.exitCode = 2\n return\n }\n\n const res = await ipc({ op: 'get', agentId, plane: 'gh', repoFullName: target.repo })\n if (!res.ok || !res.password) {\n process.stderr.write(\n `agentconnect: no gh credentials for agent ${agentId}${target.repo ? ` on ${target.repo}` : ''}: ${res.error ?? 'unknown error'}\\n`\n )\n process.exitCode = 1\n return\n }\n process.stdout.write(res.password)\n}\n\n/**\n * Normalize the wrapper's raw repo argument. gh accepts `OWNER/REPO`,\n * `HOST/OWNER/REPO` and full URLs for `-R`/`GH_REPO`; the cwd fallback hands\n * us whatever `git remote get-url origin` prints. Unparseable ⇒ workspace\n * token (repo undefined); a clearly non-github.com target ⇒ defer (the\n * wrapper runs the real gh untouched — `gitRepoLabel` strips ANY host, so\n * the github.com assertion lives here).\n */\nexport function normalizeRepoArg(raw?: string): { repo?: string; defer?: boolean } {\n const s = raw?.trim()\n if (!s) return {}\n // Bare OWNER/REPO (no host, no scheme).\n if (/^[^/\\s:@]+\\/[^/\\s:@]+$/.test(s)) return { repo: s.replace(/\\.git$/i, '') }\n // Full URL (https://host/…) or scp form (git@host:…).\n const host = /^[a-z][a-z0-9+.-]*:\\/\\/(?:[^/@]+@)?([^/:]+)/i.exec(s)?.[1] ?? /^[\\w.-]+@([\\w.-]+):/.exec(s)?.[1]\n if (host) {\n if (host.toLowerCase() !== 'github.com') return { defer: true }\n const segs = gitRepoLabel(s).split('/')\n return segs.length >= 2 && segs[0] && segs[1] ? { repo: `${segs[0]}/${segs[1]}` } : {}\n }\n // HOST/OWNER/REPO plain form (gh's own shape).\n const segs = s.split('/')\n if (segs.length === 3 && segs[0]!.includes('.')) {\n if (segs[0]!.toLowerCase() !== 'github.com') return { defer: true }\n return { repo: `${segs[1]}/${segs[2]!.replace(/\\.git$/i, '')}` }\n }\n return {}\n}\n\ninterface IpcReply {\n ok: boolean\n password?: string\n error?: string\n}\n\nfunction ipc(msg: unknown): Promise<IpcReply> {\n const path = gitcredSocketPath(resolveRoot())\n return new Promise((resolve) => {\n const sock = createConnection(path)\n let buf = ''\n const fail = (error: string) => resolve({ ok: false, error })\n sock.setTimeout(15_000, () => {\n sock.destroy()\n fail('daemon did not answer in time')\n })\n sock.on('connect', () => sock.write(JSON.stringify(msg) + '\\n'))\n sock.on('data', (c) => {\n buf += c.toString('utf8')\n const nl = buf.indexOf('\\n')\n if (nl === -1) return\n sock.destroy()\n try {\n resolve(JSON.parse(buf.slice(0, nl)) as IpcReply)\n } catch {\n fail('malformed daemon reply')\n }\n })\n sock.on('error', (e) => fail(`cannot reach the daemon socket at ${path}: ${e.message}`))\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,eAAsB,WAAW,SAAiB,SAAiC;CACjF,MAAM,SAAS,iBAAiB,OAAO;CACvC,IAAI,OAAO,OAAO;EAChB,QAAQ,WAAW;EACnB;CACF;CAEA,MAAM,MAAM,MAAM,IAAI;EAAE,IAAI;EAAO;EAAS,OAAO;EAAM,cAAc,OAAO;CAAK,CAAC;CACpF,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,UAAU;EAC5B,QAAQ,OAAO,MACb,6CAA6C,UAAU,OAAO,OAAO,OAAO,OAAO,SAAS,GAAG,IAAI,IAAI,SAAS,gBAAgB,GAClI;EACA,QAAQ,WAAW;EACnB;CACF;CACA,QAAQ,OAAO,MAAM,IAAI,QAAQ;AACnC;;;;;;;;;AAUA,SAAgB,iBAAiB,KAAkD;CACjF,MAAM,IAAI,KAAK,KAAK;CACpB,IAAI,CAAC,GAAG,OAAO,CAAC;CAEhB,IAAI,yBAAyB,KAAK,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,QAAQ,WAAW,EAAE,EAAE;CAE9E,MAAM,OAAO,+CAA+C,KAAK,CAAC,CAAC,GAAG,MAAM,sBAAsB,KAAK,CAAC,CAAC,GAAG;CAC5G,IAAI,MAAM;EACR,IAAI,KAAK,YAAY,MAAM,cAAc,OAAO,EAAE,OAAO,KAAK;EAC9D,MAAM,OAAO,aAAa,CAAC,CAAC,CAAC,MAAM,GAAG;EACtC,OAAO,KAAK,UAAU,KAAK,KAAK,MAAM,KAAK,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG,GAAG,KAAK,KAAK,IAAI,CAAC;CACvF;CAEA,MAAM,OAAO,EAAE,MAAM,GAAG;CACxB,IAAI,KAAK,WAAW,KAAK,KAAK,EAAE,CAAE,SAAS,GAAG,GAAG;EAC/C,IAAI,KAAK,EAAE,CAAE,YAAY,MAAM,cAAc,OAAO,EAAE,OAAO,KAAK;EAClE,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,CAAE,QAAQ,WAAW,EAAE,IAAI;CACjE;CACA,OAAO,CAAC;AACV;AAQA,SAAS,IAAI,KAAiC;CAC5C,MAAM,OAAO,kBAAkB,YAAY,CAAC;CAC5C,OAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,OAAO,iBAAiB,IAAI;EAClC,IAAI,MAAM;EACV,MAAM,QAAQ,UAAkB,QAAQ;GAAE,IAAI;GAAO;EAAM,CAAC;EAC5D,KAAK,WAAW,YAAc;GAC5B,KAAK,QAAQ;GACb,KAAK,+BAA+B;EACtC,CAAC;EACD,KAAK,GAAG,iBAAiB,KAAK,MAAM,KAAK,UAAU,GAAG,IAAI,IAAI,CAAC;EAC/D,KAAK,GAAG,SAAS,MAAM;GACrB,OAAO,EAAE,SAAS,MAAM;GACxB,MAAM,KAAK,IAAI,QAAQ,IAAI;GAC3B,IAAI,OAAO,IAAI;GACf,KAAK,QAAQ;GACb,IAAI;IACF,QAAQ,KAAK,MAAM,IAAI,MAAM,GAAG,EAAE,CAAC,CAAa;GAClD,QAAQ;IACN,KAAK,wBAAwB;GAC/B;EACF,CAAC;EACD,KAAK,GAAG,UAAU,MAAM,KAAK,qCAAqC,KAAK,IAAI,EAAE,SAAS,CAAC;CACzF,CAAC;AACH"}
1
+ {"version":3,"file":"gh-token-CppI9sti.js","names":[],"sources":["../src/cli/gh-token.ts"],"sourcesContent":["/**\n * `agentconnect gh-token <agentId> [repo]` — the gh wrapper's token fetch\n * (agent-multi-repo-authorization.md decision 4, issue #457).\n *\n * Prints a fresh installation token for the named repo (or the agent's\n * workspace repo when none is given) to STDOUT — nothing else ever lands\n * there. Proxies gitcred.sock with `plane: 'gh'` (the widened GH_TOKEN\n * capability set); caching/coalescing/clamping all live daemon/CP-side.\n *\n * Exit codes (the wrapper's contract):\n * 0 — token on stdout\n * 1 — daemon refused / unreachable (actionable reason on stderr; the wrapper\n * falls back to the spawn-time env token)\n * 2 — \"not ours\": the repo argument names a non-github.com host — the\n * wrapper runs the real gh untouched\n */\nimport { createConnection } from 'node:net'\nimport { gitRepoLabel } from '@agentconnect.md/protocol'\nimport { resolveRoot } from '../paths.js'\nimport { gitcredSocketPath } from '../cp/gitcred-server.js'\n\nexport async function runGhToken(agentId: string, repoRaw?: string): Promise<void> {\n const target = normalizeRepoArg(repoRaw)\n if (target.defer) {\n process.exitCode = 2\n return\n }\n\n const res = await ipc({ op: 'get', agentId, plane: 'gh', repoFullName: target.repo })\n if (!res.ok || !res.password) {\n process.stderr.write(\n `agentconnect: no gh credentials for agent ${agentId}${target.repo ? ` on ${target.repo}` : ''}: ${res.error ?? 'unknown error'}\\n`\n )\n process.exitCode = 1\n return\n }\n process.stdout.write(res.password)\n}\n\n/**\n * Normalize the wrapper's raw repo argument. gh accepts `OWNER/REPO`,\n * `HOST/OWNER/REPO` and full URLs for `-R`/`GH_REPO`; the cwd fallback hands\n * us whatever `git remote get-url origin` prints. Unparseable ⇒ workspace\n * token (repo undefined); a clearly non-github.com target ⇒ defer (the\n * wrapper runs the real gh untouched — `gitRepoLabel` strips ANY host, so\n * the github.com assertion lives here).\n */\nexport function normalizeRepoArg(raw?: string): { repo?: string; defer?: boolean } {\n const s = raw?.trim()\n if (!s) return {}\n // Bare OWNER/REPO (no host, no scheme).\n if (/^[^/\\s:@]+\\/[^/\\s:@]+$/.test(s)) return { repo: s.replace(/\\.git$/i, '') }\n // Full URL (https://host/…) or scp form (git@host:…).\n const host = /^[a-z][a-z0-9+.-]*:\\/\\/(?:[^/@]+@)?([^/:]+)/i.exec(s)?.[1] ?? /^[\\w.-]+@([\\w.-]+):/.exec(s)?.[1]\n if (host) {\n if (host.toLowerCase() !== 'github.com') return { defer: true }\n const segs = gitRepoLabel(s).split('/')\n return segs.length >= 2 && segs[0] && segs[1] ? { repo: `${segs[0]}/${segs[1]}` } : {}\n }\n // HOST/OWNER/REPO plain form (gh's own shape).\n const segs = s.split('/')\n if (segs.length === 3 && segs[0]!.includes('.')) {\n if (segs[0]!.toLowerCase() !== 'github.com') return { defer: true }\n return { repo: `${segs[1]}/${segs[2]!.replace(/\\.git$/i, '')}` }\n }\n return {}\n}\n\ninterface IpcReply {\n ok: boolean\n password?: string\n error?: string\n}\n\nfunction ipc(msg: unknown): Promise<IpcReply> {\n const path = gitcredSocketPath(resolveRoot())\n return new Promise((resolve) => {\n const sock = createConnection(path)\n let buf = ''\n const fail = (error: string) => resolve({ ok: false, error })\n sock.setTimeout(15_000, () => {\n sock.destroy()\n fail('daemon did not answer in time')\n })\n sock.on('connect', () => sock.write(JSON.stringify(msg) + '\\n'))\n sock.on('data', (c) => {\n buf += c.toString('utf8')\n const nl = buf.indexOf('\\n')\n if (nl === -1) return\n sock.destroy()\n try {\n resolve(JSON.parse(buf.slice(0, nl)) as IpcReply)\n } catch {\n fail('malformed daemon reply')\n }\n })\n sock.on('error', (e) => fail(`cannot reach the daemon socket at ${path}: ${e.message}`))\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,eAAsB,WAAW,SAAiB,SAAiC;CACjF,MAAM,SAAS,iBAAiB,OAAO;CACvC,IAAI,OAAO,OAAO;EAChB,QAAQ,WAAW;EACnB;CACF;CAEA,MAAM,MAAM,MAAM,IAAI;EAAE,IAAI;EAAO;EAAS,OAAO;EAAM,cAAc,OAAO;CAAK,CAAC;CACpF,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,UAAU;EAC5B,QAAQ,OAAO,MACb,6CAA6C,UAAU,OAAO,OAAO,OAAO,OAAO,SAAS,GAAG,IAAI,IAAI,SAAS,gBAAgB,GAClI;EACA,QAAQ,WAAW;EACnB;CACF;CACA,QAAQ,OAAO,MAAM,IAAI,QAAQ;AACnC;;;;;;;;;AAUA,SAAgB,iBAAiB,KAAkD;CACjF,MAAM,IAAI,KAAK,KAAK;CACpB,IAAI,CAAC,GAAG,OAAO,CAAC;CAEhB,IAAI,yBAAyB,KAAK,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,QAAQ,WAAW,EAAE,EAAE;CAE9E,MAAM,OAAO,+CAA+C,KAAK,CAAC,CAAC,GAAG,MAAM,sBAAsB,KAAK,CAAC,CAAC,GAAG;CAC5G,IAAI,MAAM;EACR,IAAI,KAAK,YAAY,MAAM,cAAc,OAAO,EAAE,OAAO,KAAK;EAC9D,MAAM,OAAO,aAAa,CAAC,CAAC,CAAC,MAAM,GAAG;EACtC,OAAO,KAAK,UAAU,KAAK,KAAK,MAAM,KAAK,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG,GAAG,KAAK,KAAK,IAAI,CAAC;CACvF;CAEA,MAAM,OAAO,EAAE,MAAM,GAAG;CACxB,IAAI,KAAK,WAAW,KAAK,KAAK,EAAE,CAAE,SAAS,GAAG,GAAG;EAC/C,IAAI,KAAK,EAAE,CAAE,YAAY,MAAM,cAAc,OAAO,EAAE,OAAO,KAAK;EAClE,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,CAAE,QAAQ,WAAW,EAAE,IAAI;CACjE;CACA,OAAO,CAAC;AACV;AAQA,SAAS,IAAI,KAAiC;CAC5C,MAAM,OAAO,kBAAkB,YAAY,CAAC;CAC5C,OAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,OAAO,iBAAiB,IAAI;EAClC,IAAI,MAAM;EACV,MAAM,QAAQ,UAAkB,QAAQ;GAAE,IAAI;GAAO;EAAM,CAAC;EAC5D,KAAK,WAAW,YAAc;GAC5B,KAAK,QAAQ;GACb,KAAK,+BAA+B;EACtC,CAAC;EACD,KAAK,GAAG,iBAAiB,KAAK,MAAM,KAAK,UAAU,GAAG,IAAI,IAAI,CAAC;EAC/D,KAAK,GAAG,SAAS,MAAM;GACrB,OAAO,EAAE,SAAS,MAAM;GACxB,MAAM,KAAK,IAAI,QAAQ,IAAI;GAC3B,IAAI,OAAO,IAAI;GACf,KAAK,QAAQ;GACb,IAAI;IACF,QAAQ,KAAK,MAAM,IAAI,MAAM,GAAG,EAAE,CAAC,CAAa;GAClD,QAAQ;IACN,KAAK,wBAAwB;GAC/B;EACF,CAAC;EACD,KAAK,GAAG,UAAU,MAAM,KAAK,qCAAqC,KAAK,IAAI,EAAE,SAAS,CAAC;CACzF,CAAC;AACH"}
@@ -1,5 +1,5 @@
1
1
  import { l as resolveRoot } from "./paths-DEcUO8lC.js";
2
- import { n as gitcredSocketPath } from "./gitcred-server-KXxLyYxc.js";
2
+ import { n as gitcredSocketPath } from "./gitcred-server-Bu-Svl7L.js";
3
3
  import { createConnection } from "node:net";
4
4
  //#region src/cli/git-credential.ts
5
5
  /**
@@ -114,4 +114,4 @@ function ipc(msg) {
114
114
  //#endregion
115
115
  export { runGitCredential };
116
116
 
117
- //# sourceMappingURL=git-credential-DhWn1IBJ.js.map
117
+ //# sourceMappingURL=git-credential-BJUvnCyW.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-credential-DhWn1IBJ.js","names":[],"sources":["../src/cli/git-credential.ts"],"sourcesContent":["/**\n * `agentconnect git-credential --agent <id>` — the git credential helper\n * (docs/designs/github-app-git-credentials.md §本地 helper 通道).\n *\n * Speaks git's credential-helper protocol on stdin/stdout and proxies to the\n * daemon's gitcred.sock; the token exists only on the reply pipe. Actions:\n * get → username=x-access-token / password=<token> (host must be github.com)\n * erase → forward to the daemon (invalidate the cached token git just had rejected)\n * store → no-op\n *\n * The `path` git sends (useHttpPath=true is part of the injection) ROUTES the\n * request since issue #457: it is parsed to \"owner/repo\" and forwarded, so an\n * authorized non-workspace repo gets ITS token (multi-repo design decision 5).\n * Path absent/unparseable ⇒ the workspace token, the pre-#457 behavior. This\n * is routing, not authorization — the CP gate decides; token scope enforces\n * the real boundary at GitHub either way.\n *\n * Exit style: on ANY failure print a human-actionable line to stderr and exit 1\n * — since #251 surfaces turn failures to end users, this text is user-facing.\n * NEVER print or log the token outside the protocol response.\n */\nimport { createConnection } from 'node:net'\nimport { resolveRoot } from '../paths.js'\nimport { gitcredSocketPath } from '../cp/gitcred-server.js'\n\ninterface HelperInput {\n protocol?: string\n host?: string\n path?: string\n password?: string\n}\n\nexport async function runGitCredential(action: string, agentId: string): Promise<void> {\n if (action === 'store') return // git offers the accepted credential back — nothing to do\n\n const input = parseStdin(await readStdin())\n const repo = input.path !== undefined ? repoFromPath(input.path) : undefined\n\n if (action === 'erase') {\n // Route the invalidation to the same (agent, repo) key the get used.\n await ipc({ op: 'erase', agentId, password: input.password, repoFullName: repo }).catch(() => undefined) // best-effort\n return\n }\n if (action !== 'get') return // unknown actions are ignored per the helper contract\n\n if (input.host !== undefined && input.host.toLowerCase() !== 'github.com') {\n // Not ours — stay silent so git can try other helpers / fail cleanly.\n return\n }\n\n const res = await ipc({ op: 'get', agentId, repoFullName: repo })\n if (!res.ok || !res.username || !res.password) {\n process.stderr.write(\n `agentconnect: no git credentials for agent ${agentId}${repo ? ` on ${repo}` : ''}: ${res.error ?? 'unknown error'}\\n` +\n `(the daemon must be running and connected to the control plane)\\n`\n )\n process.exitCode = 1\n return\n }\n // Diagnostics-only sanity (the daemon guard already refuses cross-repo\n // grants): a mismatch here means an old daemon answered with the workspace\n // token — GitHub's single-repo token scope still enforces the boundary.\n if (repo !== undefined) {\n const want = normalizeRepoPath(res.repoFullName ?? '')\n if (want && want !== repo) {\n process.stderr.write(\n `agentconnect: note — git asked for ${repo} but the daemon answered for ${want} ` +\n `(daemon predates per-repo credentials?)\\n`\n )\n }\n }\n process.stdout.write(`username=${res.username}\\npassword=${res.password}\\n`)\n}\n\nfunction normalizeRepoPath(p: string): string {\n return p\n .replace(/^\\/+/, '')\n .replace(/\\.git$/i, '')\n .toLowerCase()\n}\n\n/** \"owner/repo\" from git's credential `path` — tolerates a leading slash, a\n * `.git` suffix, and LFS-ish subpaths (`owner/repo.git/info/lfs`). */\nexport function repoFromPath(p: string): string | undefined {\n const segs = p.replace(/^\\/+/, '').split('/')\n const owner = segs[0]\n const repo = segs[1]?.replace(/\\.git$/i, '')\n if (!owner || !repo) return undefined\n return `${owner}/${repo}`.toLowerCase()\n}\n\nfunction parseStdin(text: string): HelperInput {\n const out: Record<string, string> = {}\n for (const line of text.split('\\n')) {\n const eq = line.indexOf('=')\n if (eq > 0) out[line.slice(0, eq)] = line.slice(eq + 1)\n }\n return out\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let buf = ''\n process.stdin.setEncoding('utf8')\n process.stdin.on('data', (c) => (buf += c))\n process.stdin.on('end', () => resolve(buf))\n process.stdin.on('error', () => resolve(buf))\n })\n}\n\ninterface IpcReply {\n ok: boolean\n username?: string\n password?: string\n repoFullName?: string\n error?: string\n}\n\nfunction ipc(msg: unknown): Promise<IpcReply> {\n const path = gitcredSocketPath(resolveRoot())\n return new Promise((resolve) => {\n const sock = createConnection(path)\n let buf = ''\n const fail = (error: string) => resolve({ ok: false, error })\n sock.setTimeout(15_000, () => {\n sock.destroy()\n fail('daemon did not answer in time')\n })\n sock.on('connect', () => sock.write(JSON.stringify(msg) + '\\n'))\n sock.on('data', (c) => {\n buf += c.toString('utf8')\n const nl = buf.indexOf('\\n')\n if (nl === -1) return\n sock.destroy()\n try {\n resolve(JSON.parse(buf.slice(0, nl)) as IpcReply)\n } catch {\n fail('malformed daemon reply')\n }\n })\n sock.on('error', (e) => fail(`cannot reach the daemon socket at ${path}: ${e.message}`))\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"git-credential-BJUvnCyW.js","names":[],"sources":["../src/cli/git-credential.ts"],"sourcesContent":["/**\n * `agentconnect git-credential --agent <id>` — the git credential helper\n * (docs/designs/github-app-git-credentials.md §本地 helper 通道).\n *\n * Speaks git's credential-helper protocol on stdin/stdout and proxies to the\n * daemon's gitcred.sock; the token exists only on the reply pipe. Actions:\n * get → username=x-access-token / password=<token> (host must be github.com)\n * erase → forward to the daemon (invalidate the cached token git just had rejected)\n * store → no-op\n *\n * The `path` git sends (useHttpPath=true is part of the injection) ROUTES the\n * request since issue #457: it is parsed to \"owner/repo\" and forwarded, so an\n * authorized non-workspace repo gets ITS token (multi-repo design decision 5).\n * Path absent/unparseable ⇒ the workspace token, the pre-#457 behavior. This\n * is routing, not authorization — the CP gate decides; token scope enforces\n * the real boundary at GitHub either way.\n *\n * Exit style: on ANY failure print a human-actionable line to stderr and exit 1\n * — since #251 surfaces turn failures to end users, this text is user-facing.\n * NEVER print or log the token outside the protocol response.\n */\nimport { createConnection } from 'node:net'\nimport { resolveRoot } from '../paths.js'\nimport { gitcredSocketPath } from '../cp/gitcred-server.js'\n\ninterface HelperInput {\n protocol?: string\n host?: string\n path?: string\n password?: string\n}\n\nexport async function runGitCredential(action: string, agentId: string): Promise<void> {\n if (action === 'store') return // git offers the accepted credential back — nothing to do\n\n const input = parseStdin(await readStdin())\n const repo = input.path !== undefined ? repoFromPath(input.path) : undefined\n\n if (action === 'erase') {\n // Route the invalidation to the same (agent, repo) key the get used.\n await ipc({ op: 'erase', agentId, password: input.password, repoFullName: repo }).catch(() => undefined) // best-effort\n return\n }\n if (action !== 'get') return // unknown actions are ignored per the helper contract\n\n if (input.host !== undefined && input.host.toLowerCase() !== 'github.com') {\n // Not ours — stay silent so git can try other helpers / fail cleanly.\n return\n }\n\n const res = await ipc({ op: 'get', agentId, repoFullName: repo })\n if (!res.ok || !res.username || !res.password) {\n process.stderr.write(\n `agentconnect: no git credentials for agent ${agentId}${repo ? ` on ${repo}` : ''}: ${res.error ?? 'unknown error'}\\n` +\n `(the daemon must be running and connected to the control plane)\\n`\n )\n process.exitCode = 1\n return\n }\n // Diagnostics-only sanity (the daemon guard already refuses cross-repo\n // grants): a mismatch here means an old daemon answered with the workspace\n // token — GitHub's single-repo token scope still enforces the boundary.\n if (repo !== undefined) {\n const want = normalizeRepoPath(res.repoFullName ?? '')\n if (want && want !== repo) {\n process.stderr.write(\n `agentconnect: note — git asked for ${repo} but the daemon answered for ${want} ` +\n `(daemon predates per-repo credentials?)\\n`\n )\n }\n }\n process.stdout.write(`username=${res.username}\\npassword=${res.password}\\n`)\n}\n\nfunction normalizeRepoPath(p: string): string {\n return p\n .replace(/^\\/+/, '')\n .replace(/\\.git$/i, '')\n .toLowerCase()\n}\n\n/** \"owner/repo\" from git's credential `path` — tolerates a leading slash, a\n * `.git` suffix, and LFS-ish subpaths (`owner/repo.git/info/lfs`). */\nexport function repoFromPath(p: string): string | undefined {\n const segs = p.replace(/^\\/+/, '').split('/')\n const owner = segs[0]\n const repo = segs[1]?.replace(/\\.git$/i, '')\n if (!owner || !repo) return undefined\n return `${owner}/${repo}`.toLowerCase()\n}\n\nfunction parseStdin(text: string): HelperInput {\n const out: Record<string, string> = {}\n for (const line of text.split('\\n')) {\n const eq = line.indexOf('=')\n if (eq > 0) out[line.slice(0, eq)] = line.slice(eq + 1)\n }\n return out\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let buf = ''\n process.stdin.setEncoding('utf8')\n process.stdin.on('data', (c) => (buf += c))\n process.stdin.on('end', () => resolve(buf))\n process.stdin.on('error', () => resolve(buf))\n })\n}\n\ninterface IpcReply {\n ok: boolean\n username?: string\n password?: string\n repoFullName?: string\n error?: string\n}\n\nfunction ipc(msg: unknown): Promise<IpcReply> {\n const path = gitcredSocketPath(resolveRoot())\n return new Promise((resolve) => {\n const sock = createConnection(path)\n let buf = ''\n const fail = (error: string) => resolve({ ok: false, error })\n sock.setTimeout(15_000, () => {\n sock.destroy()\n fail('daemon did not answer in time')\n })\n sock.on('connect', () => sock.write(JSON.stringify(msg) + '\\n'))\n sock.on('data', (c) => {\n buf += c.toString('utf8')\n const nl = buf.indexOf('\\n')\n if (nl === -1) return\n sock.destroy()\n try {\n resolve(JSON.parse(buf.slice(0, nl)) as IpcReply)\n } catch {\n fail('malformed daemon reply')\n }\n })\n sock.on('error', (e) => fail(`cannot reach the daemon socket at ${path}: ${e.message}`))\n })\n}\n"],"mappings":"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"}
@@ -36,6 +36,9 @@ var GitCredentialCache = class {
36
36
  deps;
37
37
  entries = /* @__PURE__ */ new Map();
38
38
  inflight = /* @__PURE__ */ new Map();
39
+ /** Poster keys whose next successful CP mint must bypass the CP token cache
40
+ * after GitHub rejected the previous grant with 401/403. */
41
+ refreshPosts = /* @__PURE__ */ new Set();
39
42
  /** Workspace keys the CP told us to stop asking for (agent-level
40
43
  * SCOPE_DENIED — moved/mode-off). Per KEY, so a gh-plane denial never
41
44
  * silences the git plane. */
@@ -74,19 +77,35 @@ var GitCredentialCache = class {
74
77
  /**
75
78
  * The GithubPoster's comment token (P3 出向): scoped to `repo`, requesting
76
79
  * issues/pull_requests only (no contents — the token can talk on threads,
77
- * not read code). The CP admits it through the agent's AgentRepoAuthorization
78
- * rows like every repo-targeted mint and clamps to the row's tier (a `read`
79
- * tier yields read-only scopes and the poster's writes 403 — authorize the
80
- * repo at `comment` or above). Same cache/coalesce/degradation discipline as
81
- * {@link get}; the token never enters the agent's environment.
80
+ * not read code). `purpose` keeps this daemon-owned writer distinct from an
81
+ * agent's general git/gh credentials: the CP admits it only when this agent
82
+ * has an enabled GitHub hook for the repo. Same cache/coalesce/degradation
83
+ * discipline as {@link get}; the token never enters the agent environment.
82
84
  */
83
- async getPostToken(agentId, repo) {
84
- return this.getKeyed(POST_KEY(agentId, repo), agentId, {
85
+ async getPostToken(agentId, repo, hookId) {
86
+ const key = POST_KEY(agentId, repo);
87
+ const forceRefresh = this.refreshPosts.has(key);
88
+ const entry = await this.getKeyed(key, agentId, {
85
89
  agentId,
86
90
  reason: "helper",
87
91
  capabilities: ["issues", "pull_requests"],
88
- repoFullName: repo
92
+ repoFullName: repo,
93
+ purpose: "github_hook_reply",
94
+ hookId,
95
+ ...forceRefresh ? { forceRefresh: true } : {}
89
96
  });
97
+ if (forceRefresh) this.refreshPosts.delete(key);
98
+ return entry;
99
+ }
100
+ /** Drop the cached GithubPoster token after GitHub rejects it. Matching the
101
+ * presented token avoids deleting a newer grant that won a concurrent race. */
102
+ invalidatePost(agentId, repo, presentedToken) {
103
+ const key = POST_KEY(agentId, repo);
104
+ const entry = this.entries.get(key);
105
+ if (!entry) return;
106
+ if (presentedToken !== void 0 && entry.token !== presentedToken) return;
107
+ this.entries.delete(key);
108
+ this.refreshPosts.add(key);
90
109
  }
91
110
  async getKeyed(key, agentId, payload) {
92
111
  if (this.denied.has(key)) throw new GitCredUnavailableError("control plane denied git credentials for this agent", true);
@@ -132,6 +151,7 @@ var GitCredentialCache = class {
132
151
  for (const key of [...this.entries.keys()]) if (key.includes(marker)) this.entries.delete(key);
133
152
  for (const key of [...this.deniedRepos.keys()]) if (key.includes(marker)) this.deniedRepos.delete(key);
134
153
  for (const key of [...this.denied]) if (key.includes(marker)) this.denied.delete(key);
154
+ for (const key of [...this.refreshPosts]) if (key.includes(marker)) this.refreshPosts.delete(key);
135
155
  }
136
156
  async fetch(key, payload) {
137
157
  let grant;
@@ -334,4 +354,4 @@ function writeGitcredShim(root, cliEntry) {
334
354
  //#endregion
335
355
  export { GitCredentialCache as i, gitcredSocketPath as n, writeGitcredShim as r, GitCredServer as t };
336
356
 
337
- //# sourceMappingURL=gitcred-server-KXxLyYxc.js.map
357
+ //# sourceMappingURL=gitcred-server-Bu-Svl7L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitcred-server-Bu-Svl7L.js","names":[],"sources":["../src/cp/git-credential.ts","../src/cp/gitcred-server.ts"],"sourcesContent":["/**\n * GitCredentialCache — daemon-side memory for CP-minted git credentials\n * (docs/designs/github-app-git-credentials.md §daemon 侧;\n * docs/designs/agent-multi-repo-authorization.md §daemon 侧 for the\n * per-repo keying, issue #457).\n *\n * Tokens live ONLY here (and transiently on helper stdout pipes): never disk,\n * never argv, never a log line. Expiry is tracked on the MONOTONIC clock as\n * `receivedAt + ttlSec` — a skewed wall clock must never resurrect a dead\n * token (`expiresAt` in the grant is observability only).\n *\n * Keying: `(agentId, plane, repo?)` — the git plane ('contents' capability)\n * and the gh plane (contents+issues+pull_requests) are distinct tokens with\n * separate lifecycles, and every non-workspace repo is its own token (single\n * repo scope). `repo` absent ⇒ the agent's workspace repo, the pre-#457 keys.\n *\n * Threshold nesting: entries are handed out only while >10min remain (the CP\n * serves grants with ≥15min, so a fresh grant always clears this). Per-key\n * in-flight coalescing (the `cloneInFlight` pattern) keeps a cold cache from\n * fanning N concurrent helper/pull/prefetch calls into N WS REQs.\n *\n * Denial semantics split (multi-repo design §安全边界): an AGENT-level\n * SCOPE_DENIED (workspace request) is terminal — stop asking until the spec\n * replicates. A REPO-level SCOPE_DENIED (request that named a repo) is a\n * 60s NEGATIVE CACHE only: the operator authorizing the repo in the console\n * must take effect on the next gh/git call within a minute, without a spec\n * push or daemon restart.\n *\n * Degradation: outside READY the fetch fails fast (see CpClient.requestGitCred)\n * and an UNEXPIRED cached token keeps serving — CP downtime only breaks remote\n * git once the hour runs out.\n */\nimport type { GitCredCapability, GitCredGrant } from '@agentconnect.md/protocol'\n\n/** Hand out only while more than this remains (nests under the CP's 15min floor). */\nconst HANDOUT_MIN_MS = 10 * 60 * 1000\n\n/** Repo-level denials are retried after this — the console-authorization lag. */\nconst REPO_DENIAL_TTL_MS = 60 * 1000\n\n/** The widened scope set behind `GH_TOKEN` (P2.5 回写): git contents plus the\n * issue/PR scopes `gh issue comment` / `gh pr comment` need. The CP clamps\n * every capability — the workspace repo to the agent's gitAccess, an\n * additional repo to its authorization tier (comment ⇒ contents:read +\n * issues/PR:write). */\nconst GH_TOKEN_CAPABILITIES: readonly GitCredCapability[] = ['contents', 'issues', 'pull_requests']\n\n/** The credential plane: 'git' = contents-only (the credential helper), 'gh' =\n * the widened GH_TOKEN set. Distinct capability sets are distinct tokens. */\nexport type CredPlane = 'git' | 'gh'\n\n/** Cache key — NUL-joined so no owner/repo string can collide across fields\n * (the GH_KEY precedent). */\nconst keyOf = (agentId: string, plane: CredPlane, repo?: string): string =>\n `${plane}\\u0000${agentId}\\u0000${repo?.toLowerCase() ?? ''}`\n\n/** Cache key for a repo-targeted COMMENT token (the GithubPoster's — issues/PR\n * only, no contents requested; its capability set differs from both planes so\n * it gets its own keyspace). One per (agent, repo). */\nconst POST_KEY = (agentId: string, repo: string): string => `post\\u0000${agentId}\\u0000${repo.toLowerCase()}`\n\nexport class GitCredUnavailableError extends Error {\n constructor(\n message: string,\n /** Terminal (agent-level SCOPE_DENIED): stop asking for this agent until config changes. */\n readonly terminal: boolean\n ) {\n super(message)\n this.name = 'GitCredUnavailableError'\n }\n}\n\ninterface Entry {\n username: string\n token: string\n repoFullName: string\n access: 'read' | 'write'\n /** Monotonic deadline (ms on the injected monotonic clock). */\n expiresAtMono: number\n}\n\nexport interface GitCredentialCacheDeps {\n /** D→C `gitcred/request` — throws WireError-shaped errors (`code`, `retryable`). */\n request: (payload: {\n agentId: string\n reason?: 'clone' | 'fetch' | 'pull' | 'push' | 'helper'\n capabilities?: GitCredCapability[]\n repoFullName?: string\n purpose?: 'github_hook_reply'\n hookId?: string\n forceRefresh?: boolean\n }) => Promise<GitCredGrant>\n log: { warn: (msg: string) => void }\n /** Monotonic ms; injectable for tests. */\n monoNow?: () => number\n}\n\nexport class GitCredentialCache {\n private readonly entries = new Map<string, Entry>()\n private readonly inflight = new Map<string, Promise<Entry>>()\n /** Poster keys whose next successful CP mint must bypass the CP token cache\n * after GitHub rejected the previous grant with 401/403. */\n private readonly refreshPosts = new Set<string>()\n /** Workspace keys the CP told us to stop asking for (agent-level\n * SCOPE_DENIED — moved/mode-off). Per KEY, so a gh-plane denial never\n * silences the git plane. */\n private readonly denied = new Set<string>()\n /** Repo-keyed denials → monotonic retry-after deadline (60s negative cache). */\n private readonly deniedRepos = new Map<string, number>()\n private readonly monoNow: () => number\n\n constructor(private readonly deps: GitCredentialCacheDeps) {\n this.monoNow = deps.monoNow ?? (() => performance.now())\n }\n\n /**\n * A live credential for one of the agent's authorized repos — cached,\n * coalesced, or freshly pulled. `repo` (\"owner/repo\") absent ⇒ the workspace\n * repo (the pre-#457 behavior, byte-identical on the wire). `plane` picks the\n * capability set: 'git' (contents, the credential helper) or 'gh' (the\n * widened GH_TOKEN set for the gh wrapper).\n */\n async get(\n agentId: string,\n reason: 'clone' | 'fetch' | 'pull' | 'push' | 'helper',\n opts: { plane?: CredPlane; repo?: string } = {}\n ): Promise<Entry> {\n const plane = opts.plane ?? 'git'\n return this.getKeyed(keyOf(agentId, plane, opts.repo), agentId, {\n agentId,\n reason,\n ...(plane === 'gh' ? { capabilities: [...GH_TOKEN_CAPABILITIES] } : {}),\n ...(opts.repo !== undefined ? { repoFullName: opts.repo } : {})\n })\n }\n\n /**\n * The widened GH_TOKEN grant (P2.5 回写) for the workspace repo — kept as a\n * named entry point for the spawn-time env injection. Per-invocation gh calls\n * go through {@link get} with `plane: 'gh'` (and a repo) instead.\n */\n async getGhToken(agentId: string): Promise<Entry> {\n return this.get(agentId, 'helper', { plane: 'gh' })\n }\n\n /**\n * The GithubPoster's comment token (P3 出向): scoped to `repo`, requesting\n * issues/pull_requests only (no contents — the token can talk on threads,\n * not read code). `purpose` keeps this daemon-owned writer distinct from an\n * agent's general git/gh credentials: the CP admits it only when this agent\n * has an enabled GitHub hook for the repo. Same cache/coalesce/degradation\n * discipline as {@link get}; the token never enters the agent environment.\n */\n async getPostToken(agentId: string, repo: string, hookId: string): Promise<Entry> {\n const key = POST_KEY(agentId, repo)\n const forceRefresh = this.refreshPosts.has(key)\n const entry = await this.getKeyed(key, agentId, {\n agentId,\n reason: 'helper',\n capabilities: ['issues', 'pull_requests'],\n repoFullName: repo,\n purpose: 'github_hook_reply',\n hookId,\n ...(forceRefresh ? { forceRefresh: true } : {})\n })\n if (forceRefresh) this.refreshPosts.delete(key)\n return entry\n }\n\n /** Drop the cached GithubPoster token after GitHub rejects it. Matching the\n * presented token avoids deleting a newer grant that won a concurrent race. */\n invalidatePost(agentId: string, repo: string, presentedToken?: string): void {\n const key = POST_KEY(agentId, repo)\n const entry = this.entries.get(key)\n if (!entry) return\n if (presentedToken !== undefined && entry.token !== presentedToken) return\n this.entries.delete(key)\n this.refreshPosts.add(key)\n }\n\n private async getKeyed(\n key: string,\n agentId: string,\n payload: Parameters<GitCredentialCacheDeps['request']>[0]\n ): Promise<Entry> {\n if (this.denied.has(key)) {\n throw new GitCredUnavailableError('control plane denied git credentials for this agent', true)\n }\n const retryAfter = this.deniedRepos.get(key)\n if (retryAfter !== undefined) {\n if (retryAfter > this.monoNow()) {\n throw new GitCredUnavailableError(\n `${payload.repoFullName ?? 'repo'} is not authorized for this agent — authorize it under the agent's Repositories settings`,\n false\n )\n }\n this.deniedRepos.delete(key)\n }\n const cached = this.entries.get(key)\n if (cached && cached.expiresAtMono - this.monoNow() > HANDOUT_MIN_MS) return cached\n\n let pending = this.inflight.get(key)\n if (!pending) {\n pending = this.fetch(key, payload).finally(() => this.inflight.delete(key))\n this.inflight.set(key, pending)\n }\n try {\n return await pending\n } catch (e) {\n // CP unreachable / transient: an UNEXPIRED cached token still serves.\n // A DENIAL is authoritative, not transient — fetch() deletes the entry on\n // SCOPE_DENIED, so the identity check (`=== cached`) fails and we do NOT\n // resurrect the token the denial just purged (a revoked repo must break on\n // the discovering call, not one call later).\n if (cached && this.entries.get(key) === cached && cached.expiresAtMono > this.monoNow()) {\n this.deps.log.warn(`gitcred: refresh failed for agent ${agentId} — serving cached token`)\n return cached\n }\n throw e\n }\n }\n\n /**\n * Invalidate on rejection. Git calls the helper's `erase` with the failing\n * credential when the remote refuses it (GitHub revokes instantly on\n * uninstall/suspend) — matching on the presented password keeps a racing\n * fresh grant from being wiped by a stale erase. `repo` routes the erase to\n * the same key the `get` used; absent ⇒ the workspace git-plane entry.\n */\n invalidate(agentId: string, presentedPassword?: string, opts: { plane?: CredPlane; repo?: string } = {}): void {\n const key = keyOf(agentId, opts.plane ?? 'git', opts.repo)\n const entry = this.entries.get(key)\n if (!entry) return\n if (presentedPassword !== undefined && entry.token !== presentedPassword) return\n this.entries.delete(key)\n }\n\n /** agent/remove: drop the orphaned bearer tokens with the agent dir. */\n remove(agentId: string): void {\n const marker = `\\u0000${agentId}\\u0000`\n for (const key of [...this.entries.keys()]) if (key.includes(marker)) this.entries.delete(key)\n for (const key of [...this.deniedRepos.keys()]) if (key.includes(marker)) this.deniedRepos.delete(key)\n for (const key of [...this.denied]) if (key.includes(marker)) this.denied.delete(key)\n for (const key of [...this.refreshPosts]) if (key.includes(marker)) this.refreshPosts.delete(key)\n }\n\n private async fetch(key: string, payload: Parameters<GitCredentialCacheDeps['request']>[0]): Promise<Entry> {\n let grant: GitCredGrant\n try {\n grant = await this.deps.request(payload)\n } catch (e) {\n const code = (e as { code?: string }).code\n if (code === 'SCOPE_DENIED') {\n if (payload.repoFullName !== undefined) {\n // Repo-keyed: negative-cache and retry in a minute — the operator may\n // be authorizing the repo in the console right now.\n this.deniedRepos.set(key, this.monoNow() + REPO_DENIAL_TTL_MS)\n this.entries.delete(key)\n throw new GitCredUnavailableError((e as Error).message, false)\n }\n // Workspace-keyed terminal: the agent moved daemons or left github-app\n // mode. Stop asking on THIS key until the CP replicates a new spec\n // (which clears it).\n this.denied.add(key)\n this.entries.delete(key)\n throw new GitCredUnavailableError((e as Error).message, true)\n }\n throw new GitCredUnavailableError((e as Error).message, false)\n }\n // Old-CP mismatch guard (multi-repo design §协议改动): a CP that predates\n // `repoFullName` strips the field and answers with the WORKSPACE grant —\n // serving that under a repo key would hand the wrong repo's token to gh/git.\n // A NEW CP ECHOES the requested name back verbatim (even after a GitHub\n // canonical rename — it mints by numeric id but reports what we asked), so\n // this only ever fires on the genuine old-CP case, never on a rename.\n if (payload.repoFullName !== undefined && grant.repoFullName.toLowerCase() !== payload.repoFullName.toLowerCase()) {\n throw new GitCredUnavailableError(\n `control plane is too old for per-repo credentials (asked ${payload.repoFullName}, got ${grant.repoFullName})`,\n false\n )\n }\n const entry: Entry = {\n username: grant.username,\n token: grant.token,\n repoFullName: grant.repoFullName,\n access: grant.access,\n expiresAtMono: this.monoNow() + grant.ttlSec * 1000\n }\n this.entries.set(key, entry)\n return entry\n }\n\n /** A replicated spec change may re-enable a previously denied agent. */\n clearDenied(agentId: string): void {\n const marker = `\\u0000${agentId}\\u0000`\n for (const key of [...this.denied]) if (key.includes(marker)) this.denied.delete(key)\n for (const key of [...this.deniedRepos.keys()]) if (key.includes(marker)) this.deniedRepos.delete(key)\n }\n}\n","/**\n * gitcred.sock — the local credential channel for agent-run git AND gh\n * (docs/designs/github-app-git-credentials.md §本地 helper 通道;\n * agent-multi-repo-authorization.md §daemon 侧 for per-repo routing, #457).\n *\n * A tiny newline-delimited-JSON server over a unix socket (0700 dir + 0600\n * socket, stale-socket cleanup — the `mcp/control-server.ts` pattern). The\n * `agentconnect git-credential` / `gh-token` CLI subcommands connect per\n * invocation:\n * { op: 'get', agentId, repoFullName?, plane? } → { ok, username, password } | { ok:false, error }\n * { op: 'erase', agentId, password?, repoFullName?, plane? } → { ok: true }\n *\n * `repoFullName` (\"owner/repo\") routes to that repo's token; absent — or equal\n * to the agent's workspace repo, which is NORMALIZED onto the repo-less key so\n * the helper path and the pre-warm/spawn paths share one cache entry — ⇒ the\n * workspace token. `plane: 'gh'` picks the widened GH_TOKEN capability set.\n *\n * Trust model (design §信任模型 #1): same host + same OS user = ONE trust\n * domain. `agentId`/`repoFullName` here are routing, not authorization (the CP\n * gate decides) — any same-user process can already read every workspace on\n * this daemon. Tokens transit the socket and helper stdout only; nothing lands\n * on disk.\n *\n * Alongside the socket the daemon (re)writes SECRET-FREE files per boot:\n * the shim `run/git-credential-helper.sh` (pins the current node + CLI path so\n * `.git/config` survives daemon upgrades), the `run/bin/gh` wrapper (see\n * cp/gh-shim.ts) and per-agent gitconfig includes for the session-env channel\n * (see workspace/git-injection.ts).\n */\nimport { createServer, type Server, type Socket } from 'node:net'\nimport { chmodSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { dirname, join } from 'node:path'\nimport { GitCredentialCache, GitCredUnavailableError, type CredPlane } from './git-credential.js'\n\nexport function gitcredSocketPath(root: string): string {\n return join(root, 'run', 'gitcred.sock')\n}\n\nexport function gitcredShimPath(root: string): string {\n return join(root, 'run', 'git-credential-helper.sh')\n}\n\ninterface GitCredIpcRequest {\n op: 'get' | 'erase'\n agentId: string\n password?: string\n /** \"owner/repo\" to route to (multi-repo, #457); absent ⇒ workspace. */\n repoFullName?: string\n /** 'gh' ⇒ the widened GH_TOKEN capability set; absent/'git' ⇒ contents-only. */\n plane?: string\n}\n\nexport interface GitCredServerDeps {\n log: { info: (m: string) => void; warn: (m: string) => void }\n /** The agent's workspace \"owner/repo\" label (lowercase-insensitive compare) —\n * lets a helper request that names the workspace repo share the repo-less\n * cache key with pre-warm/spawn instead of splitting the cache. */\n workspaceRepoOf?: (agentId: string) => string | undefined\n}\n\nexport class GitCredServer {\n private server?: Server\n private readonly log: GitCredServerDeps['log']\n private readonly workspaceRepoOf?: (agentId: string) => string | undefined\n\n constructor(\n private readonly cache: GitCredentialCache,\n private readonly path: string,\n deps: GitCredServerDeps\n ) {\n this.log = deps.log\n if (deps.workspaceRepoOf) this.workspaceRepoOf = deps.workspaceRepoOf\n }\n\n start(): void {\n const dir = dirname(this.path)\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n try {\n chmodSync(dir, 0o700) // defeat a loose umask; best-effort on non-POSIX\n } catch {\n /* best-effort */\n }\n rmSync(this.path, { force: true }) // a stale socket from a crash makes listen() throw\n\n const server = createServer((sock) => this.serve(sock))\n server.listen(this.path, () => {\n try {\n chmodSync(this.path, 0o600)\n } catch {\n /* best-effort */\n }\n this.log.info(`gitcred: helper socket at ${this.path}`)\n })\n server.on('error', (e) => this.log.warn(`gitcred: socket error: ${e.message}`))\n this.server = server\n }\n\n stop(): void {\n this.server?.close()\n rmSync(this.path, { force: true })\n }\n\n private serve(sock: Socket): void {\n let buf = ''\n sock.on('data', (chunk) => {\n buf += chunk.toString('utf8')\n const nl = buf.indexOf('\\n')\n if (nl === -1) return\n const line = buf.slice(0, nl)\n buf = buf.slice(nl + 1)\n void this.handle(line, sock)\n })\n sock.on('error', () => sock.destroy())\n }\n\n private async handle(line: string, sock: Socket): Promise<void> {\n const reply = (msg: unknown) => {\n sock.write(JSON.stringify(msg) + '\\n')\n sock.end()\n }\n let req: GitCredIpcRequest\n try {\n req = JSON.parse(line) as GitCredIpcRequest\n } catch {\n return reply({ ok: false, error: 'malformed request' })\n }\n const plane: CredPlane = req.plane === 'gh' ? 'gh' : 'git'\n // Workspace normalization: a request naming the workspace repo folds onto\n // the repo-less key (one cache entry with pre-warm/spawn; and old CPs that\n // strip the wire field keep serving the workspace unchanged).\n let repo = typeof req.repoFullName === 'string' && req.repoFullName.includes('/') ? req.repoFullName : undefined\n if (repo !== undefined) {\n const workspace = this.workspaceRepoOf?.(req.agentId)\n if (workspace && workspace.toLowerCase() === repo.toLowerCase()) repo = undefined\n }\n if (req.op === 'erase') {\n // Git presents the rejected credential — GitHub revokes instantly on\n // uninstall/suspend, and this is how the daemon cache learns.\n this.cache.invalidate(req.agentId, req.password, { plane, ...(repo !== undefined ? { repo } : {}) })\n return reply({ ok: true })\n }\n if (req.op !== 'get' || typeof req.agentId !== 'string') {\n return reply({ ok: false, error: 'unsupported op' })\n }\n try {\n const cred = await this.cache.get(req.agentId, 'helper', { plane, ...(repo !== undefined ? { repo } : {}) })\n return reply({ ok: true, username: cred.username, password: cred.token, repoFullName: cred.repoFullName })\n } catch (e) {\n const msg =\n e instanceof GitCredUnavailableError ? e.message : `git credentials unavailable: ${(e as Error).message}`\n return reply({ ok: false, error: msg })\n }\n }\n}\n\n/**\n * (Re)write the secret-free shim `.git/config` helper lines exec through. The\n * absolute node + CLI paths are re-pinned every daemon boot, so repo configs\n * keep working across upgrades/relocations. Quoted throughout — a home dir\n * with a space (macOS \"/Users/John Smith/…\") must not word-split.\n */\nexport function writeGitcredShim(root: string, cliEntry: string): string {\n const shim = gitcredShimPath(root)\n mkdirSync(dirname(shim), { recursive: true, mode: 0o700 })\n const q = (v: string) => `'${v.replaceAll(\"'\", \"'\\\\''\")}'`\n // Production runs the built dist (a .js entry node executes directly). A dev\n // daemon runs under tsx with a .ts argv[1] — route the shim through the tsx\n // CLI then, or plain `node entry.ts` would die resolving .js-suffixed imports.\n const argv = [q(process.execPath)]\n if (cliEntry.endsWith('.ts')) {\n const req = createRequire(import.meta.url)\n argv.push(q(req.resolve('tsx/cli')))\n }\n argv.push(q(cliEntry))\n const body = [\n '#!/bin/sh',\n '# agentconnect git credential helper shim — regenerated on daemon start; NO secrets.',\n `AGENTCONNECT_ROOT=${q(root)} \\\\`,\n ` exec ${argv.join(' ')} git-credential \"$@\"`,\n ''\n ].join('\\n')\n writeFileSync(shim, body, { mode: 0o755 })\n return shim\n}\n"],"mappings":"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"}
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-D8QIIHor.js"),
68496
- import("./login-BqF1z9wE.js"),
68497
- import("./run-foreground-DEmppQGW.js"),
68495
+ import("./chat-CsR5oJP2.js"),
68496
+ import("./login-D1KMrVDs.js"),
68497
+ import("./run-foreground-PrZ6IyPn.js"),
68498
68498
  import("./service-6w0WKhdx.js"),
68499
68499
  import("./lock-CA9Bm8_s.js"),
68500
68500
  import("./paths-CcRUjFjy.js")
@@ -68646,17 +68646,17 @@ program.command("login").description("Interactive onboarding: test the Control P
68646
68646
  }
68647
68647
  });
68648
68648
  program.command("git-credential").description("Git credential helper backed by the local daemon (github-app workspaces)").argument("<agentId>", "agent whose workspace credentials to serve").argument("<action>", "git credential action: get | store | erase").action(async (agentId, action) => {
68649
- const { runGitCredential } = await import("./git-credential-DhWn1IBJ.js");
68649
+ const { runGitCredential } = await import("./git-credential-BJUvnCyW.js");
68650
68650
  await runGitCredential(action, agentId);
68651
68651
  });
68652
68652
  program.command("gh-token").description("Fetch a per-repo GH_TOKEN from the local daemon (gh wrapper backend)").argument("<agentId>", "agent whose credentials to serve").argument("[repo]", "target repo (owner/repo, host/owner/repo, or URL); absent = workspace").action(async (agentId, repo) => {
68653
- const { runGhToken } = await import("./gh-token-BfPlYQav.js");
68653
+ const { runGhToken } = await import("./gh-token-CppI9sti.js");
68654
68654
  await runGhToken(agentId, repo);
68655
68655
  });
68656
68656
  program.command("agent").description("Inspect local agent directories").command("list").description("List agents discovered under --agents-dir (id, status, runtime, name, dir)").action(async () => {
68657
68657
  const opts = program.opts();
68658
68658
  try {
68659
- const { runAgentList } = await import("./agent-list-Da9Osn4O.js");
68659
+ const { runAgentList } = await import("./agent-list-Drju-EDG.js");
68660
68660
  await runAgentList({
68661
68661
  agentsDir: opts.agentsDir,
68662
68662
  configPath: opts.config,
@@ -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 { m as RelayRosterEntry } from "./dist-Cq1Kimg_.js";
2
+ import { m as RelayRosterEntry } from "./dist-CO_RU4EO.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";
@@ -398,4 +398,4 @@ function selectAgent(agentsDir, name) {
398
398
  //#endregion
399
399
  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 };
400
400
 
401
- //# sourceMappingURL=load-agents-gTqVkOwl.js.map
401
+ //# sourceMappingURL=load-agents-BC4zZ0xL.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-agents-gTqVkOwl.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 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. Silent: skipped turns are dropped, not\n // recorded. A flip is 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-BC4zZ0xL.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 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. Silent: skipped turns are dropped, not\n // recorded. A flip is 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":";;;;;;;AAIA,MAAM,gBAAgBA,MAAQC,OAAS;CAAE,MAAMC,OAAS;CAAG,OAAOA,OAAS;AAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAE3F,MAAa,mBAAmBD,OAAS;CACvC,SAASC,OAAS;CAClB,MAAMF,MAAQE,OAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CACpC,KAAK;AACP,CAAC;AAOD,MAAa,qBAAqBC,OACxB;CACN,WAAWC,MAAO;EAAC;EAAS;EAAQ;CAAK,CAAC,CAAC,CAAC,QAAQ,OAAO;CAE3D,SAASF,OAAS,CAAC,CAAC,SAAS;CAC7B,MAAMF,MAAQE,OAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CACpC,KAAK;CAEL,KAAKA,OAAS,CAAC,CAAC,SAAS;CACzB,SAAS;AACX,CAAC,CAAC,CACD,aAAa,KAAK,QAAQ;CACzB,IAAI,IAAI,cAAc,WAAW,CAAC,IAAI,SACpC,IAAI,SAAS;EAAE,MAAM;EAAU,MAAM,CAAC,SAAS;EAAG,SAAS;CAAwC,CAAC;CACtG,IAAI,IAAI,cAAc,WAAW,CAAC,IAAI,KACpC,IAAI,SAAS;EAAE,MAAM;EAAU,MAAM,CAAC,KAAK;EAAG,SAAS,KAAK,IAAI,UAAU;CAA4B,CAAC;AAC3G,CAAC;AAGH,MAAa,eAAeD,OAAS;CACnC,SAASI,QAAU,CAAC;CACpB,UAAUH,OAAS,CAAC,CAAC,SAAS;CAK9B,WAAWA,OAAS,CAAC,CAAC,SAAS;CAC/B,cAAcI,OACJ;EACN,SAASC,QAAU,CAAC,CAAC,QAAQ,IAAI;EACjC,KAAKL,OAAS,CAAC,CAAC,SAAS;EACzB,KAAKA,OAAS,CAAC,CAAC,SAAS;EACzB,aAAaM,OAAS,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAK;CAC7C,CAAC,CAAC,CACD,QAAQ;EAAE,SAAS;EAAO,aAAa;CAAM,CAAC;CACjD,WAAWN,OAAS,CAAC,CAAC,SAAS;CAC/B,UAAUO,OAASP,OAAS,GAAG,gBAAgB,CAAC,CAAC,SAAS;CAG1D,YAAYO,OAASP,OAAS,GAAG,kBAAkB,CAAC,CAAC,SAAS;CAK9D,QAAQF,MAAQ,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC5C,SAASM,OACC,EAAE,OAAOF,MAAO;EAAC;EAAS;EAAS;EAAQ;EAAQ;CAAO,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,CACtF,QAAQ,EAAE,OAAO,OAAO,CAAC;CAC5B,QAAQE,OACE;EACN,WAAWE,OAAS,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;EACrC,uBAAuBA,OAAS,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;EAClD,oBAAoBA,OAAS,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAO;EAIpD,aAAaA,OAAS,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAM;EAG5C,iBAAiBA,OAAS,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAM;EAGhD,kBAAkBA,OAAS,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAM;EAKjD,oBAAoBA,OAAS,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;EAErD,qBAAqBA,OAAS,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,GAAG;EAIxD,oBAAoBE,OACV,CAAC,CACR,IAAI,CAAC,CACL,QAAQ,IAAI,OAAO,IAAI;CAC5B,CAAC,CAAC,CACD,QAAQ;EACP,WAAW;EACX,uBAAuB;EACvB,oBAAoB;EACpB,aAAa;EACb,iBAAiB;EACjB,kBAAkB;EAClB,oBAAoB;EACpB,qBAAqB;EACrB,oBAAoB,IAAI,OAAO;CACjC,CAAC;AACL,CAAC;;;AC1FD,SAAgB,WACd,OAAoH,CAAC,GAC7G;CACR,MAAM,OAAO,YAAY,KAAK,IAAI;CAClC,MAAM,OAAO,KAAK,cAAc,WAAW,IAAI;CAM/C,IAAI;CACJ,IAAI,WAAW,IAAI,GACjB,MAAM,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;MACtC,IAAI,KAAK,YAAY;EAC1B,MAAM,EAAE,SAAS,EAAE;EACnB,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC5C,cAAc,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,IAAI;CACzD,OAAO,IAAI,KAAK,UACd,MAAM,EAAE,SAAS,EAAE;MAEnB,MAAM,IAAI,MAAM,qBAAqB,KAAK,2DAA2D;CAEvG,MAAM,MAAM,aAAa,MAAM,GAAG;CAElC,MAAM,IAAI,KAAK,aAAa,CAAC;CAC7B,IAAI,EAAE,UAAU,IAAI,WAAW,EAAE;CACjC,IAAI,EAAE,UAAU,IAAI,QAAQ,QAAQ,EAAE;CACtC,IAAI,EAAE,cAAc,KAAA,GAAW,IAAI,OAAO,YAAY,EAAE;CACxD,IAAI,EAAE,OAAO,IAAI,aAAa,MAAM,EAAE;CACtC,IAAI,EAAE,OAAO,IAAI,aAAa,MAAM,EAAE;CAGtC,IAAI,EAAE,SAAS,EAAE,OAAO,IAAI,aAAa,UAAU;CACnD,IAAI,EAAE,MAAM,IAAI,aAAa,UAAU;CAEvC,IAAI,YAAY,EAAE,aAAa,IAAI,aAAa,iBAAiB,IAAI;CACrE,OAAO;AACT;;;;;;AAOA,SAAgB,gBAAgB,MAA0B,UAAwB;CAChF,IAAI;EACF,MAAM,OAAO,WAAW,YAAY,IAAI,CAAC;EACzC,MAAM,MAAM,WAAW,IAAI,IAAI,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE;EACrF,IAAI,WAAW;EACf,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC5C,cAAc,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,IAAI;CACzD,QAAQ,CAER;AACF;;;;;;;;AASA,SAAgB,cAAc,MAA0B,QAAkC;CACxF,IAAI;EACF,MAAM,OAAO,WAAW,YAAY,IAAI,CAAC;EACzC,MAAM,MAAM,WAAW,IAAI,IAAI,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE;EACrF,IAAI,SAAS;EACb,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC5C,cAAc,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,IAAI;CACzD,QAAQ,CAER;AACF;;;ACvFA,MAAa,kBAAkBC,mBAAqB,QAAQ;CAC1DC,OAAS,EAAE,MAAMC,QAAU,SAAS,EAAE,CAAC;CACvCD,OAAS,EAAE,MAAMC,QAAU,IAAI,EAAE,CAAC;CAClCD,OAAS;EAAE,MAAMC,QAAU,SAAS;EAAG,OAAOC,OAAS;CAAE,CAAC;CAC1DF,OAAS,EAAE,MAAMC,QAAU,MAAM,EAAE,CAAC;AACtC,CAAC;AAGD,MAAa,uBAAuBD,OAAS;CAC3C,SAASE,OAAS,CAAC,CAAC,SAAS;CAC7B,QAAQA,OAAS,CAAC,CAAC,SAAS;CAC5B,OAAO;AACT,CAAC;AAGD,MAAa,oBAAoBF,OAAS;CAKxC,MAAMG,MAAO,CAAC,UAAU,QAAQ,CAAC,CAAC,CAAC,QAAQ,QAAQ;CACnD,UAAUD,OAAS;CACnB,UAAUA,OAAS,CAAC,CAAC,SAAS;CAC9B,eAAeA,OAAS,CAAC,CAAC,SAAS;CACnC,WAAWA,OAAS,CAAC,CAAC,SAAS;CAC/B,gBAAgBE,MAAQF,OAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC9C,WAAWE,MAAQ,oBAAoB,CAAC,CAAC,QAAQ,CAAC,CAAC;AACrD,CAAC;AAGD,MAAa,uBAAuBJ,OAAS;CAC3C,UAAUE,OAAS;CACnB,WAAWA,OAAS,CAAC,CAAC,SAAS;CAC/B,aAAaA,OAAS,CAAC,CAAC,SAAS;CACjC,gBAAgBE,MAAQF,OAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC9C,WAAWE,MAAQ,oBAAoB,CAAC,CAAC,QAAQ,CAAC,CAAC;AACrD,CAAC;AAGD,MAAa,sBAAsBJ,OAAS;CAC1C,UAAUE,OAAS;CACnB,eAAeA,OAAS,CAAC,CAAC,SAAS;CACnC,WAAWA,OAAS,CAAC,CAAC,SAAS;CAC/B,gBAAgBE,MAAQF,OAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC9C,WAAWE,MAAQ,oBAAoB,CAAC,CAAC,QAAQ,CAAC,CAAC;AACrD,CAAC;AAGD,MAAa,oBAAoBL,mBAAqB,YAAY;CAChEC,OAAS;EACP,IAAIE,OAAS;EACb,UAAUD,QAAU,OAAO;EAC3B,OAAO;CACT,CAAC;CACDD,OAAS;EACP,IAAIE,OAAS;EACb,UAAUD,QAAU,UAAU;EAC9B,UAAU;CACZ,CAAC;CACDD,OAAS;EACP,IAAIE,OAAS;EACb,UAAUD,QAAU,SAAS;EAC7B,SAAS;CACX,CAAC;AACH,CAAC;;;;;;;AASD,MAAa,gBAAgBD,OAAS;CACpC,IAAIE,OAAS;CACb,UAAUA,OAAS;CAGnB,QAAQG,OACE;EACN,UAAUF,MAAO;GAAC;GAAS;GAAY;EAAS,CAAC;EACjD,SAASD,OAAS;EAClB,eAAeA,OAAS,CAAC,CAAC,SAAS;CACrC,CAAC,CAAC,CACD,SAAS;CACZ,SAASA,OAAS;CAClB,SAASI,QAAU,CAAC,CAAC,QAAQ,IAAI;CACjC,QAAQL,QAAU,IAAI,CAAC,CAAC,SAAS;AACnC,CAAC;AAGD,MAAa,cAAcD,OAAS;CAClC,IAAIE,OAAS;CACb,MAAMA,OAAS;CACf,QAAQC,MAAO;EAAC;EAAU;EAAY;CAAQ,CAAC,CAAC,CAAC,QAAQ,QAAQ;CAMjE,OAAOG,QAAU,CAAC,CAAC,QAAQ,KAAK;CAChC,SAASJ,OAAS;CAGlB,aAAaA,OAAS,CAAC,CAAC,SAAS;CACjC,iBAAiBA,OAAS,CAAC,CAAC,SAAS;CACrC,eAAeA,OAAS,CAAC,CAAC,SAAS;CAGnC,UAAUI,QAAU,CAAC,CAAC,SAAS;CAI/B,gBAAgBJ,OAAS,CAAC,CAAC,QAAQ,SAAS;CAC5C,kBAAkBG,OACR;EACN,OAAOH,OAAS,CAAC,CAAC,SAAS;EAC3B,KAAKE,MAAQJ,OAAS;GAAE,MAAME,OAAS;GAAG,OAAOA,OAAS;EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC5E,CAAC,CAAC,CACD,SAAS;CAIZ,YAAYE,MAAQF,OAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAM1C,YAAYC,MAAO,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,QAAQ,KAAK;CACrD,uBAAuBC,MAAQF,OAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAIrD,QAAQF,OAAS,EAAE,UAAUG,MAAO;EAAC;EAAU;EAAW;CAAU,CAAC,CAAC,CAAC,QAAQ,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS;CACtG,WAAWH,OAAS;EAClB,MAAMG,MAAO,CAAC,YAAY,cAAc,CAAC;EACzC,MAAMD,OAAS;EACf,SAASA,OAAS,CAAC,CAAC,SAAS;EAC7B,WAAWA,OAAS,CAAC,CAAC,QAAQ,MAAM;EAIpC,eAAeC,MAAO,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;EAC/C,kBAAkBG,QAAU,CAAC,CAAC,QAAQ,IAAI;EAC1C,QAAQF,MAAQF,OAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CACxC,CAAC;CACD,cAAcE,MAAQ,iBAAiB,CAAC,CAAC,QAAQ,CAAC,CAAC;CAEnD,QAAQJ,OAAS,EAAE,MAAMG,MAAO;EAAC;EAAO;EAAU;CAAM,CAAC,CAAC,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,MAAM,CAAC;CACpG,aAAaE,OACH;EAAE,QAAQF,MAAO,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,QAAQ,KAAK;EAAG,aAAaC,MAAQF,OAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAAE,CAAC,CAAC,CACxG,QAAQ;EAAE,QAAQ;EAAO,aAAa,CAAC;CAAE,CAAC;CAC7C,OAAOE,MAAQ,aAAa,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;;;ACrJD,SAAgB,YAAY,MAAsC;CAChE,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,WAAW,KAAK,MAAM,IAAI,GAAG;EACtC,MAAM,OAAO,QAAQ,QAAQ,OAAO,EAAE,CAAC,CAAC,KAAK;EAC7C,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,GAAG;EACnC,MAAM,OAAO,KAAK,WAAW,SAAS,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;EACjE,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,OAAO,IAAI;EACf,MAAM,MAAM,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK;EACnC,IAAI,CAAC,KAAK;EACV,IAAI,QAAQ,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK;EACpC,MAAM,IAAI,MAAM;EAChB,IAAI,MAAM,UAAU,MAAM,MAAM,QAAO,MAAM,QAAQ,MAAM,GAAG,EAAE,MAAM,GACpE,QAAQ,MAAM,MAAM,GAAG,EAAE;EAE3B,IAAI,OAAO;CACb;CACA,OAAO;AACT;AAGA,SAAgB,WAAW,KAAqC;CAC9D,MAAM,OAAO,KAAK,KAAK,MAAM;CAC7B,IAAI,CAAC,WAAW,IAAI,GAAG,OAAO,CAAC;CAC/B,OAAO,YAAY,aAAa,MAAM,MAAM,CAAC;AAC/C;AAEA,MAAM,SAAS;AAEf,SAAS,kBACP,OACA,QACA,MACA,KACQ;CACR,OAAO,MAAM,QAAQ,SAAS,IAAI,SAAiB;EACjD,MAAM,WAAW,OAAO;EACxB,IAAI,aAAa,KAAA,GACf,MAAM,IAAI,MAAM,UAAU,IAAI,GAAG,mBAAmB,KAAK,OAAO,KAAK,UAAU,IAAI,QAAQ,EAAE;EAE/F,OAAO;CACT,CAAC;AACH;AAEA,SAAS,KACP,MACA,QACA,MACA,KACS;CACT,IAAI,OAAO,SAAS,UAAU,OAAO,kBAAkB,MAAM,QAAQ,MAAM,GAAG;CAC9E,IAAI,MAAM,QAAQ,IAAI,GAAG;EACvB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,QAAQ,GAAG,KAAK,GAAG,EAAE,IAAI,GAAG;EAC1F,OAAO;CACT;CACA,IAAI,QAAQ,OAAO,SAAS,UAAU;EACpC,MAAM,MAAM;EACZ,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,GAC/B,IAAI,OAAO,KAAK,IAAI,MAAM,QAAQ,OAAO,GAAG,KAAK,GAAG,QAAQ,KAAK,GAAG;EAEtE,OAAO;CACT;CACA,OAAO;AACT;AAIA,SAAgB,iBACd,OACA,QACA,KACM;CACN,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC7B;AAIA,SAAgB,cAAc,OAGH;CACzB,OAAO;EACL,GAAG,MAAM;EACT,GAAG,OAAO,aAAa,MAAM,kBAAkB,OAAO,CAAC,EAAA,CAAG,KAAK,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;CACzF;AACF;;;ACvFA,MAAM,+BAAe,IAAI,IAAI,CAAC,gBAAgB,MAAM,CAAC;AACrD,MAAM,YAAY;AAOlB,SAAS,eAAe,MAA2B;CACjD,MAAM,MAAM,QAAQ,IAAI;CACxB,MAAM,MAAM,WAAW,GAAG;CAC1B,MAAM,QAAQ,YAAY,MAAM,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC,CAAC;CACtE,iBAAiB,OAAO;EAAE,GAAG,QAAQ;EAAK,GAAG;CAAI,GAAG;EAAE,IAAI,MAAM;EAAI,SAAS,KAAK,KAAK,MAAM;CAAE,CAAC;CAChG,IAAI,CAAC,WAAW,MAAM,UAAU,IAAI,GAClC,MAAM,UAAU,OAAO,QAAQ,KAAK,MAAM,UAAU,IAAI;CAE1D,OAAO;EAAE,GAAG;EAAO;EAAK;CAAI;AAC9B;AAKA,SAAgB,eAAe,KAAa,QAAQ,GAAa;CAC/D,IAAI,QAAQ,aAAa,CAAC,WAAW,GAAG,GAAG,OAAO,CAAC;CACnD,IAAI;CACJ,IAAI;EACF,UAAU,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;CACpD,QAAQ;EACN,OAAO,CAAC;CACV;CACA,IAAI,QAAQ,MAAM,MAAM,EAAE,OAAO,KAAK,EAAE,SAAS,YAAY,GAC3D,OAAO,CAAC,KAAK,KAAK,YAAY,CAAC;CAEjC,MAAM,MAAgB,CAAC;CACvB,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,CAAC,MAAM,YAAY,GAAG;EAC1B,IAAI,aAAa,IAAI,MAAM,IAAI,KAAK,MAAM,KAAK,WAAW,GAAG,GAAG;EAChE,IAAI,KAAK,GAAG,eAAe,KAAK,KAAK,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC;CAC9D;CACA,OAAO;AACT;AAIA,SAAgB,eAAe,WAA0D;CACvF,OAAO,eAAe,SAAS,CAAC,CAAC,KAAK,SAAS;EAC7C,IAAI;GACF,OAAO;IAAE,OAAO,eAAe,IAAI;IAAG,KAAK,QAAQ,IAAI;GAAE;EAC3D,SAAS,KAAK;GACZ,MAAM,IAAI,MAAM,yBAAyB,KAAK,IAAK,IAAc,SAAS;EAC5E;CACF,CAAC;AACH;AAGA,SAAgB,WAAW,WAAkC;CAC3D,OAAO,eAAe,SAAS,CAAC,CAC7B,KAAK,MAAM,EAAE,KAAK,CAAC,CACnB,QAAQ,MAAM,EAAE,WAAW,QAAQ;AACxC;AAKA,SAAgB,YAAY,WAAmB,MAA4B;CACzE,MAAM,SAAS,eAAe,SAAS,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK;CAC3D,IAAI,MAAM;EACR,MAAM,QAAQ,OAAO,MAAM,MAAM,EAAE,OAAO,IAAI;EAC9C,IAAI,CAAC,OAAO;GACV,MAAM,YACJ,OACG,KAAK,MAAM,EAAE,EAAE,CAAC,CAChB,KAAK,CAAC,CACN,KAAK,IAAI,KAAK;GACnB,MAAM,IAAI,MAAM,UAAU,KAAK,iBAAiB,UAAU,eAAe,WAAW;EACtF;EACA,OAAO;CACT;CACA,IAAI,OAAO,WAAW,GAAG,MAAM,IAAI,MAAM,0BAA0B,WAAW;CAC9E,IAAI,OAAO,SAAS,GAAG;EACrB,MAAM,MAAM,OACT,KAAK,MAAM,EAAE,EAAE,CAAC,CAChB,KAAK,CAAC,CACN,KAAK,IAAI;EACZ,MAAM,IAAI,MAAM,4BAA4B,UAAU,IAAI,IAAI,oCAAoC;CACpG;CACA,OAAO,OAAO;AAChB"}
@@ -1,7 +1,7 @@
1
1
  import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
2
- import { d as encode, l as buildEnvelope, t as isFrame, u as decodeEnvelope } from "./dist-Cq1Kimg_.js";
3
- import { i as ClientTransport, t as runForeground } from "./run-foreground-syksXj_b.js";
4
- import { l as ConfigSchema } from "./load-agents-gTqVkOwl.js";
2
+ import { d as encode, l as buildEnvelope, t as isFrame, u as decodeEnvelope } from "./dist-CO_RU4EO.js";
3
+ import { i as ClientTransport, t as runForeground } from "./run-foreground-CN_44eeH.js";
4
+ import { l as ConfigSchema } from "./load-agents-BC4zZ0xL.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-BqF1z9wE.js.map
232
+ //# sourceMappingURL=login-D1KMrVDs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"login-BqF1z9wE.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":";;;;;;;;;;;;;;;;;;AA2BA,eAAsB,UAAU,MAAuC;CACrE,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,OAAO,KAAK,kBAAkB,gBAAgB,KAAK,KAAK,KAAK;EAAE,aAAA;EAA6B,MAAA;CAAiB,CAAC;CAEpH,IAAI;CACJ,IAAI;EACF,YAAY,MAAM,KAAK;CACzB,SAAS,KAAK;EACZ,OAAO;GAAE,IAAI;GAAO,QAAQ,gBAAgB,KAAK,IAAI,IAAK,IAAc;EAAU;CACpF;CAEA,MAAM,cAAuC;EAC3C,QAAQ,KAAK;EACb,cAAc,KAAK,gBAAgB;CACrC;CACA,IAAI,KAAK,UAAU,YAAY,WAAW,KAAK;CAC/C,MAAM,QAAQ,cAAc,QAAQ,WAAW;CAE/C,OAAO,MAAM,IAAI,SAAsB,YAAY;EACjD,IAAI,UAAU;EACd,MAAM,QAAQ,MAAyB;GACrC,IAAI,SAAS;GACb,UAAU;GACV,aAAa,KAAK;GAClB,IAAI;IACF,UAAU,MAAM,KAAM,YAAY;GACpC,QAAQ,CAER;GACA,QAAQ,CAAC;EACX;EAEA,MAAM,QAAQ,iBACN,KAAK;GAAE,IAAI;GAAO,QAAQ,mBAAmB,UAAU;EAAwB,CAAC,GACtF,SACF;EAEA,UAAU,WAAW,SAAS;GAC5B,MAAM,UAAU,eAAe,IAAI;GACnC,IAAI,CAAC,QAAQ,IAAI;GACjB,MAAM,IAAI,QAAQ;GAClB,IAAI,EAAE,SAAS,MAAM,IAAI;GACzB,IAAI,EAAE,SAAS,SAAS;IACtB,MAAM,IAAI,EAAE;IACZ,KAAK;KAAE,IAAI;KAAO,QAAQ,GAAG,EAAE,KAAK,IAAI,EAAE;IAAU,CAAC;IACrD;GACF;GACA,IAAI,QAAQ,SAAS,CAAC,CAAC,CAAC,GAAG;IACzB,MAAM,KAAK,EAAE;IACb,KAAK;KAAE,IAAI;KAAM,UAAU,GAAG;IAAS,CAAC;GAC1C;EACF,CAAC;EAED,UAAU,SAAS,SAAS;GAC1B,IAAI,SAAS,MAAM,KAAK;IAAE,IAAI;IAAO,QAAQ;GAAwD,CAAC;QACjG,KAAK;IAAE,IAAI;IAAO,QAAQ,sBAAsB,KAAK;GAAkB,CAAC;EAC/E,CAAC;EAED,UAAU,KAAK,OAAO,KAAK,CAAC;CAC9B,CAAC;AACH;;;;;;;;;;;;;;ACzDA,SAAgB,mBAAmB,MAAgC;CACjE,MAAM,OAAO,YAAY,KAAK,IAAI;CAClC,MAAM,OAAO,KAAK,cAAc,WAAW,IAAI;CAC/C,MAAM,MAA+B,WAAW,IAAI,IAAI,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE;CAG9G,IAAI,eAAe;EAAE,GADT,IAAI,gBAAwD,CAAC;EAC7C,KAAK,KAAK;EAAO,KAAK,KAAK;EAAO,SAAS;CAAK;CAC5E,IAAI,KAAK,UAAU,IAAI,WAAW,KAAK;CAEvC,aAAa,MAAM,GAAG;CAEtB,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CAC5C,cAAc,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,IAAI;CACvD,OAAO;AACT;;;;;;;;;AAwBA,SAAgB,iBAAiB,MAAyD;CACxF,OAAO;EACL,UAAU,QAAQ;EAClB,UAAU,KAAK,YAAY,cAAc,OAAO,KAAK,GAAG;EACxD,gBAAgB,YAAY,KAAK,IAAI,MAAM,YAAY,KAAA,CAAS;CAClE;AACF;AAWA,SAAS,SAAS,MAA+B;CAC/C,OAAO;EACL,QAAQ,MAAM,UAAU;GAAE,KAAK,EAAE;GAAK,OAAO,EAAE;GAAO,cAAc;EAAe,CAAC;EACpF,gBAAgB,YAAY;GAC1B,MAAM,aAAa,kBAAkB,EAAE,MAAM,KAAK,KAAK,CAAC;GACxD,MAAM,WAAW,QAAQ,iBAAiB,IAAI,CAAC;GAC/C,MAAM,WAAW,GAAG;EACtB;EACA,qBACEA,cAAkB;GAChB,MAAM,KAAK;GACX,WAAW;IAAE,OAAO,KAAK;IAAO,OAAO,KAAK;IAAO,UAAU,KAAK;GAAS;EAC7E,CAAC;EACH,KAAK,QAAQ;EACb,OAAO,QAAQ;EACf,OAAO,QAAQ,QAAQ,MAAM,KAAK;CACpC;AACF;;AAGA,eAAe,SACb,MACA,QACA,KACiB;CACjB,IAAI,MAAM,MAAM;CAChB,MAAM,EAAE,OAAO,SAAS,MAAM,KAAK,KAAK;CACxC,IAAI,MAAM,MAAM,IAAI,MAAM,wCAAwC,QAAQ;CAC1E,OAAQ,MAAiB,KAAK;AAChC;AAEA,eAAsB,SAAS,MAAoB,UAA8B,CAAC,GAAkB;CAClG,MAAM,OAAkB;EAAE,GAAG,SAAS,IAAI;EAAG,GAAG;CAAQ;CACxD,MAAM,EAAE,QAAQ;CAGhB,IAAI,CAAC,KAAK,OAAO;EACf,IAAI,CAAC,KAAK,OAAO,MAAM,IAAI,MAAM,+BAA+B;EAChE,IAAI,CAAC,KAAK,OAAO,MAAM,IAAI,MAAM,+BAA+B;EAChE,MAAM,IAAI,MAAM,KAAK,MAAM;GAAE,KAAK,KAAK;GAAO,OAAO,KAAK;EAAM,CAAC;EACjE,IAAI,CAAC,EAAE,IAAI,MAAM,IAAI,MAAM,0BAA0B,EAAE,QAAQ;EAC/D,mBAAmB;GAAE,GAAG;GAAM,OAAO,KAAK;GAAO,OAAO,KAAK;EAAM,CAAC;EACpE,IAAI,MAAM,kBAAkB,EAAE,WAAW,YAAY,EAAE,SAAS,KAAK,GAAG,uBAAuB;EAC/F;CACF;CAOA,IAAI,KAAK,YACP,MAAM,IAAI,MACR,6HACF;CAIF,MAAM,KAAK,gBAAgB;EAAE,OAAO,KAAK;EAAO,QAAQ;CAAI,CAAC;CAC7D,MAAM,OAAO,GAAG,OAAO,cAAc,CAAC;CACtC,IAAI,MAAM,KAAK;CACf,IAAI,QAAQ,KAAK;CACjB,IAAI;EACF,IAAI,CAAC,KAAK,MAAM,MAAM,SAAS,MAAM,uBAAuB,GAAG;EAE/D,KAAK,IAAI,UAAU,IAAK,WAAW;GACjC,IAAI,CAAC,OAAO,QAAQ,MAAM,SAAS,MAAM,oBAAoB,GAAG;GAChE,IAAI,MAAM,yBAAyB,IAAI,IAAI;GAC3C,MAAM,IAAI,MAAM,KAAK,MAAM;IAAO;IAAa;GAAO,CAAC;GACvD,IAAI,EAAE,IAAI;IACR,IAAI,MAAM,kBAAkB,EAAE,WAAW,YAAY,EAAE,SAAS,KAAK,GAAG,GAAG;IAC3E;GACF;GACA,IAAI,MAAM,KAAK,EAAE,OAAO,GAAG;GAC3B,IAAI,WAAW,GAAG,MAAM,IAAI,MAAM,uBAAuB;GACzD,QAAQ,KAAA;EACV;EAEA,mBAAmB;GAAE,GAAG;GAAM,OAAO;GAAM,OAAO;EAAO,CAAC;EAE1D,MAAM,OAAO,MAAM,SAAS,MAAM,wDAAwD,GAAG,EAAA,CAAG,YAAY;EAC5G,IAAI,QAAQ,OAAO,QAAQ,OAAO;GAChC,MAAM,KAAK,eAAe;GAC1B,IAAI,MAAM,wFAAwF;GAClG;EACF;EACA,IAAI,MAAM,gDAAgD;CAC5D,UAAU;EACR,GAAG,MAAM;CACX;CAEA,MAAM,KAAK,cAAc;AAC3B"}
1
+ {"version":3,"file":"login-D1KMrVDs.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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"}