@router-agent/runtime 0.3.31 → 0.3.33

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 (64) hide show
  1. package/dist/agents/claude-trust.d.ts +29 -0
  2. package/dist/agents/claude-trust.js +94 -0
  3. package/dist/agents/claude-trust.js.map +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/plugins/loader.d.ts +1 -0
  8. package/dist/plugins/loader.js +2 -0
  9. package/dist/plugins/loader.js.map +1 -1
  10. package/dist/plugins/types.d.ts +2 -0
  11. package/dist/port.d.ts +7 -0
  12. package/dist/port.js.map +1 -1
  13. package/dist/runtimes/builtin-options.d.ts +1 -0
  14. package/dist/runtimes/builtin-options.js +34 -0
  15. package/dist/runtimes/builtin-options.js.map +1 -1
  16. package/dist/runtimes/catalog.d.ts +1 -0
  17. package/dist/runtimes/catalog.js +9 -0
  18. package/dist/runtimes/catalog.js.map +1 -1
  19. package/dist/runtimes/options.d.ts +1 -1
  20. package/dist/runtimes/options.js +3 -3
  21. package/dist/runtimes/options.js.map +1 -1
  22. package/package.json +3 -3
  23. package/dist/orca/commands.js +0 -60
  24. package/dist/orca/commands.js.map +0 -1
  25. package/dist/orca/index.js +0 -452
  26. package/dist/orca/index.js.map +0 -1
  27. package/dist/orca/options.js +0 -33
  28. package/dist/orca/options.js.map +0 -1
  29. package/dist/orca/report-poller.js +0 -143
  30. package/dist/orca/report-poller.js.map +0 -1
  31. package/dist/runtimes/acp/admin.js +0 -470
  32. package/dist/runtimes/acp/admin.js.map +0 -1
  33. package/dist/runtimes/acp/agents.js +0 -167
  34. package/dist/runtimes/acp/agents.js.map +0 -1
  35. package/dist/runtimes/acp/detect.js +0 -379
  36. package/dist/runtimes/acp/detect.js.map +0 -1
  37. package/dist/runtimes/acp/index.js +0 -633
  38. package/dist/runtimes/acp/index.js.map +0 -1
  39. package/dist/runtimes/acp/install.js +0 -264
  40. package/dist/runtimes/acp/install.js.map +0 -1
  41. package/dist/runtimes/acp/permissions.js +0 -83
  42. package/dist/runtimes/acp/permissions.js.map +0 -1
  43. package/dist/runtimes/acp/protocol.js +0 -207
  44. package/dist/runtimes/acp/protocol.js.map +0 -1
  45. package/dist/runtimes/claude-code/index.js +0 -544
  46. package/dist/runtimes/claude-code/index.js.map +0 -1
  47. package/dist/runtimes/claude-code/inject.js +0 -71
  48. package/dist/runtimes/claude-code/inject.js.map +0 -1
  49. package/dist/runtimes/codex/client.js +0 -203
  50. package/dist/runtimes/codex/client.js.map +0 -1
  51. package/dist/runtimes/codex/detect.js +0 -240
  52. package/dist/runtimes/codex/detect.js.map +0 -1
  53. package/dist/runtimes/codex/index.js +0 -1066
  54. package/dist/runtimes/codex/index.js.map +0 -1
  55. package/dist/runtimes/herdr/agents.js +0 -38
  56. package/dist/runtimes/herdr/agents.js.map +0 -1
  57. package/dist/runtimes/herdr/commands.js +0 -68
  58. package/dist/runtimes/herdr/commands.js.map +0 -1
  59. package/dist/runtimes/herdr/index.js +0 -712
  60. package/dist/runtimes/herdr/index.js.map +0 -1
  61. package/dist/runtimes/herdr/options.js +0 -29
  62. package/dist/runtimes/herdr/options.js.map +0 -1
  63. package/dist/runtimes/herdr/server.js +0 -75
  64. package/dist/runtimes/herdr/server.js.map +0 -1
