ai-project-manage-cli 6.0.64 → 7.0.1

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" };
1174
+ function validateReceivedMailPush(o) {
1175
+ if (o.type !== "received_mail") {
1176
+ return { ok: false, reason: "\u671F\u671B received_mail" };
2224
1177
  }
2225
- if (!nonEmptyString(o.dispatchSessionId)) {
2226
- return {
2227
- ok: false,
2228
- reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchSessionId"
2229
- };
2230
- }
2231
- if (!validateCoordinatorDispatchMode(o.mode)) {
2232
- return { ok: false, reason: "coordinator_dispatch_resume.mode \u65E0\u6548" };
2233
- }
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
- };
1178
+ if (!nonEmptyString(o.id)) {
1179
+ return { ok: false, reason: "received_mail \u7F3A\u5C11 id" };
2242
1180
  }
2243
- if (!nonEmptyString(o.model)) {
2244
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 model" };
1181
+ if (!nonEmptyString(o.taskId)) {
1182
+ return { ok: false, reason: "received_mail \u7F3A\u5C11 taskId" };
2245
1183
  }
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" };
2251
- }
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,7 @@ 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;
2478
- }
2479
- if (text.startsWith("{") && text.endsWith("}")) {
2480
- try {
2481
- const parsed = JSON.parse(text);
2482
- if (typeof parsed.plan === "string") {
2483
- return formatPlanMarkdown(parsed.plan);
2484
- }
2485
- } catch {
2486
- }
2487
- }
2488
- if (!text.includes("\n") && text.includes("\\n")) {
2489
- text = text.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
2490
- }
2491
- return text.replace(/\r\n/g, "\n").trimEnd() + "\n";
2492
- }
2493
-
2494
- // src/session-utils.ts
1452
+ // src/event-session.ts
2495
1453
  var EventSession = class {
2496
1454
  events = [];
2497
1455
  dirtyIndices = /* @__PURE__ */ new Set();
@@ -2538,10 +1496,10 @@ var EventSession = class {
2538
1496
  if (latestEvent?.type === formatedEvent.type) {
2539
1497
  switch (formatedEvent.type) {
2540
1498
  case "assistant":
2541
- latestEvent.content += formatedEvent.content;
1499
+ latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
2542
1500
  break;
2543
1501
  case "thinking":
2544
- latestEvent.content += formatedEvent.content;
1502
+ latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
2545
1503
  break;
2546
1504
  case "task":
2547
1505
  latestEvent.status = formatedEvent.status;
@@ -2612,72 +1570,46 @@ var EventSession = class {
2612
1570
  this.dirtyIndices.delete(index);
2613
1571
  }
2614
1572
  }
2615
- /** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
2616
1573
  getAssistantText() {
2617
1574
  return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
2618
1575
  }
2619
- /** plan 模式下 createPlan 工具 completed 时的 plan 字段(取最后一次) */
2620
- 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");
2638
- }
2639
1576
  };
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
1577
 
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";
1578
+ // src/commands/connect/abort-signal-debug.ts
1579
+ import {
1580
+ getEventListeners,
1581
+ getMaxListeners,
1582
+ setMaxListeners
1583
+ } from "node:events";
1584
+ function isAbortSignalDebugEnabled() {
1585
+ const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
1586
+ return v === "1" || v === "true" || v === "yes";
1587
+ }
1588
+ function formatAbortSignalStats(signal, label) {
1589
+ if (!signal) {
1590
+ return `[apm:abort-debug] ${label}: (no signal)`;
2661
1591
  }
2662
- return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type}
2663
-
2664
- \`\`\`json
2665
- ${JSON.stringify(event, null, 2)}
2666
- \`\`\``;
1592
+ const listeners = getEventListeners(signal, "abort");
1593
+ const max = getMaxListeners(signal);
1594
+ return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
1595
+ }
1596
+ function logAbortSignalStats(signal, label) {
1597
+ if (!isAbortSignalDebugEnabled()) return;
1598
+ console.log(formatAbortSignalStats(signal, label));
2667
1599
  }
2668
1600
 
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";
1601
+ // src/commands/connect/agent-task-registry.ts
1602
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
2671
1603
  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");
1604
+ function registryPath(workdir, taskId) {
1605
+ return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
2674
1606
  }
