@agentconnect.md/daemon 1.0.0-rc.286 → 1.0.0-rc.288

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.
@@ -1,4 +1,4 @@
1
- import { o as loadConfig, t as discoverAgents } from "./load-agents-DLEvTgkd.js";
1
+ import { o as loadConfig, t as discoverAgents } from "./load-agents-BLNSM987.js";
2
2
  //#region src/cli/agent-list.ts
3
3
  async function runAgentList(opts) {
4
4
  const out = opts.out ?? process.stdout;
@@ -18,4 +18,4 @@ async function runAgentList(opts) {
18
18
  //#endregion
19
19
  export { runAgentList };
20
20
 
21
- //# sourceMappingURL=agent-list-BgEiQL1D.js.map
21
+ //# sourceMappingURL=agent-list-DS3NGUJO.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-list-BgEiQL1D.js","names":[],"sources":["../src/cli/agent-list.ts"],"sourcesContent":["import { loadConfig } from '../config/load-config.js'\nimport { discoverAgents } from '../agents/load-agents.js'\n\nexport interface RunAgentListOpts {\n agentsDir?: string\n configPath?: string\n root?: string\n out?: NodeJS.WritableStream\n}\n\n// Read-only view of the local agent directory. Mutating agents (add/remove/\n// enable/disable) is intentionally not a CLI command — agent business config is\n// owned by the Control Plane (agent/upsert + register/ok roster, see\n// cp/cp-agent-registry.ts) or by hand-editing agent.json; the daemon only\n// reconciles what it finds on disk. This lists what that discovery sees.\nexport async function runAgentList(opts: RunAgentListOpts): Promise<void> {\n const out = opts.out ?? process.stdout\n const cfg = loadConfig({\n root: opts.root,\n configPath: opts.configPath,\n optional: true,\n overrides: { agentsDir: opts.agentsDir }\n })\n const agentsDir = cfg.agentsDir!\n const discovered = discoverAgents(agentsDir)\n if (discovered.length === 0) {\n out.write(`no agents found under ${agentsDir}\\n`)\n return\n }\n for (const { agent, dir } of discovered) {\n out.write(`${agent.id}\\t${agent.status}\\t${agent.runtime}\\t${agent.name}\\t${dir}\\n`)\n }\n}\n"],"mappings":";;AAeA,eAAsB,aAAa,MAAuC;CACxE,MAAM,MAAM,KAAK,OAAO,QAAQ;CAOhC,MAAM,YANM,WAAW;EACrB,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,UAAU;EACV,WAAW,EAAE,WAAW,KAAK,UAAU;CACzC,CACoB,CAAC,CAAC;CACtB,MAAM,aAAa,eAAe,SAAS;CAC3C,IAAI,WAAW,WAAW,GAAG;EAC3B,IAAI,MAAM,yBAAyB,UAAU,GAAG;EAChD;CACF;CACA,KAAK,MAAM,EAAE,OAAO,SAAS,YAC3B,IAAI,MAAM,GAAG,MAAM,GAAG,IAAI,MAAM,OAAO,IAAI,MAAM,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,GAAG;AAEvF"}
1
+ {"version":3,"file":"agent-list-DS3NGUJO.js","names":[],"sources":["../src/cli/agent-list.ts"],"sourcesContent":["import { loadConfig } from '../config/load-config.js'\nimport { discoverAgents } from '../agents/load-agents.js'\n\nexport interface RunAgentListOpts {\n agentsDir?: string\n configPath?: string\n root?: string\n out?: NodeJS.WritableStream\n}\n\n// Read-only view of the local agent directory. Mutating agents (add/remove/\n// enable/disable) is intentionally not a CLI command — agent business config is\n// owned by the Control Plane (agent/upsert + register/ok roster, see\n// cp/cp-agent-registry.ts) or by hand-editing agent.json; the daemon only\n// reconciles what it finds on disk. This lists what that discovery sees.\nexport async function runAgentList(opts: RunAgentListOpts): Promise<void> {\n const out = opts.out ?? process.stdout\n const cfg = loadConfig({\n root: opts.root,\n configPath: opts.configPath,\n optional: true,\n overrides: { agentsDir: opts.agentsDir }\n })\n const agentsDir = cfg.agentsDir!\n const discovered = discoverAgents(agentsDir)\n if (discovered.length === 0) {\n out.write(`no agents found under ${agentsDir}\\n`)\n return\n }\n for (const { agent, dir } of discovered) {\n out.write(`${agent.id}\\t${agent.status}\\t${agent.runtime}\\t${agent.name}\\t${dir}\\n`)\n }\n}\n"],"mappings":";;AAeA,eAAsB,aAAa,MAAuC;CACxE,MAAM,MAAM,KAAK,OAAO,QAAQ;CAOhC,MAAM,YANM,WAAW;EACrB,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,UAAU;EACV,WAAW,EAAE,WAAW,KAAK,UAAU;CACzC,CACoB,CAAC,CAAC;CACtB,MAAM,aAAa,eAAe,SAAS;CAC3C,IAAI,WAAW,WAAW,GAAG;EAC3B,IAAI,MAAM,yBAAyB,UAAU,GAAG;EAChD;CACF;CACA,KAAK,MAAM,EAAE,OAAO,SAAS,YAC3B,IAAI,MAAM,GAAG,MAAM,GAAG,IAAI,MAAM,OAAO,IAAI,MAAM,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,GAAG;AAEvF"}
@@ -1,6 +1,6 @@
1
- import { a as agentChildEnv, i as selectAgent, o as loadConfig } from "./load-agents-DLEvTgkd.js";
1
+ import { a as agentChildEnv, i as selectAgent, o as loadConfig } from "./load-agents-BLNSM987.js";
2
2
  import { l as resolveRoot } from "./paths-DEcUO8lC.js";
3
- import { S as resolveRuntimes, d as memoryProviderFor, i as prepareWorkspace, t as AcpHost } from "./acp-host-CZQVPKE-.js";
3
+ import { S as resolveRuntimes, d as memoryProviderFor, i as prepareWorkspace, t as AcpHost } from "./acp-host-BlkeNrQB.js";
4
4
  import { createInterface } from "node:readline";
5
5
  //#region src/cli/chat.ts
6
6
  function renderUpdate(out) {
@@ -84,4 +84,4 @@ async function runChat(opts) {
84
84
  //#endregion
85
85
  export { renderUpdate, runChat };
86
86
 
87
- //# sourceMappingURL=chat-12bZUnhV.js.map
87
+ //# sourceMappingURL=chat-Nq98n5LI.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-12bZUnhV.js","names":[],"sources":["../src/cli/chat.ts"],"sourcesContent":["import { createInterface } from 'node:readline'\nimport type { ContentBlock, SessionUpdate } from '@agentclientprotocol/sdk'\nimport { loadConfig } from '../config/load-config.js'\nimport { resolveRuntimes } from '../runtimes/registry.js'\nimport { selectAgent } from '../agents/load-agents.js'\nimport { agentChildEnv } from '../agents/agent-env.js'\nimport { memoryProviderFor } from '../agents/memory-provider.js'\nimport { prepareWorkspace } from '../workspace/workspace-manager.js'\nimport { AcpHost } from '../acp/acp-host.js'\nimport { resolveRoot } from '../paths.js'\n\nexport function renderUpdate(out: NodeJS.WritableStream) {\n return (_sessionId: string, update: SessionUpdate): void => {\n if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {\n out.write(update.content.text)\n } else if (update.sessionUpdate === 'tool_call') {\n const title = ('title' in update && update.title) || update.toolCallId || 'tool'\n out.write(`\\n[tool] ${title}\\n`)\n }\n }\n}\n\nexport interface RunChatOpts {\n agentsDir?: string\n agentName?: string\n message?: string\n configPath?: string\n root?: string\n out?: NodeJS.WritableStream\n input?: NodeJS.ReadableStream\n}\n\nexport async function runChat(opts: RunChatOpts): Promise<void> {\n const out = opts.out ?? process.stdout\n const root = resolveRoot(opts.root)\n const cfg = loadConfig({\n root: opts.root,\n configPath: opts.configPath,\n optional: true,\n overrides: { agentsDir: opts.agentsDir }\n })\n const agent = selectAgent(cfg.agentsDir!, opts.agentName)\n\n const runtimes = await resolveRuntimes(cfg, root, { neededRuntimes: [agent.runtime], mode: 'cache-first' })\n const runtime = runtimes[agent.runtime]\n if (!runtime) {\n const available = Object.keys(runtimes).sort().join(', ') || '(none)'\n throw new Error(`runtime \"${agent.runtime}\" not found. Available: ${available}`)\n }\n\n const host = new AcpHost(runtime, {\n onUpdate: renderUpdate(out),\n // Memory-backend env for parity with the daemon (managed disables the runtime's\n // own memory; native redirects it under the agent root).\n env: { ...agentChildEnv(agent), ...memoryProviderFor(agent, runtime).runtimeEnv() },\n configPrefs: {\n model: agent.runtimeOverrides?.model,\n permissionMode: agent.permissionMode,\n reasoningEffort: agent.reasoningEffort,\n fastMode: agent.fastMode\n }\n })\n // The adapter child runs in its own process group (see AcpHost.start), so a\n // terminal Ctrl-C no longer kills it as a side effect — Node's default SIGINT\n // exit would skip the finally below and leak it. Stop the host, then re-exit.\n // Registered BEFORE start(): a first-run `npx -y` runtime download takes long\n // enough that Ctrl-C mid-start is common, and the spawn happens early in start().\n const onSigint = () => {\n void host.stop().finally(() => process.exit(130))\n }\n process.once('SIGINT', onSigint)\n try {\n await host.start()\n const cwd = await prepareWorkspace(agent)\n const sessionId = await host.newSession(cwd)\n\n const send = async (text: string) => {\n const blocks: ContentBlock[] = [{ type: 'text', text }]\n await host.prompt(sessionId, blocks)\n out.write('\\n')\n }\n\n if (typeof opts.message === 'string' && opts.message.length > 0) {\n await send(opts.message)\n return\n }\n\n const rl = createInterface({ input: opts.input ?? process.stdin })\n out.write(`Chatting with ${agent.name} (${agent.id}). Type .exit to quit.\\n> `)\n try {\n for await (const line of rl) {\n const text = line.trim()\n if (text === '.exit' || text === '.quit') break\n if (text.length > 0) await send(text)\n out.write('> ')\n }\n } finally {\n rl.close()\n }\n } finally {\n process.removeListener('SIGINT', onSigint)\n await host.stop()\n }\n}\n"],"mappings":";;;;;AAWA,SAAgB,aAAa,KAA4B;CACvD,QAAQ,YAAoB,WAAgC;EAC1D,IAAI,OAAO,kBAAkB,yBAAyB,OAAO,QAAQ,SAAS,QAC5E,IAAI,MAAM,OAAO,QAAQ,IAAI;OACxB,IAAI,OAAO,kBAAkB,aAAa;GAC/C,MAAM,QAAS,WAAW,UAAU,OAAO,SAAU,OAAO,cAAc;GAC1E,IAAI,MAAM,YAAY,MAAM,GAAG;EACjC;CACF;AACF;AAYA,eAAsB,QAAQ,MAAkC;CAC9D,MAAM,MAAM,KAAK,OAAO,QAAQ;CAChC,MAAM,OAAO,YAAY,KAAK,IAAI;CAClC,MAAM,MAAM,WAAW;EACrB,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,UAAU;EACV,WAAW,EAAE,WAAW,KAAK,UAAU;CACzC,CAAC;CACD,MAAM,QAAQ,YAAY,IAAI,WAAY,KAAK,SAAS;CAExD,MAAM,WAAW,MAAM,gBAAgB,KAAK,MAAM;EAAE,gBAAgB,CAAC,MAAM,OAAO;EAAG,MAAM;CAAc,CAAC;CAC1G,MAAM,UAAU,SAAS,MAAM;CAC/B,IAAI,CAAC,SAAS;EACZ,MAAM,YAAY,OAAO,KAAK,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,KAAK;EAC7D,MAAM,IAAI,MAAM,YAAY,MAAM,QAAQ,0BAA0B,WAAW;CACjF;CAEA,MAAM,OAAO,IAAI,QAAQ,SAAS;EAChC,UAAU,aAAa,GAAG;EAG1B,KAAK;GAAE,GAAG,cAAc,KAAK;GAAG,GAAG,kBAAkB,OAAO,OAAO,CAAC,CAAC,WAAW;EAAE;EAClF,aAAa;GACX,OAAO,MAAM,kBAAkB;GAC/B,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACvB,UAAU,MAAM;EAClB;CACF,CAAC;CAMD,MAAM,iBAAiB;EACrB,KAAU,KAAK,CAAC,CAAC,cAAc,QAAQ,KAAK,GAAG,CAAC;CAClD;CACA,QAAQ,KAAK,UAAU,QAAQ;CAC/B,IAAI;EACF,MAAM,KAAK,MAAM;EACjB,MAAM,MAAM,MAAM,iBAAiB,KAAK;EACxC,MAAM,YAAY,MAAM,KAAK,WAAW,GAAG;EAE3C,MAAM,OAAO,OAAO,SAAiB;GACnC,MAAM,SAAyB,CAAC;IAAE,MAAM;IAAQ;GAAK,CAAC;GACtD,MAAM,KAAK,OAAO,WAAW,MAAM;GACnC,IAAI,MAAM,IAAI;EAChB;EAEA,IAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,SAAS,GAAG;GAC/D,MAAM,KAAK,KAAK,OAAO;GACvB;EACF;EAEA,MAAM,KAAK,gBAAgB,EAAE,OAAO,KAAK,SAAS,QAAQ,MAAM,CAAC;EACjE,IAAI,MAAM,iBAAiB,MAAM,KAAK,IAAI,MAAM,GAAG,2BAA2B;EAC9E,IAAI;GACF,WAAW,MAAM,QAAQ,IAAI;IAC3B,MAAM,OAAO,KAAK,KAAK;IACvB,IAAI,SAAS,WAAW,SAAS,SAAS;IAC1C,IAAI,KAAK,SAAS,GAAG,MAAM,KAAK,IAAI;IACpC,IAAI,MAAM,IAAI;GAChB;EACF,UAAU;GACR,GAAG,MAAM;EACX;CACF,UAAU;EACR,QAAQ,eAAe,UAAU,QAAQ;EACzC,MAAM,KAAK,KAAK;CAClB;AACF"}
1
+ {"version":3,"file":"chat-Nq98n5LI.js","names":[],"sources":["../src/cli/chat.ts"],"sourcesContent":["import { createInterface } from 'node:readline'\nimport type { ContentBlock, SessionUpdate } from '@agentclientprotocol/sdk'\nimport { loadConfig } from '../config/load-config.js'\nimport { resolveRuntimes } from '../runtimes/registry.js'\nimport { selectAgent } from '../agents/load-agents.js'\nimport { agentChildEnv } from '../agents/agent-env.js'\nimport { memoryProviderFor } from '../agents/memory-provider.js'\nimport { prepareWorkspace } from '../workspace/workspace-manager.js'\nimport { AcpHost } from '../acp/acp-host.js'\nimport { resolveRoot } from '../paths.js'\n\nexport function renderUpdate(out: NodeJS.WritableStream) {\n return (_sessionId: string, update: SessionUpdate): void => {\n if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {\n out.write(update.content.text)\n } else if (update.sessionUpdate === 'tool_call') {\n const title = ('title' in update && update.title) || update.toolCallId || 'tool'\n out.write(`\\n[tool] ${title}\\n`)\n }\n }\n}\n\nexport interface RunChatOpts {\n agentsDir?: string\n agentName?: string\n message?: string\n configPath?: string\n root?: string\n out?: NodeJS.WritableStream\n input?: NodeJS.ReadableStream\n}\n\nexport async function runChat(opts: RunChatOpts): Promise<void> {\n const out = opts.out ?? process.stdout\n const root = resolveRoot(opts.root)\n const cfg = loadConfig({\n root: opts.root,\n configPath: opts.configPath,\n optional: true,\n overrides: { agentsDir: opts.agentsDir }\n })\n const agent = selectAgent(cfg.agentsDir!, opts.agentName)\n\n const runtimes = await resolveRuntimes(cfg, root, { neededRuntimes: [agent.runtime], mode: 'cache-first' })\n const runtime = runtimes[agent.runtime]\n if (!runtime) {\n const available = Object.keys(runtimes).sort().join(', ') || '(none)'\n throw new Error(`runtime \"${agent.runtime}\" not found. Available: ${available}`)\n }\n\n const host = new AcpHost(runtime, {\n onUpdate: renderUpdate(out),\n // Memory-backend env for parity with the daemon (managed disables the runtime's\n // own memory; native redirects it under the agent root).\n env: { ...agentChildEnv(agent), ...memoryProviderFor(agent, runtime).runtimeEnv() },\n configPrefs: {\n model: agent.runtimeOverrides?.model,\n permissionMode: agent.permissionMode,\n reasoningEffort: agent.reasoningEffort,\n fastMode: agent.fastMode\n }\n })\n // The adapter child runs in its own process group (see AcpHost.start), so a\n // terminal Ctrl-C no longer kills it as a side effect — Node's default SIGINT\n // exit would skip the finally below and leak it. Stop the host, then re-exit.\n // Registered BEFORE start(): a first-run `npx -y` runtime download takes long\n // enough that Ctrl-C mid-start is common, and the spawn happens early in start().\n const onSigint = () => {\n void host.stop().finally(() => process.exit(130))\n }\n process.once('SIGINT', onSigint)\n try {\n await host.start()\n const cwd = await prepareWorkspace(agent)\n const sessionId = await host.newSession(cwd)\n\n const send = async (text: string) => {\n const blocks: ContentBlock[] = [{ type: 'text', text }]\n await host.prompt(sessionId, blocks)\n out.write('\\n')\n }\n\n if (typeof opts.message === 'string' && opts.message.length > 0) {\n await send(opts.message)\n return\n }\n\n const rl = createInterface({ input: opts.input ?? process.stdin })\n out.write(`Chatting with ${agent.name} (${agent.id}). Type .exit to quit.\\n> `)\n try {\n for await (const line of rl) {\n const text = line.trim()\n if (text === '.exit' || text === '.quit') break\n if (text.length > 0) await send(text)\n out.write('> ')\n }\n } finally {\n rl.close()\n }\n } finally {\n process.removeListener('SIGINT', onSigint)\n await host.stop()\n }\n}\n"],"mappings":";;;;;AAWA,SAAgB,aAAa,KAA4B;CACvD,QAAQ,YAAoB,WAAgC;EAC1D,IAAI,OAAO,kBAAkB,yBAAyB,OAAO,QAAQ,SAAS,QAC5E,IAAI,MAAM,OAAO,QAAQ,IAAI;OACxB,IAAI,OAAO,kBAAkB,aAAa;GAC/C,MAAM,QAAS,WAAW,UAAU,OAAO,SAAU,OAAO,cAAc;GAC1E,IAAI,MAAM,YAAY,MAAM,GAAG;EACjC;CACF;AACF;AAYA,eAAsB,QAAQ,MAAkC;CAC9D,MAAM,MAAM,KAAK,OAAO,QAAQ;CAChC,MAAM,OAAO,YAAY,KAAK,IAAI;CAClC,MAAM,MAAM,WAAW;EACrB,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,UAAU;EACV,WAAW,EAAE,WAAW,KAAK,UAAU;CACzC,CAAC;CACD,MAAM,QAAQ,YAAY,IAAI,WAAY,KAAK,SAAS;CAExD,MAAM,WAAW,MAAM,gBAAgB,KAAK,MAAM;EAAE,gBAAgB,CAAC,MAAM,OAAO;EAAG,MAAM;CAAc,CAAC;CAC1G,MAAM,UAAU,SAAS,MAAM;CAC/B,IAAI,CAAC,SAAS;EACZ,MAAM,YAAY,OAAO,KAAK,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,KAAK;EAC7D,MAAM,IAAI,MAAM,YAAY,MAAM,QAAQ,0BAA0B,WAAW;CACjF;CAEA,MAAM,OAAO,IAAI,QAAQ,SAAS;EAChC,UAAU,aAAa,GAAG;EAG1B,KAAK;GAAE,GAAG,cAAc,KAAK;GAAG,GAAG,kBAAkB,OAAO,OAAO,CAAC,CAAC,WAAW;EAAE;EAClF,aAAa;GACX,OAAO,MAAM,kBAAkB;GAC/B,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACvB,UAAU,MAAM;EAClB;CACF,CAAC;CAMD,MAAM,iBAAiB;EACrB,KAAU,KAAK,CAAC,CAAC,cAAc,QAAQ,KAAK,GAAG,CAAC;CAClD;CACA,QAAQ,KAAK,UAAU,QAAQ;CAC/B,IAAI;EACF,MAAM,KAAK,MAAM;EACjB,MAAM,MAAM,MAAM,iBAAiB,KAAK;EACxC,MAAM,YAAY,MAAM,KAAK,WAAW,GAAG;EAE3C,MAAM,OAAO,OAAO,SAAiB;GACnC,MAAM,SAAyB,CAAC;IAAE,MAAM;IAAQ;GAAK,CAAC;GACtD,MAAM,KAAK,OAAO,WAAW,MAAM;GACnC,IAAI,MAAM,IAAI;EAChB;EAEA,IAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,SAAS,GAAG;GAC/D,MAAM,KAAK,KAAK,OAAO;GACvB;EACF;EAEA,MAAM,KAAK,gBAAgB,EAAE,OAAO,KAAK,SAAS,QAAQ,MAAM,CAAC;EACjE,IAAI,MAAM,iBAAiB,MAAM,KAAK,IAAI,MAAM,GAAG,2BAA2B;EAC9E,IAAI;GACF,WAAW,MAAM,QAAQ,IAAI;IAC3B,MAAM,OAAO,KAAK,KAAK;IACvB,IAAI,SAAS,WAAW,SAAS,SAAS;IAC1C,IAAI,KAAK,SAAS,GAAG,MAAM,KAAK,IAAI;IACpC,IAAI,MAAM,IAAI;GAChB;EACF,UAAU;GACR,GAAG,MAAM;EACX;CACF,UAAU;EACR,QAAQ,eAAe,UAAU,QAAQ;EACzC,MAAM,KAAK,KAAK;CAClB;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { l as resolveRoot } from "./paths-DEcUO8lC.js";
2
- import { n as gitcredSocketPath } from "./gitcred-server-CMC80O_1.js";
2
+ import { n as gitcredSocketPath } from "./gitcred-server-Dhp-tyEI.js";
3
3
  import { createConnection } from "node:net";
4
4
  //#region src/cli/git-credential.ts
5
5
  /**
@@ -96,4 +96,4 @@ function ipc(msg) {
96
96
  //#endregion
97
97
  export { runGitCredential };
98
98
 
99
- //# sourceMappingURL=git-credential-n8fSKCLK.js.map
99
+ //# sourceMappingURL=git-credential-DLRF9bOx.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-credential-n8fSKCLK.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 * 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\n if (action === 'erase') {\n await ipc({ op: 'erase', agentId, password: input.password }).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 })\n if (!res.ok || !res.username || !res.password) {\n process.stderr.write(\n `agentconnect: no git credentials for agent ${agentId}: ${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 // Path check is diagnostics-only (requires useHttpPath=true to even arrive):\n // token scope enforces the real boundary at GitHub. Tolerate case + .git +\n // LFS-ish subpaths; on a hard mismatch, hint at the rename story.\n if (input.path !== undefined) {\n const want = normalizeRepoPath(res.repoFullName ?? '')\n const got = normalizeRepoPath(input.path)\n if (want && got && got !== want && !got.startsWith(`${want}/`)) {\n process.stderr.write(\n `agentconnect: note — git asked for ${got} but this agent's repo is ${want} ` +\n `(repo renamed? recreate the agent to re-point it)\\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\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":";;;;;;;;;;;;;;;;;;AAyBA,eAAsB,iBAAiB,QAAgB,SAAgC;CACrF,IAAI,WAAW,SAAS;CAExB,MAAM,QAAQ,WAAW,MAAM,UAAU,CAAC;CAE1C,IAAI,WAAW,SAAS;EACtB,MAAM,IAAI;GAAE,IAAI;GAAS;GAAS,UAAU,MAAM;EAAS,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;EACnF;CACF;CACA,IAAI,WAAW,OAAO;CAEtB,IAAI,MAAM,SAAS,KAAA,KAAa,MAAM,KAAK,YAAY,MAAM,cAE3D;CAGF,MAAM,MAAM,MAAM,IAAI;EAAE,IAAI;EAAO;CAAQ,CAAC;CAC5C,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,YAAY,CAAC,IAAI,UAAU;EAC7C,QAAQ,OAAO,MACb,8CAA8C,QAAQ,IAAI,IAAI,SAAS,gBAAgB,oEAEzF;EACA,QAAQ,WAAW;EACnB;CACF;CAIA,IAAI,MAAM,SAAS,KAAA,GAAW;EAC5B,MAAM,OAAO,kBAAkB,IAAI,gBAAgB,EAAE;EACrD,MAAM,MAAM,kBAAkB,MAAM,IAAI;EACxC,IAAI,QAAQ,OAAO,QAAQ,QAAQ,CAAC,IAAI,WAAW,GAAG,KAAK,EAAE,GAC3D,QAAQ,OAAO,MACb,sCAAsC,IAAI,4BAA4B,KAAK,qDAE7E;CAEJ;CACA,QAAQ,OAAO,MAAM,YAAY,IAAI,SAAS,aAAa,IAAI,SAAS,GAAG;AAC7E;AAEA,SAAS,kBAAkB,GAAmB;CAC5C,OAAO,EACJ,QAAQ,QAAQ,EAAE,CAAC,CACnB,QAAQ,WAAW,EAAE,CAAC,CACtB,YAAY;AACjB;AAEA,SAAS,WAAW,MAA2B;CAC7C,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;EACnC,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,KAAK,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,MAAM,KAAK,CAAC;CACxD;CACA,OAAO;AACT;AAEA,SAAS,YAA6B;CACpC,OAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,MAAM;EACV,QAAQ,MAAM,YAAY,MAAM;EAChC,QAAQ,MAAM,GAAG,SAAS,MAAO,OAAO,CAAE;EAC1C,QAAQ,MAAM,GAAG,aAAa,QAAQ,GAAG,CAAC;EAC1C,QAAQ,MAAM,GAAG,eAAe,QAAQ,GAAG,CAAC;CAC9C,CAAC;AACH;AAUA,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":"git-credential-DLRF9bOx.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 * 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\n if (action === 'erase') {\n await ipc({ op: 'erase', agentId, password: input.password }).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 })\n if (!res.ok || !res.username || !res.password) {\n process.stderr.write(\n `agentconnect: no git credentials for agent ${agentId}: ${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 // Path check is diagnostics-only (requires useHttpPath=true to even arrive):\n // token scope enforces the real boundary at GitHub. Tolerate case + .git +\n // LFS-ish subpaths; on a hard mismatch, hint at the rename story.\n if (input.path !== undefined) {\n const want = normalizeRepoPath(res.repoFullName ?? '')\n const got = normalizeRepoPath(input.path)\n if (want && got && got !== want && !got.startsWith(`${want}/`)) {\n process.stderr.write(\n `agentconnect: note — git asked for ${got} but this agent's repo is ${want} ` +\n `(repo renamed? recreate the agent to re-point it)\\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\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":";;;;;;;;;;;;;;;;;;AAyBA,eAAsB,iBAAiB,QAAgB,SAAgC;CACrF,IAAI,WAAW,SAAS;CAExB,MAAM,QAAQ,WAAW,MAAM,UAAU,CAAC;CAE1C,IAAI,WAAW,SAAS;EACtB,MAAM,IAAI;GAAE,IAAI;GAAS;GAAS,UAAU,MAAM;EAAS,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;EACnF;CACF;CACA,IAAI,WAAW,OAAO;CAEtB,IAAI,MAAM,SAAS,KAAA,KAAa,MAAM,KAAK,YAAY,MAAM,cAE3D;CAGF,MAAM,MAAM,MAAM,IAAI;EAAE,IAAI;EAAO;CAAQ,CAAC;CAC5C,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,YAAY,CAAC,IAAI,UAAU;EAC7C,QAAQ,OAAO,MACb,8CAA8C,QAAQ,IAAI,IAAI,SAAS,gBAAgB,oEAEzF;EACA,QAAQ,WAAW;EACnB;CACF;CAIA,IAAI,MAAM,SAAS,KAAA,GAAW;EAC5B,MAAM,OAAO,kBAAkB,IAAI,gBAAgB,EAAE;EACrD,MAAM,MAAM,kBAAkB,MAAM,IAAI;EACxC,IAAI,QAAQ,OAAO,QAAQ,QAAQ,CAAC,IAAI,WAAW,GAAG,KAAK,EAAE,GAC3D,QAAQ,OAAO,MACb,sCAAsC,IAAI,4BAA4B,KAAK,qDAE7E;CAEJ;CACA,QAAQ,OAAO,MAAM,YAAY,IAAI,SAAS,aAAa,IAAI,SAAS,GAAG;AAC7E;AAEA,SAAS,kBAAkB,GAAmB;CAC5C,OAAO,EACJ,QAAQ,QAAQ,EAAE,CAAC,CACnB,QAAQ,WAAW,EAAE,CAAC,CACtB,YAAY;AACjB;AAEA,SAAS,WAAW,MAA2B;CAC7C,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;EACnC,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,KAAK,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,MAAM,KAAK,CAAC;CACxD;CACA,OAAO;AACT;AAEA,SAAS,YAA6B;CACpC,OAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,MAAM;EACV,QAAQ,MAAM,YAAY,MAAM;EAChC,QAAQ,MAAM,GAAG,SAAS,MAAO,OAAO,CAAE;EAC1C,QAAQ,MAAM,GAAG,aAAa,QAAQ,GAAG,CAAC;EAC1C,QAAQ,MAAM,GAAG,eAAe,QAAQ,GAAG,CAAC;CAC9C,CAAC;AACH;AAUA,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"}
@@ -17,6 +17,9 @@ const GH_TOKEN_CAPABILITIES = [
17
17
  /** Cache-key prefix separating the GH_TOKEN grant from the plain git one —
18
18
  * different capability sets are different tokens with separate lifecycles. */
19
19
  const GH_KEY = (agentId) => `gh\u0000${agentId}`;
20
+ /** Cache key for a repo-targeted COMMENT token (the GithubPoster's — issues/PR
21
+ * write on the hook-watched repo, no contents). One per (agent, repo). */
22
+ const POST_KEY = (agentId, repo) => `post\u0000${agentId}\u0000${repo.toLowerCase()}`;
20
23
  var GitCredUnavailableError = class extends Error {
21
24
  terminal;
22
25
  constructor(message, terminal) {
@@ -57,6 +60,20 @@ var GitCredentialCache = class {
57
60
  capabilities: [...GH_TOKEN_CAPABILITIES]
58
61
  });
59
62
  }
63
+ /**
64
+ * The GithubPoster's comment token for a hook-watched repo (P3 出向): scoped
65
+ * to `repo`, issues/pull_requests only — the CP refuses contents on repo-
66
+ * targeted mints. Same cache/coalesce/degradation discipline as {@link get};
67
+ * the token never enters the agent's environment.
68
+ */
69
+ async getPostToken(agentId, repo) {
70
+ return this.getKeyed(POST_KEY(agentId, repo), agentId, {
71
+ agentId,
72
+ reason: "helper",
73
+ capabilities: ["issues", "pull_requests"],
74
+ repo
75
+ });
76
+ }
60
77
  async getKeyed(key, agentId, payload) {
61
78
  if (this.denied.has(key)) throw new GitCredUnavailableError("control plane denied git credentials for this agent", true);
62
79
  const cached = this.entries.get(key);
@@ -265,4 +282,4 @@ function writeGitcredShim(root, cliEntry) {
265
282
  //#endregion
266
283
  export { GitCredentialCache as i, gitcredSocketPath as n, writeGitcredShim as r, GitCredServer as t };
267
284
 
268
- //# sourceMappingURL=gitcred-server-CMC80O_1.js.map
285
+ //# sourceMappingURL=gitcred-server-Dhp-tyEI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitcred-server-Dhp-tyEI.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 *\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 * 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-agent\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 * 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/** 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 to the agent's gitAccess — a read-only workspace gets a\n * read-only token, never a write scope. */\nconst GH_TOKEN_CAPABILITIES: readonly GitCredCapability[] = ['contents', 'issues', 'pull_requests']\n\n/** Cache-key prefix separating the GH_TOKEN grant from the plain git one —\n * different capability sets are different tokens with separate lifecycles. */\nconst GH_KEY = (agentId: string): string => `gh\\u0000${agentId}`\n\n/** Cache key for a repo-targeted COMMENT token (the GithubPoster's — issues/PR\n * write on the hook-watched repo, no contents). 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 (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 repo?: string\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 /** Agents the CP told us to stop asking for (SCOPE_DENIED — moved/mode-off). */\n private readonly denied = new Set<string>()\n private readonly monoNow: () => number\n\n constructor(private readonly deps: GitCredentialCacheDeps) {\n this.monoNow = deps.monoNow ?? (() => performance.now())\n }\n\n /** A live credential for the agent's workspace repo — cached, coalesced, or freshly pulled. */\n async get(agentId: string, reason: 'clone' | 'fetch' | 'pull' | 'push' | 'helper'): Promise<Entry> {\n return this.getKeyed(agentId, agentId, { agentId, reason })\n }\n\n /**\n * The widened GH_TOKEN grant (P2.5 回写) — same cache/coalesce/degradation\n * discipline as {@link get}, under its own key: a distinct capability set is\n * a distinct token. Injected into the agent's runtime env at host spawn;\n * expiry (≤1h, non-renewable) means write-back goes stale until the next\n * host spawn — the documented MVP limitation.\n */\n async getGhToken(agentId: string): Promise<Entry> {\n return this.getKeyed(GH_KEY(agentId), agentId, {\n agentId,\n reason: 'helper',\n capabilities: [...GH_TOKEN_CAPABILITIES]\n })\n }\n\n /**\n * The GithubPoster's comment token for a hook-watched repo (P3 出向): scoped\n * to `repo`, issues/pull_requests only — the CP refuses contents on repo-\n * targeted mints. Same cache/coalesce/degradation discipline as {@link get};\n * the token never enters the agent's environment.\n */\n async getPostToken(agentId: string, repo: string): Promise<Entry> {\n return this.getKeyed(POST_KEY(agentId, repo), agentId, {\n agentId,\n reason: 'helper',\n capabilities: ['issues', 'pull_requests'],\n repo\n })\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 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 if (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.\n */\n invalidate(agentId: string, presentedPassword?: string): void {\n const entry = this.entries.get(agentId)\n if (!entry) return\n if (presentedPassword !== undefined && entry.token !== presentedPassword) return\n this.entries.delete(agentId)\n }\n\n /** agent/remove: drop the orphaned bearer tokens with the agent dir. */\n remove(agentId: string): void {\n for (const key of [agentId, GH_KEY(agentId)]) {\n this.entries.delete(key)\n this.denied.delete(key)\n }\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 // Terminal: the agent moved daemons or left github-app mode. Stop\n // asking until the CP replicates a new spec (which clears the flag).\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 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 this.denied.delete(agentId)\n this.denied.delete(GH_KEY(agentId))\n }\n}\n","/**\n * gitcred.sock — the local credential channel for agent-run git\n * (docs/designs/github-app-git-credentials.md §本地 helper 通道).\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` CLI subcommand connects per git invocation:\n * { op: 'get', agentId } → { ok, username, password } | { ok:false, error }\n * { op: 'erase', agentId, password? } → { ok: true }\n *\n * Trust model (design §信任模型 #1): same host + same OS user = ONE trust\n * domain. `agentId` here is routing, not authorization — any same-user process\n * can already read every workspace on this daemon. Tokens transit the socket\n * and helper stdout only; nothing lands on disk.\n *\n * Alongside the socket the daemon (re)writes two 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) and per-agent gitconfig includes for\n * the session-env channel (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 } 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}\n\nexport class GitCredServer {\n private server?: Server\n\n constructor(\n private readonly cache: GitCredentialCache,\n private readonly path: string,\n private readonly log: { info: (m: string) => void; warn: (m: string) => void }\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 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)\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')\n return reply({ ok: true, username: cred.username, password: cred.token })\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-12bZUnhV.js"),
68496
- import("./login-CXPIehhv.js"),
68497
- import("./run-foreground-CtYL7rRR.js"),
68495
+ import("./chat-Nq98n5LI.js"),
68496
+ import("./login-B4KagoWu.js"),
68497
+ import("./run-foreground-COW0zak6.js"),
68498
68498
  import("./service-6w0WKhdx.js"),
68499
68499
  import("./lock-CA9Bm8_s.js"),
68500
68500
  import("./paths-CcRUjFjy.js")
@@ -68646,13 +68646,13 @@ 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-n8fSKCLK.js");
68649
+ const { runGitCredential } = await import("./git-credential-DLRF9bOx.js");
68650
68650
  await runGitCredential(action, agentId);
68651
68651
  });
68652
68652
  program.command("agent").description("Inspect local agent directories").command("list").description("List agents discovered under --agents-dir (id, status, runtime, name, dir)").action(async () => {
68653
68653
  const opts = program.opts();
68654
68654
  try {
68655
- const { runAgentList } = await import("./agent-list-BgEiQL1D.js");
68655
+ const { runAgentList } = await import("./agent-list-DS3NGUJO.js");
68656
68656
  await runAgentList({
68657
68657
  agentsDir: opts.agentsDir,
68658
68658
  configPath: opts.config,
@@ -723,7 +723,8 @@ const GitCredRequest = object({
723
723
  "push",
724
724
  "helper"
725
725
  ]).optional(),
726
- capabilities: array(GitCredCapability).nonempty().optional()
726
+ capabilities: array(GitCredCapability).nonempty().optional(),
727
+ repo: string().optional()
727
728
  });
728
729
  const GitCredGrant = object({
729
730
  username: literal("x-access-token"),
@@ -2467,4 +2468,4 @@ function selectAgent(agentsDir, name) {
2467
2468
  //#endregion
2468
2469
  export { ZodIssueCode as C, RESERVED_MCP_SERVER_NAME as S, buildEnvelope as _, agentChildEnv as a, MAX_FRAME_BYTES as b, persistRelays as c, normalizeGitUrl as d, RELAY_DAEMON_SUBPROTOCOL as f, SHARED_CONFIG_ACTION_ID as g, decodeRelayDaemonFrame as h, selectAgent as i, ConfigSchema as l, buildRelayDaemonFrame as m, findAgentFiles as n, loadConfig as o, RELAY_DAEMON_WS_PATH as p, loadAgents as r, persistDaemonId as s, discoverAgents as t, isFrame as u, decodeEnvelope as v, MEMORY_INDEX as x, encode as y };
2469
2470
 
2470
- //# sourceMappingURL=load-agents-DLEvTgkd.js.map
2471
+ //# sourceMappingURL=load-agents-BLNSM987.js.map