@@ -1,633 +0,0 @@
1
- import { appendFileSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { realRunner } from '@router-agent/infra/process.js';
4
- import { GitWorktreeProvider } from '../../workspaces/git-worktree.js';
5
- import { workBranch, worktreeName } from '../../naming.js';
6
- import { WorkerRuntimeError, } from '../../port.js';
7
- import { latestActivityAt } from '../../status.js';
8
- import { AcpConnection, AcpProtocolError } from './protocol.js';
9
- import { decidePermission } from './permissions.js';
10
- const CLIENT_NAME = 'router-agent';
11
- const CLIENT_VERSION = '0.1.0';
12
- const CHANNEL = 'acp';
13
- const READ_ONLY_TOOLS = { fileAccess: 'read-only', shell: 'deny', allowNetwork: false };
14
- /**
15
- * ACP(Agent Client Protocol)运行时。
16
- *
17
- * 客户端侧实现:spawn 一个 ACP Agent 子进程,通过 stdio JSON-RPC 驱动
18
- * initialize → session/new → session/prompt,并处理 session/update 与权限请求。
19
- * 工作区仍由 GitWorktreeProvider 负责,与 Claude/Orca 运行时保持一致。
20
- */
21
- export class AcpRuntime {
22
- id;
23
- capabilities = {
24
- agent: { liveStatus: true, processExit: true, output: false, activity: true, structuredInput: true, nudge: false },
25
- workspace: { cleanup: true, prLink: false, discovery: false },
26
- };
27
- command;
28
- args;
29
- env;
30
- workersDir;
31
- home;
32
- initializeTimeoutMs;
33
- unavailableReason;
34
- installHint;
35
- probeArgs;
36
- gitProvider;
37
- workspaces;
38
- sessions = new Map();
39
- bySessionId = new Map();
40
- constructor(opts) {
41
- this.id = opts.id ?? 'acp';
42
- this.command = opts.command?.trim() || undefined;
43
- this.args = opts.args ?? [];
44
- this.env = opts.env ?? {};
45
- this.workersDir = opts.workersDir;
46
- this.home = opts.home;
47
- this.initializeTimeoutMs = opts.initializeTimeoutMs ?? 30_000;
48
- this.unavailableReason = opts.unavailableReason;
49
- this.installHint = opts.installHint;
50
- this.probeArgs = opts.probeArgs ?? ['--version'];
51
- this.gitProvider = new GitWorktreeProvider({
52
- git: opts.git,
53
- worktreesRoot: opts.worktreesRoot ?? join(opts.home, 'worktrees'),
54
- });
55
- this.workspaces = opts.workspaceProvider ?? this.gitProvider;
56
- }
57
- async start(ctx) {
58
- if (!this.command) {
59
- throw new WorkerRuntimeError('runtime:unavailable', this.unavailableReason ?? `ACP Agent ${this.id} 不可用`, {
60
- retryable: false,
61
- detail: {
62
- runtimeId: this.id,
63
- ...(this.unavailableReason ? { reason: this.unavailableReason } : {}),
64
- ...(this.installHint ? { hint: this.installHint } : {}),
65
- },
66
- });
67
- }
68
- const ticket = ctx.ticket;
69
- const repo = ticket.repo;
70
- const baseBranch = ticket.branch;
71
- if (!repo)
72
- throw new WorkerRuntimeError('workspace:missing-repo', '工单没有指定仓库,拒绝创建 worktree');
73
- if (!baseBranch)
74
- throw new WorkerRuntimeError('workspace:missing-branch', '工单没有指定起始分支,拒绝创建 worktree');
75
- const branch = workBranch(ticket);
76
- const name = worktreeName(ticket);
77
- const workdir = ctx.workdir || join(this.home, 'worktrees', name);
78
- const dir = join(this.workersDir, ticket.id);
79
- mkdirSync(dir, { recursive: true });
80
- const briefPath = join(dir, 'brief.md');
81
- const workspace = await this.workspaces.create({
82
- ticketId: ticket.id,
83
- repo,
84
- baseBranch,
85
- workBranch: branch,
86
- name,
87
- workdir,
88
- });
89
- const contract = (ctx.brief?.contract ?? `执行工单 ${ticket.id}:${ticket.title}`)
90
- .replace('(由运行时决定)', workspace.workdir);
91
- writeFileSync(briefPath, contract, 'utf8');
92
- const meta = {
93
- schemaVersion: 3,
94
- ticketId: ticket.id,
95
- ...(ctx.assignment.id ? { assignmentId: ctx.assignment.id } : {}),
96
- runtime: this.id,
97
- handle: '',
98
- workdir: workspace.workdir,
99
- repo,
100
- branch: baseBranch,
101
- workBranch: workspace.branch,
102
- status: 'starting',
103
- startedAt: new Date().toISOString(),
104
- tools: ctx.tools ?? READ_ONLY_TOOLS,
105
- };
106
- this.writeMeta(ticket.id, meta);
107
- let connection;
108
- try {
109
- connection = this.spawnConnection(workspace.workdir);
110
- const init = await this.initialize(connection);
111
- const sessionId = await this.newSession(connection, workspace.workdir);
112
- const session = this.registerSession({
113
- ticketId: ticket.id,
114
- sessionId,
115
- workdir: workspace.workdir,
116
- branch: workspace.branch,
117
- tools: ctx.tools ?? READ_ONLY_TOOLS,
118
- loadSession: init.agentCapabilities?.loadSession === true,
119
- connection,
120
- });
121
- meta.handle = sessionId;
122
- meta.sessionId = sessionId;
123
- meta.loadSession = session.loadSession;
124
- if (connection.pid !== undefined)
125
- meta.pid = connection.pid;
126
- meta.status = 'running';
127
- this.writeMeta(ticket.id, meta);
128
- this.prompt(session, `${contract}\n\n完成后用 ra report 上报。`);
129
- return {
130
- handle: sessionId,
131
- workdir: workspace.workdir,
132
- branch: workspace.branch,
133
- sessionId,
134
- workspaceRef: workspace.workspaceRef,
135
- };
136
- }
137
- catch (error) {
138
- await connection?.close().catch(() => { });
139
- await this.rollback(workspace.workdir, workspace.branch, repo);
140
- throw this.launchError(error, workspace.workdir, workspace.branch);
141
- }
142
- }
143
- async deliver(ref, inputs, opts = {}) {
144
- const inputIds = inputs.map(input => input.id);
145
- if (inputs.length === 0)
146
- return { status: 'accepted', inputIds };
147
- let meta = this.readMeta(ref.ticketId);
148
- let session = this.sessions.get(ref.ticketId);
149
- if (session && !session.connection.alive) {
150
- this.dropSession(session);
151
- session = undefined;
152
- }
153
- if (!session) {
154
- const revived = await this.revive(meta).catch(() => null);
155
- if (!revived) {
156
- return { status: 'rejected', inputIds, reason: 'acp:agent-gone', retryable: true, retryAfterMs: 30_000 };
157
- }
158
- session = revived;
159
- meta = this.readMeta(ref.ticketId) ?? meta;
160
- }
161
- if (session.inFlight) {
162
- return { status: 'queued', inputIds, reason: 'acp:busy' };
163
- }
164
- if (opts.redeliver !== true
165
- && meta?.lastHandedOffInputId === inputs.at(-1)?.id
166
- && meta?.lastHandedOffAttempt === (opts.attempt ?? 1)) {
167
- return { status: 'accepted', inputIds };
168
- }
169
- const text = inputs.map(input => `【Router 输入 ${input.id}】\n${input.text}`).join('\n\n');
170
- try {
171
- this.prompt(session, `${text}\n\n继续执行工单,完成后用 ra report 上报。`);
172
- }
173
- catch (error) {
174
- return {
175
- status: 'rejected',
176
- inputIds,
177
- reason: error instanceof WorkerRuntimeError ? error.reason : 'acp:deliver-failed',
178
- retryable: true,
179
- retryAfterMs: 30_000,
180
- };
181
- }
182
- if (meta) {
183
- meta.lastHandedOffInputId = inputs.at(-1)?.id;
184
- meta.lastHandedOffAttempt = opts.attempt ?? 1;
185
- meta.status = 'running';
186
- this.writeMeta(ref.ticketId, meta);
187
- }
188
- return { status: 'accepted', inputIds };
189
- }
190
- async stop(ref, _reason) {
191
- const session = this.sessions.get(ref.ticketId);
192
- if (session) {
193
- try {
194
- session.connection.notify('session/cancel', { sessionId: session.sessionId });
195
- }
196
- catch {
197
- // 进程已退出时 cancel 无意义,继续清理。
198
- }
199
- this.dropSession(session);
200
- await session.connection.close().catch(() => { });
201
- }
202
- const meta = this.readMeta(ref.ticketId);
203
- if (meta) {
204
- meta.status = 'stopped';
205
- this.writeMeta(ref.ticketId, meta);
206
- }
207
- }
208
- /** 重启对账用:把磁盘上仍可 load 的会话直接接回来,失败返回 false 交给上层兜底。 */
209
- async reattach(ref) {
210
- const session = this.sessions.get(ref.ticketId);
211
- if (session?.connection.alive)
212
- return true;
213
- if (session)
214
- this.dropSession(session);
215
- return (await this.revive(this.readMeta(ref.ticketId))) !== null;
216
- }
217
- async status(ref) {
218
- const session = this.sessions.get(ref.ticketId);
219
- const meta = this.readMeta(ref.ticketId);
220
- if (!session) {
221
- if (!meta)
222
- return null;
223
- // 重启后内存会话已丢,用活动日志的落盘时间估算最后活动,别拿启动时间冒充。
224
- const lastActivityAt = this.lastEventAtFromFile(ref.ticketId) ?? meta.startedAt;
225
- return {
226
- alive: false,
227
- phase: 'exited',
228
- acceptingInput: meta.loadSession === true && Boolean(meta.sessionId),
229
- ...(lastActivityAt ? { lastActivityAt } : {}),
230
- ...(meta.lastError ? { detail: { source: this.id, error: meta.lastError } } : { detail: { source: this.id, status: meta.status } }),
231
- };
232
- }
233
- const lastActivityAt = latestActivityAt(session.lastEventAt, session.promptStartedAt) ?? meta?.startedAt;
234
- if (!session.connection.alive) {
235
- return {
236
- alive: false,
237
- phase: 'exited',
238
- acceptingInput: session.loadSession,
239
- ...(lastActivityAt ? { lastActivityAt } : {}),
240
- ...(session.connection.exit.code !== null ? { exitCode: session.connection.exit.code } : {}),
241
- detail: { source: this.id, status: 'exited' },
242
- };
243
- }
244
- if (session.inFlight) {
245
- return {
246
- alive: true,
247
- phase: 'running',
248
- acceptingInput: false,
249
- ...(lastActivityAt ? { lastActivityAt } : {}),
250
- detail: { source: this.id, status: 'working' },
251
- };
252
- }
253
- return {
254
- alive: true,
255
- phase: 'running',
256
- acceptingInput: true,
257
- ...(lastActivityAt ? { lastActivityAt } : {}),
258
- detail: {
259
- source: this.id,
260
- status: session.lastError ? 'error' : 'idle',
261
- ...(session.lastError ? { error: session.lastError } : {}),
262
- ...(session.lastStopReason ? { stopReason: session.lastStopReason } : {}),
263
- },
264
- };
265
- }
266
- async activity(query) {
267
- const meta = this.readMeta(query.ref.ticketId);
268
- const session = this.sessions.get(query.ref.ticketId);
269
- if (!meta && !session)
270
- return null;
271
- const events = this.readEvents(query.ref.ticketId);
272
- const max = query.opts?.maxEvents ?? 40;
273
- const tail = events.slice(-max);
274
- const sessionId = query.ref.sessionId ?? session?.sessionId ?? meta?.sessionId;
275
- const at = tail.at(-1)?.at ?? session?.lastEventAt ?? meta?.startedAt ?? new Date().toISOString();
276
- return { at, events: tail, ...(sessionId ? { sessionId } : {}) };
277
- }
278
- async inspectWorkspace(ref) {
279
- return this.workspaces.inspect(ref.workdir);
280
- }
281
- async removeWorkspace(ref) {
282
- const meta = this.readMeta(ref.ticketId);
283
- await this.workspaces.remove(ref.workdir, { repo: ref.repo ?? meta?.repo });
284
- }
285
- async repoSlug(ref) {
286
- if (!ref.workdir)
287
- return null;
288
- return this.gitProvider.remoteRepo(ref.workdir);
289
- }
290
- async doctor() {
291
- const checks = [];
292
- if (!this.command) {
293
- checks.push({
294
- name: `ACP Agent (${this.id})`,
295
- status: 'fail',
296
- detail: this.unavailableReason ?? '未找到可执行文件',
297
- hint: this.installHint ?? `安装该 Agent 后重启服务,或在 runtime.options.${this.id}.command 指定可执行文件`,
298
- });
299
- }
300
- else {
301
- const version = await realRunner(this.command).run(this.probeArgs, {
302
- cwd: this.home,
303
- env: { ...process.env, ...this.env },
304
- timeoutMs: 5_000,
305
- });
306
- checks.push({
307
- name: `ACP Agent (${this.id})`,
308
- status: version.code === 0 ? 'ok' : 'fail',
309
- detail: version.code === 0
310
- ? version.stdout.trim().split('\n')[0] ?? this.command
311
- : `${version.stderr || version.stdout}`.trim().slice(0, 160),
312
- ...(version.code === 0 ? {} : { hint: this.installHint ?? `确认 ${this.command} 可执行;命令与参数由 runtime.options.${this.id}.command/args 配置` }),
313
- });
314
- }
315
- const git = await this.gitProvider.version();
316
- checks.push({
317
- name: 'git worktree',
318
- status: git === null ? 'fail' : 'ok',
319
- detail: git ?? 'git --version 不可用',
320
- ...(git === null ? { hint: '安装 git,并确认 git worktree 可用' } : {}),
321
- });
322
- return checks;
323
- }
324
- spawnConnection(cwd) {
325
- if (!this.command) {
326
- throw new WorkerRuntimeError('runtime:unavailable', this.unavailableReason ?? `ACP Agent ${this.id} 不可用`);
327
- }
328
- return new AcpConnection({
329
- command: this.command,
330
- args: this.args,
331
- cwd,
332
- env: { ...process.env, ...this.env },
333
- onNotification: (method, params) => this.onNotification(method, params),
334
- onRequest: (method, params) => this.onRequest(method, params),
335
- onExit: (code, signal, stderr) => this.onExit(cwd, code, signal, stderr),
336
- });
337
- }
338
- async initialize(connection) {
339
- const init = await connection.request('initialize', {
340
- protocolVersion: 1,
341
- clientCapabilities: {
342
- fs: { readTextFile: false, writeTextFile: false },
343
- terminal: false,
344
- },
345
- clientInfo: { name: CLIENT_NAME, title: 'Router Agent', version: CLIENT_VERSION },
346
- }, this.initializeTimeoutMs);
347
- if (Number(init?.protocolVersion) !== 1) {
348
- throw new WorkerRuntimeError('agent:launch-failed', `ACP Agent 协议版本不兼容: ${init?.protocolVersion ?? 'unknown'}`, {
349
- detail: { protocolVersion: init?.protocolVersion },
350
- });
351
- }
352
- return init;
353
- }
354
- async newSession(connection, workdir) {
355
- try {
356
- const result = await connection.request('session/new', {
357
- cwd: workdir,
358
- mcpServers: [],
359
- }, this.initializeTimeoutMs);
360
- if (!result?.sessionId) {
361
- throw new WorkerRuntimeError('agent:launch-failed', 'ACP session/new 没有返回 sessionId');
362
- }
363
- return result.sessionId;
364
- }
365
- catch (error) {
366
- if (error instanceof AcpProtocolError && error.code === -32000) {
367
- throw new WorkerRuntimeError('agent:launch-failed', `ACP Agent 未完成认证: ${error.message}`, {
368
- detail: { code: error.code, data: error.data },
369
- });
370
- }
371
- throw error;
372
- }
373
- }
374
- registerSession(input) {
375
- const session = { ...input, inFlight: false, suppressUpdates: false };
376
- this.sessions.set(session.ticketId, session);
377
- this.bySessionId.set(session.sessionId, session);
378
- return session;
379
- }
380
- prompt(session, text) {
381
- if (session.inFlight) {
382
- throw new WorkerRuntimeError('agent:not-accepting-input', 'ACP 会话仍在执行上一轮');
383
- }
384
- session.inFlight = true;
385
- session.promptStartedAt = new Date().toISOString();
386
- session.lastError = undefined;
387
- session.connection
388
- .request('session/prompt', {
389
- sessionId: session.sessionId,
390
- prompt: [{ type: 'text', text }],
391
- }, 0)
392
- .then(result => {
393
- if (this.bySessionId.get(session.sessionId) !== session)
394
- return;
395
- session.inFlight = false;
396
- session.promptStartedAt = undefined;
397
- session.lastStopReason = result?.stopReason;
398
- session.lastEventAt = new Date().toISOString();
399
- })
400
- .catch(error => {
401
- if (this.bySessionId.get(session.sessionId) !== session)
402
- return;
403
- session.inFlight = false;
404
- session.promptStartedAt = undefined;
405
- session.lastError = error instanceof Error ? error.message : String(error);
406
- this.writeMetaStatus(session.ticketId, { status: 'error', lastError: session.lastError });
407
- });
408
- }
409
- async revive(meta) {
410
- if (!meta?.sessionId || meta.loadSession !== true)
411
- return null;
412
- const connection = this.spawnConnection(meta.workdir);
413
- let session;
414
- try {
415
- const init = await this.initialize(connection);
416
- if (init.agentCapabilities?.loadSession !== true) {
417
- throw new WorkerRuntimeError('agent:launch-failed', 'ACP Agent 未声明 loadSession,无法恢复会话');
418
- }
419
- session = this.registerSession({
420
- ticketId: meta.ticketId,
421
- sessionId: meta.sessionId,
422
- workdir: meta.workdir,
423
- branch: meta.workBranch,
424
- tools: meta.tools ?? READ_ONLY_TOOLS,
425
- loadSession: true,
426
- connection,
427
- });
428
- session.suppressUpdates = true;
429
- try {
430
- await connection.request('session/load', {
431
- sessionId: meta.sessionId,
432
- cwd: meta.workdir,
433
- mcpServers: [],
434
- }, this.initializeTimeoutMs);
435
- }
436
- finally {
437
- session.suppressUpdates = false;
438
- }
439
- meta.status = 'running';
440
- if (connection.pid !== undefined)
441
- meta.pid = connection.pid;
442
- this.writeMeta(meta.ticketId, meta);
443
- // 重连本身算一次活动,避免用重启前的 startedAt 立即判 idle。
444
- session.lastEventAt = new Date().toISOString();
445
- return session;
446
- }
447
- catch (error) {
448
- if (session)
449
- this.dropSession(session);
450
- await connection.close().catch(() => { });
451
- return null;
452
- }
453
- }
454
- dropSession(session) {
455
- this.sessions.delete(session.ticketId);
456
- this.bySessionId.delete(session.sessionId);
457
- }
458
- onNotification(method, params) {
459
- if (method !== 'session/update')
460
- return;
461
- const payload = params;
462
- const session = payload?.sessionId ? this.bySessionId.get(payload.sessionId) : undefined;
463
- if (!session || session.suppressUpdates)
464
- return;
465
- const update = payload?.update;
466
- if (!update)
467
- return;
468
- const kind = String(update.sessionUpdate ?? '');
469
- const at = new Date().toISOString();
470
- if (kind === 'agent_message_chunk') {
471
- const text = contentText(update.content);
472
- if (text)
473
- this.recordEvent(session, { at, kind: 'text', text });
474
- return;
475
- }
476
- if (kind === 'tool_call') {
477
- const title = typeof update.title === 'string' ? update.title : undefined;
478
- const toolKind = typeof update.kind === 'string' ? update.kind : 'tool';
479
- const target = toolTarget(update);
480
- this.recordEvent(session, {
481
- at,
482
- kind: 'tool',
483
- text: title ?? toolKind,
484
- tool: { name: title ?? toolKind, ...(target ? { target } : {}) },
485
- });
486
- }
487
- // tool_call_update / plan / usage 等只更新状态,不重复写入活动。
488
- }
489
- async onRequest(method, params) {
490
- if (method === 'session/request_permission') {
491
- const payload = params;
492
- const session = payload?.sessionId ? this.bySessionId.get(payload.sessionId) : undefined;
493
- const tools = session?.tools ?? READ_ONLY_TOOLS;
494
- const options = Array.isArray(payload?.options) ? payload.options : [];
495
- const choice = payload?.toolCall ? decidePermission(tools, payload.toolCall, options) : null;
496
- if (!choice)
497
- return { outcome: { outcome: 'cancelled' } };
498
- return { outcome: { outcome: 'selected', optionId: choice.optionId } };
499
- }
500
- throw new AcpProtocolError(-32601, `ACP 客户端不支持该方法: ${method}`);
501
- }
502
- onExit(cwd, code, signal, stderr) {
503
- for (const session of this.sessions.values()) {
504
- if (session.workdir !== cwd || session.connection.alive)
505
- continue;
506
- session.inFlight = false;
507
- session.promptStartedAt = undefined;
508
- session.lastError = `ACP agent 退出(code=${code ?? 'null'}, signal=${signal ?? 'null'})${stderr ? `: ${stderr.slice(-300)}` : ''}`;
509
- this.writeMetaStatus(session.ticketId, { status: 'exited', lastError: session.lastError });
510
- }
511
- }
512
- recordEvent(session, event) {
513
- session.lastEventAt = event.at;
514
- try {
515
- const dir = join(this.workersDir, session.ticketId);
516
- mkdirSync(dir, { recursive: true });
517
- appendFileSync(join(dir, `${CHANNEL}-events.jsonl`), `${JSON.stringify(event)}\n`, 'utf8');
518
- }
519
- catch {
520
- // 活动落盘失败不应影响主流程;内存里仍可从文件缺失时降级。
521
- }
522
- }
523
- readEvents(ticketId) {
524
- try {
525
- const text = readFileSync(join(this.workersDir, ticketId, `${CHANNEL}-events.jsonl`), 'utf8');
526
- const events = [];
527
- for (const line of text.split('\n')) {
528
- if (!line.trim())
529
- continue;
530
- try {
531
- const parsed = JSON.parse(line);
532
- if (parsed && typeof parsed.at === 'string' && (parsed.kind === 'text' || parsed.kind === 'tool')) {
533
- events.push(parsed);
534
- }
535
- }
536
- catch {
537
- // 跳过损坏行。
538
- }
539
- }
540
- return events;
541
- }
542
- catch {
543
- return [];
544
- }
545
- }
546
- /** 活动日志的最后落盘时间;文件不存在时返回 undefined。 */
547
- lastEventAtFromFile(ticketId) {
548
- try {
549
- return statSync(join(this.workersDir, ticketId, `${CHANNEL}-events.jsonl`)).mtime.toISOString();
550
- }
551
- catch {
552
- return undefined;
553
- }
554
- }
555
- async rollback(workdir, branch, repo) {
556
- await this.workspaces.remove(workdir, { repo }).catch(() => { });
557
- if (this.workspaces.deleteBranch) {
558
- await this.workspaces.deleteBranch(branch, repo).catch(() => { });
559
- }
560
- }
561
- launchError(error, workdir, branch) {
562
- if (error instanceof WorkerRuntimeError) {
563
- return new WorkerRuntimeError(error.code, error.reason, {
564
- retryable: error.retryable,
565
- cause: error.cause,
566
- detail: { ...error.detail, workdir, branch, command: this.command },
567
- });
568
- }
569
- const message = error instanceof Error ? error.message : String(error);
570
- return new WorkerRuntimeError('agent:launch-failed', `ACP Agent 启动失败: ${message}`, {
571
- cause: error instanceof Error ? error.stack : undefined,
572
- detail: { workdir, branch, command: this.command },
573
- });
574
- }
575
- metaPath(ticketId) {
576
- return join(this.workersDir, ticketId, 'meta.json');
577
- }
578
- readMeta(ticketId) {
579
- try {
580
- return JSON.parse(readFileSync(this.metaPath(ticketId), 'utf8'));
581
- }
582
- catch {
583
- return null;
584
- }
585
- }
586
- writeMeta(ticketId, meta) {
587
- const path = this.metaPath(ticketId);
588
- mkdirSync(join(this.workersDir, ticketId), { recursive: true });
589
- const tmp = `${path}.tmp`;
590
- writeFileSync(tmp, `${JSON.stringify(meta, null, 2)}\n`, 'utf8');
591
- renameSync(tmp, path);
592
- }
593
- writeMetaStatus(ticketId, patch) {
594
- const meta = this.readMeta(ticketId);
595
- if (!meta)
596
- return;
597
- this.writeMeta(ticketId, { ...meta, ...patch });
598
- }
599
- }
600
- function contentText(content) {
601
- if (typeof content === 'string')
602
- return content;
603
- if (Array.isArray(content))
604
- return content.map(contentText).join('');
605
- if (content && typeof content === 'object') {
606
- const record = content;
607
- if (record.type === 'text' && typeof record.text === 'string')
608
- return record.text;
609
- }
610
- return '';
611
- }
612
- function toolTarget(update) {
613
- const locations = update.locations;
614
- if (Array.isArray(locations)) {
615
- for (const item of locations) {
616
- if (item && typeof item === 'object' && typeof item.path === 'string') {
617
- return item.path;
618
- }
619
- }
620
- }
621
- const raw = update.rawInput;
622
- if (raw && typeof raw === 'object') {
623
- const record = raw;
624
- if (typeof record.command === 'string')
625
- return record.command;
626
- if (typeof record.file_path === 'string')
627
- return record.file_path;
628
- if (typeof record.path === 'string')
629
- return record.path;
630
- }
631
- return undefined;
632
- }
633
- //# sourceMappingURL=index.js.map