2675
1607
  function readRegistry(path10) {
2676
- if (!existsSync11(path10)) {
1608
+ if (!existsSync7(path10)) {
2677
1609
  return {};
2678
1610
  }
2679
1611
  try {
2680
- const parsed = JSON.parse(readFileSync10(path10, "utf8"));
1612
+ const parsed = JSON.parse(readFileSync8(path10, "utf8"));
2681
1613
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2682
1614
  const result = {};
2683
1615
  for (const [key, value] of Object.entries(
@@ -2695,21 +1627,20 @@ function readRegistry(path10) {
2695
1627
  }
2696
1628
  function writeRegistry(path10, registry) {
2697
1629
  mkdirSync5(dirname4(path10), { recursive: true });
2698
- writeFileSync10(path10, `${JSON.stringify(registry, null, 2)}
1630
+ writeFileSync7(path10, `${JSON.stringify(registry, null, 2)}
2699
1631
  `, "utf8");
2700
1632
  }
2701
- function loadSessionAgentId(workdir, sessionId, user) {
2702
- const registry = readRegistry(registryPath(workdir, sessionId));
2703
- return registry[user];
1633
+ function loadTaskAgentId(workdir, taskId, user) {
1634
+ return readRegistry(registryPath(workdir, taskId))[user];
2704
1635
  }
2705
- function saveSessionAgentId(workdir, sessionId, user, agentId) {
2706
- const path10 = registryPath(workdir, sessionId);
1636
+ function saveTaskAgentId(workdir, taskId, user, agentId) {
1637
+ const path10 = registryPath(workdir, taskId);
2707
1638
  const registry = readRegistry(path10);
2708
1639
  registry[user] = agentId;
2709
1640
  writeRegistry(path10, registry);
2710
1641
  }
2711
- function clearSessionAgentId(workdir, sessionId, user) {
2712
- const path10 = registryPath(workdir, sessionId);
1642
+ function clearTaskAgentId(workdir, taskId, user) {
1643
+ const path10 = registryPath(workdir, taskId);
2713
1644
  const registry = readRegistry(path10);
2714
1645
  if (!(user in registry)) {
2715
1646
  return;
@@ -2718,9 +1649,9 @@ function clearSessionAgentId(workdir, sessionId, user) {
2718
1649
  writeRegistry(path10, registry);
2719
1650
  }
2720
1651
 
2721
- // src/commands/connect/cursor-message-log.ts
2722
- var CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS = 2e3;
2723
- function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
1652
+ // src/commands/connect/cursor-log.ts
1653
+ var CURSOR_LOG_SYNC_INTERVAL_MS = 2e3;
1654
+ function createThrottledCursorLogSync(cfg, ctx, onError) {
2724
1655
  let lastRunAt = 0;
2725
1656
  let timer;
2726
1657
  let latestSession;
@@ -2732,7 +1663,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2732
1663
  }
2733
1664
  lastRunAt = Date.now();
2734
1665
  try {
2735
- await syncCursorMessageLog(cfg, ctx, events);
1666
+ await syncCursorLog(cfg, ctx, events);
2736
1667
  session.clearDirty(events.map((event) => event.index));
2737
1668
  } catch (err) {
2738
1669
  onError(err);
@@ -2751,7 +1682,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2751
1682
  latestSession = session;
2752
1683
  const now = Date.now();
2753
1684
  const elapsed = now - lastRunAt;
2754
- if (elapsed >= CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS) {
1685
+ if (elapsed >= CURSOR_LOG_SYNC_INTERVAL_MS) {
2755
1686
  if (timer) {
2756
1687
  clearTimeout(timer);
2757
1688
  timer = void 0;
@@ -2765,7 +1696,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2765
1696
  timer = setTimeout(() => {
2766
1697
  timer = void 0;
2767
1698
  enqueueSync(latestSession);
2768
- }, CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS - elapsed);
1699
+ }, CURSOR_LOG_SYNC_INTERVAL_MS - elapsed);
2769
1700
  },
2770
1701
  async flush(session) {
2771
1702
  latestSession = session;
@@ -2778,336 +1709,51 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2778
1709
  }
2779
1710
  };
2780
1711
  }
2781
- async function syncCursorMessageLog(cfg, ctx, events) {
1712
+ async function syncCursorLog(cfg, ctx, events) {
2782
1713
  const agentId = ctx.agentId.trim();
2783
1714
  if (!agentId || events.length === 0) {
2784
1715
  return;
2785
1716
  }
2786
1717
  const api = createApmApiClient(cfg);
2787
- await api.cli.upsertCursorMessageLog({
2788
- sessionId: ctx.sessionId,
2789
- messageId: ctx.messageId,
1718
+ await api.cli.upsertCursorLog({
1719
+ taskId: ctx.taskId,
1720
+ mailboxMessageId: ctx.mailboxMessageId,
2790
1721
  agentId,
2791
1722
  events
2792
1723
  });
2793
1724
  }
2794
1725
 
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
1726
  // src/commands/connect/cursor-agent.ts
3064
1727
  setMaxListeners2(50);
3065
- installAbortSignalDebug();
3066
1728
  var noopRemoteLogSync = {
3067
1729
  schedule(_session) {
3068
1730
  },
3069
1731
  async flush(_session) {
3070
1732
  }
3071
1733
  };
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")}`;
1734
+ function collectAssistantText(events) {
1735
+ return events.flatMap((event) => {
1736
+ if (event.type !== "assistant") return [];
1737
+ const text = "text" in event ? String(event.text ?? "") : "";
1738
+ return text ? [text] : [];
1739
+ }).join("");
3089
1740
  }
3090
1741
  async function obtainAgent(ctx) {
3091
1742
  const agentOptions = {
3092
1743
  apiKey: ctx.apiKey,
3093
1744
  model: { id: ctx.model || "default" },
3094
1745
  local: {
3095
- cwd: ctx.cwd,
1746
+ cwd: ctx.workdir,
3096
1747
  ...ctx.customTools ? { customTools: ctx.customTools } : {}
3097
- },
3098
- ...ctx.mode ? { mode: ctx.mode } : {}
3099
- // mcpServers: createPlaywrightMcpServers(),
1748
+ }
3100
1749
  };
3101
- const explicitAgentId = ctx.resumeAgentId?.trim();
3102
- const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
1750
+ const savedAgentId = ctx.user ? loadTaskAgentId(ctx.workdir, ctx.taskId, ctx.user) : void 0;
3103
1751
  if (savedAgentId) {
3104
1752
  try {
3105
1753
  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
- );
1754
+ console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
3109
1755
  if (ctx.user) {
3110
- saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent2.agentId);
1756
+ saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent2.agentId);
3111
1757
  }
3112
1758
  return { agent: agent2, resumed: true };
3113
1759
  } catch (err) {
@@ -3115,14 +1761,14 @@ async function obtainAgent(ctx) {
3115
1761
  `[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
3116
1762
  err instanceof Error ? err.message : err
3117
1763
  );
3118
- if (!explicitAgentId && ctx.user) {
3119
- clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
1764
+ if (ctx.user) {
1765
+ clearTaskAgentId(ctx.workdir, ctx.taskId, ctx.user);
3120
1766
  }
3121
1767
  }
3122
1768
  }
3123
1769
  const agent = await Agent.create(agentOptions);
3124
1770
  if (ctx.user) {
3125
- saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent.agentId);
1771
+ saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent.agentId);
3126
1772
  }
3127
1773
  return { agent, resumed: false };
3128
1774
  }
@@ -3134,34 +1780,36 @@ async function runCursorAgent(cfg, ctx, options) {
3134
1780
  }
3135
1781
  const apiKey = ctx.apiKey.trim();
3136
1782
  if (!apiKey) {
3137
- throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
1783
+ throw new Error("\u7F3A\u5C11 Cursor API Key");
3138
1784
  }
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);
1785
+ const workdir = requireExistingWorkdir(ctx.workdir);
1786
+ const customTools = options?.customTools ?? {};
1787
+ const prompt = `${ctx.prompt.trim()}
1788
+
1789
+ ---
1790
+ \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`;
3144
1791
  console.log(
3145
- `[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${workdir}`
1792
+ `[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} cwd=${workdir}`
3146
1793
  );
3147
1794
  const { agent, resumed } = await obtainAgent({
3148
1795
  apiKey,
3149
1796
  model: ctx.model,
3150
- cwd: workdir,
3151
1797
  workdir,
3152
- sessionId: ctx.sessionId,
1798
+ taskId: ctx.taskId,
3153
1799
  user: ctx.user,
3154
- mode: ctx.mode,
3155
- resumeAgentId: ctx.resumeAgentId,
3156
1800
  customTools
3157
1801
  });
3158
1802
  const eventSession = new EventSession(prompt);
3159
- const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorMessageLogSync(
1803
+ const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorLogSync(
3160
1804
  cfg,
3161
- logCtx(ctx, agent.agentId),
1805
+ {
1806
+ taskId: ctx.taskId,
1807
+ mailboxMessageId: ctx.mailId,
1808
+ agentId: agent.agentId
1809
+ },
3162
1810
  (err) => {
3163
1811
  console.warn(
3164
- "[apm] \u540C\u6B65 Cursor \u6D88\u606F\u65E5\u5FD7\u5931\u8D25:",
1812
+ `[apm] Cursor \u65E5\u5FD7\u540C\u6B65\u5931\u8D25 mailId=${ctx.mailId}:`,
3165
1813
  err instanceof Error ? err.message : err
3166
1814
  );
3167
1815
  }
@@ -3172,85 +1820,47 @@ async function runCursorAgent(cfg, ctx, options) {
3172
1820
  void activeRun.cancel().catch(() => void 0);
3173
1821
  };
3174
1822
  signal?.addEventListener("abort", abortRun, { once: true });
3175
- logAbortSignalStats(signal, "runCursorAgent:after-addListener");
1823
+ const streamEvents = [];
3176
1824
  try {
3177
1825
  const run = await agent.send(prompt, {
3178
- ...ctx.mode ? { mode: ctx.mode } : {},
3179
- // mcpServers: createPlaywrightMcpServers(),
3180
- local: {
3181
- ...options?.forceSend ? { force: true } : {},
3182
- customTools
3183
- }
1826
+ local: { customTools }
3184
1827
  });
3185
1828
  activeRun = run;
3186
- logAbortSignalStats(signal, "runCursorAgent:after-send");
3187
1829
  console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
3188
- let lastRunErrorStatus;
3189
1830
  for await (const event of run.stream()) {
3190
1831
  if (signal?.aborted) {
3191
1832
  abortRun();
3192
1833
  throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
3193
1834
  }
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);
1835
+ streamEvents.push(event);
3204
1836
  eventSession.addEvent(event);
3205
1837
  syncRemoteLog.schedule(eventSession);
3206
1838
  }
3207
1839
  await syncRemoteLog.flush(eventSession);
3208
1840
  const result = await run.wait();
1841
+ const assistantText = eventSession.getAssistantText() || collectAssistantText(streamEvents);
3209
1842
  if (result.status === "error") {
3210
- const failureMessage = formatCursorRunFailure(result.id, {
3211
- statusError: lastRunErrorStatus,
3212
- resultText: result.result
3213
- });
3214
- console.error(`[apm] ${failureMessage}`);
3215
1843
  if (resumed) {
3216
- clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
1844
+ clearTaskAgentId(workdir, ctx.taskId, ctx.user);
3217
1845
  }
3218
- throw new Error(failureMessage);
1846
+ throw new Error(
1847
+ `Cursor run \u5931\u8D25: ${result.id}${result.result ? ` \u2014 ${result.result}` : ""}`
1848
+ );
3219
1849
  }
3220
1850
  if (result.status === "cancelled") {
3221
1851
  throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
3222
1852
  }
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
- }
1853
+ console.log(`[apm] Cursor Agent \u5B8C\u6210 mailId=${ctx.mailId}`);
3239
1854
  return {
3240
1855
  runId: result.id,
3241
1856
  agentId: agent.agentId,
3242
1857
  status: result.status,
3243
- result: result.result,
3244
- durationMs: result.durationMs,
3245
- assistantText: eventSession.getAssistantText(),
3246
- createPlan: eventSession.getCreatePlanContent(),
3247
- artifacts,
3248
- artifactDocuments
1858
+ assistantText: assistantText || result.result || ""
3249
1859
  };
3250
1860
  } catch (err) {
3251
1861
  if (err instanceof CursorAgentError) {
3252
1862
  if (resumed) {
3253
- clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
1863
+ clearTaskAgentId(workdir, ctx.taskId, ctx.user);
3254
1864
  }
3255
1865
  throw new Error(
3256
1866
  `Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
@@ -3258,338 +1868,428 @@ async function runCursorAgent(cfg, ctx, options) {
3258
1868
  }
3259
1869
  throw err;
3260
1870
  } finally {
3261
- logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
3262
1871
  signal?.removeEventListener("abort", abortRun);
3263
- logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
3264
1872
  await agent[Symbol.asyncDispose]();
3265
1873
  }
3266
1874
  }
3267
1875
 
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
1876
+ // src/commands/connect/mail-store.ts
1877
+ var pendingMails = [];
1878
+ var seenMailIds = /* @__PURE__ */ new Set();
1879
+ var repliedMailIds = /* @__PURE__ */ new Set();
1880
+ function hasMailReplied(mailId) {
1881
+ return repliedMailIds.has(mailId);
1882
+ }
1883
+ function markMailReplied(mailId) {
1884
+ repliedMailIds.add(mailId);
1885
+ seenMailIds.add(mailId);
1886
+ removeMailById(mailId);
1887
+ }
1888
+ function enqueueReceivedMail(mail) {
1889
+ if (seenMailIds.has(mail.id)) {
1890
+ return false;
1891
+ }
1892
+ seenMailIds.add(mail.id);
1893
+ pendingMails.push(mail);
1894
+ console.log(
1895
+ `[apm] \u6536\u5230\u4FE1\u4EF6 id=${mail.id} taskId=${mail.taskId} createdAt=${mail.createdAt}`
1896
+ );
1897
+ return true;
1898
+ }
1899
+ function dequeueNextMail() {
1900
+ return pendingMails.shift();
1901
+ }
1902
+ function hasPendingMail() {
1903
+ return pendingMails.length > 0;
1904
+ }
1905
+ function removeMailById(mailId) {
1906
+ const index = pendingMails.findIndex((mail) => mail.id === mailId);
1907
+ if (index < 0) {
1908
+ return false;
1909
+ }
1910
+ pendingMails.splice(index, 1);
1911
+ return true;
1912
+ }
1913
+
1914
+ // src/commands/connect/reply-mail-tool.ts
1915
+ function createMailReplyDraft() {
1916
+ const parts = [];
1917
+ const tool = {
1918
+ 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",
1919
+ inputSchema: {
1920
+ type: "object",
1921
+ properties: {
1922
+ content: {
1923
+ type: "string",
1924
+ description: "\u672C\u6B21\u8FFD\u52A0\u7684\u56DE\u590D\u7247\u6BB5"
1925
+ }
3292
1926
  },
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
- });
1927
+ required: ["content"]
1928
+ },
1929
+ execute: async (args) => {
1930
+ const content = typeof args.content === "string" ? args.content.trim() : "";
1931
+ if (!content) {
1932
+ return {
1933
+ content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
1934
+ isError: true
1935
+ };
3311
1936
  }
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
- });
1937
+ parts.push(content);
1938
+ console.log(`[apm] \u589E\u91CF\u66F4\u65B0\u56DE\u4FE1\uFF08\u7B2C ${parts.length} \u6BB5\uFF09`);
1939
+ return "\u5DF2\u8FFD\u52A0\u5230\u56DE\u4FE1\u8349\u7A3F\uFF0CAgent \u7ED3\u675F\u540E\u5C06\u4E00\u5E76\u63D0\u4EA4\u3002";
3337
1940
  }
3338
- }
1941
+ };
1942
+ return {
1943
+ tool,
1944
+ getReplyContent: () => parts.join("\n\n"),
1945
+ hasReplyContent: () => parts.some((part) => part.trim().length > 0)
1946
+ };
1947
+ }
1948
+
1949
+ // src/commands/connect/task-pull.ts
1950
+ import { writeFileSync as writeFileSync9 } from "fs";
1951
+ import { join as join12 } from "path";
1952
+ import { stringify as yamlStringify } from "yaml";
1953
+
1954
+ // src/rules-sync.ts
1955
+ import { basename as basename2, extname, join as join11 } from "path";
1956
+ import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
1957
+ var MANIFEST_FILE2 = ".rules-sync-manifest.json";
1958
+ function ruleLocalFileName(ruleName) {
1959
+ const trimmed = ruleName.trim();
1960
+ if (!trimmed) return "rule.md";
1961
+ const sanitized = trimmed.replace(/[/\\:*?"<>|]/g, "_");
1962
+ if (extname(sanitized).toLowerCase() === ".md") return sanitized;
1963
+ return `${sanitized}.md`;
3339
1964
  }
3340
-
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;
1965
+ function loadManifest(rulesDir) {
1966
+ const path10 = join11(rulesDir, MANIFEST_FILE2);
1967
+ if (!existsSync8(toFsPath(path10))) {
1968
+ return { version: 1, rules: {} };
3352
1969
  }
3353
1970
  try {
3354
1971
  const parsed = JSON.parse(
3355
- readFileSync11(path10, "utf8")
1972
+ readFileSync9(toFsPath(path10), "utf8")
3356
1973
  );
3357
- if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
1974
+ if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
3358
1975
  return parsed;
3359
1976
  }
3360
1977
  } catch {
3361
1978
  }
3362
- return null;
1979
+ return { version: 1, rules: {} };
3363
1980
  }
3364
- function saveManifest3(apmDir, cliVersion) {
3365
- const manifest = { version: 1, cliVersion };
3366
- writeFileSync11(
3367
- toFsPath(manifestPath(apmDir)),
1981
+ function saveManifest(rulesDir, manifest) {
1982
+ writeFileSync8(
1983
+ toFsPath(join11(rulesDir, MANIFEST_FILE2)),
3368
1984
  `${JSON.stringify(manifest, null, 2)}
3369
1985
  `,
3370
1986
  "utf8"
3371
1987
  );
3372
1988
  }
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;
1989
+ function isBaseRuleFileName(fileName) {
1990
+ return listBaseRuleFileNames().includes(basename2(fileName));
3385
1991
  }
3386
- function markSkillsSyncedForCliVersion(workdir, cliVersion) {
3387
- saveManifest3(workspaceApmDir(workdir), cliVersion);
3388
- syncedInSession.set(workdir, cliVersion);
1992
+ function isRuleUpToDate(entry, rule, dest) {
1993
+ if (!entry || !existsSync8(toFsPath(dest))) return false;
1994
+ if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
1995
+ const updatedAt = rule.updatedAt ?? "";
1996
+ if (entry.updatedAt !== updatedAt) return false;
1997
+ const localContent = readFileSync9(toFsPath(dest), "utf8");
1998
+ return localContent === (rule.content ?? "");
3389
1999
  }
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;
2000
+ async function syncPlatformRules(cfg, workdirPath, apmRoot) {
2001
+ const api = createApmApiClient(cfg);
2002
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
2003
+ const repositoryId = baseline.repositoryId;
2004
+ const rulesDir = join11(apmRoot ?? workspaceApmDir(workdirPath), "rules");
2005
+ await ensureDirExists(rulesDir);
2006
+ if (!repositoryId) {
2007
+ console.log(
2008
+ `[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`
2009
+ );
2010
+ return { written: [], skipped: [], removed: [], repositoryId: null };
3409
2011
  }
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();
2012
+ const { list } = await api.cli.listRules({ repositoryId });
2013
+ const manifest = loadManifest(rulesDir);
2014
+ const nextManifest = { version: 1, rules: {} };
2015
+ const remoteIds = /* @__PURE__ */ new Set();
2016
+ const written = [];
2017
+ const skipped = [];
2018
+ for (const rule of list) {
2019
+ remoteIds.add(rule.id);
2020
+ const fileName = ruleLocalFileName(rule.name);
2021
+ const dest = join11(rulesDir, fileName);
2022
+ const entry = manifest.rules[rule.id];
2023
+ const updatedAt = rule.updatedAt ?? "";
2024
+ if (isRuleUpToDate(entry, rule, dest)) {
2025
+ nextManifest.rules[rule.id] = entry;
2026
+ skipped.push(fileName);
2027
+ console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
2028
+ continue;
3425
2029
  }
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();
2030
+ writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
2031
+ nextManifest.rules[rule.id] = { fileName, updatedAt };
2032
+ written.push(fileName);
2033
+ console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
2034
+ }
2035
+ const removed = [];
2036
+ for (const [ruleId, entry] of Object.entries(manifest.rules)) {
2037
+ if (remoteIds.has(ruleId)) continue;
2038
+ if (isBaseRuleFileName(entry.fileName)) continue;
2039
+ const dest = join11(rulesDir, entry.fileName);
2040
+ if (existsSync8(toFsPath(dest))) {
2041
+ rmSync3(toFsPath(dest), { force: true });
3438
2042
  }
3439
- };
3440
- return { acquire, release };
2043
+ removed.push(entry.fileName);
2044
+ console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
2045
+ }
2046
+ saveManifest(rulesDir, nextManifest);
2047
+ return { written, skipped, removed, repositoryId };
3441
2048
  }
3442
2049
 
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}`);
3454
- }
3455
- var SHUTDOWN_DRAIN_MS = 3e3;
3456
- function isUserCancelled(ctx) {
3457
- return ctx.perMessageSignal.aborted && !ctx.shutdownSignal.aborted;
3458
- }
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);
2050
+ // src/commands/connect/task-pull.ts
2051
+ async function runTaskPull(cfg, detail) {
2052
+ const { taskId, workdir } = detail;
2053
+ assertWorkspaceApmDirExists(workdir);
3464
2054
  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
- };
2055
+ const dir = taskDir(taskId, apmRoot, workdir);
2056
+ const docsDir = taskDocsDir(taskId, apmRoot, workdir);
2057
+ await ensureDirExists(docsDir);
2058
+ writeFileSync9(taskRulePath(taskId, apmRoot, workdir), "", "utf8");
2059
+ writeFileSync9(
2060
+ taskTaskPath(taskId, apmRoot, workdir),
2061
+ detail.task.description ?? "",
2062
+ "utf8"
2063
+ );
2064
+ for (const doc of detail.documents) {
2065
+ const fileName = documentLocalFileName(doc.name);
2066
+ writeFileSync9(join12(docsDir, fileName), doc.content ?? "", "utf8");
2067
+ }
2068
+ const members = detail.studio?.members ?? [];
2069
+ const taskYaml = yamlStringify(
2070
+ {
2071
+ name: detail.studio?.title ?? detail.task.title,
2072
+ phase: detail.studio?.phase ?? null,
2073
+ task: "./TASK.md",
2074
+ members: members.map((member) => ({
2075
+ name: member.displayName,
2076
+ oxcAgent: member.oxcAgent?.name ?? "",
2077
+ description: member.oxcAgent?.description ?? "",
2078
+ isLead: member.isLead
2079
+ })),
2080
+ attachments: detail.attachments.map((item) => ({ name: item.name }))
2081
+ },
2082
+ { lineWidth: 0 }
2083
+ );
2084
+ writeFileSync9(
2085
+ taskYamlPath(taskId, apmRoot, workdir),
2086
+ taskYaml.endsWith("\n") ? taskYaml : `${taskYaml}
2087
+ `,
2088
+ "utf8"
2089
+ );
2090
+ await syncPlatformRules(cfg, workdir, apmRoot);
2091
+ await syncRemoteDeploymentConfig(workdir, apmRoot);
2092
+ await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
2093
+ console.log(`[apm] \u5DF2\u540C\u6B65\u4EFB\u52A1\u5DE5\u4F5C\u533A: ${toFsPath(dir)}`);
2094
+ return dir;
2095
+ }
2096
+
2097
+ // src/commands/connect/mail-processor.ts
2098
+ async function processMail(mail, options) {
2099
+ const api = createApmApiClient(options.cfg);
2100
+ console.log(`[apm] \u5F00\u59CB\u5904\u7406\u4FE1\u4EF6 id=${mail.id}`);
2101
+ if (hasMailReplied(mail.id)) {
2102
+ console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u5DF2\u56DE\u590D\u8FC7\uFF0C\u8DF3\u8FC7`);
2103
+ return;
2104
+ }
2105
+ const cursorApiKey = options.getCursorApiKey().trim();
2106
+ if (!cursorApiKey) {
2107
+ options.onFatalError(
2108
+ "[apm] \u5BA2\u6237\u673A\u672A\u914D\u7F6E Cursor API Key\uFF0C\u65E0\u6CD5\u5904\u7406\u4FE1\u4EF6\uFF0C\u65AD\u5F00\u8FDE\u63A5"
2109
+ );
2110
+ return;
2111
+ }
2112
+ let detail;
3476
2113
  try {
3477
- if (signal.aborted) return;
3478
- const { didInit } = await runStep(
3479
- "workspace-init",
3480
- () => ensureWorkspaceInitialized(workdir)
2114
+ detail = await api.cli.getMailboxMessageDetail({ id: mail.id });
2115
+ } catch (err) {
2116
+ console.error(
2117
+ `[apm] \u83B7\u53D6\u4FE1\u4EF6\u8BE6\u60C5\u5931\u8D25 id=${mail.id}:`,
2118
+ err instanceof Error ? err.message : err
3481
2119
  );
3482
- if (!didInit) {
3483
- assertApmGitignoredInRepo(workdir);
3484
- }
3485
- await runStep(
3486
- "status-typing",
3487
- () => updateMessageStatus(cfg, messageId, "TYPING")
2120
+ return;
2121
+ }
2122
+ try {
2123
+ await runTaskPull(options.cfg, detail);
2124
+ } catch (err) {
2125
+ console.error(
2126
+ `[apm] \u540C\u6B65\u4EFB\u52A1\u5DE5\u4F5C\u533A\u5931\u8D25 taskId=${detail.taskId}:`,
2127
+ err instanceof Error ? err.message : err
3488
2128
  );
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
- );
2129
+ return;
2130
+ }
2131
+ if (detail.status !== "PENDING") {
2132
+ console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u72B6\u6001\u4E3A ${detail.status}\uFF0C\u8DF3\u8FC7\u5904\u7406`);
2133
+ if (detail.status === "SUCCEEDED" || detail.status === "FAILED") {
2134
+ markMailReplied(mail.id);
3533
2135
  }
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
- );
2136
+ return;
2137
+ }
2138
+ try {
2139
+ await api.cli.claimMailboxMessage({ id: mail.id });
3566
2140
  } catch (err) {
3567
- if (isUserCancelled(ctx)) {
3568
- console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
3569
- return;
3570
- }
3571
2141
  console.error(
3572
- "[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
3573
- err instanceof Error ? err.message : String(err)
2142
+ `[apm] \u8BA4\u9886\u4FE1\u4EF6\u5931\u8D25 id=${mail.id}:`,
2143
+ err instanceof Error ? err.message : err
3574
2144
  );
3575
- if (err instanceof Error && err.stack) {
3576
- console.error(err.stack);
3577
- }
2145
+ return;
2146
+ }
2147
+ removeMailById(mail.id);
2148
+ const {
2149
+ tool: replyTool,
2150
+ getReplyContent,
2151
+ hasReplyContent
2152
+ } = createMailReplyDraft();
2153
+ try {
2154
+ const result = await runCursorAgent(
2155
+ options.cfg,
2156
+ {
2157
+ mailId: detail.id,
2158
+ taskId: detail.taskId,
2159
+ prompt: detail.content,
2160
+ model: detail.recipient.model?.trim() || "default",
2161
+ apiKey: cursorApiKey,
2162
+ workdir: detail.workdir,
2163
+ user: detail.recipient.displayName
2164
+ },
2165
+ {
2166
+ signal: options.signal,
2167
+ customTools: { append_mail_reply: replyTool }
2168
+ }
2169
+ );
2170
+ const replyContent = (hasReplyContent() ? getReplyContent() : result.assistantText).trim();
2171
+ if (!replyContent) {
2172
+ throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
2173
+ }
2174
+ await api.cli.completeMailboxMessage({
2175
+ id: detail.id,
2176
+ status: "SUCCEEDED",
2177
+ replyContent
2178
+ });
2179
+ markMailReplied(detail.id);
2180
+ await syncTaskDocuments(options.cfg, detail.taskId, detail.workdir, {
2181
+ api,
2182
+ remoteDocuments: detail.documents
2183
+ });
2184
+ console.log(`[apm] \u4FE1\u4EF6\u5904\u7406\u5B8C\u6210 id=${mail.id}`);
2185
+ } catch (err) {
2186
+ const message = err instanceof Error ? err.message : String(err);
2187
+ console.error(`[apm] \u4FE1\u4EF6\u5904\u7406\u5931\u8D25 id=${mail.id}: ${message}`);
3578
2188
  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) {
2189
+ await api.cli.completeMailboxMessage({
2190
+ id: detail.id,
2191
+ status: "FAILED",
2192
+ error: message
2193
+ });
2194
+ } catch (completeErr) {
3586
2195
  console.error(
3587
- "[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
3588
- statusErr instanceof Error ? statusErr.message : statusErr
2196
+ `[apm] \u6807\u8BB0\u4FE1\u4EF6\u5931\u8D25\u72B6\u6001\u65F6\u51FA\u9519 id=${mail.id}:`,
2197
+ completeErr instanceof Error ? completeErr.message : completeErr
3589
2198
  );
3590
2199
  }
3591
2200
  }
3592
2201
  }
2202
+ function createMailProcessor(options) {
2203
+ let running = false;
2204
+ const pump = () => {
2205
+ if (running || options.signal.aborted) {
2206
+ return;
2207
+ }
2208
+ running = true;
2209
+ void (async () => {
2210
+ try {
2211
+ while (!options.signal.aborted) {
2212
+ const mail = dequeueNextMail();
2213
+ if (!mail) {
2214
+ break;
2215
+ }
2216
+ await processMail(mail, options);
2217
+ }
2218
+ } finally {
2219
+ running = false;
2220
+ if (hasPendingMail() && !options.signal.aborted) {
2221
+ pump();
2222
+ }
2223
+ }
2224
+ })();
2225
+ };
2226
+ const ingest = (mail) => {
2227
+ if (enqueueReceivedMail(mail)) {
2228
+ pump();
2229
+ }
2230
+ };
2231
+ return {
2232
+ receive(msg) {
2233
+ ingest({
2234
+ id: msg.id,
2235
+ taskId: msg.taskId,
2236
+ createdAt: msg.createdAt
2237
+ });
2238
+ },
2239
+ syncPendingMails(mails) {
2240
+ let added = 0;
2241
+ for (const mail of mails) {
2242
+ if (enqueueReceivedMail(mail)) {
2243
+ added++;
2244
+ }
2245
+ }
2246
+ if (added > 0) {
2247
+ console.log(`[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 ${added} \u5C01`);
2248
+ } else if (mails.length > 0) {
2249
+ console.log(
2250
+ `[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 0 \u5C01\uFF08${mails.length} \u5C01\u5DF2\u5728\u961F\u5217\u4E2D\uFF09`
2251
+ );
2252
+ } else {
2253
+ console.log("[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 0 \u5C01");
2254
+ }
2255
+ pump();
2256
+ }
2257
+ };
2258
+ }
2259
+
2260
+ // src/commands/connect/mail-sync.ts
2261
+ function parsePendingMail(value) {
2262
+ if (typeof value !== "object" || value === null) {
2263
+ return null;
2264
+ }
2265
+ const row = value;
2266
+ if (typeof row.id !== "string" || typeof row.taskId !== "string") {
2267
+ return null;
2268
+ }
2269
+ const createdAt = typeof row.createdAt === "string" ? row.createdAt : row.createdAt instanceof Date ? row.createdAt.toISOString() : "";
2270
+ if (!createdAt) {
2271
+ return null;
2272
+ }
2273
+ return {
2274
+ id: row.id,
2275
+ taskId: row.taskId,
2276
+ createdAt
2277
+ };
2278
+ }
2279
+ async function fetchPendingMails(cfg) {
2280
+ const api = createApmApiClient(cfg);
2281
+ const list = await api.cli.listPendingMailboxMessages({});
2282
+ if (!Array.isArray(list)) {
2283
+ return [];
2284
+ }
2285
+ return list.flatMap((item) => {
2286
+ const mail = parsePendingMail(item);
2287
+ return mail ? [mail] : [];
2288
+ });
2289
+ }
2290
+
2291
+ // src/commands/connect.ts
2292
+ var HEARTBEAT_MS = 3e4;
3593
2293
  function startHeartbeat(ws, clientMachineId) {
3594
2294
  const send = () => {
3595
2295
  if (ws.readyState === WebSocket.OPEN) {
@@ -3639,11 +2339,17 @@ async function runConnect(options) {
3639
2339
  let stopHeartbeat;
3640
2340
  let shuttingDown = false;
3641
2341
  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) => {
2342
+ let cursorApiKey = "";
2343
+ const mailProcessor = createMailProcessor({
2344
+ cfg,
2345
+ signal: shutdownAbort.signal,
2346
+ getCursorApiKey: () => cursorApiKey,
2347
+ onFatalError: (message) => {
2348
+ console.error(message);
2349
+ shutdown(1);
2350
+ }
2351
+ });
2352
+ const shutdown = (code = 0) => {
3647
2353
  if (shuttingDown) return;
3648
2354
  shuttingDown = true;
3649
2355
  logAbortSignalStats(
@@ -3656,19 +2362,34 @@ async function runConnect(options) {
3656
2362
  if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
3657
2363
  ws.terminate();
3658
2364
  }
3659
- try {
3660
- await Promise.race([
3661
- Promise.all(activeTasks),
3662
- new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
3663
- ]);
3664
- } catch {
3665
- }
3666
2365
  resolve5();
3667
2366
  process.exit(code);
3668
2367
  };
3669
2368
  ws.on("open", () => {
3670
2369
  console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
3671
2370
  stopHeartbeat = startHeartbeat(ws, clientMachineId);
2371
+ void (async () => {
2372
+ try {
2373
+ const api = createApmApiClient(cfg);
2374
+ const me = await api.cli.me({});
2375
+ cursorApiKey = me.cursorApiKey?.trim() ?? "";
2376
+ if (!cursorApiKey) {
2377
+ console.error(
2378
+ "[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"
2379
+ );
2380
+ shutdown(1);
2381
+ return;
2382
+ }
2383
+ const mails = await fetchPendingMails(cfg);
2384
+ mailProcessor.syncPendingMails(mails);
2385
+ } catch (err) {
2386
+ console.error(
2387
+ "[apm] \u8FDE\u63A5\u540E\u521D\u59CB\u5316\u5931\u8D25:",
2388
+ err instanceof Error ? err.message : err
2389
+ );
2390
+ shutdown(1);
2391
+ }
2392
+ })();
3672
2393
  });
3673
2394
  ws.on("message", (data) => {
3674
2395
  if (shuttingDown) return;
@@ -3686,93 +2407,19 @@ async function runConnect(options) {
3686
2407
  console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
3687
2408
  return;
3688
2409
  }
3689
- if (validated.data.type === "cancel") {
3690
- const { messageId } = validated.data;
3691
- pendingCancels.add(messageId);
3692
- activeRuns.get(messageId)?.abort();
3693
- return;
3694
- }
3695
2410
  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
- });
2411
+ void handleInboundDeploy(cfg, validated.data, shutdownAbort.signal);
3735
2412
  return;
3736
2413
  }
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;
2414
+ if (validated.data.type === "received_mail") {
2415
+ mailProcessor.receive(validated.data);
3747
2416
  }
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
2417
  });
3771
2418
  ws.on("close", (code, reason) => {
3772
2419
  console.log(
3773
2420
  `[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
3774
2421
  );
3775
- void shutdown();
2422
+ shutdown();
3776
2423
  });
3777
2424
  ws.on("error", (err) => {
3778
2425
  console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
@@ -3780,55 +2427,31 @@ async function runConnect(options) {
3780
2427
  });
3781
2428
  process.on("SIGINT", () => {
3782
2429
  console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
3783
- void shutdown();
2430
+ shutdown();
3784
2431
  });
3785
2432
  process.on("SIGTERM", () => {
3786
- void shutdown();
2433
+ shutdown();
3787
2434
  });
3788
2435
  });
3789
2436
  }
3790
2437
 
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
2438
  // src/commands/deploy/backend.ts
3816
2439
  import path5 from "node:path";
3817
2440
 
3818
2441
  // src/commands/deploy/internal/apm-config.ts
3819
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
2442
+ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
3820
2443
  import { resolve as resolve4 } from "node:path";
3821
2444
  function loadApmConfig(options) {
3822
2445
  const p = resolve4(
3823
2446
  process.cwd(),
3824
2447
  options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
3825
2448
  );
3826
- if (!existsSync13(p)) {
2449
+ if (!existsSync9(p)) {
3827
2450
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
3828
2451
  process.exit(1);
3829
2452
  }
3830
2453
  try {
3831
- const raw = readFileSync12(p, "utf8");
2454
+ const raw = readFileSync10(p, "utf8");
3832
2455
  return JSON.parse(raw);
3833
2456
  } catch (e) {
3834
2457
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -3950,7 +2573,7 @@ import path4 from "node:path";
3950
2573
  import Docker from "dockerode";
3951
2574
 
3952
2575
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
3953
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
2576
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
3954
2577
  import path from "node:path";
3955
2578
  function asOptionalTlsBuffer(value) {
3956
2579
  if (typeof value !== "string") {
@@ -3962,8 +2585,8 @@ function asOptionalTlsBuffer(value) {
3962
2585
  if (normalized === "") {
3963
2586
  return void 0;
3964
2587
  }
3965
- if (existsSync14(normalized)) {
3966
- return readFileSync13(normalized);
2588
+ if (existsSync10(normalized)) {
2589
+ return readFileSync11(normalized);
3967
2590
  }
3968
2591
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
3969
2592
  if (looksLikePath) {
@@ -4173,7 +2796,7 @@ var DockerodeClient = class {
4173
2796
  var createDockerodeClient = (config) => new DockerodeClient(config);
4174
2797
 
4175
2798
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
4176
- import { existsSync as existsSync15, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
2799
+ import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
4177
2800
  import path2 from "node:path";
4178
2801
  function stripSurroundingQuotes(value) {
4179
2802
  const t = value.trim();
@@ -4190,10 +2813,10 @@ function loadEnvFromFile(envFilePath) {
4190
2813
  return {};
4191
2814
  }
4192
2815
  const targetPath = path2.resolve(envFilePath);
4193
- if (!existsSync15(targetPath) || !statSync5(targetPath).isFile()) {
2816
+ if (!existsSync11(targetPath) || !statSync4(targetPath).isFile()) {
4194
2817
  return {};
4195
2818
  }
4196
- const raw = readFileSync14(targetPath, "utf-8");
2819
+ const raw = readFileSync12(targetPath, "utf-8");
4197
2820
  const result = {};
4198
2821
  for (const line of raw.split(/\r?\n/)) {
4199
2822
  const normalized = line.trim();
@@ -4364,12 +2987,12 @@ function dockerPushImage(params, cwd) {
4364
2987
  }
4365
2988
 
4366
2989
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
4367
- import { existsSync as existsSync16 } from "node:fs";
2990
+ import { existsSync as existsSync12 } from "node:fs";
4368
2991
  import path3 from "node:path";
4369
2992
  function resolveDockerBuildPaths(cwd) {
4370
2993
  const dockerfilePath = path3.join(cwd, "Dockerfile");
4371
2994
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
4372
- if (!existsSync16(dockerfilePath)) {
2995
+ if (!existsSync12(dockerfilePath)) {
4373
2996
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
4374
2997
  }
4375
2998
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -4498,14 +3121,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
4498
3121
  import path7 from "node:path";
4499
3122
 
4500
3123
  // src/commands/deploy/internal/minio.ts
4501
- import { statSync as statSync6 } from "node:fs";
3124
+ import { statSync as statSync5 } from "node:fs";
4502
3125
  import { readdir, readFile } from "node:fs/promises";
4503
3126
  import path6 from "node:path";
4504
3127
  import * as Minio from "minio";
4505
3128
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
4506
3129
  async function isDirectoryPath(dir) {
4507
3130
  try {
4508
- const st = statSync6(dir);
3131
+ const st = statSync5(dir);
4509
3132
  return st.isDirectory();
4510
3133
  } catch {
4511
3134
  return false;
@@ -4535,7 +3158,7 @@ async function collectFiles(root) {
4535
3158
  if (e.isDirectory()) {
4536
3159
  await walk(abs, rel);
4537
3160
  } else if (e.isFile()) {
4538
- const st = statSync6(abs);
3161
+ const st = statSync5(abs);
4539
3162
  out.push({
4540
3163
  absPath: abs,
4541
3164
  relativePath: rel.replace(/\\/g, "/"),
@@ -4993,7 +3616,7 @@ function registerDeployCommands(program) {
4993
3616
  function buildProgram() {
4994
3617
  const program = new Command();
4995
3618
  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
3619
+ `\u6BD4\u90BB\u661F\u56FE\u547D\u4EE4\u884C\uFF08\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
4997
3620
  \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
3621
  ).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
4999
3622
  program.command("login").description(
@@ -5016,63 +3639,11 @@ function buildProgram() {
5016
3639
  ).action(async () => {
5017
3640
  await runUpdateSkills();
5018
3641
  });
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
3642
  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"
3643
+ "\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
3644
  ).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
5052
3645
  await runConnect(opts);
5053
3646
  });
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
3647
  registerDeployCommands(program);
5077
3648
  return program;
5078
3649
  }