ai-project-manage-cli 6.0.64 → 7.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11,10 +11,10 @@ var APM_CONFIG_DIR = join(homedir(), ".config", "apm");
11
11
  var APM_CONFIG_PATH = join(APM_CONFIG_DIR, "config.json");
12
12
  var DEFAULT_BASE_URL = "http://127.0.0.1:3000";
13
13
  function resolveClientMachineId(cfg) {
14
- return (cfg.clientMachineId ?? cfg.userId ?? "").trim();
14
+ return (cfg.clientMachineId ?? "").trim();
15
15
  }
16
16
  function resolveApiKey(cfg) {
17
- return (cfg.apiKey ?? cfg.token ?? "").trim();
17
+ return cfg.apiKey.trim();
18
18
  }
19
19
  async function tryReadApmConfig() {
20
20
  try {
@@ -23,14 +23,17 @@ async function tryReadApmConfig() {
23
23
  if (typeof v !== "object" || v === null) {
24
24
  return null;
25
25
  }
26
- const rawCfg = v;
27
- if (typeof rawCfg.baseUrl !== "string") {
26
+ const cfg = v;
27
+ if (typeof cfg.baseUrl !== "string" || typeof cfg.apiKey !== "string") {
28
28
  return null;
29
29
  }
30
- const apiKey = resolveApiKey(rawCfg);
30
+ const apiKey = cfg.apiKey.trim();
31
31
  if (!apiKey) return null;
32
- const cfg = v;
33
- const clientMachineId = resolveClientMachineId(cfg);
32
+ const clientMachineId = resolveClientMachineId({
33
+ baseUrl: cfg.baseUrl,
34
+ apiKey,
35
+ clientMachineId: cfg.clientMachineId
36
+ });
34
37
  return {
35
38
  baseUrl: cfg.baseUrl.trim().replace(/\/+$/, ""),
36
39
  apiKey,
@@ -86,18 +89,18 @@ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "
86
89
  // src/command-utils.ts
87
90
  import {
88
91
  copyFileSync,
89
- existsSync,
92
+ existsSync as existsSync2,
90
93
  mkdirSync as mkdirSync2,
91
94
  readFileSync as readFileSync2,
92
95
  readdirSync,
93
96
  statSync,
94
97
  writeFileSync as writeFileSync2
95
98
  } from "fs";
96
- import { basename, dirname, extname, join as join2, resolve as resolve2 } from "path";
99
+ import { dirname, join as join2, resolve as resolve2, basename } from "path";
97
100
  import { fileURLToPath } from "url";
98
101
 
99
102
  // src/workdir-path.ts
100
- import { realpathSync } from "fs";
103
+ import { realpathSync, existsSync } from "fs";
101
104
  import { platform } from "os";
102
105
  import { resolve } from "path";
103
106
  function toFsPath(inputPath) {
@@ -142,6 +145,17 @@ function requireRemoteWorkdir(workdir) {
142
145
  }
143
146
  return resolveWorkdirPath(trimmed);
144
147
  }
148
+ function requireExistingWorkdir(workdir) {
149
+ const trimmed = typeof workdir === "string" ? workdir.trim() : "";
150
+ if (!trimmed) {
151
+ throw new Error("[apm] \u5DE5\u4F5C\u76EE\u5F55 workdir \u4E0D\u80FD\u4E3A\u7A7A");
152
+ }
153
+ const resolved = resolveWorkdirPath(trimmed);
154
+ if (!existsSync(toFsPath(resolved))) {
155
+ throw new Error(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728: ${resolved}`);
156
+ }
157
+ return resolved;
158
+ }
145
159
 
146
160
  // src/command-utils.ts
147
161
  var __dirname = dirname(fileURLToPath(import.meta.url));
@@ -152,7 +166,7 @@ function workspaceApmDir(cwd = resolveWorkdirPath()) {
152
166
  function isWorkspaceApmInitialized(workdir) {
153
167
  const apmDir = workspaceApmDir(workdir);
154
168
  const fsApmDir = toFsPath(apmDir);
155
- if (!existsSync(fsApmDir)) {
169
+ if (!existsSync2(fsApmDir)) {
156
170
  return false;
157
171
  }
158
172
  const st = statSync(fsApmDir);
@@ -163,6 +177,20 @@ function isWorkspaceApmInitialized(workdir) {
163
177
  }
164
178
  return readdirSync(fsApmDir).length > 0;
165
179
  }
180
+ function assertWorkspaceApmDirExists(workdir) {
181
+ if (!isWorkspaceApmInitialized(workdir)) {
182
+ const apmDir = workspaceApmDir(workdir);
183
+ const fsApmDir = toFsPath(apmDir);
184
+ if (!existsSync2(fsApmDir)) {
185
+ throw new Error(
186
+ `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B\u672A\u68C0\u6D4B\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5728\u8BE5\u4ED3\u5E93\u6839\u76EE\u5F55\u6267\u884C apm init \u5B8C\u6210\u63A5\u5165\u3002`
187
+ );
188
+ }
189
+ throw new Error(
190
+ `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B .apm \u76EE\u5F55\u4E3A\u7A7A\uFF0C\u8BF7\u5728\u8BE5\u4ED3\u5E93\u6839\u76EE\u5F55\u6267\u884C apm init \u5B8C\u6210\u63A5\u5165\u3002`
191
+ );
192
+ }
193
+ }
166
194
  var APM_GITIGNORE_PATTERNS = [
167
195
  /^\.apm\/?$/,
168
196
  /^\.apm\/\*\*$/,
@@ -187,7 +215,7 @@ var APM_GITIGNORE_LINE = "**/.apm/**";
187
215
  function ensureApmGitignoredInRepo(workdir) {
188
216
  const gitignorePath = join2(workdir, ".gitignore");
189
217
  const fsGitignorePath = toFsPath(gitignorePath);
190
- if (!existsSync(fsGitignorePath)) {
218
+ if (!existsSync2(fsGitignorePath)) {
191
219
  writeFileSync2(fsGitignorePath, `${APM_GITIGNORE_LINE}
192
220
  `, "utf8");
193
221
  return true;
@@ -205,63 +233,6 @@ function ensureApmGitignoredInRepo(workdir) {
205
233
  );
206
234
  return true;
207
235
  }
208
- function assertApmGitignoredInRepo(workdir) {
209
- const gitignorePath = join2(workdir, ".gitignore");
210
- const fsGitignorePath = toFsPath(gitignorePath);
211
- if (!existsSync(fsGitignorePath)) {
212
- throw new Error(
213
- `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7F3A\u5C11 .gitignore \u6587\u4EF6\uFF0C\u8BF7\u6DFB\u52A0\u5BF9 .apm \u7684\u5FFD\u7565\u89C4\u5219\uFF08\u4F8B\u5982 **/.apm/**\uFF09\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
214
- );
215
- }
216
- const content = readFileSync2(fsGitignorePath, "utf8");
217
- if (!content.split(/\r?\n/).some(gitignoreIgnoresApm)) {
218
- throw new Error(
219
- `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7684 .gitignore \u672A\u5FFD\u7565 .apm\uFF0C\u8BF7\u6DFB\u52A0 **/.apm/** \u6216 .apm/\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
220
- );
221
- }
222
- }
223
- var SESSIONS_SUBDIR = "sessions";
224
- var SESSION_DOCS_SUBDIR = "docs";
225
- var SESSION_ATTACHMENTS_SUBDIR = "attachments";
226
- function sessionDir(sessionId, apmRoot) {
227
- return join2(apmRoot ?? workspaceApmDir(), SESSIONS_SUBDIR, sessionId);
228
- }
229
- function sessionDocsDir(sessionId, apmRoot) {
230
- return join2(sessionDir(sessionId, apmRoot), SESSION_DOCS_SUBDIR);
231
- }
232
- function sessionRulePath(sessionId, apmRoot) {
233
- return join2(sessionDir(sessionId, apmRoot), "RULE.md");
234
- }
235
- function sessionTaskPath(sessionId, apmRoot) {
236
- return join2(sessionDir(sessionId, apmRoot), "TASK.md");
237
- }
238
- function sessionTodoPath(sessionId, apmRoot) {
239
- return join2(sessionDir(sessionId, apmRoot), "TODO.md");
240
- }
241
- function sessionYamlPath(sessionId, apmRoot) {
242
- return join2(sessionDir(sessionId, apmRoot), "session.yaml");
243
- }
244
- function sessionMessagesXmlPath(sessionId, apmRoot) {
245
- return join2(sessionDir(sessionId, apmRoot), "messages.xml");
246
- }
247
- function documentLocalFileName(platformName) {
248
- const trimmed = platformName.trim();
249
- if (!trimmed) return "document.md";
250
- if (extname(trimmed).toLowerCase() === ".md") return trimmed;
251
- return `${trimmed}.md`;
252
- }
253
- function documentPlatformName(filePath) {
254
- const base = basename(filePath.trim().replace(/\\/g, "/"));
255
- if (base.toLowerCase().endsWith(".md")) {
256
- return base.slice(0, -3);
257
- }
258
- return base;
259
- }
260
- function resolveSessionDocumentPath(sessionId, documentName, apmRoot) {
261
- const base = basename(documentName.trim().replace(/\\/g, "/"));
262
- const fileName = documentLocalFileName(base);
263
- return join2(sessionDocsDir(sessionId, apmRoot), fileName);
264
- }
265
236
  async function ensureLoggedConfig() {
266
237
  const cfg = await ensureApmConfig();
267
238
  if (!resolveApiKey(cfg)) {
@@ -278,7 +249,7 @@ async function ensureDirExists(dir) {
278
249
  async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
279
250
  const dir = workspaceApmDir(cwd);
280
251
  const fsDir = toFsPath(dir);
281
- if (!existsSync(fsDir)) {
252
+ if (!existsSync2(fsDir)) {
282
253
  mkdirSync2(fsDir, { recursive: true });
283
254
  return;
284
255
  }
@@ -293,7 +264,7 @@ async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
293
264
  }
294
265
  }
295
266
  var WORKSPACE_TEMPLATE_SUBDIRS = [
296
- "sessions",
267
+ "project",
297
268
  "skills",
298
269
  "rules",
299
270
  "deploy"
@@ -323,17 +294,17 @@ function assertTemplateCopiedToApm(apmDir, workdir) {
323
294
  "apm.config.json",
324
295
  "rules",
325
296
  "skills",
326
- "sessions"
297
+ "project"
327
298
  ];
328
299
  for (const item of required) {
329
300
  const path10 = join2(apmDir, item);
330
- if (!existsSync(toFsPath(path10))) {
301
+ if (!existsSync2(toFsPath(path10))) {
331
302
  throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path10}`);
332
303
  }
333
304
  }
334
305
  const leakedRules = join2(workdir, "rules");
335
306
  const apmRules = join2(apmDir, "rules");
336
- if (existsSync(toFsPath(leakedRules)) && !existsSync(toFsPath(join2(apmRules, "reply.md")))) {
307
+ if (existsSync2(toFsPath(leakedRules)) && !existsSync2(toFsPath(join2(apmRules, "reply.md")))) {
337
308
  throw new Error(
338
309
  `[apm] \u6A21\u677F\u88AB\u590D\u5236\u5230\u9519\u8BEF\u4F4D\u7F6E: ${leakedRules}\uFF08\u5E94\u5728 ${apmRules}\uFF09`
339
310
  );
@@ -343,7 +314,7 @@ async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
343
314
  const resolvedTarget = resolve2(targetDir);
344
315
  const templateDir = resolve2(CLI_TEMPLATE_DIR);
345
316
  const fsTemplateDir = toFsPath(templateDir);
346
- if (!existsSync(fsTemplateDir)) {
317
+ if (!existsSync2(fsTemplateDir)) {
347
318
  throw new Error(`[apm] \u672A\u627E\u5230 CLI \u6A21\u677F\u76EE\u5F55: ${templateDir}`);
348
319
  }
349
320
  const dirStat = statSync(fsTemplateDir);
@@ -365,6 +336,40 @@ async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
365
336
  }
366
337
  assertTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
367
338
  }
339
+ var TASKS_SUBDIR = "tasks";
340
+ var TASK_DOCS_SUBDIR = "docs";
341
+ function taskDir(taskId, apmRoot, workdir) {
342
+ return join2(
343
+ apmRoot ?? workspaceApmDir(workdir ?? resolveWorkdirPath()),
344
+ TASKS_SUBDIR,
345
+ taskId
346
+ );
347
+ }
348
+ function taskDocsDir(taskId, apmRoot, workdir) {
349
+ return join2(taskDir(taskId, apmRoot, workdir), TASK_DOCS_SUBDIR);
350
+ }
351
+ function taskRulePath(taskId, apmRoot, workdir) {
352
+ return join2(taskDir(taskId, apmRoot, workdir), "RULE.md");
353
+ }
354
+ function taskTaskPath(taskId, apmRoot, workdir) {
355
+ return join2(taskDir(taskId, apmRoot, workdir), "TASK.md");
356
+ }
357
+ function taskYamlPath(taskId, apmRoot, workdir) {
358
+ return join2(taskDir(taskId, apmRoot, workdir), "task.yaml");
359
+ }
360
+ function documentLocalFileName(platformName) {
361
+ const trimmed = platformName.trim();
362
+ if (!trimmed) return "document.md";
363
+ if (trimmed.toLowerCase().endsWith(".md")) return trimmed;
364
+ return `${trimmed}.md`;
365
+ }
366
+ function documentPlatformName(filePath) {
367
+ const base = basename(filePath.trim().replace(/\\/g, "/"));
368
+ if (base.toLowerCase().endsWith(".md")) {
369
+ return base.slice(0, -3);
370
+ }
371
+ return base;
372
+ }
368
373
 
369
374
  // src/deployment-config-sync.ts
370
375
  import { join as join3 } from "path";
@@ -377,61 +382,9 @@ import { createApiClient } from "listpage-http";
377
382
  import { defineEndpoint } from "listpage-http";
378
383
  var requestConfig = {
379
384
  cli: {
380
- me: defineEndpoint(
381
- {
382
- method: "GET",
383
- path: "/cli/me"
384
- }
385
- ),
386
- sessionDetail: defineEndpoint({
387
- method: "GET",
388
- path: "/cli/sessions/detail"
389
- }),
390
- sessionMembers: defineEndpoint({
391
- method: "GET",
392
- path: "/cli/sessions/members"
393
- }),
394
- listSessionMessages: defineEndpoint({
395
- method: "GET",
396
- path: "/cli/messages"
397
- }),
398
- listDocuments: defineEndpoint({
399
- method: "GET",
400
- path: "/cli/documents"
401
- }),
402
- listAttachments: defineEndpoint(
403
- {
404
- method: "GET",
405
- path: "/cli/attachments"
406
- }
407
- ),
408
- upsertDocument: defineEndpoint({
409
- method: "PUT",
410
- path: "/cli/documents/upsert"
411
- }),
412
- appendMessageContent: defineEndpoint({
413
- method: "PUT",
414
- path: "/cli/messages/content"
415
- }),
416
- setMessageError: defineEndpoint({
417
- method: "PUT",
418
- path: "/cli/messages/error"
419
- }),
420
- upsertCursorMessageLog: defineEndpoint({
421
- method: "PUT",
422
- path: "/cli/cursor-message-logs"
423
- }),
424
- updateMessageStatus: defineEndpoint({
425
- method: "PUT",
426
- path: "/cli/messages/status"
427
- }),
428
- branchBaseline: defineEndpoint({
429
- method: "GET",
430
- path: "/cli/tasks/branch-baseline"
431
- }),
432
- listSessionsForBranchCleanup: defineEndpoint({
385
+ me: defineEndpoint({
433
386
  method: "GET",
434
- path: "/cli/sessions/branch-cleanup"
387
+ path: "/cli/me"
435
388
  }),
436
389
  workspaceBaseline: defineEndpoint({
437
390
  method: "GET",
@@ -453,10 +406,6 @@ var requestConfig = {
453
406
  method: "GET",
454
407
  path: "/cli/rules"
455
408
  }),
456
- createPullRequest: defineEndpoint({
457
- method: "POST",
458
- path: "/cli/pull-requests"
459
- }),
460
409
  getRepositoryProjectDocumentManifest: defineEndpoint({
461
410
  method: "GET",
462
411
  path: "/cli/repository-project-documents/manifest"
@@ -473,33 +422,47 @@ var requestConfig = {
473
422
  method: "DELETE",
474
423
  path: "/cli/repository-project-documents"
475
424
  }),
476
- updateCoordinatorDeploymentStatus: defineEndpoint({
425
+ updateTaskDeploymentStatus: defineEndpoint({
477
426
  method: "PUT",
478
- path: "/cli/coordinator-deployments/status"
427
+ path: "/cli/task-deployments/status"
479
428
  }),
480
- syncCoordinatorDeploymentLog: defineEndpoint({
429
+ syncTaskDeploymentLog: defineEndpoint(
430
+ {
431
+ method: "PUT",
432
+ path: "/cli/task-deployments/log"
433
+ }
434
+ ),
435
+ completeTaskDeployment: defineEndpoint({
481
436
  method: "PUT",
482
- path: "/cli/coordinator-deployments/log"
437
+ path: "/cli/task-deployments/complete"
483
438
  }),
484
- completeCoordinatorDeployment: defineEndpoint({
485
- method: "PUT",
486
- path: "/cli/coordinator-deployments/complete"
439
+ listPendingMailboxMessages: defineEndpoint({
440
+ method: "GET",
441
+ path: "/cli/mailbox-messages/pending"
442
+ }),
443
+ getMailboxMessageDetail: defineEndpoint({
444
+ method: "GET",
445
+ path: "/cli/mailbox-messages/detail"
446
+ }),
447
+ listDocuments: defineEndpoint({
448
+ method: "GET",
449
+ path: "/cli/documents"
487
450
  }),
488
- updateCoordinatorDispatchStatus: defineEndpoint({
451
+ upsertDocument: defineEndpoint({
489
452
  method: "PUT",
490
- path: "/cli/coordinator-dispatches/status"
453
+ path: "/cli/documents/upsert"
491
454
  }),
492
- appendCoordinatorDispatchResponse: defineEndpoint({
455
+ claimMailboxMessage: defineEndpoint({
493
456
  method: "PUT",
494
- path: "/cli/coordinator-dispatches/append-response"
457
+ path: "/cli/mailbox-messages/claim"
495
458
  }),
496
- completeCoordinatorDispatch: defineEndpoint({
459
+ completeMailboxMessage: defineEndpoint({
497
460
  method: "PUT",
498
- path: "/cli/coordinator-dispatches/complete"
461
+ path: "/cli/mailbox-messages/complete"
499
462
  }),
500
- createCoordinatorDispatchQuestions: defineEndpoint({
501
- method: "POST",
502
- path: "/cli/coordinator-dispatches/questions"
463
+ upsertCursorLog: defineEndpoint({
464
+ method: "PUT",
465
+ path: "/cli/cursor-logs"
503
466
  })
504
467
  }
505
468
  };
@@ -535,7 +498,7 @@ async function tryReadGitOriginUrl(cwd) {
535
498
 
536
499
  // src/deployment-config-sync.ts
537
500
  var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json \u4E0E .apm/deploy/README.md";
538
- var SYNC_HINT = "\u767B\u8BB0\u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u3001\u7ED1\u5B9A\u4ED3\u5E93\u540E\uFF0C\u53EF\u6267\u884C: apm sync-deploy-config";
501
+ var SYNC_HINT = "\u767B\u8BB0\u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u3001\u7ED1\u5B9A\u4ED3\u5E93\u540E\uFF0C\u91CD\u65B0 apm init \u53EF\u540C\u6B65\u90E8\u7F72\u914D\u7F6E";
539
502
  async function resolveRepositoryIdForSync(api, workdirPath) {
540
503
  const baseline = await api.cli.workspaceBaseline({ workdirPath });
541
504
  if (baseline.repositoryId) {
@@ -559,7 +522,7 @@ async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
559
522
  if (!cfg || !resolveApiKey(cfg)) {
560
523
  console.log(
561
524
  `[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E\u540C\u6B65\uFF08${TEMPLATE_HINT}\uFF09\u3002
562
- [apm] \u8BF7\u5148\u6267\u884C apm login\uFF0C\u518D\u6267\u884C apm sync-deploy-config \u62C9\u53D6\u6700\u65B0\u914D\u7F6E\u3002`
525
+ [apm] \u8BF7\u5148\u6267\u884C apm login\uFF0C\u518D\u91CD\u65B0 apm init \u62C9\u53D6\u6700\u65B0\u914D\u7F6E\u3002`
563
526
  );
564
527
  return { synced: false, repositoryId: null };
565
528
  }
@@ -580,7 +543,7 @@ ${diagnostic ?? ""}
580
543
  if (!config) {
581
544
  console.log(
582
545
  `[apm] \u672A\u627E\u5230\u5173\u8054\u4ED3\u5E93\u7684\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF0CrepositoryId\uFF1A${repositoryId}\uFF09\u3002
583
- [apm] \u8BF7\u5728\u5E73\u53F0\u300C\u90E8\u7F72\u914D\u7F6E\u300D\u4E2D\u521B\u5EFA\u914D\u7F6E\u5E76\u5173\u8054\u8BE5\u4ED3\u5E93\uFF0C\u7136\u540E\u6267\u884C: apm sync-deploy-config`
546
+ [apm] \u8BF7\u5728\u5E73\u53F0\u300C\u90E8\u7F72\u914D\u7F6E\u300D\u4E2D\u521B\u5EFA\u914D\u7F6E\u5E76\u5173\u8054\u8BE5\u4ED3\u5E93\uFF0C\u7136\u540E\u91CD\u65B0 apm init \u540C\u6B65\u3002`
584
547
  );
585
548
  return { synced: false, repositoryId };
586
549
  }
@@ -611,13 +574,12 @@ ${diagnostic ?? ""}
611
574
 
612
575
  // src/repository-project-documents-sync.ts
613
576
  import {
614
- existsSync as existsSync2,
577
+ existsSync as existsSync3,
615
578
  readdirSync as readdirSync2,
616
579
  readFileSync as readFileSync3,
617
580
  rmSync,
618
581
  writeFileSync as writeFileSync4
619
582
  } from "fs";
620
- import { createHash } from "crypto";
621
583
  import { dirname as dirname2, join as join4, relative, sep } from "path";
622
584
  var MANIFEST_FILE = "manifest.json";
623
585
  function projectDocumentsDir(apmRoot) {
@@ -638,45 +600,19 @@ function normalizeLocalDocumentPath(path10) {
638
600
  }
639
601
  return segments.join("/");
640
602
  }
641
- function hashLocalFileContent(content) {
642
- return createHash("sha256").update(content, "utf8").digest("hex");
643
- }
644
603
  function readLocalManifest(apmRoot) {
645
- const manifestPath2 = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
646
- if (!existsSync2(manifestPath2)) {
604
+ const manifestPath = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
605
+ if (!existsSync3(manifestPath)) {
647
606
  return null;
648
607
  }
649
608
  try {
650
609
  return JSON.parse(
651
- readFileSync3(manifestPath2, "utf8")
610
+ readFileSync3(manifestPath, "utf8")
652
611
  );
653
612
  } catch {
654
613
  return null;
655
614
  }
656
615
  }
657
- function listLocalDocumentPaths(apmRoot) {
658
- const root = projectDocumentsDir(apmRoot);
659
- if (!existsSync2(root)) {
660
- return [];
661
- }
662
- const paths = [];
663
- const walk = (dir) => {
664
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
665
- const abs = join4(dir, entry.name);
666
- if (entry.isDirectory()) {
667
- walk(abs);
668
- continue;
669
- }
670
- if (entry.isFile() && entry.name === MANIFEST_FILE) {
671
- continue;
672
- }
673
- const rel = relative(root, abs).split(sep).join("/");
674
- paths.push(rel);
675
- }
676
- };
677
- walk(root);
678
- return paths.sort();
679
- }
680
616
  function diffManifestPaths(remote, local) {
681
617
  const remoteMap = new Map(
682
618
  (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
@@ -755,7 +691,7 @@ ${diagnostic ?? ""}`
755
691
  let deleted = 0;
756
692
  for (const path10 of deleteLocal) {
757
693
  const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
758
- if (existsSync2(absPath)) {
694
+ if (existsSync3(absPath)) {
759
695
  rmSync(absPath, { force: true });
760
696
  deleted += 1;
761
697
  }
@@ -776,50 +712,6 @@ ${diagnostic ?? ""}`
776
712
  deleted
777
713
  };
778
714
  }
779
- async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
780
- const api = createApmApiClient(cfg);
781
- const { repositoryId } = await resolveRepositoryIdForSync(api, workdirPath);
782
- if (!repositoryId) {
783
- return 0;
784
- }
785
- const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
786
- const localPaths = listLocalDocumentPaths(targetApmDir);
787
- if (localPaths.length === 0) {
788
- console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
789
- return 0;
790
- }
791
- const remoteManifest = (await api.cli.getRepositoryProjectDocumentManifest({ repositoryId })).manifest ?? null;
792
- const remoteHashByPath = new Map(
793
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
794
- );
795
- const remoteDescriptionByPath = new Map(
796
- (remoteManifest?.documents ?? []).map((doc) => [
797
- doc.path,
798
- doc.description
799
- ])
800
- );
801
- let synced = 0;
802
- for (const path10 of localPaths) {
803
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
804
- const content = readFileSync3(absPath, "utf8");
805
- const contentHash = hashLocalFileContent(content);
806
- if (remoteHashByPath.get(path10) === contentHash) {
807
- continue;
808
- }
809
- await api.cli.upsertRepositoryProjectDocument({
810
- repositoryId,
811
- path: path10,
812
- content,
813
- description: remoteDescriptionByPath.get(path10) ?? void 0
814
- });
815
- synced += 1;
816
- console.log(`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: ${path10}`);
817
- }
818
- if (synced === 0) {
819
- console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
820
- }
821
- return synced;
822
- }
823
715
 
824
716
  // src/git-utils.ts
825
717
  import { execFile as execFile2 } from "child_process";
@@ -929,14 +821,13 @@ async function runInit(name) {
929
821
  console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
930
822
  if (syncResult && !syncResult.synced) {
931
823
  console.log(
932
- "[apm] \u5F53\u524D .apm/apm.config.json \u4E0E .apm/deploy/README.md \u4E3A\u6A21\u677F\u9ED8\u8BA4\u503C\uFF1B\u5B8C\u6210\u5E73\u53F0\u767B\u8BB0\u540E\u6267\u884C apm sync-deploy-config"
824
+ "[apm] \u5F53\u524D .apm/apm.config.json \u4E0E .apm/deploy/README.md \u4E3A\u6A21\u677F\u9ED8\u8BA4\u503C\uFF1B\u5B8C\u6210\u5E73\u53F0\u767B\u8BB0\u540E\u91CD\u65B0 apm init \u53EF\u540C\u6B65"
933
825
  );
934
826
  }
935
827
  console.log("[apm] \u8BF7\u5728\u5E73\u53F0\u300C\u63A5\u5165\u7BA1\u7406 \u2192 \u5DE5\u4F5C\u7A7A\u95F4\u300D\u767B\u8BB0\u4E0A\u8FF0\u76EE\u5F55\u8DEF\u5F84");
936
828
  }
937
829
 
938
830
  // src/commands/login.ts
939
- import { existsSync as existsSync3 } from "fs";
940
831
  import { ApiError } from "listpage-http";
941
832
  async function runLogin(opts) {
942
833
  const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
@@ -989,500 +880,106 @@ async function runLogin(opts) {
989
880
  2
990
881
  )
991
882
  );
992
- const workdir = resolveWorkdirPath();
993
- const apmDir = workspaceApmDir(workdir);
994
- if (existsSync3(apmDir)) {
995
- await syncRemoteDeploymentConfig(workdir, apmDir);
996
- }
997
883
  }
998
884
 
999
- // src/commands/branch.ts
1000
- import { execFile as execFile3 } from "child_process";
1001
- import { promisify as promisify3 } from "util";
1002
- var execFileAsync3 = promisify3(execFile3);
1003
- var SESSION_BRANCH_PREFIX = "feat/session-";
1004
- function branchNameForSession(sessionId) {
1005
- const id = sessionId.trim();
1006
- if (!id) {
1007
- throw new Error("[apm] \u4F1A\u8BDD ID \u4E0D\u80FD\u4E3A\u7A7A");
1008
- }
1009
- if (/[\s/\\]/.test(id)) {
1010
- throw new Error(
1011
- "[apm] \u4F1A\u8BDD ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
1012
- );
1013
- }
1014
- return `${SESSION_BRANCH_PREFIX}${id}`;
1015
- }
1016
- function sessionIdFromBranchName(branch) {
1017
- const name = branch.trim().replace(/^origin\//, "");
1018
- if (!name.startsWith(SESSION_BRANCH_PREFIX)) {
1019
- return null;
1020
- }
1021
- const sessionId = name.slice(SESSION_BRANCH_PREFIX.length).trim();
1022
- return sessionId || null;
1023
- }
1024
- async function execGit2(cwd, args, quiet) {
885
+ // src/commands/update.ts
886
+ import { spawnSync } from "child_process";
887
+
888
+ // src/version.ts
889
+ import { readFileSync as readFileSync5 } from "fs";
890
+ import { dirname as dirname3, join as join6 } from "path";
891
+ import { fileURLToPath as fileURLToPath2 } from "url";
892
+ var CLI_PACKAGE_NAME = "ai-project-manage-cli";
893
+ function readCliVersion() {
1025
894
  try {
1026
- const { stdout, stderr } = await execFileAsync3("git", args, {
1027
- cwd,
1028
- encoding: "utf8",
1029
- maxBuffer: 10 * 1024 * 1024
1030
- });
1031
- if (!quiet && stderr.trim()) {
1032
- process.stderr.write(stderr);
1033
- }
1034
- return stdout;
1035
- } catch (err) {
1036
- const e = err;
1037
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1038
- throw new Error(
1039
- `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
1040
- );
895
+ const dir = dirname3(fileURLToPath2(import.meta.url));
896
+ const pkgPath = join6(dir, "..", "package.json");
897
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
898
+ return pkg.version ?? "0.0.0";
899
+ } catch {
900
+ return "0.0.0";
1041
901
  }
1042
902
  }
1043
- async function ensureGitRepo(cwd) {
1044
- await execGit2(cwd, ["rev-parse", "--git-dir"], true);
1045
- }
1046
- async function getCurrentBranch(cwd) {
1047
- const name = (await execGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1048
- return name;
1049
- }
1050
- async function isWorkingTreeDirty(cwd) {
1051
- const out = await execGit2(cwd, ["status", "--porcelain"], true);
1052
- return out.trim().length > 0;
903
+
904
+ // src/commands/update.ts
905
+ var useNpmShell = process.platform === "win32";
906
+ function runNpm(args, options = {}) {
907
+ return spawnSync(useNpmShell ? "npm.cmd" : "npm", args, {
908
+ ...options,
909
+ shell: useNpmShell
910
+ });
1053
911
  }
1054
- async function remoteHeadBranchExists(cwd, branch) {
1055
- const out = await execGit2(
1056
- cwd,
1057
- ["ls-remote", "--heads", "origin", branch],
1058
- true
1059
- );
1060
- return out.trim().length > 0;
912
+ function registryBaseUrl() {
913
+ const fromEnv = process.env.npm_config_registry?.trim() || process.env.NPM_CONFIG_REGISTRY?.trim();
914
+ return (fromEnv || "https://registry.npmjs.org").replace(/\/+$/, "");
1061
915
  }
1062
- async function localBranchExists(cwd, branch) {
916
+ async function fetchLatestPublishedVersion() {
917
+ const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
1063
918
  try {
1064
- await execGit2(
1065
- cwd,
1066
- ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
1067
- true
1068
- );
1069
- return true;
919
+ const res = await fetch(url);
920
+ if (!res.ok) return null;
921
+ const data = await res.json();
922
+ return data.version?.trim() || null;
1070
923
  } catch {
1071
- return false;
924
+ return null;
1072
925
  }
1073
926
  }
1074
- async function commitWorkingTreeIfDirty(cwd, message) {
1075
- await ensureGitRepo(cwd);
1076
- if (!await isWorkingTreeDirty(cwd)) {
1077
- return false;
1078
- }
1079
- const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
1080
- await execGit2(cwd, ["add", "-A"]);
1081
- await execGit2(cwd, ["commit", "-m", commitMessage]);
1082
- console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
1083
- return true;
927
+ function npmAvailable() {
928
+ const r = runNpm(["--version"], { encoding: "utf8" });
929
+ return !r.error && r.status === 0;
1084
930
  }
1085
- async function ensureFeatureBranch(branch, baselineBranch, options) {
1086
- const cwd = options.cwd ?? process.cwd();
1087
- const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
1088
- await ensureGitRepo(cwd);
1089
- if (!await remoteHeadBranchExists(cwd, baselineBranch)) {
1090
- throw new Error(
1091
- `[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F ${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
1092
- );
1093
- }
1094
- const current = await getCurrentBranch(cwd);
1095
- const dirty = await isWorkingTreeDirty(cwd);
1096
- if (dirty) {
1097
- if (current === branch) {
1098
- await commitWorkingTreeIfDirty(cwd, commitMessage);
1099
- } else {
1100
- await execGit2(cwd, [
1101
- "stash",
1102
- "push",
1103
- "-u",
1104
- "-m",
1105
- `apm: switch to ${branch}`
1106
- ]);
1107
- }
1108
- }
1109
- const onTargetBranch = await getCurrentBranch(cwd) === branch;
1110
- if (onTargetBranch) {
1111
- await execGit2(cwd, ["fetch", "origin", baselineBranch]);
1112
- await execGit2(cwd, ["merge", `origin/${baselineBranch}`, "--no-edit"]);
1113
- } else {
1114
- const remoteExists = await remoteHeadBranchExists(cwd, branch);
1115
- if (remoteExists) {
1116
- await execGit2(cwd, ["fetch", "origin", branch]);
1117
- await execGit2(cwd, ["checkout", "-B", branch, `origin/${branch}`]);
1118
- } else if (await localBranchExists(cwd, branch)) {
1119
- if (await getCurrentBranch(cwd) !== branch) {
1120
- await execGit2(cwd, ["checkout", branch]);
1121
- }
1122
- console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
1123
- } else {
1124
- await execGit2(cwd, ["fetch", "origin", baselineBranch]);
1125
- try {
1126
- await execGit2(cwd, [
1127
- "checkout",
1128
- "-b",
1129
- branch,
1130
- `origin/${baselineBranch}`
1131
- ]);
1132
- await execGit2(cwd, ["push", "-u", "origin", branch]);
1133
- } catch (err) {
1134
- if (await localBranchExists(cwd, branch)) {
1135
- if (await getCurrentBranch(cwd) !== branch) {
1136
- await execGit2(cwd, ["checkout", branch]);
1137
- }
1138
- console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
1139
- } else {
1140
- throw err;
1141
- }
1142
- }
1143
- }
931
+ async function runUpdate() {
932
+ const current = readCliVersion();
933
+ const latest = await fetchLatestPublishedVersion();
934
+ if (latest && current === latest) {
935
+ console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
936
+ return { didUpdate: false };
1144
937
  }
1145
- console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
1146
- return branch;
1147
- }
1148
- async function runBranch(sessionId, options = {}) {
1149
- const trimmedSessionId = sessionId.trim();
1150
- if (!trimmedSessionId) {
1151
- console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
938
+ if (!npmAvailable()) {
939
+ console.error(
940
+ `[apm] \u672A\u627E\u5230 npm\u3002\u8BF7\u5B89\u88C5 Node.js \u540E\u6267\u884C\uFF1Anpm install -g ${CLI_PACKAGE_NAME}@latest`
941
+ );
1152
942
  process.exit(1);
1153
943
  }
1154
- const cfg = await ensureLoggedConfig();
1155
- const api = createApmApiClient(cfg);
1156
- const cwd = options.cwd ?? process.cwd();
1157
- const workdirPath = resolveWorkdirPath(cwd);
1158
- const baseline = await api.cli.branchBaseline({
1159
- sessionId: trimmedSessionId,
1160
- workdirPath
944
+ const targetLabel = latest ?? "latest";
945
+ console.error(
946
+ `[apm] \u5F53\u524D\u7248\u672C ${current}\uFF0C\u6B63\u5728\u5B89\u88C5 ${CLI_PACKAGE_NAME}@${targetLabel} \u2026`
947
+ );
948
+ const install = runNpm(["install", "-g", `${CLI_PACKAGE_NAME}@latest`], {
949
+ stdio: "inherit"
1161
950
  });
1162
- if (!baseline.repositoryId) {
1163
- const detail = baseline.diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09
1164
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
1165
- throw new Error(`[apm] ${detail}`);
951
+ if (install.error) {
952
+ console.error("[apm] \u66F4\u65B0\u5931\u8D25:", install.error.message);
953
+ process.exit(1);
954
+ }
955
+ if (install.status !== 0) {
956
+ process.exit(install.status ?? 1);
1166
957
  }
1167
- const baselineBranch = (baseline.defaultBranch ?? "").trim();
1168
- if (!baselineBranch) {
1169
- throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
958
+ const after = readCliVersion();
959
+ if (latest && after === latest) {
960
+ console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
961
+ } else {
962
+ console.log(
963
+ `[apm] \u66F4\u65B0\u5B8C\u6210\u3002\u82E5\u7248\u672C\u53F7\u672A\u53D8\u5316\uFF0C\u8BF7\u5728\u65B0\u7EC8\u7AEF\u6267\u884C apm -V \u786E\u8BA4\uFF08\u5168\u5C40\u5B89\u88C5\u8DEF\u5F84\u53EF\u80FD\u672A\u5237\u65B0\uFF09`
964
+ );
1170
965
  }
1171
- const branch = branchNameForSession(trimmedSessionId);
1172
- return ensureFeatureBranch(branch, baselineBranch, options);
966
+ return { didUpdate: true };
1173
967
  }
1174
968
 
1175
- // src/commands/clean-branches.ts
1176
- import { execFile as execFile4 } from "child_process";
1177
- import { promisify as promisify4 } from "util";
1178
- var execFileAsync4 = promisify4(execFile4);
1179
- async function execGit3(cwd, args, quiet) {
1180
- try {
1181
- const { stdout, stderr } = await execFileAsync4("git", args, {
1182
- cwd,
1183
- encoding: "utf8",
1184
- maxBuffer: 10 * 1024 * 1024
1185
- });
1186
- if (!quiet && stderr.trim()) {
1187
- process.stderr.write(stderr);
1188
- }
1189
- return stdout;
1190
- } catch (err) {
1191
- const e = err;
1192
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1193
- throw new Error(
1194
- `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
1195
- );
1196
- }
1197
- }
1198
- async function ensureGitRepo2(cwd) {
1199
- await execGit3(cwd, ["rev-parse", "--git-dir"], true);
1200
- }
1201
- async function getCurrentBranch2(cwd) {
1202
- return (await execGit3(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1203
- }
1204
- async function resolveDefaultBranch(cwd) {
1205
- try {
1206
- const ref = (await execGit3(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], true)).trim();
1207
- const match = ref.match(/^refs\/remotes\/origin\/(.+)$/);
1208
- if (match?.[1]) {
1209
- return match[1];
1210
- }
1211
- } catch {
1212
- }
1213
- const out = await execGit3(cwd, ["remote", "show", "origin"], true);
1214
- const headLine = out.split(/\r?\n/).find((line) => line.includes("HEAD branch"));
1215
- const branch = headLine?.split(":").pop()?.trim();
1216
- if (branch) {
1217
- return branch;
1218
- }
1219
- throw new Error("[apm] \u65E0\u6CD5\u89E3\u6790 origin \u9ED8\u8BA4\u5206\u652F\uFF0C\u8BF7\u5148\u6267\u884C git fetch origin");
1220
- }
1221
- async function listLocalSessionBranches(cwd) {
1222
- const out = await execGit3(
1223
- cwd,
1224
- [
1225
- "for-each-ref",
1226
- "--format=%(refname:short)",
1227
- "refs/heads/",
1228
- SESSION_BRANCH_PREFIX + "*"
1229
- ],
1230
- true
1231
- );
1232
- return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1233
- }
1234
- async function listRemoteSessionBranches(cwd) {
1235
- const out = await execGit3(
1236
- cwd,
1237
- [
1238
- "for-each-ref",
1239
- "--format=%(refname:short)",
1240
- "refs/remotes/origin/",
1241
- SESSION_BRANCH_PREFIX + "*"
1242
- ],
1243
- true
1244
- );
1245
- return out.split(/\r?\n/).map((line) => line.trim().replace(/^origin\//, "")).filter(Boolean);
1246
- }
1247
- async function localBranchExists2(cwd, branch) {
1248
- try {
1249
- await execGit3(
1250
- cwd,
1251
- ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
1252
- true
1253
- );
1254
- return true;
1255
- } catch {
1256
- return false;
1257
- }
1258
- }
1259
- async function remoteBranchExists(cwd, branch) {
1260
- const out = await execGit3(
1261
- cwd,
1262
- ["ls-remote", "--heads", "origin", branch],
1263
- true
1264
- );
1265
- return out.trim().length > 0;
1266
- }
1267
- function reasonForCleanup(sessionId, sessionStatusById) {
1268
- if (!sessionStatusById.has(sessionId)) {
1269
- return "\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D";
1270
- }
1271
- if (sessionStatusById.get(sessionId) === "COMPLETED") {
1272
- return "\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210";
1273
- }
1274
- return "\u4FDD\u7559";
1275
- }
1276
- async function isBranchMergedIntoDefault(cwd, branch, defaultBranch) {
1277
- const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
1278
- try {
1279
- await execGit3(
1280
- cwd,
1281
- ["merge-base", "--is-ancestor", ref, `origin/${defaultBranch}`],
1282
- true
1283
- );
1284
- return true;
1285
- } catch {
1286
- return false;
1287
- }
1288
- }
1289
- async function runCleanBranches(options = {}) {
1290
- const cwd = options.cwd ?? process.cwd();
1291
- const dryRun = options.dryRun ?? false;
1292
- await ensureGitRepo2(cwd);
1293
- await execGit3(cwd, ["fetch", "--prune", "origin"], true);
1294
- const cfg = await ensureLoggedConfig();
1295
- const api = createApmApiClient(cfg);
1296
- const { sessions } = await api.cli.listSessionsForBranchCleanup({});
1297
- const sessionStatusById = new Map(
1298
- sessions.map((item) => [item.sessionId, item.taskStatus])
1299
- );
1300
- const branchNames = /* @__PURE__ */ new Set([
1301
- ...await listLocalSessionBranches(cwd),
1302
- ...await listRemoteSessionBranches(cwd)
1303
- ]);
1304
- if (branchNames.size === 0) {
1305
- console.log("[apm] \u672A\u53D1\u73B0 feat/session-* \u5206\u652F");
1306
- return;
1307
- }
1308
- const toDelete = [...branchNames].map((branch) => {
1309
- const sessionId = sessionIdFromBranchName(branch);
1310
- if (!sessionId) {
1311
- return null;
1312
- }
1313
- const reason = reasonForCleanup(sessionId, sessionStatusById);
1314
- if (reason === "\u4FDD\u7559") {
1315
- return null;
1316
- }
1317
- return { branch, sessionId, reason };
1318
- }).filter((item) => item != null).sort((a, b) => a.branch.localeCompare(b.branch));
1319
- if (toDelete.length === 0) {
1320
- console.log("[apm] \u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684 feat/session-* \u5206\u652F");
1321
- return;
1322
- }
1323
- let currentBranch = await getCurrentBranch2(cwd);
1324
- let defaultBranch = null;
1325
- if (dryRun) {
1326
- defaultBranch = await resolveDefaultBranch(cwd);
1327
- }
1328
- for (const item of toDelete) {
1329
- const { branch, sessionId, reason } = item;
1330
- const label = `${branch} (${sessionId}: ${reason})`;
1331
- if (dryRun) {
1332
- const merged = await isBranchMergedIntoDefault(
1333
- cwd,
1334
- branch,
1335
- defaultBranch
1336
- );
1337
- const mergeTag = merged ? "\u5DF2\u5408\u5E76" : "\u672A\u5408\u5E76";
1338
- console.log(`[apm] [dry-run] \u5C06\u5220\u9664 ${label} [${mergeTag}]`);
1339
- continue;
1340
- }
1341
- if (currentBranch === branch) {
1342
- defaultBranch ??= await resolveDefaultBranch(cwd);
1343
- await execGit3(cwd, ["checkout", defaultBranch], true);
1344
- currentBranch = defaultBranch;
1345
- }
1346
- if (await localBranchExists2(cwd, branch)) {
1347
- await execGit3(cwd, ["branch", "-D", branch], true);
1348
- console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
1349
- }
1350
- if (await remoteBranchExists(cwd, branch)) {
1351
- await execGit3(cwd, ["push", "origin", "--delete", branch], true);
1352
- console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
1353
- }
1354
- }
1355
- if (dryRun) {
1356
- console.log(`[apm] [dry-run] \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`);
1357
- } else {
1358
- console.log(`[apm] \u5DF2\u6E05\u7406 ${toDelete.length} \u4E2A feat/session-* \u5206\u652F`);
1359
- }
1360
- }
1361
-
1362
- // src/commands/pull.ts
1363
- import { writeFileSync as writeFileSync9 } from "fs";
1364
- import { join as join9 } from "path";
1365
- import { stringify as yamlStringify } from "yaml";
1366
-
1367
- // src/session-messages-xml.ts
1368
- function asXmlText(value) {
1369
- return value ?? "";
1370
- }
1371
- function escapeXmlAttr(value) {
1372
- return asXmlText(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1373
- }
1374
- function wrapCdata(value) {
1375
- return `<![CDATA[${asXmlText(value).replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
1376
- }
1377
- function formatSessionMessagesXml(sessionId, messages) {
1378
- const lines = [
1379
- '<?xml version="1.0" encoding="UTF-8"?>',
1380
- `<messages sessionId="${escapeXmlAttr(sessionId)}">`
1381
- ];
1382
- for (const message of messages) {
1383
- const roundAttr = message.round != null && message.round > 0 ? ` round="${message.round}"` : "";
1384
- lines.push(
1385
- ` <message id="${escapeXmlAttr(message.id)}" name="${escapeXmlAttr(
1386
- message.name
1387
- )}" agent="${escapeXmlAttr(message.oxcAgent)}"${roundAttr}>`,
1388
- ` <content>${wrapCdata(message.content)}</content>`,
1389
- " </message>"
1390
- );
1391
- }
1392
- lines.push("</messages>", "");
1393
- return lines.join("\n");
1394
- }
1395
-
1396
- // src/commands/sync-session-attachments.ts
1397
- import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
1398
- import { join as join6 } from "path";
1399
- var MANIFEST_FILE2 = ".sync-manifest.json";
1400
- async function downloadAttachment(cfg, attachmentId) {
1401
- const base = cfg.baseUrl.trim().replace(/\/+$/, "");
1402
- const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({ attachmentId })}`;
1403
- const res = await fetch(url);
1404
- if (!res.ok) {
1405
- throw new Error(
1406
- `[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
1407
- );
1408
- }
1409
- return Buffer.from(await res.arrayBuffer());
1410
- }
1411
- function loadManifest(dir) {
1412
- const path10 = join6(dir, MANIFEST_FILE2);
1413
- if (!existsSync4(path10)) {
1414
- return { version: 1, attachments: {} };
1415
- }
1416
- try {
1417
- const parsed = JSON.parse(
1418
- readFileSync5(path10, "utf8")
1419
- );
1420
- if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
1421
- return parsed;
1422
- }
1423
- } catch {
1424
- }
1425
- return { version: 1, attachments: {} };
1426
- }
1427
- function saveManifest(dir, manifest) {
1428
- writeFileSync6(
1429
- join6(dir, MANIFEST_FILE2),
1430
- `${JSON.stringify(manifest, null, 2)}
1431
- `,
1432
- "utf8"
1433
- );
1434
- }
1435
- function isAttachmentUpToDate(entry, item, dest) {
1436
- if (!entry || !existsSync4(dest)) return false;
1437
- if (entry.name !== item.name) return false;
1438
- const createdAt = item.createdAt ?? "";
1439
- return entry.createdAt === createdAt;
1440
- }
1441
- async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
1442
- const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
1443
- await ensureDirExists(dir);
1444
- if (attachments.length === 0) {
1445
- saveManifest(dir, { version: 1, attachments: {} });
1446
- return;
1447
- }
1448
- const manifest = loadManifest(dir);
1449
- const nextManifest = { version: 1, attachments: {} };
1450
- for (const item of attachments) {
1451
- const dest = join6(dir, item.name);
1452
- const entry = manifest.attachments[item.id];
1453
- const createdAt = item.createdAt ?? "";
1454
- if (isAttachmentUpToDate(entry, item, dest)) {
1455
- nextManifest.attachments[item.id] = entry;
1456
- console.log(
1457
- `[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
1458
- );
1459
- continue;
1460
- }
1461
- const buffer = await downloadAttachment(cfg, item.id);
1462
- writeFileSync6(dest, buffer);
1463
- nextManifest.attachments[item.id] = {
1464
- name: item.name,
1465
- createdAt
1466
- };
1467
- console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`);
1468
- }
1469
- saveManifest(dir, nextManifest);
1470
- }
1471
-
1472
- // src/rules-sync.ts
1473
- import { basename as basename2, extname as extname2, join as join8 } from "path";
1474
- import { existsSync as existsSync6, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
969
+ // src/commands/update-skills.ts
970
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
971
+ import { join as join8 } from "path";
1475
972
 
1476
973
  // src/skills-sync.ts
1477
974
  import {
1478
975
  copyFileSync as copyFileSync2,
1479
976
  cpSync,
1480
- existsSync as existsSync5,
977
+ existsSync as existsSync4,
1481
978
  mkdirSync as mkdirSync3,
1482
979
  readdirSync as readdirSync3,
1483
980
  rmSync as rmSync2,
1484
981
  statSync as statSync2,
1485
- writeFileSync as writeFileSync7
982
+ writeFileSync as writeFileSync6
1486
983
  } from "fs";
1487
984
  import { join as join7 } from "path";
1488
985
  var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
@@ -1494,20 +991,20 @@ function sanitizeSkillDirName(name) {
1494
991
  return trimmed.replace(/[/\\:*?"<>|]/g, "_");
1495
992
  }
1496
993
  function listBaseSkillDirNames() {
1497
- if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
994
+ if (!existsSync4(BASE_SKILLS_TEMPLATE_DIR)) return [];
1498
995
  return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
1499
996
  const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
1500
997
  return statSync2(path10).isDirectory();
1501
998
  });
1502
999
  }
1503
1000
  function syncAgentsGuide(apmDir) {
1504
- if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
1001
+ if (!existsSync4(AGENTS_TEMPLATE_PATH)) return false;
1505
1002
  mkdirSync3(apmDir, { recursive: true });
1506
1003
  copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
1507
1004
  return true;
1508
1005
  }
1509
1006
  function listBaseRuleFileNames() {
1510
- if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
1007
+ if (!existsSync4(BASE_RULES_TEMPLATE_DIR)) return [];
1511
1008
  return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
1512
1009
  const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
1513
1010
  return statSync2(path10).isFile();
@@ -1547,11 +1044,11 @@ function syncSupplementarySkills(skillsDir, list) {
1547
1044
  }
1548
1045
  const skillDir = join7(skillsDir, dirName);
1549
1046
  mkdirSync3(skillDir, { recursive: true });
1550
- writeFileSync7(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
1047
+ writeFileSync6(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
1551
1048
  written.push(dirName);
1552
1049
  }
1553
1050
  const removed = [];
1554
- if (!existsSync5(skillsDir)) return { written, skipped, removed };
1051
+ if (!existsSync4(skillsDir)) return { written, skipped, removed };
1555
1052
  for (const entry of readdirSync3(skillsDir)) {
1556
1053
  const full = join7(skillsDir, entry);
1557
1054
  if (!statSync2(full).isDirectory()) continue;
@@ -1563,263 +1060,11 @@ function syncSupplementarySkills(skillsDir, list) {
1563
1060
  return { written, skipped, removed };
1564
1061
  }
1565
1062
 
1566
- // src/rules-sync.ts
1567
- var MANIFEST_FILE3 = ".rules-sync-manifest.json";
1568
- function ruleLocalFileName(ruleName) {
1569
- const trimmed = ruleName.trim();
1570
- if (!trimmed) return "rule.md";
1571
- const sanitized = trimmed.replace(/[/\\:*?"<>|]/g, "_");
1572
- if (extname2(sanitized).toLowerCase() === ".md") return sanitized;
1573
- return `${sanitized}.md`;
1574
- }
1575
- function loadManifest2(rulesDir) {
1576
- const path10 = join8(rulesDir, MANIFEST_FILE3);
1577
- if (!existsSync6(toFsPath(path10))) {
1578
- return { version: 1, rules: {} };
1579
- }
1580
- try {
1581
- const parsed = JSON.parse(
1582
- readFileSync6(toFsPath(path10), "utf8")
1583
- );
1584
- if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
1585
- return parsed;
1586
- }
1587
- } catch {
1588
- }
1589
- return { version: 1, rules: {} };
1590
- }
1591
- function saveManifest2(rulesDir, manifest) {
1592
- writeFileSync8(
1593
- toFsPath(join8(rulesDir, MANIFEST_FILE3)),
1594
- `${JSON.stringify(manifest, null, 2)}
1595
- `,
1596
- "utf8"
1597
- );
1598
- }
1599
- function isBaseRuleFileName(fileName) {
1600
- return listBaseRuleFileNames().includes(basename2(fileName));
1601
- }
1602
- function isRuleUpToDate(entry, rule, dest) {
1603
- if (!entry || !existsSync6(toFsPath(dest))) return false;
1604
- if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
1605
- const updatedAt = rule.updatedAt ?? "";
1606
- if (entry.updatedAt !== updatedAt) return false;
1607
- const localContent = readFileSync6(toFsPath(dest), "utf8");
1608
- return localContent === (rule.content ?? "");
1609
- }
1610
- async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
1611
- const api = createApmApiClient(cfg);
1612
- const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
1613
- const repositoryId = baseline.repositoryId;
1614
- const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
1615
- await ensureDirExists(rulesDir);
1616
- if (!repositoryId) {
1617
- console.log(
1618
- `[apm] \u672A\u5339\u914D\u5230\u7ED1\u5B9A\u4ED3\u5E93\u7684\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u8DF3\u8FC7\u5E73\u53F0\u89C4\u5219\u540C\u6B65\uFF08\u8DEF\u5F84\uFF1A${workdirPath}\uFF09`
1619
- );
1620
- return { written: [], skipped: [], removed: [], repositoryId: null };
1621
- }
1622
- const { list } = await api.cli.listRules({ repositoryId });
1623
- const manifest = loadManifest2(rulesDir);
1624
- const nextManifest = { version: 1, rules: {} };
1625
- const remoteIds = /* @__PURE__ */ new Set();
1626
- const written = [];
1627
- const skipped = [];
1628
- for (const rule of list) {
1629
- remoteIds.add(rule.id);
1630
- const fileName = ruleLocalFileName(rule.name);
1631
- const dest = join8(rulesDir, fileName);
1632
- const entry = manifest.rules[rule.id];
1633
- const updatedAt = rule.updatedAt ?? "";
1634
- if (isRuleUpToDate(entry, rule, dest)) {
1635
- nextManifest.rules[rule.id] = entry;
1636
- skipped.push(fileName);
1637
- console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
1638
- continue;
1639
- }
1640
- writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
1641
- nextManifest.rules[rule.id] = { fileName, updatedAt };
1642
- written.push(fileName);
1643
- console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
1644
- }
1645
- const removed = [];
1646
- for (const [ruleId, entry] of Object.entries(manifest.rules)) {
1647
- if (remoteIds.has(ruleId)) continue;
1648
- if (isBaseRuleFileName(entry.fileName)) continue;
1649
- const dest = join8(rulesDir, entry.fileName);
1650
- if (existsSync6(toFsPath(dest))) {
1651
- rmSync3(toFsPath(dest), { force: true });
1652
- }
1653
- removed.push(entry.fileName);
1654
- console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
1655
- }
1656
- saveManifest2(rulesDir, nextManifest);
1657
- return { written, skipped, removed, repositoryId };
1658
- }
1659
-
1660
- // src/commands/pull.ts
1661
- async function runPull(sessionId, remoteWorkdir) {
1662
- const trimmedId = sessionId.trim();
1663
- if (!trimmedId) {
1664
- console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1665
- process.exit(1);
1666
- }
1667
- const cfg = await ensureLoggedConfig();
1668
- const api = createApmApiClient(cfg);
1669
- const workdir = remoteWorkdir === void 0 ? resolveWorkdirPath() : requireRemoteWorkdir(remoteWorkdir);
1670
- const apmRoot = workspaceApmDir(workdir);
1671
- const [detail, members, documents, attachments, messages] = await Promise.all(
1672
- [
1673
- api.cli.sessionDetail({ sessionId: trimmedId }),
1674
- api.cli.sessionMembers({ sessionId: trimmedId }),
1675
- api.cli.listDocuments({ sessionId: trimmedId }),
1676
- api.cli.listAttachments({ sessionId: trimmedId }),
1677
- api.cli.listSessionMessages({ sessionId: trimmedId })
1678
- ]
1679
- );
1680
- const dir = sessionDir(trimmedId, apmRoot);
1681
- const docsDir = sessionDocsDir(trimmedId, apmRoot);
1682
- await ensureDirExists(docsDir);
1683
- writeFileSync9(
1684
- sessionRulePath(trimmedId, apmRoot),
1685
- detail.description ?? "",
1686
- "utf8"
1687
- );
1688
- writeFileSync9(
1689
- sessionTaskPath(trimmedId, apmRoot),
1690
- detail.task.description ?? "",
1691
- "utf8"
1692
- );
1693
- writeFileSync9(sessionTodoPath(trimmedId, apmRoot), detail.todo ?? "", "utf8");
1694
- for (const doc of documents) {
1695
- const fileName = documentLocalFileName(doc.name);
1696
- writeFileSync9(join9(docsDir, fileName), doc.content ?? "", "utf8");
1697
- }
1698
- const sessionYaml = yamlStringify(
1699
- {
1700
- name: detail.title,
1701
- task: "./TASK.md",
1702
- todo: "./TODO.md",
1703
- rule: "./RULE.md",
1704
- members: members.map((m) => ({
1705
- name: m.displayName,
1706
- oxcAgent: m.oxcAgent,
1707
- description: m.description ?? ""
1708
- })),
1709
- attachments: attachments.map((a) => ({ name: a.name }))
1710
- },
1711
- { lineWidth: 0 }
1712
- );
1713
- writeFileSync9(
1714
- sessionYamlPath(trimmedId, apmRoot),
1715
- sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
1716
- `,
1717
- "utf8"
1718
- );
1719
- writeFileSync9(
1720
- sessionMessagesXmlPath(trimmedId, apmRoot),
1721
- formatSessionMessagesXml(trimmedId, messages),
1722
- "utf8"
1723
- );
1724
- await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
1725
- await syncPlatformRules(cfg, trimmedId, workdir, apmRoot);
1726
- await syncRemoteDeploymentConfig(workdir, apmRoot);
1727
- await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
1728
- console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
1729
- return dir;
1730
- }
1731
-
1732
- // src/commands/update.ts
1733
- import { spawnSync } from "child_process";
1734
-
1735
- // src/version.ts
1736
- import { readFileSync as readFileSync7 } from "fs";
1737
- import { dirname as dirname3, join as join10 } from "path";
1738
- import { fileURLToPath as fileURLToPath2 } from "url";
1739
- var CLI_PACKAGE_NAME = "ai-project-manage-cli";
1740
- function readCliVersion() {
1741
- try {
1742
- const dir = dirname3(fileURLToPath2(import.meta.url));
1743
- const pkgPath = join10(dir, "..", "package.json");
1744
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
1745
- return pkg.version ?? "0.0.0";
1746
- } catch {
1747
- return "0.0.0";
1748
- }
1749
- }
1750
-
1751
- // src/commands/update.ts
1752
- var useNpmShell = process.platform === "win32";
1753
- function runNpm(args, options = {}) {
1754
- return spawnSync(useNpmShell ? "npm.cmd" : "npm", args, {
1755
- ...options,
1756
- shell: useNpmShell
1757
- });
1758
- }
1759
- function registryBaseUrl() {
1760
- const fromEnv = process.env.npm_config_registry?.trim() || process.env.NPM_CONFIG_REGISTRY?.trim();
1761
- return (fromEnv || "https://registry.npmjs.org").replace(/\/+$/, "");
1762
- }
1763
- async function fetchLatestPublishedVersion() {
1764
- const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
1765
- try {
1766
- const res = await fetch(url);
1767
- if (!res.ok) return null;
1768
- const data = await res.json();
1769
- return data.version?.trim() || null;
1770
- } catch {
1771
- return null;
1772
- }
1773
- }
1774
- function npmAvailable() {
1775
- const r = runNpm(["--version"], { encoding: "utf8" });
1776
- return !r.error && r.status === 0;
1777
- }
1778
- async function runUpdate() {
1779
- const current = readCliVersion();
1780
- const latest = await fetchLatestPublishedVersion();
1781
- if (latest && current === latest) {
1782
- console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
1783
- return { didUpdate: false };
1784
- }
1785
- if (!npmAvailable()) {
1786
- console.error(
1787
- `[apm] \u672A\u627E\u5230 npm\u3002\u8BF7\u5B89\u88C5 Node.js \u540E\u6267\u884C\uFF1Anpm install -g ${CLI_PACKAGE_NAME}@latest`
1788
- );
1789
- process.exit(1);
1790
- }
1791
- const targetLabel = latest ?? "latest";
1792
- console.error(
1793
- `[apm] \u5F53\u524D\u7248\u672C ${current}\uFF0C\u6B63\u5728\u5B89\u88C5 ${CLI_PACKAGE_NAME}@${targetLabel} \u2026`
1794
- );
1795
- const install = runNpm(["install", "-g", `${CLI_PACKAGE_NAME}@latest`], {
1796
- stdio: "inherit"
1797
- });
1798
- if (install.error) {
1799
- console.error("[apm] \u66F4\u65B0\u5931\u8D25:", install.error.message);
1800
- process.exit(1);
1801
- }
1802
- if (install.status !== 0) {
1803
- process.exit(install.status ?? 1);
1804
- }
1805
- const after = readCliVersion();
1806
- if (latest && after === latest) {
1807
- console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
1808
- } else {
1809
- console.log(
1810
- `[apm] \u66F4\u65B0\u5B8C\u6210\u3002\u82E5\u7248\u672C\u53F7\u672A\u53D8\u5316\uFF0C\u8BF7\u5728\u65B0\u7EC8\u7AEF\u6267\u884C apm -V \u786E\u8BA4\uFF08\u5168\u5C40\u5B89\u88C5\u8DEF\u5F84\u53EF\u80FD\u672A\u5237\u65B0\uFF09`
1811
- );
1812
- }
1813
- return { didUpdate: true };
1814
- }
1815
-
1816
1063
  // src/commands/update-skills.ts
1817
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
1818
- import { join as join11 } from "path";
1819
1064
  async function syncWorkspaceSkills(cfg, workdir) {
1820
1065
  const apmDir = workspaceApmDir(workdir);
1821
1066
  const fsApmDir = toFsPath(apmDir);
1822
- if (!existsSync7(fsApmDir)) {
1067
+ if (!existsSync5(fsApmDir)) {
1823
1068
  throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1824
1069
  }
1825
1070
  const apmStat = statSync3(fsApmDir);
@@ -1831,12 +1076,12 @@ async function syncWorkspaceSkills(cfg, workdir) {
1831
1076
  if (syncAgentsGuide(apmDir)) {
1832
1077
  console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
1833
1078
  }
1834
- const rulesDir = join11(apmDir, "rules");
1079
+ const rulesDir = join8(apmDir, "rules");
1835
1080
  const ruleNames = syncBaseRules(rulesDir);
1836
1081
  for (const name of ruleNames) {
1837
1082
  console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
1838
1083
  }
1839
- const skillsDir = join11(apmDir, "skills");
1084
+ const skillsDir = join8(apmDir, "skills");
1840
1085
  mkdirSync4(toFsPath(skillsDir), { recursive: true });
1841
1086
  const baseNames = syncBaseSkills(skillsDir);
1842
1087
  for (const name of baseNames) {
@@ -1863,7 +1108,7 @@ async function syncWorkspaceSkills(cfg, workdir) {
1863
1108
  }
1864
1109
  async function runUpdateSkills() {
1865
1110
  const apmDir = workspaceApmDir();
1866
- if (!existsSync7(apmDir)) {
1111
+ if (!existsSync5(apmDir)) {
1867
1112
  console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1868
1113
  process.exit(1);
1869
1114
  }
@@ -1875,197 +1120,6 @@ async function runUpdateSkills() {
1875
1120
  await syncWorkspaceSkills(cfg, resolveWorkdirPath());
1876
1121
  }
1877
1122
 
1878
- // src/commands/sync-deploy-config.ts
1879
- import { existsSync as existsSync8, statSync as statSync4 } from "fs";
1880
- async function runSyncDeployConfig() {
1881
- const workdir = resolveWorkdirPath();
1882
- const apmDir = workspaceApmDir(workdir);
1883
- if (!existsSync8(apmDir)) {
1884
- console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1885
- process.exit(1);
1886
- }
1887
- const apmStat = statSync4(apmDir);
1888
- if (!apmStat.isDirectory()) {
1889
- throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1890
- }
1891
- await ensureLoggedConfig();
1892
- const result = await syncRemoteDeploymentConfig(workdir, apmDir);
1893
- if (!result.synced) {
1894
- process.exit(1);
1895
- }
1896
- }
1897
-
1898
- // src/commands/sync-project-documents.ts
1899
- async function runSyncProjectDocuments(options) {
1900
- const pull = options?.pull ?? !options?.push;
1901
- const push = options?.push ?? false;
1902
- const workdir = resolveWorkdirPath();
1903
- const apmRoot = workspaceApmDir(workdir);
1904
- if (pull) {
1905
- await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
1906
- }
1907
- if (push) {
1908
- const cfg = await ensureLoggedConfig();
1909
- await syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot);
1910
- }
1911
- }
1912
-
1913
- // src/commands/sync-document.ts
1914
- import { existsSync as existsSync10 } from "fs";
1915
- import { basename as basename3 } from "path";
1916
-
1917
- // src/commands/sync-session-documents.ts
1918
- import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
1919
- import { join as join12 } from "path";
1920
- function listLocalMarkdownFiles(docsDir) {
1921
- if (!existsSync9(docsDir)) {
1922
- return [];
1923
- }
1924
- return readdirSync4(docsDir).filter(
1925
- (name) => name.toLowerCase().endsWith(".md")
1926
- );
1927
- }
1928
- function remoteDocumentByLocalName(remoteDocuments, localFileName) {
1929
- const platformName = documentPlatformName(localFileName);
1930
- return remoteDocuments.find((doc) => {
1931
- const remoteLocalName = documentLocalFileName(doc.name);
1932
- return remoteLocalName === localFileName || documentPlatformName(doc.name) === platformName;
1933
- });
1934
- }
1935
- async function upsertLocalDocumentFile(api, sessionId, docsDir, fileName) {
1936
- const absPath = join12(docsDir, fileName);
1937
- const content = readFileSync8(absPath, "utf8");
1938
- const name = documentPlatformName(absPath);
1939
- return api.cli.upsertDocument({
1940
- sessionId,
1941
- name,
1942
- content
1943
- });
1944
- }
1945
- async function syncSessionDocuments(cfg, sessionId, apmRoot, options) {
1946
- const trimmedSessionId = sessionId.trim();
1947
- if (!trimmedSessionId) {
1948
- return 0;
1949
- }
1950
- const docsDir = sessionDocsDir(trimmedSessionId, apmRoot);
1951
- const localFiles = listLocalMarkdownFiles(docsDir);
1952
- if (localFiles.length === 0) {
1953
- return 0;
1954
- }
1955
- const api = options?.api ?? createApmApiClient(cfg);
1956
- const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ sessionId: trimmedSessionId });
1957
- let synced = 0;
1958
- for (const fileName of localFiles) {
1959
- const absPath = join12(docsDir, fileName);
1960
- const content = readFileSync8(absPath, "utf8");
1961
- const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
1962
- if (remote && remote.content === content) {
1963
- continue;
1964
- }
1965
- const doc = await upsertLocalDocumentFile(
1966
- api,
1967
- trimmedSessionId,
1968
- docsDir,
1969
- fileName
1970
- );
1971
- synced += 1;
1972
- console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
1973
- }
1974
- if (synced === 0) {
1975
- console.log("[apm] \u4F1A\u8BDD\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
1976
- }
1977
- return synced;
1978
- }
1979
-
1980
- // src/commands/sync-document.ts
1981
- async function runSyncDocument(sessionId, options) {
1982
- const trimmedSessionId = sessionId.trim();
1983
- if (!trimmedSessionId) {
1984
- console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1985
- process.exit(1);
1986
- }
1987
- const fileArg = options.file?.trim();
1988
- if (!fileArg) {
1989
- console.error("[apm] \u8BF7\u6307\u5B9A --file <\u6587\u6863\u540D\u79F0>");
1990
- process.exit(1);
1991
- }
1992
- const absPath = resolveSessionDocumentPath(trimmedSessionId, fileArg);
1993
- if (!existsSync10(absPath)) {
1994
- const docsDir2 = sessionDocsDir(trimmedSessionId);
1995
- console.error(
1996
- `[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
1997
- [apm] \u8BF7\u786E\u8BA4\u5DF2 pull\uFF0C\u4E14 ${docsDir2} \u4E0B\u5B58\u5728\u5BF9\u5E94\u6587\u4EF6`
1998
- );
1999
- process.exit(1);
2000
- }
2001
- const cfg = await ensureLoggedConfig();
2002
- const api = createApmApiClient(cfg);
2003
- const docsDir = sessionDocsDir(trimmedSessionId);
2004
- const doc = await upsertLocalDocumentFile(
2005
- api,
2006
- trimmedSessionId,
2007
- docsDir,
2008
- basename3(absPath)
2009
- );
2010
- console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
2011
- }
2012
-
2013
- // src/commands/append-message.ts
2014
- async function appendMessageContent(cfg, messageId, content) {
2015
- const trimmedId = messageId.trim();
2016
- if (!trimmedId) {
2017
- throw new Error("messageId \u4E0D\u80FD\u4E3A\u7A7A");
2018
- }
2019
- if (!content) {
2020
- throw new Error("content \u4E0D\u80FD\u4E3A\u7A7A");
2021
- }
2022
- const api = createApmApiClient(cfg);
2023
- await api.cli.appendMessageContent({ id: trimmedId, content });
2024
- }
2025
- async function runAppendMessage(options) {
2026
- const messageId = options.id?.trim();
2027
- if (!messageId) {
2028
- console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
2029
- process.exit(1);
2030
- }
2031
- const content = options.content ?? "";
2032
- if (!content) {
2033
- console.error("[apm] \u8BF7\u6307\u5B9A --content <\u5185\u5BB9>");
2034
- process.exit(1);
2035
- }
2036
- const cfg = await ensureLoggedConfig();
2037
- await appendMessageContent(cfg, messageId, content);
2038
- console.log(`[apm] \u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9: ${messageId}`);
2039
- }
2040
-
2041
- // src/commands/update-message-status.ts
2042
- var VALID_STATUSES = [
2043
- "CREATED",
2044
- "QUEUED",
2045
- "TYPING",
2046
- "SUCCESS",
2047
- "FAILED",
2048
- "CANCELLED"
2049
- ];
2050
- async function runUpdateMessageStatus(options) {
2051
- const messageId = options.id?.trim();
2052
- const status = options.status?.trim().toUpperCase();
2053
- if (!messageId) {
2054
- console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
2055
- process.exit(1);
2056
- }
2057
- if (!VALID_STATUSES.includes(status)) {
2058
- console.error(
2059
- `[apm] \u65E0\u6548\u72B6\u6001: ${options.status}\uFF0C\u53EF\u9009: ${VALID_STATUSES.join(", ")}`
2060
- );
2061
- process.exit(1);
2062
- }
2063
- const cfg = await ensureLoggedConfig();
2064
- const api = createApmApiClient(cfg);
2065
- await api.cli.updateMessageStatus({ id: messageId, status });
2066
- console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
2067
- }
2068
-
2069
1123
  // src/commands/connect.ts
2070
1124
  import { spawnSync as spawnSync2 } from "child_process";
2071
1125
  import WebSocket from "ws";
@@ -2093,57 +1147,6 @@ function validateHeartbeat(o) {
2093
1147
  }
2094
1148
  return { ok: true, data: { type: "heartbeat", userId: o.userId.trim() } };
2095
1149
  }
2096
- function validateMessagePush(o) {
2097
- if (o.type !== "message") {
2098
- return { ok: false, reason: "\u671F\u671B message" };
2099
- }
2100
- if (!nonEmptyString(o.messageId)) {
2101
- return { ok: false, reason: "message \u7F3A\u5C11 messageId" };
2102
- }
2103
- if (!nonEmptyString(o.sessionId)) {
2104
- return { ok: false, reason: "message \u7F3A\u5C11 sessionId" };
2105
- }
2106
- if (!nonEmptyString(o.content)) {
2107
- return { ok: false, reason: "message \u7F3A\u5C11 content" };
2108
- }
2109
- if (!nonEmptyString(o.model)) {
2110
- return { ok: false, reason: "message \u7F3A\u5C11 model" };
2111
- }
2112
- if (!nonEmptyString(o.apiKey)) {
2113
- return { ok: false, reason: "message \u7F3A\u5C11 apiKey" };
2114
- }
2115
- if (!nonEmptyString(o.workdir)) {
2116
- return { ok: false, reason: "message \u7F3A\u5C11 workdir" };
2117
- }
2118
- if (!nonEmptyString(o.user)) {
2119
- return { ok: false, reason: "message \u7F3A\u5C11 user" };
2120
- }
2121
- return {
2122
- ok: true,
2123
- data: {
2124
- type: "message",
2125
- messageId: o.messageId.trim(),
2126
- sessionId: o.sessionId.trim(),
2127
- content: o.content,
2128
- model: o.model.trim(),
2129
- apiKey: o.apiKey.trim(),
2130
- workdir: o.workdir.trim(),
2131
- user: o.user.trim()
2132
- }
2133
- };
2134
- }
2135
- function validateCancel(o) {
2136
- if (o.type !== "cancel") {
2137
- return { ok: false, reason: "\u671F\u671B cancel" };
2138
- }
2139
- if (!nonEmptyString(o.messageId)) {
2140
- return { ok: false, reason: "cancel \u7F3A\u5C11 messageId" };
2141
- }
2142
- return {
2143
- ok: true,
2144
- data: { type: "cancel", messageId: o.messageId.trim() }
2145
- };
2146
- }
2147
1150
  function validateDeployPush(o) {
2148
1151
  if (o.type !== "deploy") {
2149
1152
  return { ok: false, reason: "\u671F\u671B deploy" };
@@ -2168,103 +1171,26 @@ function validateDeployPush(o) {
2168
1171
  }
2169
1172
  };
2170
1173
  }
2171
- function validateCoordinatorDispatchMode(mode) {
2172
- return mode === "plan" || mode === "agent";
2173
- }
2174
- function validateCoordinatorDispatchPush(o) {
2175
- if (o.type !== "coordinator_dispatch") {
2176
- return { ok: false, reason: "\u671F\u671B coordinator_dispatch" };
2177
- }
2178
- if (!nonEmptyString(o.dispatchId)) {
2179
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 dispatchId" };
2180
- }
2181
- if (!nonEmptyString(o.dispatchSessionId)) {
2182
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 dispatchSessionId" };
2183
- }
2184
- if (!validateCoordinatorDispatchMode(o.mode)) {
2185
- return { ok: false, reason: "coordinator_dispatch.mode \u65E0\u6548" };
2186
- }
2187
- if (!nonEmptyString(o.content)) {
2188
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 content" };
2189
- }
2190
- if (!nonEmptyString(o.model)) {
2191
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 model" };
2192
- }
2193
- if (!nonEmptyString(o.apiKey)) {
2194
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 apiKey" };
2195
- }
2196
- if (!nonEmptyString(o.workdir)) {
2197
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 workdir" };
2198
- }
2199
- if (!nonEmptyString(o.user)) {
2200
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 user" };
2201
- }
2202
- const data = {
2203
- type: "coordinator_dispatch",
2204
- dispatchId: o.dispatchId.trim(),
2205
- dispatchSessionId: o.dispatchSessionId.trim(),
2206
- mode: o.mode,
2207
- content: o.content,
2208
- model: o.model.trim(),
2209
- apiKey: o.apiKey.trim(),
2210
- workdir: o.workdir.trim(),
2211
- user: o.user.trim()
2212
- };
2213
- if (nonEmptyString(o.resumeAgentId)) {
2214
- data.resumeAgentId = o.resumeAgentId.trim();
2215
- }
2216
- return { ok: true, data };
2217
- }
2218
- function validateCoordinatorDispatchResumePush(o) {
2219
- if (o.type !== "coordinator_dispatch_resume") {
2220
- return { ok: false, reason: "\u671F\u671B coordinator_dispatch_resume" };
2221
- }
2222
- if (!nonEmptyString(o.dispatchId)) {
2223
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchId" };
2224
- }
2225
- if (!nonEmptyString(o.dispatchSessionId)) {
2226
- return {
2227
- ok: false,
2228
- reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchSessionId"
2229
- };
1174
+ function validateReceivedMailPush(o) {
1175
+ if (o.type !== "received_mail") {
1176
+ return { ok: false, reason: "\u671F\u671B received_mail" };
2230
1177
  }
2231
- if (!validateCoordinatorDispatchMode(o.mode)) {
2232
- return { ok: false, reason: "coordinator_dispatch_resume.mode \u65E0\u6548" };
1178
+ if (!nonEmptyString(o.id)) {
1179
+ return { ok: false, reason: "received_mail \u7F3A\u5C11 id" };
2233
1180
  }
2234
- if (!nonEmptyString(o.content)) {
2235
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 content" };
2236
- }
2237
- if (!nonEmptyString(o.resumeAgentId)) {
2238
- return {
2239
- ok: false,
2240
- reason: "coordinator_dispatch_resume \u7F3A\u5C11 resumeAgentId"
2241
- };
2242
- }
2243
- if (!nonEmptyString(o.model)) {
2244
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 model" };
2245
- }
2246
- if (!nonEmptyString(o.apiKey)) {
2247
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 apiKey" };
2248
- }
2249
- if (!nonEmptyString(o.workdir)) {
2250
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 workdir" };
1181
+ if (!nonEmptyString(o.taskId)) {
1182
+ return { ok: false, reason: "received_mail \u7F3A\u5C11 taskId" };
2251
1183
  }
2252
- if (!nonEmptyString(o.user)) {
2253
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 user" };
1184
+ if (!nonEmptyString(o.createdAt)) {
1185
+ return { ok: false, reason: "received_mail \u7F3A\u5C11 createdAt" };
2254
1186
  }
2255
1187
  return {
2256
1188
  ok: true,
2257
1189
  data: {
2258
- type: "coordinator_dispatch_resume",
2259
- dispatchId: o.dispatchId.trim(),
2260
- dispatchSessionId: o.dispatchSessionId.trim(),
2261
- mode: o.mode,
2262
- content: o.content,
2263
- resumeAgentId: o.resumeAgentId.trim(),
2264
- model: o.model.trim(),
2265
- apiKey: o.apiKey.trim(),
2266
- workdir: o.workdir.trim(),
2267
- user: o.user.trim()
1190
+ type: "received_mail",
1191
+ id: o.id.trim(),
1192
+ taskId: o.taskId.trim(),
1193
+ createdAt: o.createdAt.trim()
2268
1194
  }
2269
1195
  };
2270
1196
  }
@@ -2274,36 +1200,27 @@ function validateAgentWsMessage(value, kind) {
2274
1200
  }
2275
1201
  const o = value;
2276
1202
  const type = o.type;
2277
- if (type !== "heartbeat" && type !== "message" && type !== "cancel" && type !== "deploy" && type !== "coordinator_dispatch" && type !== "coordinator_dispatch_resume") {
1203
+ if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail") {
2278
1204
  return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
2279
1205
  }
2280
1206
  if (kind === "heartbeat" || type === "heartbeat") {
2281
1207
  return validateHeartbeat(o);
2282
1208
  }
2283
- if (type === "cancel") {
2284
- return validateCancel(o);
2285
- }
2286
1209
  if (type === "deploy") {
2287
1210
  return validateDeployPush(o);
2288
1211
  }
2289
- if (type === "coordinator_dispatch") {
2290
- return validateCoordinatorDispatchPush(o);
2291
- }
2292
- if (type === "coordinator_dispatch_resume") {
2293
- return validateCoordinatorDispatchResumePush(o);
2294
- }
2295
- return validateMessagePush(o);
1212
+ return validateReceivedMailPush(o);
2296
1213
  }
2297
1214
 
2298
1215
  // src/commands/connect/deploy-run.ts
2299
1216
  import { spawn } from "node:child_process";
2300
- import { readFileSync as readFileSync9 } from "node:fs";
2301
- import { join as join13 } from "node:path";
1217
+ import { readFileSync as readFileSync6 } from "node:fs";
1218
+ import { join as join9 } from "node:path";
2302
1219
  var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
2303
1220
  function readDeployConfig(workdir) {
2304
- const configPath = join13(workspaceApmDir(workdir), "apm.config.json");
1221
+ const configPath = join9(workspaceApmDir(workdir), "apm.config.json");
2305
1222
  try {
2306
- const raw = readFileSync9(configPath, "utf8");
1223
+ const raw = readFileSync6(configPath, "utf8");
2307
1224
  const parsed = JSON.parse(raw);
2308
1225
  return parsed.deploy;
2309
1226
  } catch {
@@ -2378,7 +1295,7 @@ function createDeployLogSyncer(api, deploymentRunId) {
2378
1295
  if (!latestLog || latestLog === lastSyncedLog) {
2379
1296
  return;
2380
1297
  }
2381
- await api.cli.syncCoordinatorDeploymentLog({
1298
+ await api.cli.syncTaskDeploymentLog({
2382
1299
  id: deploymentRunId,
2383
1300
  log: latestLog
2384
1301
  });
@@ -2409,7 +1326,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
2409
1326
  const api = createApmApiClient(cfg);
2410
1327
  const deploymentRunId = msg.deploymentRunId;
2411
1328
  if (signal.aborted) return;
2412
- await api.cli.updateCoordinatorDeploymentStatus({
1329
+ await api.cli.updateTaskDeploymentStatus({
2413
1330
  id: deploymentRunId,
2414
1331
  status: "DEPLOYING"
2415
1332
  });
@@ -2418,7 +1335,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
2418
1335
  if (!command) {
2419
1336
  const error = missingDeployCommandMessage(msg.environment);
2420
1337
  console.error(`[apm] ${error}`);
2421
- await api.cli.completeCoordinatorDeployment({
1338
+ await api.cli.completeTaskDeployment({
2422
1339
  id: deploymentRunId,
2423
1340
  status: "FAILED",
2424
1341
  log: error,
@@ -2440,7 +1357,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
2440
1357
  latestLog = log;
2441
1358
  logSyncer.updateLog(log);
2442
1359
  await logSyncer.flush();
2443
- await api.cli.completeCoordinatorDeployment({
1360
+ await api.cli.completeTaskDeployment({
2444
1361
  id: deploymentRunId,
2445
1362
  status: "SUCCESS",
2446
1363
  log
@@ -2451,7 +1368,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
2451
1368
  const log = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
2452
1369
  logSyncer.updateLog(log);
2453
1370
  await logSyncer.flush();
2454
- await api.cli.completeCoordinatorDeployment({
1371
+ await api.cli.completeTaskDeployment({
2455
1372
  id: deploymentRunId,
2456
1373
  status: "FAILED",
2457
1374
  log,
@@ -2463,6 +1380,68 @@ async function handleInboundDeploy(cfg, msg, signal) {
2463
1380
  }
2464
1381
  }
2465
1382
 
1383
+ // src/commands/sync-task-documents.ts
1384
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
1385
+ import { join as join10 } from "path";
1386
+ function listLocalMarkdownFiles(docsDir) {
1387
+ if (!existsSync6(docsDir)) {
1388
+ return [];
1389
+ }
1390
+ return readdirSync4(docsDir).filter(
1391
+ (name) => name.toLowerCase().endsWith(".md")
1392
+ );
1393
+ }
1394
+ function remoteDocumentByLocalName(remoteDocuments, localFileName) {
1395
+ const platformName = documentPlatformName(localFileName);
1396
+ return remoteDocuments.find((doc) => {
1397
+ const remoteLocalName = documentLocalFileName(doc.name);
1398
+ return remoteLocalName === localFileName || documentPlatformName(doc.name) === platformName;
1399
+ });
1400
+ }
1401
+ async function upsertLocalDocumentFile(api, taskId, docsDir, fileName) {
1402
+ const content = readFileSync7(join10(docsDir, fileName), "utf8");
1403
+ const name = documentPlatformName(join10(docsDir, fileName));
1404
+ return api.cli.upsertDocument({
1405
+ taskId,
1406
+ name,
1407
+ content
1408
+ });
1409
+ }
1410
+ async function syncTaskDocuments(cfg, taskId, workdir, options) {
1411
+ const trimmedTaskId = taskId.trim();
1412
+ if (!trimmedTaskId) {
1413
+ return 0;
1414
+ }
1415
+ const apmRoot = workspaceApmDir(workdir);
1416
+ const docsDir = taskDocsDir(trimmedTaskId, apmRoot, workdir);
1417
+ const localFiles = listLocalMarkdownFiles(docsDir);
1418
+ if (localFiles.length === 0) {
1419
+ return 0;
1420
+ }
1421
+ const api = options?.api ?? createApmApiClient(cfg);
1422
+ const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ taskId: trimmedTaskId });
1423
+ let synced = 0;
1424
+ for (const fileName of localFiles) {
1425
+ const content = readFileSync7(join10(docsDir, fileName), "utf8");
1426
+ const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
1427
+ if (remote && remote.content === content) {
1428
+ continue;
1429
+ }
1430
+ const doc = await upsertLocalDocumentFile(
1431
+ api,
1432
+ trimmedTaskId,
1433
+ docsDir,
1434
+ fileName
1435
+ );
1436
+ synced += 1;
1437
+ console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
1438
+ }
1439
+ if (synced === 0) {
1440
+ console.log("[apm] \u4EFB\u52A1\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
1441
+ }
1442
+ return synced;
1443
+ }
1444
+
2466
1445
  // src/commands/connect/cursor-agent.ts
2467
1446
  import {
2468
1447
  Agent,
@@ -2470,28 +1449,87 @@ import {
2470
1449
  } from "@cursor/sdk";
2471
1450
  import { setMaxListeners as setMaxListeners2 } from "node:events";
2472
1451
 
2473
- // src/plan-format.ts
2474
- function formatPlanMarkdown(raw) {
2475
- let text = raw.trim();
2476
- if (!text) {
2477
- return text;
1452
+ // src/commands/connect/plan-document.ts
1453
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "node:fs";
1454
+ import { join as join11 } from "node:path";
1455
+ function normalizePlanContent(plan) {
1456
+ return plan.replace(/\\n/g, "\n").trim();
1457
+ }
1458
+ function extractPlanFromArgs(args) {
1459
+ if (args == null) {
1460
+ return void 0;
2478
1461
  }
2479
- if (text.startsWith("{") && text.endsWith("}")) {
1462
+ let parsed = args;
1463
+ if (typeof args === "string") {
1464
+ const trimmed = args.trim();
1465
+ if (!trimmed) {
1466
+ return void 0;
1467
+ }
2480
1468
  try {
2481
- const parsed = JSON.parse(text);
2482
- if (typeof parsed.plan === "string") {
2483
- return formatPlanMarkdown(parsed.plan);
2484
- }
1469
+ parsed = JSON.parse(trimmed);
2485
1470
  } catch {
1471
+ return normalizePlanContent(trimmed);
1472
+ }
1473
+ }
1474
+ if (typeof parsed === "object" && parsed !== null && "plan" in parsed) {
1475
+ const plan = parsed.plan;
1476
+ if (typeof plan === "string" && plan.trim()) {
1477
+ return normalizePlanContent(plan);
1478
+ }
1479
+ }
1480
+ return void 0;
1481
+ }
1482
+ function memberPlanFileName(displayName) {
1483
+ const sanitized = displayName.trim().replace(/[/\\:*?"<>|]/g, "-").replace(/\s+/g, " ").trim() || "member";
1484
+ return `${sanitized}-PLAN.md`;
1485
+ }
1486
+ function collectCreatePlanContent(events) {
1487
+ for (let i = events.length - 1; i >= 0; i--) {
1488
+ const event = events[i];
1489
+ if (event.type !== "tool_call") continue;
1490
+ if (event.name !== "createPlan") continue;
1491
+ if (event.status !== "completed") continue;
1492
+ const content = extractPlanFromArgs(event.args);
1493
+ if (content) {
1494
+ return content;
1495
+ }
1496
+ }
1497
+ return void 0;
1498
+ }
1499
+ function extractCreatePlanFromSessionEvents(events) {
1500
+ for (let i = events.length - 1; i >= 0; i--) {
1501
+ const event = events[i];
1502
+ if (event.type !== "tool_call") continue;
1503
+ if (event.name !== "createPlan") continue;
1504
+ if (event.status !== "completed") continue;
1505
+ const content = extractPlanFromArgs(event.args);
1506
+ if (content) {
1507
+ return content;
2486
1508
  }
2487
1509
  }
2488
- if (!text.includes("\n") && text.includes("\\n")) {
2489
- text = text.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
1510
+ return void 0;
1511
+ }
1512
+ function saveMemberPlanDocument(input) {
1513
+ const trimmedContent = input.content.trim();
1514
+ if (!trimmedContent) {
1515
+ throw new Error("\u8BA1\u5212\u5185\u5BB9\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u6587\u6863");
2490
1516
  }
2491
- return text.replace(/\r\n/g, "\n").trimEnd() + "\n";
1517
+ const fileName = memberPlanFileName(input.memberDisplayName);
1518
+ const docsDir = taskDocsDir(
1519
+ input.taskId,
1520
+ workspaceApmDir(input.workdir),
1521
+ input.workdir
1522
+ );
1523
+ mkdirSync5(toFsPath(docsDir), { recursive: true });
1524
+ const filePath = join11(docsDir, fileName);
1525
+ const normalized = trimmedContent.endsWith("\n") ? trimmedContent : `${trimmedContent}
1526
+ `;
1527
+ writeFileSync7(toFsPath(filePath), normalized, "utf8");
1528
+ console.log(`[apm] \u5DF2\u4FDD\u5B58\u8BA1\u5212\u6587\u6863: ${fileName}`);
1529
+ return fileName;
2492
1530
  }
2493
1531
 
2494
- // src/session-utils.ts
1532
+ // src/event-session.ts
2495
1533
  var EventSession = class {
2496
1534
  events = [];
2497
1535
  dirtyIndices = /* @__PURE__ */ new Set();
@@ -2538,10 +1576,10 @@ var EventSession = class {
2538
1576
  if (latestEvent?.type === formatedEvent.type) {
2539
1577
  switch (formatedEvent.type) {
2540
1578
  case "assistant":
2541
- latestEvent.content += formatedEvent.content;
1579
+ latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
2542
1580
  break;
2543
1581
  case "thinking":
2544
- latestEvent.content += formatedEvent.content;
1582
+ latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
2545
1583
  break;
2546
1584
  case "task":
2547
1585
  latestEvent.status = formatedEvent.status;
@@ -2612,72 +1650,49 @@ var EventSession = class {
2612
1650
  this.dirtyIndices.delete(index);
2613
1651
  }
2614
1652
  }
2615
- /** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
2616
1653
  getAssistantText() {
2617
1654
  return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
2618
1655
  }
2619
- /** plan 模式下 createPlan 工具 completed 时的 plan 字段(取最后一次) */
2620
1656
  getCreatePlanContent() {
2621
- for (let i = this.events.length - 1; i >= 0; i--) {
2622
- const event = this.events[i];
2623
- if (event.type !== "tool_call") {
2624
- continue;
2625
- }
2626
- if (event.name !== "createPlan" || event.status !== "completed") {
2627
- continue;
2628
- }
2629
- const plan = event.args?.plan;
2630
- if (typeof plan === "string" && plan.trim()) {
2631
- return formatPlanMarkdown(plan);
2632
- }
2633
- }
2634
- return void 0;
2635
- }
2636
- resolveLogContent() {
2637
- return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
1657
+ return extractCreatePlanFromSessionEvents(this.events);
2638
1658
  }
2639
1659
  };
2640
- function formatLogEvent(type, event) {
2641
- if (type === "input") {
2642
- return `## \u7528\u6237\u8F93\u5165
2643
-
2644
- ${String(event.content ?? "")}
2645
- `;
2646
- }
2647
- if (type === "assistant") {
2648
- return `## \u6A21\u578B\u8F93\u51FA
2649
1660
 
2650
- ${String(event.content ?? "")}
2651
- `;
2652
- }
2653
- if (type === "thinking") {
2654
- return `## \u6A21\u578B\u601D\u8003
2655
-
2656
- ${String(event.content ?? "")}
2657
- `;
2658
- }
2659
- if (type === "tool_call") {
2660
- return "````toolcall\n" + JSON.stringify(event, null, 2) + "\n````\n";
1661
+ // src/commands/connect/abort-signal-debug.ts
1662
+ import {
1663
+ getEventListeners,
1664
+ getMaxListeners,
1665
+ setMaxListeners
1666
+ } from "node:events";
1667
+ function isAbortSignalDebugEnabled() {
1668
+ const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
1669
+ return v === "1" || v === "true" || v === "yes";
1670
+ }
1671
+ function formatAbortSignalStats(signal, label) {
1672
+ if (!signal) {
1673
+ return `[apm:abort-debug] ${label}: (no signal)`;
2661
1674
  }
2662
- return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type}
2663
-
2664
- \`\`\`json
2665
- ${JSON.stringify(event, null, 2)}
2666
- \`\`\``;
1675
+ const listeners = getEventListeners(signal, "abort");
1676
+ const max = getMaxListeners(signal);
1677
+ return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
1678
+ }
1679
+ function logAbortSignalStats(signal, label) {
1680
+ if (!isAbortSignalDebugEnabled()) return;
1681
+ console.log(formatAbortSignalStats(signal, label));
2667
1682
  }
2668
1683
 
2669
- // src/commands/connect/agent-session-registry.ts
2670
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "node:fs";
1684
+ // src/commands/connect/agent-task-registry.ts
1685
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "node:fs";
2671
1686
  import { dirname as dirname4, resolve as resolve3 } from "node:path";
2672
- function registryPath(workdir, sessionId) {
2673
- return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
1687
+ function registryPath(workdir, taskId) {
1688
+ return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
2674
1689
  }
2675
1690
  function readRegistry(path10) {
2676
- if (!existsSync11(path10)) {
1691
+ if (!existsSync7(path10)) {
2677
1692
  return {};
2678
1693
  }
2679
1694
  try {
2680
- const parsed = JSON.parse(readFileSync10(path10, "utf8"));
1695
+ const parsed = JSON.parse(readFileSync8(path10, "utf8"));
2681
1696
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2682
1697
  const result = {};
2683
1698
  for (const [key, value] of Object.entries(
@@ -2694,22 +1709,21 @@ function readRegistry(path10) {
2694
1709
  return {};
2695
1710
  }
2696
1711
  function writeRegistry(path10, registry) {
2697
- mkdirSync5(dirname4(path10), { recursive: true });
2698
- writeFileSync10(path10, `${JSON.stringify(registry, null, 2)}
1712
+ mkdirSync6(dirname4(path10), { recursive: true });
1713
+ writeFileSync8(path10, `${JSON.stringify(registry, null, 2)}
2699
1714
  `, "utf8");
2700
1715
  }
2701
- function loadSessionAgentId(workdir, sessionId, user) {
2702
- const registry = readRegistry(registryPath(workdir, sessionId));
2703
- return registry[user];
1716
+ function loadTaskAgentId(workdir, taskId, user) {
1717
+ return readRegistry(registryPath(workdir, taskId))[user];
2704
1718
  }
2705
- function saveSessionAgentId(workdir, sessionId, user, agentId) {
2706
- const path10 = registryPath(workdir, sessionId);
1719
+ function saveTaskAgentId(workdir, taskId, user, agentId) {
1720
+ const path10 = registryPath(workdir, taskId);
2707
1721
  const registry = readRegistry(path10);
2708
1722
  registry[user] = agentId;
2709
1723
  writeRegistry(path10, registry);
2710
1724
  }
2711
- function clearSessionAgentId(workdir, sessionId, user) {
2712
- const path10 = registryPath(workdir, sessionId);
1725
+ function clearTaskAgentId(workdir, taskId, user) {
1726
+ const path10 = registryPath(workdir, taskId);
2713
1727
  const registry = readRegistry(path10);
2714
1728
  if (!(user in registry)) {
2715
1729
  return;
@@ -2718,9 +1732,9 @@ function clearSessionAgentId(workdir, sessionId, user) {
2718
1732
  writeRegistry(path10, registry);
2719
1733
  }
2720
1734
 
2721
- // src/commands/connect/cursor-message-log.ts
2722
- var CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS = 2e3;
2723
- function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
1735
+ // src/commands/connect/cursor-log.ts
1736
+ var CURSOR_LOG_SYNC_INTERVAL_MS = 2e3;
1737
+ function createThrottledCursorLogSync(cfg, ctx, onError) {
2724
1738
  let lastRunAt = 0;
2725
1739
  let timer;
2726
1740
  let latestSession;
@@ -2732,7 +1746,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2732
1746
  }
2733
1747
  lastRunAt = Date.now();
2734
1748
  try {
2735
- await syncCursorMessageLog(cfg, ctx, events);
1749
+ await syncCursorLog(cfg, ctx, events);
2736
1750
  session.clearDirty(events.map((event) => event.index));
2737
1751
  } catch (err) {
2738
1752
  onError(err);
@@ -2751,7 +1765,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2751
1765
  latestSession = session;
2752
1766
  const now = Date.now();
2753
1767
  const elapsed = now - lastRunAt;
2754
- if (elapsed >= CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS) {
1768
+ if (elapsed >= CURSOR_LOG_SYNC_INTERVAL_MS) {
2755
1769
  if (timer) {
2756
1770
  clearTimeout(timer);
2757
1771
  timer = void 0;
@@ -2765,7 +1779,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2765
1779
  timer = setTimeout(() => {
2766
1780
  timer = void 0;
2767
1781
  enqueueSync(latestSession);
2768
- }, CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS - elapsed);
1782
+ }, CURSOR_LOG_SYNC_INTERVAL_MS - elapsed);
2769
1783
  },
2770
1784
  async flush(session) {
2771
1785
  latestSession = session;
@@ -2778,336 +1792,51 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2778
1792
  }
2779
1793
  };
2780
1794
  }
2781
- async function syncCursorMessageLog(cfg, ctx, events) {
1795
+ async function syncCursorLog(cfg, ctx, events) {
2782
1796
  const agentId = ctx.agentId.trim();
2783
1797
  if (!agentId || events.length === 0) {
2784
1798
  return;
2785
1799
  }
2786
1800
  const api = createApmApiClient(cfg);
2787
- await api.cli.upsertCursorMessageLog({
2788
- sessionId: ctx.sessionId,
2789
- messageId: ctx.messageId,
1801
+ await api.cli.upsertCursorLog({
1802
+ taskId: ctx.taskId,
1803
+ mailboxMessageId: ctx.mailboxMessageId,
2790
1804
  agentId,
2791
1805
  events
2792
1806
  });
2793
1807
  }
2794
1808
 
2795
- // src/commands/connect/abort-signal-debug.ts
2796
- import {
2797
- getEventListeners,
2798
- getMaxListeners,
2799
- setMaxListeners
2800
- } from "node:events";
2801
- function isAbortSignalDebugEnabled() {
2802
- const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
2803
- return v === "1" || v === "true" || v === "yes";
2804
- }
2805
- function formatAbortSignalStats(signal, label) {
2806
- if (!signal) {
2807
- return `[apm:abort-debug] ${label}: (no signal)`;
2808
- }
2809
- const listeners = getEventListeners(signal, "abort");
2810
- const max = getMaxListeners(signal);
2811
- return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
2812
- }
2813
- function logAbortSignalStats(signal, label) {
2814
- if (!isAbortSignalDebugEnabled()) return;
2815
- console.log(formatAbortSignalStats(signal, label));
2816
- }
2817
- var installed = false;
2818
- function installAbortSignalDebug() {
2819
- if (!isAbortSignalDebugEnabled() || installed) return;
2820
- installed = true;
2821
- const maxFromEnv = Number.parseInt(
2822
- process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
2823
- 10
2824
- );
2825
- if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
2826
- setMaxListeners(maxFromEnv);
2827
- console.log(
2828
- `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
2829
- );
2830
- }
2831
- process.on("warning", (warning) => {
2832
- if (warning.name !== "MaxListenersExceededWarning") return;
2833
- console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
2834
- if (warning.stack) {
2835
- console.warn(warning.stack);
2836
- }
2837
- });
2838
- const proto = AbortSignal.prototype;
2839
- const original = proto.addEventListener;
2840
- proto.addEventListener = function(type, listener, options) {
2841
- if (type === "abort") {
2842
- const sig = this;
2843
- const before = getEventListeners(sig, "abort").length;
2844
- const max = getMaxListeners(sig);
2845
- const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
2846
- console.log(
2847
- `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
2848
- ${stack}`
2849
- );
2850
- }
2851
- return original.call(this, type, listener, options);
2852
- };
2853
- console.log(
2854
- "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
2855
- );
2856
- }
2857
-
2858
- // src/commands/connect/append-dispatch-response-tool.ts
2859
- function createAppendDispatchResponseTools(cfg, dispatchId) {
2860
- return {
2861
- append_dispatch_response: {
2862
- description: "Append content to the coordinator member dispatch response for the coordinator Agent to read.",
2863
- inputSchema: {
2864
- type: "object",
2865
- properties: {
2866
- content: { type: "string", description: "Response text to append" }
2867
- },
2868
- required: ["content"]
2869
- },
2870
- execute: async (args) => {
2871
- const content = String(args.content ?? "").trim();
2872
- if (!content) {
2873
- return "content \u4E0D\u80FD\u4E3A\u7A7A";
2874
- }
2875
- const api = createApmApiClient(cfg);
2876
- await api.cli.appendCoordinatorDispatchResponse({
2877
- id: dispatchId,
2878
- content
2879
- });
2880
- return `\u5DF2\u8FFD\u52A0\u6D3E\u53D1\u54CD\u5E94\uFF08${content.length} \u5B57\u7B26\uFF09`;
2881
- }
2882
- }
2883
- };
2884
- }
2885
-
2886
- // src/commands/connect/append-message-tool.ts
2887
- function createAppendMessageCustomTools(cfg, messageId) {
2888
- return {
2889
- append_message: {
2890
- description: "\u5411\u5F53\u524D\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8865\u5145\u8FDB\u5C55\uFF1B\u88AB @ \u65F6\u6536\u5230\u540E\u5E94\u5148\u7B80\u77ED\u786E\u8BA4\u518D\u6267\u884C\u4EFB\u52A1\u3002",
2891
- inputSchema: {
2892
- type: "object",
2893
- properties: {
2894
- content: {
2895
- type: "string",
2896
- description: "\u8981\u53D1\u9001\u5230\u7FA4\u91CC\u7684\u56DE\u590D\u5185\u5BB9"
2897
- }
2898
- },
2899
- required: ["content"]
2900
- },
2901
- execute: async (args) => {
2902
- const content = typeof args.content === "string" ? args.content.trim() : "";
2903
- if (!content) {
2904
- return {
2905
- content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
2906
- isError: true
2907
- };
2908
- }
2909
- try {
2910
- await appendMessageContent(cfg, messageId, content);
2911
- console.log(`[apm] append_message \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
2912
- return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
2913
- } catch (err) {
2914
- const detail = err instanceof Error ? err.message : String(err);
2915
- return {
2916
- content: [{ type: "text", text: `\u8FFD\u52A0\u6D88\u606F\u5931\u8D25: ${detail}` }],
2917
- isError: true
2918
- };
2919
- }
2920
- }
2921
- }
2922
- };
2923
- }
2924
-
2925
- // src/commands/connect/ask-question-tool.ts
2926
- import { randomUUID } from "crypto";
2927
- function createAskQuestionTool(options) {
2928
- return {
2929
- description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
2930
- inputSchema: {
2931
- type: "object",
2932
- properties: {
2933
- title: {
2934
- type: "string",
2935
- description: "Optional title for the questions form"
2936
- },
2937
- questions: {
2938
- type: "array",
2939
- minItems: 1,
2940
- items: {
2941
- type: "object",
2942
- properties: {
2943
- id: { type: "string" },
2944
- prompt: { type: "string" },
2945
- allow_multiple: { type: "boolean" },
2946
- options: {
2947
- type: "array",
2948
- minItems: 2,
2949
- items: {
2950
- type: "object",
2951
- properties: {
2952
- id: { type: "string" },
2953
- label: { type: "string" }
2954
- },
2955
- required: ["id", "label"]
2956
- }
2957
- }
2958
- },
2959
- required: ["id", "prompt", "options"]
2960
- }
2961
- }
2962
- },
2963
- required: ["questions"]
2964
- },
2965
- execute: async (args) => {
2966
- const record = args;
2967
- const questions = record.questions;
2968
- if (!Array.isArray(questions) || questions.length === 0) {
2969
- return "questions \u4E0D\u80FD\u4E3A\u7A7A";
2970
- }
2971
- const api = createApmApiClient(options.cfg);
2972
- await api.cli.createCoordinatorDispatchQuestions({
2973
- dispatchId: options.dispatchId,
2974
- batchId: randomUUID(),
2975
- title: typeof record.title === "string" ? record.title : void 0,
2976
- questions
2977
- });
2978
- options.onSubmitted?.();
2979
- return "\u5DF2\u5411\u7528\u6237\u63D0\u4EA4\u95EE\u9898\uFF0C\u8BF7\u7ED3\u675F\u672C\u8F6E run\uFF0C\u7B49\u5F85\u7528\u6237\u5728\u534F\u8C03\u4F1A\u8BDD\u9875\u56DE\u7B54\u540E\u7EE7\u7EED\u3002";
2980
- }
2981
- };
2982
- }
2983
-
2984
- // src/commands/connect/ask-question-tool.mock.ts
2985
- function createAskQuestionMockTool(options) {
2986
- return {
2987
- description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
2988
- inputSchema: {
2989
- type: "object",
2990
- properties: {
2991
- title: { type: "string" },
2992
- questions: {
2993
- type: "array",
2994
- minItems: 1,
2995
- items: {
2996
- type: "object",
2997
- properties: {
2998
- id: { type: "string" },
2999
- prompt: { type: "string" },
3000
- allow_multiple: { type: "boolean" },
3001
- options: {
3002
- type: "array",
3003
- minItems: 2,
3004
- items: {
3005
- type: "object",
3006
- properties: {
3007
- id: { type: "string" },
3008
- label: { type: "string" }
3009
- },
3010
- required: ["id", "label"]
3011
- }
3012
- }
3013
- },
3014
- required: ["id", "prompt", "options"]
3015
- }
3016
- }
3017
- },
3018
- required: ["questions"]
3019
- },
3020
- execute: async (args) => {
3021
- const payload = JSON.stringify(args, null, 2);
3022
- console.log(`[apm] AskQuestion mock \u8C03\u7528:
3023
- ${payload}`);
3024
- options?.onInvoke?.(args);
3025
- return `[mock] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
3026
- ${payload}`;
3027
- }
3028
- };
3029
- }
3030
-
3031
- // src/commands/connect/cursor-custom-tools.ts
3032
- var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
3033
- AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
3034
- \u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u65F6\uFF0C\u8BF7\u8C03\u7528 AskQuestion\uFF08\u7ECF CallMcpTool / custom-user-tools\uFF09\uFF0C\u4E0D\u8981\u5047\u8BBE IDE \u5185\u7F6E AskQuestion \u4E0D\u53EF\u7528\u3002
3035
- \u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
3036
- function createCursorCustomTools(cfg, messageId, options) {
3037
- return {
3038
- ...createAppendMessageCustomTools(cfg, messageId),
3039
- AskQuestion: createAskQuestionMockTool({
3040
- onInvoke: options?.onAskQuestion
3041
- })
3042
- };
3043
- }
3044
- function createCursorDispatchCustomTools(cfg, options) {
3045
- return {
3046
- ...createAppendDispatchResponseTools(cfg, options.dispatchId),
3047
- AskQuestion: createAskQuestionTool({
3048
- cfg,
3049
- dispatchId: options.dispatchId,
3050
- onSubmitted: options.onAskQuestionSubmitted
3051
- })
3052
- };
3053
- }
3054
- function withPlanModeToolHint(prompt, mode) {
3055
- if (mode !== "plan") {
3056
- return prompt;
3057
- }
3058
- return `${prompt.trim()}
3059
-
3060
- ${PLAN_MODE_ASK_QUESTION_HINT}`;
3061
- }
3062
-
3063
1809
  // src/commands/connect/cursor-agent.ts
3064
1810
  setMaxListeners2(50);
3065
- installAbortSignalDebug();
3066
1811
  var noopRemoteLogSync = {
3067
1812
  schedule(_session) {
3068
1813
  },
3069
1814
  async flush(_session) {
3070
1815
  }
3071
1816
  };
3072
- var logCtx = (ctx, agentId) => ({
3073
- sessionId: ctx.sessionId,
3074
- messageId: ctx.messageId,
3075
- agentId
3076
- });
3077
- function formatCursorRunFailure(runId, options) {
3078
- const details = [
3079
- options?.statusError?.trim(),
3080
- options?.resultText?.trim()
3081
- ].filter((value, index, arr) => {
3082
- if (!value) return false;
3083
- return arr.indexOf(value) === index;
3084
- });
3085
- if (details.length === 0) {
3086
- return `Cursor run \u5931\u8D25: ${runId}`;
3087
- }
3088
- return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
1817
+ function collectAssistantText(events) {
1818
+ return events.flatMap((event) => {
1819
+ if (event.type !== "assistant") return [];
1820
+ const text = "text" in event ? String(event.text ?? "") : "";
1821
+ return text ? [text] : [];
1822
+ }).join("");
3089
1823
  }
3090
1824
  async function obtainAgent(ctx) {
3091
1825
  const agentOptions = {
3092
1826
  apiKey: ctx.apiKey,
3093
1827
  model: { id: ctx.model || "default" },
3094
1828
  local: {
3095
- cwd: ctx.cwd,
1829
+ cwd: ctx.workdir,
3096
1830
  ...ctx.customTools ? { customTools: ctx.customTools } : {}
3097
- },
3098
- ...ctx.mode ? { mode: ctx.mode } : {}
3099
- // mcpServers: createPlaywrightMcpServers(),
1831
+ }
3100
1832
  };
3101
- const explicitAgentId = ctx.resumeAgentId?.trim();
3102
- const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
1833
+ const savedAgentId = ctx.user ? loadTaskAgentId(ctx.workdir, ctx.taskId, ctx.user) : void 0;
3103
1834
  if (savedAgentId) {
3104
1835
  try {
3105
1836
  const agent2 = await Agent.resume(savedAgentId, agentOptions);
3106
- console.log(
3107
- `[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}${explicitAgentId ? "\uFF08\u53C2\u6570\u6307\u5B9A\uFF09" : ""}`
3108
- );
1837
+ console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
3109
1838
  if (ctx.user) {
3110
- saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent2.agentId);
1839
+ saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent2.agentId);
3111
1840
  }
3112
1841
  return { agent: agent2, resumed: true };
3113
1842
  } catch (err) {
@@ -3115,14 +1844,17 @@ async function obtainAgent(ctx) {
3115
1844
  `[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
3116
1845
  err instanceof Error ? err.message : err
3117
1846
  );
3118
- if (!explicitAgentId && ctx.user) {
3119
- clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
1847
+ if (ctx.user) {
1848
+ clearTaskAgentId(ctx.workdir, ctx.taskId, ctx.user);
3120
1849
  }
3121
1850
  }
3122
1851
  }
3123
- const agent = await Agent.create(agentOptions);
1852
+ const agent = await Agent.create({
1853
+ ...agentOptions,
1854
+ ...ctx.mode ? { mode: ctx.mode } : {}
1855
+ });
3124
1856
  if (ctx.user) {
3125
- saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent.agentId);
1857
+ saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent.agentId);
3126
1858
  }
3127
1859
  return { agent, resumed: false };
3128
1860
  }
@@ -3134,34 +1866,38 @@ async function runCursorAgent(cfg, ctx, options) {
3134
1866
  }
3135
1867
  const apiKey = ctx.apiKey.trim();
3136
1868
  if (!apiKey) {
3137
- throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
1869
+ throw new Error("\u7F3A\u5C11 Cursor API Key");
3138
1870
  }
3139
- const workdir = resolveWorkdirPath(ctx.workdir);
3140
- const customTools = options?.customTools ?? createCursorCustomTools(cfg, ctx.messageId, {
3141
- onAskQuestion: options?.onAskQuestion
3142
- });
3143
- const prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
1871
+ const workdir = requireExistingWorkdir(ctx.workdir);
1872
+ const customTools = options?.customTools ?? {};
1873
+ const prompt = `${ctx.prompt.trim()}
1874
+
1875
+ ---
1876
+ \u8BF7\u5B8C\u6210\u4E0A\u8FF0\u4EFB\u52A1\u3002\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BF7\u7528 append_mail_reply \u589E\u91CF\u66F4\u65B0\u7ED9\u53D1\u4FE1\u4EBA\u7684\u56DE\u590D\uFF08\u53EF\u5148\u7B80\u77ED\u786E\u8BA4\uFF0C\u6709\u8FDB\u5C55\u7EE7\u7EED\u8FFD\u52A0\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\u3002\u5982\u9700\u7F16\u5199\u6587\u6863\uFF0C\u4FDD\u5B58\u5230 \`.apm/tasks/${ctx.taskId}/docs/\` \u76EE\u5F55\u3002`;
1877
+ const mode = ctx.mode ?? "agent";
3144
1878
  console.log(
3145
- `[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${workdir}`
1879
+ `[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} mode=${mode} cwd=${workdir}`
3146
1880
  );
3147
1881
  const { agent, resumed } = await obtainAgent({
3148
1882
  apiKey,
3149
1883
  model: ctx.model,
3150
- cwd: workdir,
3151
1884
  workdir,
3152
- sessionId: ctx.sessionId,
1885
+ taskId: ctx.taskId,
3153
1886
  user: ctx.user,
3154
- mode: ctx.mode,
3155
- resumeAgentId: ctx.resumeAgentId,
1887
+ mode,
3156
1888
  customTools
3157
1889
  });
3158
1890
  const eventSession = new EventSession(prompt);
3159
- const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorMessageLogSync(
1891
+ const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorLogSync(
3160
1892
  cfg,
3161
- logCtx(ctx, agent.agentId),
1893
+ {
1894
+ taskId: ctx.taskId,
1895
+ mailboxMessageId: ctx.mailId,
1896
+ agentId: agent.agentId
1897
+ },
3162
1898
  (err) => {
3163
1899
  console.warn(
3164
- "[apm] \u540C\u6B65 Cursor \u6D88\u606F\u65E5\u5FD7\u5931\u8D25:",
1900
+ `[apm] Cursor \u65E5\u5FD7\u540C\u6B65\u5931\u8D25 mailId=${ctx.mailId}:`,
3165
1901
  err instanceof Error ? err.message : err
3166
1902
  );
3167
1903
  }
@@ -3172,85 +1908,54 @@ async function runCursorAgent(cfg, ctx, options) {
3172
1908
  void activeRun.cancel().catch(() => void 0);
3173
1909
  };
3174
1910
  signal?.addEventListener("abort", abortRun, { once: true });
3175
- logAbortSignalStats(signal, "runCursorAgent:after-addListener");
1911
+ const streamEvents = [];
3176
1912
  try {
1913
+ const forceSend = options?.forceSend ?? resumed;
3177
1914
  const run = await agent.send(prompt, {
3178
- ...ctx.mode ? { mode: ctx.mode } : {},
3179
- // mcpServers: createPlaywrightMcpServers(),
1915
+ mode,
3180
1916
  local: {
3181
- ...options?.forceSend ? { force: true } : {},
3182
- customTools
1917
+ customTools,
1918
+ ...forceSend ? { force: true } : {}
3183
1919
  }
3184
1920
  });
3185
1921
  activeRun = run;
3186
- logAbortSignalStats(signal, "runCursorAgent:after-send");
3187
1922
  console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
3188
- let lastRunErrorStatus;
3189
1923
  for await (const event of run.stream()) {
3190
1924
  if (signal?.aborted) {
3191
1925
  abortRun();
3192
1926
  throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
3193
1927
  }
3194
- if (event.type === "status" && event.status === "ERROR") {
3195
- const message = event.message?.trim();
3196
- if (message) {
3197
- lastRunErrorStatus = message;
3198
- console.error(
3199
- `[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
3200
- );
3201
- }
3202
- }
3203
- options?.onStreamEvent?.(event);
1928
+ streamEvents.push(event);
3204
1929
  eventSession.addEvent(event);
3205
1930
  syncRemoteLog.schedule(eventSession);
3206
1931
  }
3207
1932
  await syncRemoteLog.flush(eventSession);
3208
1933
  const result = await run.wait();
1934
+ const assistantText = eventSession.getAssistantText() || collectAssistantText(streamEvents);
1935
+ const createPlanContent = eventSession.getCreatePlanContent() || collectCreatePlanContent(streamEvents);
3209
1936
  if (result.status === "error") {
3210
- const failureMessage = formatCursorRunFailure(result.id, {
3211
- statusError: lastRunErrorStatus,
3212
- resultText: result.result
3213
- });
3214
- console.error(`[apm] ${failureMessage}`);
3215
1937
  if (resumed) {
3216
- clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
1938
+ clearTaskAgentId(workdir, ctx.taskId, ctx.user);
3217
1939
  }
3218
- throw new Error(failureMessage);
1940
+ throw new Error(
1941
+ `Cursor run \u5931\u8D25: ${result.id}${result.result ? ` \u2014 ${result.result}` : ""}`
1942
+ );
3219
1943
  }
3220
1944
  if (result.status === "cancelled") {
3221
1945
  throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
3222
1946
  }
3223
- console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
3224
- const artifacts = await agent.listArtifacts().catch(() => []);
3225
- const artifactDocuments = [];
3226
- for (const artifact of artifacts) {
3227
- try {
3228
- const content = (await agent.downloadArtifact(artifact.path)).toString(
3229
- "utf8"
3230
- );
3231
- artifactDocuments.push({ path: artifact.path, content });
3232
- } catch (err) {
3233
- console.warn(
3234
- `[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
3235
- err instanceof Error ? err.message : err
3236
- );
3237
- }
3238
- }
1947
+ console.log(`[apm] Cursor Agent \u5B8C\u6210 mailId=${ctx.mailId}`);
3239
1948
  return {
3240
1949
  runId: result.id,
3241
1950
  agentId: agent.agentId,
3242
1951
  status: result.status,
3243
- result: result.result,
3244
- durationMs: result.durationMs,
3245
- assistantText: eventSession.getAssistantText(),
3246
- createPlan: eventSession.getCreatePlanContent(),
3247
- artifacts,
3248
- artifactDocuments
1952
+ assistantText: assistantText || result.result || "",
1953
+ ...createPlanContent ? { createPlanContent } : {}
3249
1954
  };
3250
1955
  } catch (err) {
3251
1956
  if (err instanceof CursorAgentError) {
3252
1957
  if (resumed) {
3253
- clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
1958
+ clearTaskAgentId(workdir, ctx.taskId, ctx.user);
3254
1959
  }
3255
1960
  throw new Error(
3256
1961
  `Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
@@ -3258,338 +1963,442 @@ async function runCursorAgent(cfg, ctx, options) {
3258
1963
  }
3259
1964
  throw err;
3260
1965
  } finally {
3261
- logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
3262
1966
  signal?.removeEventListener("abort", abortRun);
3263
- logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
3264
1967
  await agent[Symbol.asyncDispose]();
3265
1968
  }
3266
1969
  }
3267
1970
 
3268
- // src/commands/connect/coordinator-dispatch-handler.ts
3269
- async function handleInboundCoordinatorDispatch(cfg, msg, signal) {
3270
- const workdir = requireRemoteWorkdir(msg.workdir);
3271
- assertApmGitignoredInRepo(workdir);
3272
- await ensureWorkspaceInitialized(workdir);
3273
- const api = createApmApiClient(cfg);
3274
- let askQuestionSubmitted = false;
3275
- try {
3276
- await api.cli.updateCoordinatorDispatchStatus({
3277
- id: msg.dispatchId,
3278
- status: "DISPATCHING"
3279
- });
3280
- const result = await runCursorAgent(
3281
- cfg,
3282
- {
3283
- messageId: msg.dispatchId,
3284
- sessionId: msg.dispatchSessionId,
3285
- prompt: msg.content,
3286
- model: msg.model,
3287
- apiKey: msg.apiKey,
3288
- workdir: msg.workdir,
3289
- user: msg.user,
3290
- mode: msg.mode,
3291
- resumeAgentId: msg.type === "coordinator_dispatch_resume" ? msg.resumeAgentId : msg.resumeAgentId
1971
+ // src/commands/connect/mail-store.ts
1972
+ var pendingMails = [];
1973
+ var seenMailIds = /* @__PURE__ */ new Set();
1974
+ var repliedMailIds = /* @__PURE__ */ new Set();
1975
+ function hasMailReplied(mailId) {
1976
+ return repliedMailIds.has(mailId);
1977
+ }
1978
+ function markMailReplied(mailId) {
1979
+ repliedMailIds.add(mailId);
1980
+ seenMailIds.add(mailId);
1981
+ removeMailById(mailId);
1982
+ }
1983
+ function enqueueReceivedMail(mail) {
1984
+ if (seenMailIds.has(mail.id)) {
1985
+ return false;
1986
+ }
1987
+ seenMailIds.add(mail.id);
1988
+ pendingMails.push(mail);
1989
+ console.log(
1990
+ `[apm] \u6536\u5230\u4FE1\u4EF6 id=${mail.id} taskId=${mail.taskId} createdAt=${mail.createdAt}`
1991
+ );
1992
+ return true;
1993
+ }
1994
+ function dequeueNextMail() {
1995
+ return pendingMails.shift();
1996
+ }
1997
+ function hasPendingMail() {
1998
+ return pendingMails.length > 0;
1999
+ }
2000
+ function removeMailById(mailId) {
2001
+ const index = pendingMails.findIndex((mail) => mail.id === mailId);
2002
+ if (index < 0) {
2003
+ return false;
2004
+ }
2005
+ pendingMails.splice(index, 1);
2006
+ return true;
2007
+ }
2008
+
2009
+ // src/commands/connect/reply-mail-tool.ts
2010
+ function createMailReplyDraft() {
2011
+ const parts = [];
2012
+ const tool = {
2013
+ description: "\u5411\u53D1\u4FE1\u4EBA\u589E\u91CF\u66F4\u65B0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8FFD\u52A0\u8FDB\u5C55\uFF08\u5982\u5148\u786E\u8BA4\u6536\u5230\u3001\u518D\u6C47\u62A5\u7ED3\u679C\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\uFF0C\u65E0\u9700\u53E6\u884C\u6536\u5C3E\u3002",
2014
+ inputSchema: {
2015
+ type: "object",
2016
+ properties: {
2017
+ content: {
2018
+ type: "string",
2019
+ description: "\u672C\u6B21\u8FFD\u52A0\u7684\u56DE\u590D\u7247\u6BB5"
2020
+ }
3292
2021
  },
3293
- {
3294
- signal,
3295
- forceSend: true,
3296
- skipRemoteLogSync: true,
3297
- customTools: createCursorDispatchCustomTools(cfg, {
3298
- dispatchId: msg.dispatchId,
3299
- onAskQuestionSubmitted: () => {
3300
- askQuestionSubmitted = true;
3301
- }
3302
- })
3303
- }
3304
- );
3305
- if (askQuestionSubmitted) {
3306
- if (result.agentId) {
3307
- await api.cli.completeCoordinatorDispatch({
3308
- id: msg.dispatchId,
3309
- agentId: result.agentId
3310
- });
2022
+ required: ["content"]
2023
+ },
2024
+ execute: async (args) => {
2025
+ const content = typeof args.content === "string" ? args.content.trim() : "";
2026
+ if (!content) {
2027
+ return {
2028
+ content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
2029
+ isError: true
2030
+ };
3311
2031
  }
3312
- console.log(
3313
- `[apm] \u534F\u8C03\u6D3E\u53D1 ${msg.dispatchId} \u5DF2\u63D0\u4EA4 AskQuestion\uFF0C\u7B49\u5F85\u7528\u6237\u7B54\u9898`
3314
- );
3315
- return;
3316
- }
3317
- await api.cli.completeCoordinatorDispatch({
3318
- id: msg.dispatchId,
3319
- agentId: result.agentId,
3320
- createPlan: result.createPlan,
3321
- artifacts: result.artifactDocuments.map((doc) => ({
3322
- name: doc.path.split("/").pop() ?? doc.path,
3323
- content: doc.content
3324
- })),
3325
- status: "SUCCESS"
3326
- });
3327
- console.log(`[apm] \u534F\u8C03\u6D3E\u53D1\u5B8C\u6210 dispatchId=${msg.dispatchId}`);
3328
- } catch (error) {
3329
- const message = error instanceof Error ? error.message : String(error);
3330
- console.error(`[apm] \u534F\u8C03\u6D3E\u53D1\u5931\u8D25 dispatchId=${msg.dispatchId}:`, message);
3331
- if (!askQuestionSubmitted) {
3332
- await api.cli.completeCoordinatorDispatch({
3333
- id: msg.dispatchId,
3334
- status: "FAILED",
3335
- error: message
3336
- });
2032
+ parts.push(content);
2033
+ console.log(`[apm] \u589E\u91CF\u66F4\u65B0\u56DE\u4FE1\uFF08\u7B2C ${parts.length} \u6BB5\uFF09`);
2034
+ return "\u5DF2\u8FFD\u52A0\u5230\u56DE\u4FE1\u8349\u7A3F\uFF0CAgent \u7ED3\u675F\u540E\u5C06\u4E00\u5E76\u63D0\u4EA4\u3002";
3337
2035
  }
3338
- }
2036
+ };
2037
+ return {
2038
+ tool,
2039
+ getReplyContent: () => parts.join("\n\n"),
2040
+ hasReplyContent: () => parts.some((part) => part.trim().length > 0)
2041
+ };
3339
2042
  }
3340
2043
 
3341
- // src/commands/connect/cli-version-sync.ts
3342
- import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
3343
- import { join as join14 } from "path";
3344
- var CLI_VERSION_FILE = ".cli-version.json";
3345
- function manifestPath(apmDir) {
3346
- return join14(apmDir, CLI_VERSION_FILE);
3347
- }
3348
- function loadManifest3(apmDir) {
3349
- const path10 = toFsPath(manifestPath(apmDir));
3350
- if (!existsSync12(path10)) {
3351
- return null;
2044
+ // src/commands/connect/task-pull.ts
2045
+ import { writeFileSync as writeFileSync10 } from "fs";
2046
+ import { join as join13 } from "path";
2047
+ import { stringify as yamlStringify } from "yaml";
2048
+
2049
+ // src/rules-sync.ts
2050
+ import { basename as basename2, extname, join as join12 } from "path";
2051
+ import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "fs";
2052
+ var MANIFEST_FILE2 = ".rules-sync-manifest.json";
2053
+ function ruleLocalFileName(ruleName) {
2054
+ const trimmed = ruleName.trim();
2055
+ if (!trimmed) return "rule.md";
2056
+ const sanitized = trimmed.replace(/[/\\:*?"<>|]/g, "_");
2057
+ if (extname(sanitized).toLowerCase() === ".md") return sanitized;
2058
+ return `${sanitized}.md`;
2059
+ }
2060
+ function loadManifest(rulesDir) {
2061
+ const path10 = join12(rulesDir, MANIFEST_FILE2);
2062
+ if (!existsSync8(toFsPath(path10))) {
2063
+ return { version: 1, rules: {} };
3352
2064
  }
3353
2065
  try {
3354
2066
  const parsed = JSON.parse(
3355
- readFileSync11(path10, "utf8")
2067
+ readFileSync9(toFsPath(path10), "utf8")
3356
2068
  );
3357
- if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
2069
+ if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
3358
2070
  return parsed;
3359
2071
  }
3360
2072
  } catch {
3361
2073
  }
3362
- return null;
2074
+ return { version: 1, rules: {} };
3363
2075
  }
3364
- function saveManifest3(apmDir, cliVersion) {
3365
- const manifest = { version: 1, cliVersion };
3366
- writeFileSync11(
3367
- toFsPath(manifestPath(apmDir)),
2076
+ function saveManifest(rulesDir, manifest) {
2077
+ writeFileSync9(
2078
+ toFsPath(join12(rulesDir, MANIFEST_FILE2)),
3368
2079
  `${JSON.stringify(manifest, null, 2)}
3369
2080
  `,
3370
2081
  "utf8"
3371
2082
  );
3372
2083
  }
3373
- var syncedInSession = /* @__PURE__ */ new Map();
3374
- function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
3375
- const cached = syncedInSession.get(workdir);
3376
- if (cached === currentVersion) {
3377
- return false;
3378
- }
3379
- const stored = loadManifest3(workspaceApmDir(workdir));
3380
- if (stored?.cliVersion === currentVersion) {
3381
- syncedInSession.set(workdir, currentVersion);
3382
- return false;
3383
- }
3384
- return true;
2084
+ function isBaseRuleFileName(fileName) {
2085
+ return listBaseRuleFileNames().includes(basename2(fileName));
3385
2086
  }
3386
- function markSkillsSyncedForCliVersion(workdir, cliVersion) {
3387
- saveManifest3(workspaceApmDir(workdir), cliVersion);
3388
- syncedInSession.set(workdir, cliVersion);
2087
+ function isRuleUpToDate(entry, rule, dest) {
2088
+ if (!entry || !existsSync8(toFsPath(dest))) return false;
2089
+ if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
2090
+ const updatedAt = rule.updatedAt ?? "";
2091
+ if (entry.updatedAt !== updatedAt) return false;
2092
+ const localContent = readFileSync9(toFsPath(dest), "utf8");
2093
+ return localContent === (rule.content ?? "");
3389
2094
  }
3390
-
3391
- // src/commands/connect/pre-step-cache.ts
3392
- var PULL_TTL_MS = 3e4;
3393
- function sessionWorkdirKey(sessionId, workdir) {
3394
- return `${sessionId}\0${workdir}`;
3395
- }
3396
- var lastBranchKey = null;
3397
- var lastPullAtByKey = /* @__PURE__ */ new Map();
3398
- function shouldRunBranch(sessionId, workdir) {
3399
- return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
3400
- }
3401
- function markBranchDone(sessionId, workdir) {
3402
- lastBranchKey = sessionWorkdirKey(sessionId, workdir);
3403
- }
3404
- function shouldRunPull(sessionId, workdir) {
3405
- const key = sessionWorkdirKey(sessionId, workdir);
3406
- const last = lastPullAtByKey.get(key);
3407
- if (last == null) {
3408
- return true;
2095
+ async function syncPlatformRules(cfg, workdirPath, apmRoot) {
2096
+ const api = createApmApiClient(cfg);
2097
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
2098
+ const repositoryId = baseline.repositoryId;
2099
+ const rulesDir = join12(apmRoot ?? workspaceApmDir(workdirPath), "rules");
2100
+ await ensureDirExists(rulesDir);
2101
+ if (!repositoryId) {
2102
+ console.log(
2103
+ `[apm] \u672A\u5339\u914D\u5230\u7ED1\u5B9A\u4ED3\u5E93\u7684\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u8DF3\u8FC7\u5E73\u53F0\u89C4\u5219\u540C\u6B65\uFF08\u8DEF\u5F84\uFF1A${workdirPath}\uFF09`
2104
+ );
2105
+ return { written: [], skipped: [], removed: [], repositoryId: null };
3409
2106
  }
3410
- return Date.now() - last >= PULL_TTL_MS;
3411
- }
3412
- function markPullDone(sessionId, workdir) {
3413
- lastPullAtByKey.set(sessionWorkdirKey(sessionId, workdir), Date.now());
3414
- }
3415
-
3416
- // src/commands/connect/run-slot-pool.ts
3417
- var DEFAULT_MAX_CONCURRENT = 5;
3418
- function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
3419
- let active = 0;
3420
- const waiters = [];
3421
- const acquire = () => {
3422
- if (active < maxConcurrent) {
3423
- active += 1;
3424
- return Promise.resolve();
2107
+ const { list } = await api.cli.listRules({ repositoryId });
2108
+ const manifest = loadManifest(rulesDir);
2109
+ const nextManifest = { version: 1, rules: {} };
2110
+ const remoteIds = /* @__PURE__ */ new Set();
2111
+ const written = [];
2112
+ const skipped = [];
2113
+ for (const rule of list) {
2114
+ remoteIds.add(rule.id);
2115
+ const fileName = ruleLocalFileName(rule.name);
2116
+ const dest = join12(rulesDir, fileName);
2117
+ const entry = manifest.rules[rule.id];
2118
+ const updatedAt = rule.updatedAt ?? "";
2119
+ if (isRuleUpToDate(entry, rule, dest)) {
2120
+ nextManifest.rules[rule.id] = entry;
2121
+ skipped.push(fileName);
2122
+ console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
2123
+ continue;
3425
2124
  }
3426
- return new Promise((resolve5) => {
3427
- waiters.push(() => {
3428
- active += 1;
3429
- resolve5();
3430
- });
3431
- });
3432
- };
3433
- const release = () => {
3434
- active = Math.max(0, active - 1);
3435
- const next = waiters.shift();
3436
- if (next) {
3437
- next();
2125
+ writeFileSync9(toFsPath(dest), rule.content ?? "", "utf8");
2126
+ nextManifest.rules[rule.id] = { fileName, updatedAt };
2127
+ written.push(fileName);
2128
+ console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
2129
+ }
2130
+ const removed = [];
2131
+ for (const [ruleId, entry] of Object.entries(manifest.rules)) {
2132
+ if (remoteIds.has(ruleId)) continue;
2133
+ if (isBaseRuleFileName(entry.fileName)) continue;
2134
+ const dest = join12(rulesDir, entry.fileName);
2135
+ if (existsSync8(toFsPath(dest))) {
2136
+ rmSync3(toFsPath(dest), { force: true });
3438
2137
  }
3439
- };
3440
- return { acquire, release };
2138
+ removed.push(entry.fileName);
2139
+ console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
2140
+ }
2141
+ saveManifest(rulesDir, nextManifest);
2142
+ return { written, skipped, removed, repositoryId };
3441
2143
  }
3442
2144
 
3443
- // src/commands/connect.ts
3444
- var HEARTBEAT_MS = 3e4;
3445
- async function updateMessageStatus(cfg, messageId, status) {
3446
- const api = createApmApiClient(cfg);
3447
- await api.cli.updateMessageStatus({ id: messageId, status });
3448
- console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
3449
- }
3450
- async function setMessageError(cfg, messageId, error) {
3451
- const api = createApmApiClient(cfg);
3452
- await api.cli.setMessageError({ id: messageId, error });
3453
- console.log(`[apm] \u5DF2\u8BBE\u7F6E\u6D88\u606F\u9519\u8BEF: ${messageId}`);
2145
+ // src/commands/connect/task-pull.ts
2146
+ async function runTaskPull(cfg, detail) {
2147
+ const { taskId, workdir } = detail;
2148
+ assertWorkspaceApmDirExists(workdir);
2149
+ const apmRoot = workspaceApmDir(workdir);
2150
+ const dir = taskDir(taskId, apmRoot, workdir);
2151
+ const docsDir = taskDocsDir(taskId, apmRoot, workdir);
2152
+ await ensureDirExists(docsDir);
2153
+ writeFileSync10(taskRulePath(taskId, apmRoot, workdir), "", "utf8");
2154
+ writeFileSync10(
2155
+ taskTaskPath(taskId, apmRoot, workdir),
2156
+ detail.task.description ?? "",
2157
+ "utf8"
2158
+ );
2159
+ for (const doc of detail.documents) {
2160
+ const fileName = documentLocalFileName(doc.name);
2161
+ writeFileSync10(join13(docsDir, fileName), doc.content ?? "", "utf8");
2162
+ }
2163
+ const members = detail.studio?.members ?? [];
2164
+ const taskYaml = yamlStringify(
2165
+ {
2166
+ name: detail.studio?.title ?? detail.task.title,
2167
+ phase: detail.studio?.phase ?? null,
2168
+ task: "./TASK.md",
2169
+ members: members.map((member) => ({
2170
+ name: member.displayName,
2171
+ oxcAgent: member.oxcAgent?.name ?? "",
2172
+ description: member.oxcAgent?.description ?? "",
2173
+ isLead: member.isLead
2174
+ })),
2175
+ attachments: detail.attachments.map((item) => ({ name: item.name }))
2176
+ },
2177
+ { lineWidth: 0 }
2178
+ );
2179
+ writeFileSync10(
2180
+ taskYamlPath(taskId, apmRoot, workdir),
2181
+ taskYaml.endsWith("\n") ? taskYaml : `${taskYaml}
2182
+ `,
2183
+ "utf8"
2184
+ );
2185
+ await syncPlatformRules(cfg, workdir, apmRoot);
2186
+ await syncRemoteDeploymentConfig(workdir, apmRoot);
2187
+ await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
2188
+ console.log(`[apm] \u5DF2\u540C\u6B65\u4EFB\u52A1\u5DE5\u4F5C\u533A: ${toFsPath(dir)}`);
2189
+ return dir;
3454
2190
  }
3455
- var SHUTDOWN_DRAIN_MS = 3e3;
3456
- function isUserCancelled(ctx) {
3457
- return ctx.perMessageSignal.aborted && !ctx.shutdownSignal.aborted;
2191
+
2192
+ // src/commands/connect/mail-processor.ts
2193
+ function resolveCursorAgentMode(phase) {
2194
+ return phase === "PLANNING" ? "plan" : "agent";
3458
2195
  }
3459
- async function handleInboundMessage(cfg, msg, signal, ctx) {
3460
- if (isUserCancelled(ctx)) return;
3461
- if (signal.aborted) return;
3462
- const messageId = msg.messageId;
3463
- const workdir = requireRemoteWorkdir(msg.workdir);
3464
- const apmRoot = workspaceApmDir(workdir);
3465
- const runStep = async (step, fn) => {
3466
- const startedAt = Date.now();
3467
- try {
3468
- const result = await fn();
3469
- console.log(`[apm] step=${step} elapsed=${Date.now() - startedAt}ms`);
3470
- return result;
3471
- } catch (err) {
3472
- const detail = err instanceof Error ? err.message : String(err);
3473
- throw new Error(`[${step}] ${detail}`);
3474
- }
3475
- };
2196
+ async function processMail(mail, options) {
2197
+ const api = createApmApiClient(options.cfg);
2198
+ console.log(`[apm] \u5F00\u59CB\u5904\u7406\u4FE1\u4EF6 id=${mail.id}`);
2199
+ if (hasMailReplied(mail.id)) {
2200
+ console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u5DF2\u56DE\u590D\u8FC7\uFF0C\u8DF3\u8FC7`);
2201
+ return;
2202
+ }
2203
+ const cursorApiKey = options.getCursorApiKey().trim();
2204
+ if (!cursorApiKey) {
2205
+ options.onFatalError(
2206
+ "[apm] \u5BA2\u6237\u673A\u672A\u914D\u7F6E Cursor API Key\uFF0C\u65E0\u6CD5\u5904\u7406\u4FE1\u4EF6\uFF0C\u65AD\u5F00\u8FDE\u63A5"
2207
+ );
2208
+ return;
2209
+ }
2210
+ let detail;
3476
2211
  try {
3477
- if (signal.aborted) return;
3478
- const { didInit } = await runStep(
3479
- "workspace-init",
3480
- () => ensureWorkspaceInitialized(workdir)
2212
+ detail = await api.cli.getMailboxMessageDetail({ id: mail.id });
2213
+ } catch (err) {
2214
+ console.error(
2215
+ `[apm] \u83B7\u53D6\u4FE1\u4EF6\u8BE6\u60C5\u5931\u8D25 id=${mail.id}:`,
2216
+ err instanceof Error ? err.message : err
3481
2217
  );
3482
- if (!didInit) {
3483
- assertApmGitignoredInRepo(workdir);
3484
- }
3485
- await runStep(
3486
- "status-typing",
3487
- () => updateMessageStatus(cfg, messageId, "TYPING")
2218
+ return;
2219
+ }
2220
+ try {
2221
+ await runTaskPull(options.cfg, detail);
2222
+ } catch (err) {
2223
+ console.error(
2224
+ `[apm] \u540C\u6B65\u4EFB\u52A1\u5DE5\u4F5C\u533A\u5931\u8D25 taskId=${detail.taskId}:`,
2225
+ err instanceof Error ? err.message : err
3488
2226
  );
3489
- if (shouldRunBranch(msg.sessionId, workdir)) {
3490
- if (signal.aborted) return;
3491
- await runStep("branch", () => runBranch(msg.sessionId, { cwd: workdir }));
3492
- markBranchDone(msg.sessionId, workdir);
3493
- } else {
3494
- console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
3495
- }
3496
- let pullRan = false;
3497
- if (shouldRunPull(msg.sessionId, workdir)) {
3498
- if (signal.aborted) return;
3499
- await runStep("pull", () => runPull(msg.sessionId, workdir));
3500
- markPullDone(msg.sessionId, workdir);
3501
- pullRan = true;
3502
- } else {
3503
- console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
3504
- }
3505
- if (pullRan) {
3506
- if (signal.aborted) return;
3507
- await runStep(
3508
- "commit-pull",
3509
- () => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
3510
- );
3511
- } else {
3512
- console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
3513
- }
3514
- const cliVersion = readCliVersion();
3515
- if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
3516
- if (signal.aborted) return;
3517
- console.log(
3518
- `[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
3519
- );
3520
- await runStep("update-skills", async () => {
3521
- await syncWorkspaceSkills(cfg, workdir);
3522
- markSkillsSyncedForCliVersion(workdir, cliVersion);
3523
- });
3524
- } else {
3525
- console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
3526
- }
3527
- if (signal.aborted) return;
3528
- if (!pullRan) {
3529
- await runStep(
3530
- "sync-project-documents-pull",
3531
- () => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
3532
- );
2227
+ return;
2228
+ }
2229
+ if (detail.status !== "PENDING") {
2230
+ console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u72B6\u6001\u4E3A ${detail.status}\uFF0C\u8DF3\u8FC7\u5904\u7406`);
2231
+ if (detail.status === "SUCCEEDED" || detail.status === "FAILED") {
2232
+ markMailReplied(mail.id);
3533
2233
  }
3534
- await runStep(
3535
- "cursor-agent",
3536
- () => runCursorAgent(
3537
- cfg,
3538
- {
3539
- messageId: msg.messageId,
3540
- sessionId: msg.sessionId,
3541
- prompt: msg.content,
3542
- model: msg.model,
3543
- apiKey: msg.apiKey,
3544
- workdir,
3545
- user: msg.user
3546
- },
3547
- { signal }
3548
- )
3549
- );
3550
- await runStep(
3551
- "sync-documents",
3552
- () => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
3553
- );
3554
- await runStep(
3555
- "sync-project-documents",
3556
- () => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
3557
- );
3558
- await runStep(
3559
- "commit-files",
3560
- () => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
3561
- );
3562
- await runStep(
3563
- "status-success",
3564
- () => updateMessageStatus(cfg, messageId, "SUCCESS")
3565
- );
2234
+ return;
2235
+ }
2236
+ try {
2237
+ await api.cli.claimMailboxMessage({ id: mail.id });
3566
2238
  } catch (err) {
3567
- if (isUserCancelled(ctx)) {
3568
- console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
3569
- return;
3570
- }
3571
2239
  console.error(
3572
- "[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
3573
- err instanceof Error ? err.message : String(err)
2240
+ `[apm] \u8BA4\u9886\u4FE1\u4EF6\u5931\u8D25 id=${mail.id}:`,
2241
+ err instanceof Error ? err.message : err
2242
+ );
2243
+ return;
2244
+ }
2245
+ removeMailById(mail.id);
2246
+ const {
2247
+ tool: replyTool,
2248
+ getReplyContent,
2249
+ hasReplyContent
2250
+ } = createMailReplyDraft();
2251
+ try {
2252
+ const cursorMode = resolveCursorAgentMode(detail.studio?.phase);
2253
+ const result = await runCursorAgent(
2254
+ options.cfg,
2255
+ {
2256
+ mailId: detail.id,
2257
+ taskId: detail.taskId,
2258
+ prompt: detail.content,
2259
+ model: detail.recipient.model?.trim() || "default",
2260
+ apiKey: cursorApiKey,
2261
+ workdir: detail.workdir,
2262
+ user: detail.recipient.displayName,
2263
+ mode: cursorMode
2264
+ },
2265
+ {
2266
+ signal: options.signal,
2267
+ customTools: { append_mail_reply: replyTool }
2268
+ }
3574
2269
  );
3575
- if (err instanceof Error && err.stack) {
3576
- console.error(err.stack);
2270
+ const replyContent = (hasReplyContent() ? getReplyContent() : result.assistantText).trim();
2271
+ if (!replyContent) {
2272
+ throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
2273
+ }
2274
+ if (cursorMode === "plan" && result.createPlanContent) {
2275
+ saveMemberPlanDocument({
2276
+ workdir: detail.workdir,
2277
+ taskId: detail.taskId,
2278
+ memberDisplayName: detail.recipient.displayName,
2279
+ content: result.createPlanContent
2280
+ });
3577
2281
  }
2282
+ await api.cli.completeMailboxMessage({
2283
+ id: detail.id,
2284
+ status: "SUCCEEDED",
2285
+ replyContent
2286
+ });
2287
+ markMailReplied(detail.id);
2288
+ await syncTaskDocuments(options.cfg, detail.taskId, detail.workdir, {
2289
+ api,
2290
+ remoteDocuments: detail.documents
2291
+ });
2292
+ console.log(`[apm] \u4FE1\u4EF6\u5904\u7406\u5B8C\u6210 id=${mail.id}`);
2293
+ } catch (err) {
2294
+ const message = err instanceof Error ? err.message : String(err);
2295
+ console.error(`[apm] \u4FE1\u4EF6\u5904\u7406\u5931\u8D25 id=${mail.id}: ${message}`);
3578
2296
  try {
3579
- await setMessageError(
3580
- cfg,
3581
- messageId,
3582
- err instanceof Error ? err.message : String(err)
3583
- );
3584
- await updateMessageStatus(cfg, messageId, "FAILED");
3585
- } catch (statusErr) {
2297
+ await api.cli.completeMailboxMessage({
2298
+ id: detail.id,
2299
+ status: "FAILED",
2300
+ error: message
2301
+ });
2302
+ } catch (completeErr) {
3586
2303
  console.error(
3587
- "[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
3588
- statusErr instanceof Error ? statusErr.message : statusErr
2304
+ `[apm] \u6807\u8BB0\u4FE1\u4EF6\u5931\u8D25\u72B6\u6001\u65F6\u51FA\u9519 id=${mail.id}:`,
2305
+ completeErr instanceof Error ? completeErr.message : completeErr
3589
2306
  );
3590
2307
  }
2308
+ markMailReplied(detail.id);
2309
+ }
2310
+ }
2311
+ function createMailProcessor(options) {
2312
+ let running = false;
2313
+ const pump = () => {
2314
+ if (running || options.signal.aborted) {
2315
+ return;
2316
+ }
2317
+ running = true;
2318
+ void (async () => {
2319
+ try {
2320
+ while (!options.signal.aborted) {
2321
+ const mail = dequeueNextMail();
2322
+ if (!mail) {
2323
+ break;
2324
+ }
2325
+ await processMail(mail, options);
2326
+ }
2327
+ } finally {
2328
+ running = false;
2329
+ if (hasPendingMail() && !options.signal.aborted) {
2330
+ pump();
2331
+ }
2332
+ }
2333
+ })();
2334
+ };
2335
+ const ingest = (mail) => {
2336
+ if (enqueueReceivedMail(mail)) {
2337
+ pump();
2338
+ }
2339
+ };
2340
+ return {
2341
+ receive(msg) {
2342
+ ingest({
2343
+ id: msg.id,
2344
+ taskId: msg.taskId,
2345
+ createdAt: msg.createdAt
2346
+ });
2347
+ },
2348
+ syncPendingMails(mails) {
2349
+ let added = 0;
2350
+ for (const mail of mails) {
2351
+ if (enqueueReceivedMail(mail)) {
2352
+ added++;
2353
+ }
2354
+ }
2355
+ if (added > 0) {
2356
+ console.log(`[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 ${added} \u5C01`);
2357
+ } else if (mails.length > 0) {
2358
+ console.log(
2359
+ `[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 0 \u5C01\uFF08${mails.length} \u5C01\u5DF2\u5728\u961F\u5217\u4E2D\uFF09`
2360
+ );
2361
+ } else {
2362
+ console.log("[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 0 \u5C01");
2363
+ }
2364
+ pump();
2365
+ }
2366
+ };
2367
+ }
2368
+
2369
+ // src/commands/connect/mail-sync.ts
2370
+ function parsePendingMail(value) {
2371
+ if (typeof value !== "object" || value === null) {
2372
+ return null;
2373
+ }
2374
+ const row = value;
2375
+ if (typeof row.id !== "string" || typeof row.taskId !== "string") {
2376
+ return null;
2377
+ }
2378
+ const createdAt = typeof row.createdAt === "string" ? row.createdAt : row.createdAt instanceof Date ? row.createdAt.toISOString() : "";
2379
+ if (!createdAt) {
2380
+ return null;
2381
+ }
2382
+ return {
2383
+ id: row.id,
2384
+ taskId: row.taskId,
2385
+ createdAt
2386
+ };
2387
+ }
2388
+ async function fetchPendingMails(cfg) {
2389
+ const api = createApmApiClient(cfg);
2390
+ const list = await api.cli.listPendingMailboxMessages({});
2391
+ if (!Array.isArray(list)) {
2392
+ return [];
3591
2393
  }
2394
+ return list.flatMap((item) => {
2395
+ const mail = parsePendingMail(item);
2396
+ return mail ? [mail] : [];
2397
+ });
3592
2398
  }
2399
+
2400
+ // src/commands/connect.ts
2401
+ var HEARTBEAT_MS = 3e4;
3593
2402
  function startHeartbeat(ws, clientMachineId) {
3594
2403
  const send = () => {
3595
2404
  if (ws.readyState === WebSocket.OPEN) {
@@ -3639,11 +2448,17 @@ async function runConnect(options) {
3639
2448
  let stopHeartbeat;
3640
2449
  let shuttingDown = false;
3641
2450
  const shutdownAbort = new AbortController();
3642
- const runSlots = createRunSlotPool();
3643
- const activeTasks = /* @__PURE__ */ new Set();
3644
- const activeRuns = /* @__PURE__ */ new Map();
3645
- const pendingCancels = /* @__PURE__ */ new Set();
3646
- const shutdown = async (code = 0) => {
2451
+ let cursorApiKey = "";
2452
+ const mailProcessor = createMailProcessor({
2453
+ cfg,
2454
+ signal: shutdownAbort.signal,
2455
+ getCursorApiKey: () => cursorApiKey,
2456
+ onFatalError: (message) => {
2457
+ console.error(message);
2458
+ shutdown(1);
2459
+ }
2460
+ });
2461
+ const shutdown = (code = 0) => {
3647
2462
  if (shuttingDown) return;
3648
2463
  shuttingDown = true;
3649
2464
  logAbortSignalStats(
@@ -3656,19 +2471,34 @@ async function runConnect(options) {
3656
2471
  if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
3657
2472
  ws.terminate();
3658
2473
  }
3659
- try {
3660
- await Promise.race([
3661
- Promise.all(activeTasks),
3662
- new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
3663
- ]);
3664
- } catch {
3665
- }
3666
2474
  resolve5();
3667
2475
  process.exit(code);
3668
2476
  };
3669
2477
  ws.on("open", () => {
3670
2478
  console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
3671
2479
  stopHeartbeat = startHeartbeat(ws, clientMachineId);
2480
+ void (async () => {
2481
+ try {
2482
+ const api = createApmApiClient(cfg);
2483
+ const me = await api.cli.me({});
2484
+ cursorApiKey = me.cursorApiKey?.trim() ?? "";
2485
+ if (!cursorApiKey) {
2486
+ console.error(
2487
+ "[apm] \u5BA2\u6237\u673A\u672A\u914D\u7F6E Cursor API Key\uFF0C\u8BF7\u5728\u5E73\u53F0\u5BA2\u6237\u673A\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u540E\u91CD\u8BD5"
2488
+ );
2489
+ shutdown(1);
2490
+ return;
2491
+ }
2492
+ const mails = await fetchPendingMails(cfg);
2493
+ mailProcessor.syncPendingMails(mails);
2494
+ } catch (err) {
2495
+ console.error(
2496
+ "[apm] \u8FDE\u63A5\u540E\u521D\u59CB\u5316\u5931\u8D25:",
2497
+ err instanceof Error ? err.message : err
2498
+ );
2499
+ shutdown(1);
2500
+ }
2501
+ })();
3672
2502
  });
3673
2503
  ws.on("message", (data) => {
3674
2504
  if (shuttingDown) return;
@@ -3686,93 +2516,19 @@ async function runConnect(options) {
3686
2516
  console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
3687
2517
  return;
3688
2518
  }
3689
- if (validated.data.type === "cancel") {
3690
- const { messageId } = validated.data;
3691
- pendingCancels.add(messageId);
3692
- activeRuns.get(messageId)?.abort();
3693
- return;
3694
- }
3695
2519
  if (validated.data.type === "deploy") {
3696
- const msg2 = validated.data;
3697
- const perDeployController = new AbortController();
3698
- const signal2 = AbortSignal.any([
3699
- shutdownAbort.signal,
3700
- perDeployController.signal
3701
- ]);
3702
- const task2 = (async () => {
3703
- await runSlots.acquire();
3704
- try {
3705
- await handleInboundDeploy(cfg, msg2, signal2);
3706
- } finally {
3707
- runSlots.release();
3708
- }
3709
- })();
3710
- activeTasks.add(task2);
3711
- void task2.finally(() => {
3712
- activeTasks.delete(task2);
3713
- });
3714
- return;
3715
- }
3716
- if (validated.data.type === "coordinator_dispatch" || validated.data.type === "coordinator_dispatch_resume") {
3717
- const msg2 = validated.data;
3718
- const perDispatchController = new AbortController();
3719
- const signal2 = AbortSignal.any([
3720
- shutdownAbort.signal,
3721
- perDispatchController.signal
3722
- ]);
3723
- const task2 = (async () => {
3724
- await runSlots.acquire();
3725
- try {
3726
- await handleInboundCoordinatorDispatch(cfg, msg2, signal2);
3727
- } finally {
3728
- runSlots.release();
3729
- }
3730
- })();
3731
- activeTasks.add(task2);
3732
- void task2.finally(() => {
3733
- activeTasks.delete(task2);
3734
- });
2520
+ void handleInboundDeploy(cfg, validated.data, shutdownAbort.signal);
3735
2521
  return;
3736
2522
  }
3737
- if (validated.data.type !== "message") {
3738
- return;
3739
- }
3740
- const msg = validated.data;
3741
- const perMessageController = new AbortController();
3742
- activeRuns.set(msg.messageId, perMessageController);
3743
- if (pendingCancels.has(msg.messageId)) {
3744
- activeRuns.delete(msg.messageId);
3745
- pendingCancels.delete(msg.messageId);
3746
- return;
2523
+ if (validated.data.type === "received_mail") {
2524
+ mailProcessor.receive(validated.data);
3747
2525
  }
3748
- const signal = AbortSignal.any([
3749
- shutdownAbort.signal,
3750
- perMessageController.signal
3751
- ]);
3752
- const ctx = {
3753
- shutdownSignal: shutdownAbort.signal,
3754
- perMessageSignal: perMessageController.signal
3755
- };
3756
- const task = (async () => {
3757
- await runSlots.acquire();
3758
- try {
3759
- await handleInboundMessage(cfg, msg, signal, ctx);
3760
- } finally {
3761
- runSlots.release();
3762
- activeRuns.delete(msg.messageId);
3763
- pendingCancels.delete(msg.messageId);
3764
- }
3765
- })();
3766
- activeTasks.add(task);
3767
- void task.finally(() => {
3768
- activeTasks.delete(task);
3769
- });
3770
2526
  });
3771
2527
  ws.on("close", (code, reason) => {
3772
2528
  console.log(
3773
2529
  `[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
3774
2530
  );
3775
- void shutdown();
2531
+ shutdown();
3776
2532
  });
3777
2533
  ws.on("error", (err) => {
3778
2534
  console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
@@ -3780,55 +2536,31 @@ async function runConnect(options) {
3780
2536
  });
3781
2537
  process.on("SIGINT", () => {
3782
2538
  console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
3783
- void shutdown();
2539
+ shutdown();
3784
2540
  });
3785
2541
  process.on("SIGTERM", () => {
3786
- void shutdown();
2542
+ shutdown();
3787
2543
  });
3788
2544
  });
3789
2545
  }
3790
2546
 
3791
- // src/commands/create-pr.ts
3792
- async function runCreatePr(options) {
3793
- const sessionId = options.sessionId.trim();
3794
- if (!sessionId) {
3795
- console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
3796
- process.exit(1);
3797
- }
3798
- const title = options.title.trim();
3799
- if (!title) {
3800
- console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
3801
- process.exit(1);
3802
- }
3803
- const cfg = await ensureLoggedConfig();
3804
- const api = createApmApiClient(cfg);
3805
- const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
3806
- const pr = await api.cli.createPullRequest({
3807
- sessionId,
3808
- workdir,
3809
- title,
3810
- content: options.content ?? ""
3811
- });
3812
- console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
3813
- }
3814
-
3815
2547
  // src/commands/deploy/backend.ts
3816
2548
  import path5 from "node:path";
3817
2549
 
3818
2550
  // src/commands/deploy/internal/apm-config.ts
3819
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
2551
+ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
3820
2552
  import { resolve as resolve4 } from "node:path";
3821
2553
  function loadApmConfig(options) {
3822
2554
  const p = resolve4(
3823
2555
  process.cwd(),
3824
2556
  options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
3825
2557
  );
3826
- if (!existsSync13(p)) {
2558
+ if (!existsSync9(p)) {
3827
2559
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
3828
2560
  process.exit(1);
3829
2561
  }
3830
2562
  try {
3831
- const raw = readFileSync12(p, "utf8");
2563
+ const raw = readFileSync10(p, "utf8");
3832
2564
  return JSON.parse(raw);
3833
2565
  } catch (e) {
3834
2566
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -3950,7 +2682,7 @@ import path4 from "node:path";
3950
2682
  import Docker from "dockerode";
3951
2683
 
3952
2684
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
3953
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
2685
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
3954
2686
  import path from "node:path";
3955
2687
  function asOptionalTlsBuffer(value) {
3956
2688
  if (typeof value !== "string") {
@@ -3962,8 +2694,8 @@ function asOptionalTlsBuffer(value) {
3962
2694
  if (normalized === "") {
3963
2695
  return void 0;
3964
2696
  }
3965
- if (existsSync14(normalized)) {
3966
- return readFileSync13(normalized);
2697
+ if (existsSync10(normalized)) {
2698
+ return readFileSync11(normalized);
3967
2699
  }
3968
2700
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
3969
2701
  if (looksLikePath) {
@@ -4173,7 +2905,7 @@ var DockerodeClient = class {
4173
2905
  var createDockerodeClient = (config) => new DockerodeClient(config);
4174
2906
 
4175
2907
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
4176
- import { existsSync as existsSync15, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
2908
+ import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
4177
2909
  import path2 from "node:path";
4178
2910
  function stripSurroundingQuotes(value) {
4179
2911
  const t = value.trim();
@@ -4190,10 +2922,10 @@ function loadEnvFromFile(envFilePath) {
4190
2922
  return {};
4191
2923
  }
4192
2924
  const targetPath = path2.resolve(envFilePath);
4193
- if (!existsSync15(targetPath) || !statSync5(targetPath).isFile()) {
2925
+ if (!existsSync11(targetPath) || !statSync4(targetPath).isFile()) {
4194
2926
  return {};
4195
2927
  }
4196
- const raw = readFileSync14(targetPath, "utf-8");
2928
+ const raw = readFileSync12(targetPath, "utf-8");
4197
2929
  const result = {};
4198
2930
  for (const line of raw.split(/\r?\n/)) {
4199
2931
  const normalized = line.trim();
@@ -4364,12 +3096,12 @@ function dockerPushImage(params, cwd) {
4364
3096
  }
4365
3097
 
4366
3098
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
4367
- import { existsSync as existsSync16 } from "node:fs";
3099
+ import { existsSync as existsSync12 } from "node:fs";
4368
3100
  import path3 from "node:path";
4369
3101
  function resolveDockerBuildPaths(cwd) {
4370
3102
  const dockerfilePath = path3.join(cwd, "Dockerfile");
4371
3103
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
4372
- if (!existsSync16(dockerfilePath)) {
3104
+ if (!existsSync12(dockerfilePath)) {
4373
3105
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
4374
3106
  }
4375
3107
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -4498,14 +3230,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
4498
3230
  import path7 from "node:path";
4499
3231
 
4500
3232
  // src/commands/deploy/internal/minio.ts
4501
- import { statSync as statSync6 } from "node:fs";
3233
+ import { statSync as statSync5 } from "node:fs";
4502
3234
  import { readdir, readFile } from "node:fs/promises";
4503
3235
  import path6 from "node:path";
4504
3236
  import * as Minio from "minio";
4505
3237
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
4506
3238
  async function isDirectoryPath(dir) {
4507
3239
  try {
4508
- const st = statSync6(dir);
3240
+ const st = statSync5(dir);
4509
3241
  return st.isDirectory();
4510
3242
  } catch {
4511
3243
  return false;
@@ -4535,7 +3267,7 @@ async function collectFiles(root) {
4535
3267
  if (e.isDirectory()) {
4536
3268
  await walk(abs, rel);
4537
3269
  } else if (e.isFile()) {
4538
- const st = statSync6(abs);
3270
+ const st = statSync5(abs);
4539
3271
  out.push({
4540
3272
  absPath: abs,
4541
3273
  relativePath: rel.replace(/\\/g, "/"),
@@ -4993,7 +3725,7 @@ function registerDeployCommands(program) {
4993
3725
  function buildProgram() {
4994
3726
  const program = new Command();
4995
3727
  program.name("apm").description(
4996
- `\u6BD4\u90BB\u661F\u56FE\u547D\u4EE4\u884C\uFF08\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
3728
+ `\u6BD4\u90BB\u661F\u56FE\u547D\u4EE4\u884C\uFF08\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
4997
3729
  \u672A\u4F20 --server \u65F6\u4F18\u5148\u4F7F\u7528\u73AF\u5883\u53D8\u91CF AI_PM_SERVER\uFF0C\u5426\u5219\u9ED8\u8BA4 ${DEFAULT_BASE_URL}\u3002`
4998
3730
  ).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
4999
3731
  program.command("login").description(
@@ -5016,63 +3748,11 @@ function buildProgram() {
5016
3748
  ).action(async () => {
5017
3749
  await runUpdateSkills();
5018
3750
  });
5019
- program.command("sync-deploy-config").description(
5020
- "\u4ECE\u5E73\u53F0\u62C9\u53D6\u90E8\u7F72\u914D\u7F6E\uFF0C\u8986\u76D6 .apm/apm.config.json \u4E0E .apm/deploy/README.md\uFF08\u9700\u5DF2 login \u4E14\u5DE5\u4F5C\u7A7A\u95F4\u5DF2\u767B\u8BB0\u5E76\u7ED1\u5B9A\u4ED3\u5E93\uFF09"
5021
- ).action(async () => {
5022
- await runSyncDeployConfig();
5023
- });
5024
- program.command("sync-project-documents").description(
5025
- "\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\uFF1A\u9ED8\u8BA4 pull \u5230 .apm/project/\uFF1B\u52A0 --push \u5C06\u672C\u5730\u53D8\u66F4\u63A8\u9001\u5230\u5E73\u53F0"
5026
- ).option("--push", "\u63A8\u9001\u672C\u5730 .apm/project/ \u5230\u5E73\u53F0").option("--pull", "\u4ECE\u5E73\u53F0\u62C9\u53D6\u5230 .apm/project/").action(async (opts) => {
5027
- await runSyncProjectDocuments(opts);
5028
- });
5029
- program.command("pull").description(
5030
- "\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF0C\u5E76\u540C\u6B65\u90E8\u7F72\u914D\u7F6E\u4E0E\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u5230 .apm/project/"
5031
- ).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
5032
- await runPull(sessionId);
5033
- });
5034
- program.command("sync-document").description("\u5C06\u672C\u5730 Markdown \u8986\u76D6\u5F0F upsert \u5230\u5E73\u53F0\u4EFB\u52A1\u6587\u6863").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption(
5035
- "--file <name>",
5036
- "\u6587\u6863\u540D\u79F0\uFF08\u5982 PRD \u6216 PRD.md\uFF09\uFF0C\u8BFB\u53D6 .apm/sessions/<sessionId>/docs/ \u4E0B\u5BF9\u5E94\u6587\u4EF6"
5037
- ).action(async (sessionId, opts) => {
5038
- await runSyncDocument(sessionId, { file: opts.file });
5039
- });
5040
- program.command("append-message").description("\u5411\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u5185\u5BB9\uFF08PUT /api/v1/cli/messages/content\uFF09").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption("--content <content>", "\u8981\u8FFD\u52A0\u7684\u6D88\u606F\u5185\u5BB9").action(async (opts) => {
5041
- await runAppendMessage(opts);
5042
- });
5043
- program.command("update-message-status").description("\u66F4\u65B0\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u72B6\u6001").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption(
5044
- "--status <status>",
5045
- "CREATED | QUEUED | TYPING | SUCCESS | FAILED | CANCELLED"
5046
- ).action(async (opts) => {
5047
- await runUpdateMessageStatus(opts);
5048
- });
5049
3751
  program.command("connect").description(
5050
- "\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u4E0B\u884C message\uFF08TYPING \u2192 Cursor \u2192 SUCCESS/FAILED\uFF09\uFF1B\u542F\u52A8\u524D\u81EA\u52A8 apm update \u5230\u6700\u65B0\u7248"
3752
+ "\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u90E8\u7F72\u4E0B\u884C\u901A\u77E5\uFF1B\u542F\u52A8\u524D\u81EA\u52A8 apm update \u5230\u6700\u65B0\u7248"
5051
3753
  ).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
5052
3754
  await runConnect(opts);
5053
3755
  });
5054
- program.command("branch").description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
5055
- "-m, --message <text>",
5056
- "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
5057
- ).action(async (sessionId, opts) => {
5058
- await runBranch(sessionId, { message: opts.message });
5059
- });
5060
- program.command("clean-branches").description(
5061
- "\u6E05\u7406\u672C\u5730\u4E0E\u8FDC\u7A0B feat/session-* \u5206\u652F\uFF1A\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D\uFF0C\u6216\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210\u65F6\u5220\u9664"
5062
- ).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (opts) => {
5063
- await runCleanBranches({ dryRun: opts.dryRun });
5064
- });
5065
- program.command("create-pr").description(
5066
- "\u4E3A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u7684\u4F1A\u8BDD\u7279\u6027\u5206\u652F\u521B\u5EFA PR\uFF08\u6807\u9898\u81EA\u52A8\u52A0 [AI] \u6807\u8BC6\uFF1B\u8FDC\u7A0B\u521B\u5EFA\u5931\u8D25\u65F6\u5E73\u53F0\u6570\u636E\u56DE\u6EDA\uFF09"
5067
- ).requiredOption("--session <sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--title <title>", "PR \u6807\u9898").option("--content <content>", "PR \u6B63\u6587\uFF08Markdown\uFF09", "").action(
5068
- async (opts) => {
5069
- await runCreatePr({
5070
- sessionId: opts.session,
5071
- title: opts.title,
5072
- content: opts.content ?? ""
5073
- });
5074
- }
5075
- );
5076
3756
  registerDeployCommands(program);
5077
3757
  return program;
5078
3758
  }