ai-project-manage-cli 6.0.63 → 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,17 +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({
436
+ method: "PUT",
437
+ path: "/cli/task-deployments/complete"
438
+ }),
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"
450
+ }),
451
+ upsertDocument: defineEndpoint({
452
+ method: "PUT",
453
+ path: "/cli/documents/upsert"
454
+ }),
455
+ claimMailboxMessage: defineEndpoint({
456
+ method: "PUT",
457
+ path: "/cli/mailbox-messages/claim"
458
+ }),
459
+ completeMailboxMessage: defineEndpoint({
481
460
  method: "PUT",
482
- path: "/cli/coordinator-deployments/log"
461
+ path: "/cli/mailbox-messages/complete"
483
462
  }),
484
- completeCoordinatorDeployment: defineEndpoint({
463
+ upsertCursorLog: defineEndpoint({
485
464
  method: "PUT",
486
- path: "/cli/coordinator-deployments/complete"
465
+ path: "/cli/cursor-logs"
487
466
  })
488
467
  }
489
468
  };
@@ -519,7 +498,7 @@ async function tryReadGitOriginUrl(cwd) {
519
498
 
520
499
  // src/deployment-config-sync.ts
521
500
  var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json \u4E0E .apm/deploy/README.md";
522
- 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";
523
502
  async function resolveRepositoryIdForSync(api, workdirPath) {
524
503
  const baseline = await api.cli.workspaceBaseline({ workdirPath });
525
504
  if (baseline.repositoryId) {
@@ -543,7 +522,7 @@ async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
543
522
  if (!cfg || !resolveApiKey(cfg)) {
544
523
  console.log(
545
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
546
- [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`
547
526
  );
548
527
  return { synced: false, repositoryId: null };
549
528
  }
@@ -564,7 +543,7 @@ ${diagnostic ?? ""}
564
543
  if (!config) {
565
544
  console.log(
566
545
  `[apm] \u672A\u627E\u5230\u5173\u8054\u4ED3\u5E93\u7684\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF0CrepositoryId\uFF1A${repositoryId}\uFF09\u3002
567
- [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`
568
547
  );
569
548
  return { synced: false, repositoryId };
570
549
  }
@@ -595,13 +574,12 @@ ${diagnostic ?? ""}
595
574
 
596
575
  // src/repository-project-documents-sync.ts
597
576
  import {
598
- existsSync as existsSync2,
577
+ existsSync as existsSync3,
599
578
  readdirSync as readdirSync2,
600
579
  readFileSync as readFileSync3,
601
580
  rmSync,
602
581
  writeFileSync as writeFileSync4
603
582
  } from "fs";
604
- import { createHash } from "crypto";
605
583
  import { dirname as dirname2, join as join4, relative, sep } from "path";
606
584
  var MANIFEST_FILE = "manifest.json";
607
585
  function projectDocumentsDir(apmRoot) {
@@ -622,45 +600,19 @@ function normalizeLocalDocumentPath(path10) {
622
600
  }
623
601
  return segments.join("/");
624
602
  }
625
- function hashLocalFileContent(content) {
626
- return createHash("sha256").update(content, "utf8").digest("hex");
627
- }
628
603
  function readLocalManifest(apmRoot) {
629
- const manifestPath2 = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
630
- if (!existsSync2(manifestPath2)) {
604
+ const manifestPath = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
605
+ if (!existsSync3(manifestPath)) {
631
606
  return null;
632
607
  }
633
608
  try {
634
609
  return JSON.parse(
635
- readFileSync3(manifestPath2, "utf8")
610
+ readFileSync3(manifestPath, "utf8")
636
611
  );
637
612
  } catch {
638
613
  return null;
639
614
  }
640
615
  }
641
- function listLocalDocumentPaths(apmRoot) {
642
- const root = projectDocumentsDir(apmRoot);
643
- if (!existsSync2(root)) {
644
- return [];
645
- }
646
- const paths = [];
647
- const walk = (dir) => {
648
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
649
- const abs = join4(dir, entry.name);
650
- if (entry.isDirectory()) {
651
- walk(abs);
652
- continue;
653
- }
654
- if (entry.isFile() && entry.name === MANIFEST_FILE) {
655
- continue;
656
- }
657
- const rel = relative(root, abs).split(sep).join("/");
658
- paths.push(rel);
659
- }
660
- };
661
- walk(root);
662
- return paths.sort();
663
- }
664
616
  function diffManifestPaths(remote, local) {
665
617
  const remoteMap = new Map(
666
618
  (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
@@ -739,7 +691,7 @@ ${diagnostic ?? ""}`
739
691
  let deleted = 0;
740
692
  for (const path10 of deleteLocal) {
741
693
  const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
742
- if (existsSync2(absPath)) {
694
+ if (existsSync3(absPath)) {
743
695
  rmSync(absPath, { force: true });
744
696
  deleted += 1;
745
697
  }
@@ -760,50 +712,6 @@ ${diagnostic ?? ""}`
760
712
  deleted
761
713
  };
762
714
  }
763
- async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
764
- const api = createApmApiClient(cfg);
765
- const { repositoryId } = await resolveRepositoryIdForSync(api, workdirPath);
766
- if (!repositoryId) {
767
- return 0;
768
- }
769
- const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
770
- const localPaths = listLocalDocumentPaths(targetApmDir);
771
- if (localPaths.length === 0) {
772
- console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
773
- return 0;
774
- }
775
- const remoteManifest = (await api.cli.getRepositoryProjectDocumentManifest({ repositoryId })).manifest ?? null;
776
- const remoteHashByPath = new Map(
777
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
778
- );
779
- const remoteDescriptionByPath = new Map(
780
- (remoteManifest?.documents ?? []).map((doc) => [
781
- doc.path,
782
- doc.description
783
- ])
784
- );
785
- let synced = 0;
786
- for (const path10 of localPaths) {
787
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
788
- const content = readFileSync3(absPath, "utf8");
789
- const contentHash = hashLocalFileContent(content);
790
- if (remoteHashByPath.get(path10) === contentHash) {
791
- continue;
792
- }
793
- await api.cli.upsertRepositoryProjectDocument({
794
- repositoryId,
795
- path: path10,
796
- content,
797
- description: remoteDescriptionByPath.get(path10) ?? void 0
798
- });
799
- synced += 1;
800
- console.log(`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: ${path10}`);
801
- }
802
- if (synced === 0) {
803
- console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
804
- }
805
- return synced;
806
- }
807
715
 
808
716
  // src/git-utils.ts
809
717
  import { execFile as execFile2 } from "child_process";
@@ -913,14 +821,13 @@ async function runInit(name) {
913
821
  console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
914
822
  if (syncResult && !syncResult.synced) {
915
823
  console.log(
916
- "[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"
917
825
  );
918
826
  }
919
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");
920
828
  }
921
829
 
922
830
  // src/commands/login.ts
923
- import { existsSync as existsSync3 } from "fs";
924
831
  import { ApiError } from "listpage-http";
925
832
  async function runLogin(opts) {
926
833
  const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
@@ -973,500 +880,106 @@ async function runLogin(opts) {
973
880
  2
974
881
  )
975
882
  );
976
- const workdir = resolveWorkdirPath();
977
- const apmDir = workspaceApmDir(workdir);
978
- if (existsSync3(apmDir)) {
979
- await syncRemoteDeploymentConfig(workdir, apmDir);
980
- }
981
883
  }
982
884
 
983
- // src/commands/branch.ts
984
- import { execFile as execFile3 } from "child_process";
985
- import { promisify as promisify3 } from "util";
986
- var execFileAsync3 = promisify3(execFile3);
987
- var SESSION_BRANCH_PREFIX = "feat/session-";
988
- function branchNameForSession(sessionId) {
989
- const id = sessionId.trim();
990
- if (!id) {
991
- throw new Error("[apm] \u4F1A\u8BDD ID \u4E0D\u80FD\u4E3A\u7A7A");
992
- }
993
- if (/[\s/\\]/.test(id)) {
994
- throw new Error(
995
- "[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"
996
- );
997
- }
998
- return `${SESSION_BRANCH_PREFIX}${id}`;
999
- }
1000
- function sessionIdFromBranchName(branch) {
1001
- const name = branch.trim().replace(/^origin\//, "");
1002
- if (!name.startsWith(SESSION_BRANCH_PREFIX)) {
1003
- return null;
1004
- }
1005
- const sessionId = name.slice(SESSION_BRANCH_PREFIX.length).trim();
1006
- return sessionId || null;
1007
- }
1008
- 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() {
1009
894
  try {
1010
- const { stdout, stderr } = await execFileAsync3("git", args, {
1011
- cwd,
1012
- encoding: "utf8",
1013
- maxBuffer: 10 * 1024 * 1024
1014
- });
1015
- if (!quiet && stderr.trim()) {
1016
- process.stderr.write(stderr);
1017
- }
1018
- return stdout;
1019
- } catch (err) {
1020
- const e = err;
1021
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1022
- throw new Error(
1023
- `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
1024
- );
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";
1025
901
  }
1026
902
  }
1027
- async function ensureGitRepo(cwd) {
1028
- await execGit2(cwd, ["rev-parse", "--git-dir"], true);
1029
- }
1030
- async function getCurrentBranch(cwd) {
1031
- const name = (await execGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1032
- return name;
1033
- }
1034
- async function isWorkingTreeDirty(cwd) {
1035
- const out = await execGit2(cwd, ["status", "--porcelain"], true);
1036
- 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
+ });
1037
911
  }
1038
- async function remoteHeadBranchExists(cwd, branch) {
1039
- const out = await execGit2(
1040
- cwd,
1041
- ["ls-remote", "--heads", "origin", branch],
1042
- true
1043
- );
1044
- 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(/\/+$/, "");
1045
915
  }
1046
- async function localBranchExists(cwd, branch) {
916
+ async function fetchLatestPublishedVersion() {
917
+ const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
1047
918
  try {
1048
- await execGit2(
1049
- cwd,
1050
- ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
1051
- true
1052
- );
1053
- 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;
1054
923
  } catch {
1055
- return false;
924
+ return null;
1056
925
  }
1057
926
  }
1058
- async function commitWorkingTreeIfDirty(cwd, message) {
1059
- await ensureGitRepo(cwd);
1060
- if (!await isWorkingTreeDirty(cwd)) {
1061
- return false;
1062
- }
1063
- const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
1064
- await execGit2(cwd, ["add", "-A"]);
1065
- await execGit2(cwd, ["commit", "-m", commitMessage]);
1066
- console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
1067
- return true;
927
+ function npmAvailable() {
928
+ const r = runNpm(["--version"], { encoding: "utf8" });
929
+ return !r.error && r.status === 0;
1068
930
  }
1069
- async function ensureFeatureBranch(branch, baselineBranch, options) {
1070
- const cwd = options.cwd ?? process.cwd();
1071
- const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
1072
- await ensureGitRepo(cwd);
1073
- if (!await remoteHeadBranchExists(cwd, baselineBranch)) {
1074
- throw new Error(
1075
- `[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`
1076
- );
1077
- }
1078
- const current = await getCurrentBranch(cwd);
1079
- const dirty = await isWorkingTreeDirty(cwd);
1080
- if (dirty) {
1081
- if (current === branch) {
1082
- await commitWorkingTreeIfDirty(cwd, commitMessage);
1083
- } else {
1084
- await execGit2(cwd, [
1085
- "stash",
1086
- "push",
1087
- "-u",
1088
- "-m",
1089
- `apm: switch to ${branch}`
1090
- ]);
1091
- }
1092
- }
1093
- const onTargetBranch = await getCurrentBranch(cwd) === branch;
1094
- if (onTargetBranch) {
1095
- await execGit2(cwd, ["fetch", "origin", baselineBranch]);
1096
- await execGit2(cwd, ["merge", `origin/${baselineBranch}`, "--no-edit"]);
1097
- } else {
1098
- const remoteExists = await remoteHeadBranchExists(cwd, branch);
1099
- if (remoteExists) {
1100
- await execGit2(cwd, ["fetch", "origin", branch]);
1101
- await execGit2(cwd, ["checkout", "-B", branch, `origin/${branch}`]);
1102
- } else if (await localBranchExists(cwd, branch)) {
1103
- if (await getCurrentBranch(cwd) !== branch) {
1104
- await execGit2(cwd, ["checkout", branch]);
1105
- }
1106
- console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
1107
- } else {
1108
- await execGit2(cwd, ["fetch", "origin", baselineBranch]);
1109
- try {
1110
- await execGit2(cwd, [
1111
- "checkout",
1112
- "-b",
1113
- branch,
1114
- `origin/${baselineBranch}`
1115
- ]);
1116
- await execGit2(cwd, ["push", "-u", "origin", branch]);
1117
- } catch (err) {
1118
- 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
- throw err;
1125
- }
1126
- }
1127
- }
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 };
1128
937
  }
1129
- console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
1130
- return branch;
1131
- }
1132
- async function runBranch(sessionId, options = {}) {
1133
- const trimmedSessionId = sessionId.trim();
1134
- if (!trimmedSessionId) {
1135
- 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
+ );
1136
942
  process.exit(1);
1137
943
  }
1138
- const cfg = await ensureLoggedConfig();
1139
- const api = createApmApiClient(cfg);
1140
- const cwd = options.cwd ?? process.cwd();
1141
- const workdirPath = resolveWorkdirPath(cwd);
1142
- const baseline = await api.cli.branchBaseline({
1143
- sessionId: trimmedSessionId,
1144
- 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"
1145
950
  });
1146
- if (!baseline.repositoryId) {
1147
- 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
1148
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
1149
- throw new Error(`[apm] ${detail}`);
951
+ if (install.error) {
952
+ console.error("[apm] \u66F4\u65B0\u5931\u8D25:", install.error.message);
953
+ process.exit(1);
1150
954
  }
1151
- const baselineBranch = (baseline.defaultBranch ?? "").trim();
1152
- if (!baselineBranch) {
1153
- throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
955
+ if (install.status !== 0) {
956
+ process.exit(install.status ?? 1);
1154
957
  }
1155
- const branch = branchNameForSession(trimmedSessionId);
1156
- return ensureFeatureBranch(branch, baselineBranch, options);
1157
- }
1158
-
1159
- // src/commands/clean-branches.ts
1160
- import { execFile as execFile4 } from "child_process";
1161
- import { promisify as promisify4 } from "util";
1162
- var execFileAsync4 = promisify4(execFile4);
1163
- async function execGit3(cwd, args, quiet) {
1164
- try {
1165
- const { stdout, stderr } = await execFileAsync4("git", args, {
1166
- cwd,
1167
- encoding: "utf8",
1168
- maxBuffer: 10 * 1024 * 1024
1169
- });
1170
- if (!quiet && stderr.trim()) {
1171
- process.stderr.write(stderr);
1172
- }
1173
- return stdout;
1174
- } catch (err) {
1175
- const e = err;
1176
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1177
- throw new Error(
1178
- `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
1179
- );
1180
- }
1181
- }
1182
- async function ensureGitRepo2(cwd) {
1183
- await execGit3(cwd, ["rev-parse", "--git-dir"], true);
1184
- }
1185
- async function getCurrentBranch2(cwd) {
1186
- return (await execGit3(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1187
- }
1188
- async function resolveDefaultBranch(cwd) {
1189
- try {
1190
- const ref = (await execGit3(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], true)).trim();
1191
- const match = ref.match(/^refs\/remotes\/origin\/(.+)$/);
1192
- if (match?.[1]) {
1193
- return match[1];
1194
- }
1195
- } catch {
1196
- }
1197
- const out = await execGit3(cwd, ["remote", "show", "origin"], true);
1198
- const headLine = out.split(/\r?\n/).find((line) => line.includes("HEAD branch"));
1199
- const branch = headLine?.split(":").pop()?.trim();
1200
- if (branch) {
1201
- return branch;
1202
- }
1203
- throw new Error("[apm] \u65E0\u6CD5\u89E3\u6790 origin \u9ED8\u8BA4\u5206\u652F\uFF0C\u8BF7\u5148\u6267\u884C git fetch origin");
1204
- }
1205
- async function listLocalSessionBranches(cwd) {
1206
- const out = await execGit3(
1207
- cwd,
1208
- [
1209
- "for-each-ref",
1210
- "--format=%(refname:short)",
1211
- "refs/heads/",
1212
- SESSION_BRANCH_PREFIX + "*"
1213
- ],
1214
- true
1215
- );
1216
- return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1217
- }
1218
- async function listRemoteSessionBranches(cwd) {
1219
- const out = await execGit3(
1220
- cwd,
1221
- [
1222
- "for-each-ref",
1223
- "--format=%(refname:short)",
1224
- "refs/remotes/origin/",
1225
- SESSION_BRANCH_PREFIX + "*"
1226
- ],
1227
- true
1228
- );
1229
- return out.split(/\r?\n/).map((line) => line.trim().replace(/^origin\//, "")).filter(Boolean);
1230
- }
1231
- async function localBranchExists2(cwd, branch) {
1232
- try {
1233
- await execGit3(
1234
- cwd,
1235
- ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
1236
- true
1237
- );
1238
- return true;
1239
- } catch {
1240
- return false;
1241
- }
1242
- }
1243
- async function remoteBranchExists(cwd, branch) {
1244
- const out = await execGit3(
1245
- cwd,
1246
- ["ls-remote", "--heads", "origin", branch],
1247
- true
1248
- );
1249
- return out.trim().length > 0;
1250
- }
1251
- function reasonForCleanup(sessionId, sessionStatusById) {
1252
- if (!sessionStatusById.has(sessionId)) {
1253
- return "\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D";
1254
- }
1255
- if (sessionStatusById.get(sessionId) === "COMPLETED") {
1256
- return "\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210";
1257
- }
1258
- return "\u4FDD\u7559";
1259
- }
1260
- async function isBranchMergedIntoDefault(cwd, branch, defaultBranch) {
1261
- const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
1262
- try {
1263
- await execGit3(
1264
- cwd,
1265
- ["merge-base", "--is-ancestor", ref, `origin/${defaultBranch}`],
1266
- true
1267
- );
1268
- return true;
1269
- } catch {
1270
- return false;
1271
- }
1272
- }
1273
- async function runCleanBranches(options = {}) {
1274
- const cwd = options.cwd ?? process.cwd();
1275
- const dryRun = options.dryRun ?? false;
1276
- await ensureGitRepo2(cwd);
1277
- await execGit3(cwd, ["fetch", "--prune", "origin"], true);
1278
- const cfg = await ensureLoggedConfig();
1279
- const api = createApmApiClient(cfg);
1280
- const { sessions } = await api.cli.listSessionsForBranchCleanup({});
1281
- const sessionStatusById = new Map(
1282
- sessions.map((item) => [item.sessionId, item.taskStatus])
1283
- );
1284
- const branchNames = /* @__PURE__ */ new Set([
1285
- ...await listLocalSessionBranches(cwd),
1286
- ...await listRemoteSessionBranches(cwd)
1287
- ]);
1288
- if (branchNames.size === 0) {
1289
- console.log("[apm] \u672A\u53D1\u73B0 feat/session-* \u5206\u652F");
1290
- return;
1291
- }
1292
- const toDelete = [...branchNames].map((branch) => {
1293
- const sessionId = sessionIdFromBranchName(branch);
1294
- if (!sessionId) {
1295
- return null;
1296
- }
1297
- const reason = reasonForCleanup(sessionId, sessionStatusById);
1298
- if (reason === "\u4FDD\u7559") {
1299
- return null;
1300
- }
1301
- return { branch, sessionId, reason };
1302
- }).filter((item) => item != null).sort((a, b) => a.branch.localeCompare(b.branch));
1303
- if (toDelete.length === 0) {
1304
- console.log("[apm] \u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684 feat/session-* \u5206\u652F");
1305
- return;
1306
- }
1307
- let currentBranch = await getCurrentBranch2(cwd);
1308
- let defaultBranch = null;
1309
- if (dryRun) {
1310
- defaultBranch = await resolveDefaultBranch(cwd);
1311
- }
1312
- for (const item of toDelete) {
1313
- const { branch, sessionId, reason } = item;
1314
- const label = `${branch} (${sessionId}: ${reason})`;
1315
- if (dryRun) {
1316
- const merged = await isBranchMergedIntoDefault(
1317
- cwd,
1318
- branch,
1319
- defaultBranch
1320
- );
1321
- const mergeTag = merged ? "\u5DF2\u5408\u5E76" : "\u672A\u5408\u5E76";
1322
- console.log(`[apm] [dry-run] \u5C06\u5220\u9664 ${label} [${mergeTag}]`);
1323
- continue;
1324
- }
1325
- if (currentBranch === branch) {
1326
- defaultBranch ??= await resolveDefaultBranch(cwd);
1327
- await execGit3(cwd, ["checkout", defaultBranch], true);
1328
- currentBranch = defaultBranch;
1329
- }
1330
- if (await localBranchExists2(cwd, branch)) {
1331
- await execGit3(cwd, ["branch", "-D", branch], true);
1332
- console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
1333
- }
1334
- if (await remoteBranchExists(cwd, branch)) {
1335
- await execGit3(cwd, ["push", "origin", "--delete", branch], true);
1336
- console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
1337
- }
1338
- }
1339
- if (dryRun) {
1340
- console.log(`[apm] [dry-run] \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`);
958
+ const after = readCliVersion();
959
+ if (latest && after === latest) {
960
+ console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
1341
961
  } else {
1342
- console.log(`[apm] \u5DF2\u6E05\u7406 ${toDelete.length} \u4E2A feat/session-* \u5206\u652F`);
1343
- }
1344
- }
1345
-
1346
- // src/commands/pull.ts
1347
- import { writeFileSync as writeFileSync9 } from "fs";
1348
- import { join as join9 } from "path";
1349
- import { stringify as yamlStringify } from "yaml";
1350
-
1351
- // src/session-messages-xml.ts
1352
- function asXmlText(value) {
1353
- return value ?? "";
1354
- }
1355
- function escapeXmlAttr(value) {
1356
- return asXmlText(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1357
- }
1358
- function wrapCdata(value) {
1359
- return `<![CDATA[${asXmlText(value).replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
1360
- }
1361
- function formatSessionMessagesXml(sessionId, messages) {
1362
- const lines = [
1363
- '<?xml version="1.0" encoding="UTF-8"?>',
1364
- `<messages sessionId="${escapeXmlAttr(sessionId)}">`
1365
- ];
1366
- for (const message of messages) {
1367
- const roundAttr = message.round != null && message.round > 0 ? ` round="${message.round}"` : "";
1368
- lines.push(
1369
- ` <message id="${escapeXmlAttr(message.id)}" name="${escapeXmlAttr(
1370
- message.name
1371
- )}" agent="${escapeXmlAttr(message.oxcAgent)}"${roundAttr}>`,
1372
- ` <content>${wrapCdata(message.content)}</content>`,
1373
- " </message>"
1374
- );
1375
- }
1376
- lines.push("</messages>", "");
1377
- return lines.join("\n");
1378
- }
1379
-
1380
- // src/commands/sync-session-attachments.ts
1381
- import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
1382
- import { join as join6 } from "path";
1383
- var MANIFEST_FILE2 = ".sync-manifest.json";
1384
- async function downloadAttachment(cfg, attachmentId) {
1385
- const base = cfg.baseUrl.trim().replace(/\/+$/, "");
1386
- const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({ attachmentId })}`;
1387
- const res = await fetch(url);
1388
- if (!res.ok) {
1389
- throw new Error(
1390
- `[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
1391
- );
1392
- }
1393
- return Buffer.from(await res.arrayBuffer());
1394
- }
1395
- function loadManifest(dir) {
1396
- const path10 = join6(dir, MANIFEST_FILE2);
1397
- if (!existsSync4(path10)) {
1398
- return { version: 1, attachments: {} };
1399
- }
1400
- try {
1401
- const parsed = JSON.parse(
1402
- readFileSync5(path10, "utf8")
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`
1403
964
  );
1404
- if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
1405
- return parsed;
1406
- }
1407
- } catch {
1408
965
  }
1409
- return { version: 1, attachments: {} };
1410
- }
1411
- function saveManifest(dir, manifest) {
1412
- writeFileSync6(
1413
- join6(dir, MANIFEST_FILE2),
1414
- `${JSON.stringify(manifest, null, 2)}
1415
- `,
1416
- "utf8"
1417
- );
1418
- }
1419
- function isAttachmentUpToDate(entry, item, dest) {
1420
- if (!entry || !existsSync4(dest)) return false;
1421
- if (entry.name !== item.name) return false;
1422
- const createdAt = item.createdAt ?? "";
1423
- return entry.createdAt === createdAt;
1424
- }
1425
- async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
1426
- const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
1427
- await ensureDirExists(dir);
1428
- if (attachments.length === 0) {
1429
- saveManifest(dir, { version: 1, attachments: {} });
1430
- return;
1431
- }
1432
- const manifest = loadManifest(dir);
1433
- const nextManifest = { version: 1, attachments: {} };
1434
- for (const item of attachments) {
1435
- const dest = join6(dir, item.name);
1436
- const entry = manifest.attachments[item.id];
1437
- const createdAt = item.createdAt ?? "";
1438
- if (isAttachmentUpToDate(entry, item, dest)) {
1439
- nextManifest.attachments[item.id] = entry;
1440
- console.log(
1441
- `[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
1442
- );
1443
- continue;
1444
- }
1445
- const buffer = await downloadAttachment(cfg, item.id);
1446
- writeFileSync6(dest, buffer);
1447
- nextManifest.attachments[item.id] = {
1448
- name: item.name,
1449
- createdAt
1450
- };
1451
- console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`);
1452
- }
1453
- saveManifest(dir, nextManifest);
966
+ return { didUpdate: true };
1454
967
  }
1455
968
 
1456
- // src/rules-sync.ts
1457
- import { basename as basename2, extname as extname2, join as join8 } from "path";
1458
- 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";
1459
972
 
1460
973
  // src/skills-sync.ts
1461
974
  import {
1462
975
  copyFileSync as copyFileSync2,
1463
976
  cpSync,
1464
- existsSync as existsSync5,
977
+ existsSync as existsSync4,
1465
978
  mkdirSync as mkdirSync3,
1466
979
  readdirSync as readdirSync3,
1467
980
  rmSync as rmSync2,
1468
981
  statSync as statSync2,
1469
- writeFileSync as writeFileSync7
982
+ writeFileSync as writeFileSync6
1470
983
  } from "fs";
1471
984
  import { join as join7 } from "path";
1472
985
  var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
@@ -1478,20 +991,20 @@ function sanitizeSkillDirName(name) {
1478
991
  return trimmed.replace(/[/\\:*?"<>|]/g, "_");
1479
992
  }
1480
993
  function listBaseSkillDirNames() {
1481
- if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
994
+ if (!existsSync4(BASE_SKILLS_TEMPLATE_DIR)) return [];
1482
995
  return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
1483
996
  const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
1484
997
  return statSync2(path10).isDirectory();
1485
998
  });
1486
999
  }
1487
1000
  function syncAgentsGuide(apmDir) {
1488
- if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
1001
+ if (!existsSync4(AGENTS_TEMPLATE_PATH)) return false;
1489
1002
  mkdirSync3(apmDir, { recursive: true });
1490
1003
  copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
1491
1004
  return true;
1492
1005
  }
1493
1006
  function listBaseRuleFileNames() {
1494
- if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
1007
+ if (!existsSync4(BASE_RULES_TEMPLATE_DIR)) return [];
1495
1008
  return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
1496
1009
  const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
1497
1010
  return statSync2(path10).isFile();
@@ -1531,523 +1044,80 @@ function syncSupplementarySkills(skillsDir, list) {
1531
1044
  }
1532
1045
  const skillDir = join7(skillsDir, dirName);
1533
1046
  mkdirSync3(skillDir, { recursive: true });
1534
- writeFileSync7(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
1047
+ writeFileSync6(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
1535
1048
  written.push(dirName);
1536
1049
  }
1537
1050
  const removed = [];
1538
- if (!existsSync5(skillsDir)) return { written, skipped, removed };
1051
+ if (!existsSync4(skillsDir)) return { written, skipped, removed };
1539
1052
  for (const entry of readdirSync3(skillsDir)) {
1540
1053
  const full = join7(skillsDir, entry);
1541
1054
  if (!statSync2(full).isDirectory()) continue;
1542
1055
  if (baseNames.has(entry)) continue;
1543
- if (apiDirNames.has(entry)) continue;
1544
- rmSync2(full, { recursive: true, force: true });
1545
- removed.push(entry);
1546
- }
1547
- return { written, skipped, removed };
1548
- }
1549
-
1550
- // src/rules-sync.ts
1551
- var MANIFEST_FILE3 = ".rules-sync-manifest.json";
1552
- function ruleLocalFileName(ruleName) {
1553
- const trimmed = ruleName.trim();
1554
- if (!trimmed) return "rule.md";
1555
- const sanitized = trimmed.replace(/[/\\:*?"<>|]/g, "_");
1556
- if (extname2(sanitized).toLowerCase() === ".md") return sanitized;
1557
- return `${sanitized}.md`;
1558
- }
1559
- function loadManifest2(rulesDir) {
1560
- const path10 = join8(rulesDir, MANIFEST_FILE3);
1561
- if (!existsSync6(toFsPath(path10))) {
1562
- return { version: 1, rules: {} };
1563
- }
1564
- try {
1565
- const parsed = JSON.parse(
1566
- readFileSync6(toFsPath(path10), "utf8")
1567
- );
1568
- if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
1569
- return parsed;
1570
- }
1571
- } catch {
1572
- }
1573
- return { version: 1, rules: {} };
1574
- }
1575
- function saveManifest2(rulesDir, manifest) {
1576
- writeFileSync8(
1577
- toFsPath(join8(rulesDir, MANIFEST_FILE3)),
1578
- `${JSON.stringify(manifest, null, 2)}
1579
- `,
1580
- "utf8"
1581
- );
1582
- }
1583
- function isBaseRuleFileName(fileName) {
1584
- return listBaseRuleFileNames().includes(basename2(fileName));
1585
- }
1586
- function isRuleUpToDate(entry, rule, dest) {
1587
- if (!entry || !existsSync6(toFsPath(dest))) return false;
1588
- if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
1589
- const updatedAt = rule.updatedAt ?? "";
1590
- if (entry.updatedAt !== updatedAt) return false;
1591
- const localContent = readFileSync6(toFsPath(dest), "utf8");
1592
- return localContent === (rule.content ?? "");
1593
- }
1594
- async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
1595
- const api = createApmApiClient(cfg);
1596
- const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
1597
- const repositoryId = baseline.repositoryId;
1598
- const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
1599
- await ensureDirExists(rulesDir);
1600
- if (!repositoryId) {
1601
- console.log(
1602
- `[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`
1603
- );
1604
- return { written: [], skipped: [], removed: [], repositoryId: null };
1605
- }
1606
- const { list } = await api.cli.listRules({ repositoryId });
1607
- const manifest = loadManifest2(rulesDir);
1608
- const nextManifest = { version: 1, rules: {} };
1609
- const remoteIds = /* @__PURE__ */ new Set();
1610
- const written = [];
1611
- const skipped = [];
1612
- for (const rule of list) {
1613
- remoteIds.add(rule.id);
1614
- const fileName = ruleLocalFileName(rule.name);
1615
- const dest = join8(rulesDir, fileName);
1616
- const entry = manifest.rules[rule.id];
1617
- const updatedAt = rule.updatedAt ?? "";
1618
- if (isRuleUpToDate(entry, rule, dest)) {
1619
- nextManifest.rules[rule.id] = entry;
1620
- skipped.push(fileName);
1621
- console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
1622
- continue;
1623
- }
1624
- writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
1625
- nextManifest.rules[rule.id] = { fileName, updatedAt };
1626
- written.push(fileName);
1627
- console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
1628
- }
1629
- const removed = [];
1630
- for (const [ruleId, entry] of Object.entries(manifest.rules)) {
1631
- if (remoteIds.has(ruleId)) continue;
1632
- if (isBaseRuleFileName(entry.fileName)) continue;
1633
- const dest = join8(rulesDir, entry.fileName);
1634
- if (existsSync6(toFsPath(dest))) {
1635
- rmSync3(toFsPath(dest), { force: true });
1636
- }
1637
- removed.push(entry.fileName);
1638
- console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
1639
- }
1640
- saveManifest2(rulesDir, nextManifest);
1641
- return { written, skipped, removed, repositoryId };
1642
- }
1643
-
1644
- // src/commands/pull.ts
1645
- async function runPull(sessionId, remoteWorkdir) {
1646
- const trimmedId = sessionId.trim();
1647
- if (!trimmedId) {
1648
- console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1649
- process.exit(1);
1650
- }
1651
- const cfg = await ensureLoggedConfig();
1652
- const api = createApmApiClient(cfg);
1653
- const workdir = remoteWorkdir === void 0 ? resolveWorkdirPath() : requireRemoteWorkdir(remoteWorkdir);
1654
- const apmRoot = workspaceApmDir(workdir);
1655
- const [detail, members, documents, attachments, messages] = await Promise.all(
1656
- [
1657
- api.cli.sessionDetail({ sessionId: trimmedId }),
1658
- api.cli.sessionMembers({ sessionId: trimmedId }),
1659
- api.cli.listDocuments({ sessionId: trimmedId }),
1660
- api.cli.listAttachments({ sessionId: trimmedId }),
1661
- api.cli.listSessionMessages({ sessionId: trimmedId })
1662
- ]
1663
- );
1664
- const dir = sessionDir(trimmedId, apmRoot);
1665
- const docsDir = sessionDocsDir(trimmedId, apmRoot);
1666
- await ensureDirExists(docsDir);
1667
- writeFileSync9(
1668
- sessionRulePath(trimmedId, apmRoot),
1669
- detail.description ?? "",
1670
- "utf8"
1671
- );
1672
- writeFileSync9(
1673
- sessionTaskPath(trimmedId, apmRoot),
1674
- detail.task.description ?? "",
1675
- "utf8"
1676
- );
1677
- writeFileSync9(sessionTodoPath(trimmedId, apmRoot), detail.todo ?? "", "utf8");
1678
- for (const doc of documents) {
1679
- const fileName = documentLocalFileName(doc.name);
1680
- writeFileSync9(join9(docsDir, fileName), doc.content ?? "", "utf8");
1681
- }
1682
- const sessionYaml = yamlStringify(
1683
- {
1684
- name: detail.title,
1685
- task: "./TASK.md",
1686
- todo: "./TODO.md",
1687
- rule: "./RULE.md",
1688
- members: members.map((m) => ({
1689
- name: m.displayName,
1690
- oxcAgent: m.oxcAgent,
1691
- description: m.description ?? ""
1692
- })),
1693
- attachments: attachments.map((a) => ({ name: a.name }))
1694
- },
1695
- { lineWidth: 0 }
1696
- );
1697
- writeFileSync9(
1698
- sessionYamlPath(trimmedId, apmRoot),
1699
- sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
1700
- `,
1701
- "utf8"
1702
- );
1703
- writeFileSync9(
1704
- sessionMessagesXmlPath(trimmedId, apmRoot),
1705
- formatSessionMessagesXml(trimmedId, messages),
1706
- "utf8"
1707
- );
1708
- await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
1709
- await syncPlatformRules(cfg, trimmedId, workdir, apmRoot);
1710
- await syncRemoteDeploymentConfig(workdir, apmRoot);
1711
- await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
1712
- console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
1713
- return dir;
1714
- }
1715
-
1716
- // src/commands/update.ts
1717
- import { spawnSync } from "child_process";
1718
-
1719
- // src/version.ts
1720
- import { readFileSync as readFileSync7 } from "fs";
1721
- import { dirname as dirname3, join as join10 } from "path";
1722
- import { fileURLToPath as fileURLToPath2 } from "url";
1723
- var CLI_PACKAGE_NAME = "ai-project-manage-cli";
1724
- function readCliVersion() {
1725
- try {
1726
- const dir = dirname3(fileURLToPath2(import.meta.url));
1727
- const pkgPath = join10(dir, "..", "package.json");
1728
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
1729
- return pkg.version ?? "0.0.0";
1730
- } catch {
1731
- return "0.0.0";
1732
- }
1733
- }
1734
-
1735
- // src/commands/update.ts
1736
- var useNpmShell = process.platform === "win32";
1737
- function runNpm(args, options = {}) {
1738
- return spawnSync(useNpmShell ? "npm.cmd" : "npm", args, {
1739
- ...options,
1740
- shell: useNpmShell
1741
- });
1742
- }
1743
- function registryBaseUrl() {
1744
- const fromEnv = process.env.npm_config_registry?.trim() || process.env.NPM_CONFIG_REGISTRY?.trim();
1745
- return (fromEnv || "https://registry.npmjs.org").replace(/\/+$/, "");
1746
- }
1747
- async function fetchLatestPublishedVersion() {
1748
- const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
1749
- try {
1750
- const res = await fetch(url);
1751
- if (!res.ok) return null;
1752
- const data = await res.json();
1753
- return data.version?.trim() || null;
1754
- } catch {
1755
- return null;
1756
- }
1757
- }
1758
- function npmAvailable() {
1759
- const r = runNpm(["--version"], { encoding: "utf8" });
1760
- return !r.error && r.status === 0;
1761
- }
1762
- async function runUpdate() {
1763
- const current = readCliVersion();
1764
- const latest = await fetchLatestPublishedVersion();
1765
- if (latest && current === latest) {
1766
- console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
1767
- return { didUpdate: false };
1768
- }
1769
- if (!npmAvailable()) {
1770
- console.error(
1771
- `[apm] \u672A\u627E\u5230 npm\u3002\u8BF7\u5B89\u88C5 Node.js \u540E\u6267\u884C\uFF1Anpm install -g ${CLI_PACKAGE_NAME}@latest`
1772
- );
1773
- process.exit(1);
1774
- }
1775
- const targetLabel = latest ?? "latest";
1776
- console.error(
1777
- `[apm] \u5F53\u524D\u7248\u672C ${current}\uFF0C\u6B63\u5728\u5B89\u88C5 ${CLI_PACKAGE_NAME}@${targetLabel} \u2026`
1778
- );
1779
- const install = runNpm(["install", "-g", `${CLI_PACKAGE_NAME}@latest`], {
1780
- stdio: "inherit"
1781
- });
1782
- if (install.error) {
1783
- console.error("[apm] \u66F4\u65B0\u5931\u8D25:", install.error.message);
1784
- process.exit(1);
1785
- }
1786
- if (install.status !== 0) {
1787
- process.exit(install.status ?? 1);
1788
- }
1789
- const after = readCliVersion();
1790
- if (latest && after === latest) {
1791
- console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
1792
- } else {
1793
- console.log(
1794
- `[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`
1795
- );
1796
- }
1797
- return { didUpdate: true };
1798
- }
1799
-
1800
- // src/commands/update-skills.ts
1801
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
1802
- import { join as join11 } from "path";
1803
- async function syncWorkspaceSkills(cfg, workdir) {
1804
- const apmDir = workspaceApmDir(workdir);
1805
- const fsApmDir = toFsPath(apmDir);
1806
- if (!existsSync7(fsApmDir)) {
1807
- throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1808
- }
1809
- const apmStat = statSync3(fsApmDir);
1810
- if (!apmStat.isDirectory()) {
1811
- throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1812
- }
1813
- const api = createApmApiClient(cfg);
1814
- const { list } = await api.cli.listSkills({});
1815
- if (syncAgentsGuide(apmDir)) {
1816
- console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
1817
- }
1818
- const rulesDir = join11(apmDir, "rules");
1819
- const ruleNames = syncBaseRules(rulesDir);
1820
- for (const name of ruleNames) {
1821
- console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
1822
- }
1823
- const skillsDir = join11(apmDir, "skills");
1824
- mkdirSync4(toFsPath(skillsDir), { recursive: true });
1825
- const baseNames = syncBaseSkills(skillsDir);
1826
- for (const name of baseNames) {
1827
- console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u6280\u80FD: skills/${name}/`);
1828
- }
1829
- const { written, skipped, removed } = syncSupplementarySkills(
1830
- skillsDir,
1831
- list
1832
- );
1833
- for (const name of written) {
1834
- console.log(`[apm] \u5DF2\u5199\u5165\u8865\u5145\u6280\u80FD: skills/${name}/SKILL.md`);
1835
- }
1836
- for (const name of skipped) {
1837
- console.log(
1838
- `[apm] \u5DF2\u8DF3\u8FC7\u4E0E\u57FA\u7840\u6280\u80FD\u540C\u540D\u7684\u8865\u5145\u6280\u80FD: ${name}\uFF08\u4FDD\u7559\u6A21\u677F\u7248\u672C\uFF09`
1839
- );
1840
- }
1841
- for (const name of removed) {
1842
- console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
1843
- }
1844
- console.log(
1845
- `[apm] \u540C\u6B65\u5B8C\u6210\uFF1A${ruleNames.length} \u4E2A\u57FA\u7840\u89C4\u5219\uFF0C${baseNames.length} \u4E2A\u57FA\u7840\u6280\u80FD\uFF0C${written.length} \u4E2A\u8865\u5145\u6280\u80FD`
1846
- );
1847
- }
1848
- async function runUpdateSkills() {
1849
- const apmDir = workspaceApmDir();
1850
- if (!existsSync7(apmDir)) {
1851
- console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1852
- process.exit(1);
1853
- }
1854
- const apmStat = statSync3(apmDir);
1855
- if (!apmStat.isDirectory()) {
1856
- throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1857
- }
1858
- const cfg = await ensureLoggedConfig();
1859
- await syncWorkspaceSkills(cfg, resolveWorkdirPath());
1860
- }
1861
-
1862
- // src/commands/sync-deploy-config.ts
1863
- import { existsSync as existsSync8, statSync as statSync4 } from "fs";
1864
- async function runSyncDeployConfig() {
1865
- const workdir = resolveWorkdirPath();
1866
- const apmDir = workspaceApmDir(workdir);
1867
- if (!existsSync8(apmDir)) {
1868
- console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1869
- process.exit(1);
1870
- }
1871
- const apmStat = statSync4(apmDir);
1872
- if (!apmStat.isDirectory()) {
1873
- throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1874
- }
1875
- await ensureLoggedConfig();
1876
- const result = await syncRemoteDeploymentConfig(workdir, apmDir);
1877
- if (!result.synced) {
1878
- process.exit(1);
1879
- }
1880
- }
1881
-
1882
- // src/commands/sync-project-documents.ts
1883
- async function runSyncProjectDocuments(options) {
1884
- const pull = options?.pull ?? !options?.push;
1885
- const push = options?.push ?? false;
1886
- const workdir = resolveWorkdirPath();
1887
- const apmRoot = workspaceApmDir(workdir);
1888
- if (pull) {
1889
- await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
1890
- }
1891
- if (push) {
1892
- const cfg = await ensureLoggedConfig();
1893
- await syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot);
1894
- }
1895
- }
1896
-
1897
- // src/commands/sync-document.ts
1898
- import { existsSync as existsSync10 } from "fs";
1899
- import { basename as basename3 } from "path";
1900
-
1901
- // src/commands/sync-session-documents.ts
1902
- import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
1903
- import { join as join12 } from "path";
1904
- function listLocalMarkdownFiles(docsDir) {
1905
- if (!existsSync9(docsDir)) {
1906
- return [];
1907
- }
1908
- return readdirSync4(docsDir).filter(
1909
- (name) => name.toLowerCase().endsWith(".md")
1910
- );
1911
- }
1912
- function remoteDocumentByLocalName(remoteDocuments, localFileName) {
1913
- const platformName = documentPlatformName(localFileName);
1914
- return remoteDocuments.find((doc) => {
1915
- const remoteLocalName = documentLocalFileName(doc.name);
1916
- return remoteLocalName === localFileName || documentPlatformName(doc.name) === platformName;
1917
- });
1918
- }
1919
- async function upsertLocalDocumentFile(api, sessionId, docsDir, fileName) {
1920
- const absPath = join12(docsDir, fileName);
1921
- const content = readFileSync8(absPath, "utf8");
1922
- const name = documentPlatformName(absPath);
1923
- return api.cli.upsertDocument({
1924
- sessionId,
1925
- name,
1926
- content
1927
- });
1928
- }
1929
- async function syncSessionDocuments(cfg, sessionId, apmRoot, options) {
1930
- const trimmedSessionId = sessionId.trim();
1931
- if (!trimmedSessionId) {
1932
- return 0;
1933
- }
1934
- const docsDir = sessionDocsDir(trimmedSessionId, apmRoot);
1935
- const localFiles = listLocalMarkdownFiles(docsDir);
1936
- if (localFiles.length === 0) {
1937
- return 0;
1938
- }
1939
- const api = options?.api ?? createApmApiClient(cfg);
1940
- const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ sessionId: trimmedSessionId });
1941
- let synced = 0;
1942
- for (const fileName of localFiles) {
1943
- const absPath = join12(docsDir, fileName);
1944
- const content = readFileSync8(absPath, "utf8");
1945
- const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
1946
- if (remote && remote.content === content) {
1947
- continue;
1948
- }
1949
- const doc = await upsertLocalDocumentFile(
1950
- api,
1951
- trimmedSessionId,
1952
- docsDir,
1953
- fileName
1954
- );
1955
- synced += 1;
1956
- console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
1957
- }
1958
- if (synced === 0) {
1959
- console.log("[apm] \u4F1A\u8BDD\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
1056
+ if (apiDirNames.has(entry)) continue;
1057
+ rmSync2(full, { recursive: true, force: true });
1058
+ removed.push(entry);
1960
1059
  }
1961
- return synced;
1060
+ return { written, skipped, removed };
1962
1061
  }
1963
1062
 
1964
- // src/commands/sync-document.ts
1965
- async function runSyncDocument(sessionId, options) {
1966
- const trimmedSessionId = sessionId.trim();
1967
- if (!trimmedSessionId) {
1968
- console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1969
- process.exit(1);
1970
- }
1971
- const fileArg = options.file?.trim();
1972
- if (!fileArg) {
1973
- console.error("[apm] \u8BF7\u6307\u5B9A --file <\u6587\u6863\u540D\u79F0>");
1974
- process.exit(1);
1063
+ // src/commands/update-skills.ts
1064
+ async function syncWorkspaceSkills(cfg, workdir) {
1065
+ const apmDir = workspaceApmDir(workdir);
1066
+ const fsApmDir = toFsPath(apmDir);
1067
+ if (!existsSync5(fsApmDir)) {
1068
+ throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1975
1069
  }
1976
- const absPath = resolveSessionDocumentPath(trimmedSessionId, fileArg);
1977
- if (!existsSync10(absPath)) {
1978
- const docsDir2 = sessionDocsDir(trimmedSessionId);
1979
- console.error(
1980
- `[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
1981
- [apm] \u8BF7\u786E\u8BA4\u5DF2 pull\uFF0C\u4E14 ${docsDir2} \u4E0B\u5B58\u5728\u5BF9\u5E94\u6587\u4EF6`
1982
- );
1983
- process.exit(1);
1070
+ const apmStat = statSync3(fsApmDir);
1071
+ if (!apmStat.isDirectory()) {
1072
+ throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1984
1073
  }
1985
- const cfg = await ensureLoggedConfig();
1986
1074
  const api = createApmApiClient(cfg);
1987
- const docsDir = sessionDocsDir(trimmedSessionId);
1988
- const doc = await upsertLocalDocumentFile(
1989
- api,
1990
- trimmedSessionId,
1991
- docsDir,
1992
- basename3(absPath)
1993
- );
1994
- console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
1995
- }
1996
-
1997
- // src/commands/append-message.ts
1998
- async function appendMessageContent(cfg, messageId, content) {
1999
- const trimmedId = messageId.trim();
2000
- if (!trimmedId) {
2001
- throw new Error("messageId \u4E0D\u80FD\u4E3A\u7A7A");
1075
+ const { list } = await api.cli.listSkills({});
1076
+ if (syncAgentsGuide(apmDir)) {
1077
+ console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
2002
1078
  }
2003
- if (!content) {
2004
- throw new Error("content \u4E0D\u80FD\u4E3A\u7A7A");
1079
+ const rulesDir = join8(apmDir, "rules");
1080
+ const ruleNames = syncBaseRules(rulesDir);
1081
+ for (const name of ruleNames) {
1082
+ console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
2005
1083
  }
2006
- const api = createApmApiClient(cfg);
2007
- await api.cli.appendMessageContent({ id: trimmedId, content });
2008
- }
2009
- async function runAppendMessage(options) {
2010
- const messageId = options.id?.trim();
2011
- if (!messageId) {
2012
- console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
2013
- process.exit(1);
1084
+ const skillsDir = join8(apmDir, "skills");
1085
+ mkdirSync4(toFsPath(skillsDir), { recursive: true });
1086
+ const baseNames = syncBaseSkills(skillsDir);
1087
+ for (const name of baseNames) {
1088
+ console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u6280\u80FD: skills/${name}/`);
2014
1089
  }
2015
- const content = options.content ?? "";
2016
- if (!content) {
2017
- console.error("[apm] \u8BF7\u6307\u5B9A --content <\u5185\u5BB9>");
2018
- process.exit(1);
1090
+ const { written, skipped, removed } = syncSupplementarySkills(
1091
+ skillsDir,
1092
+ list
1093
+ );
1094
+ for (const name of written) {
1095
+ console.log(`[apm] \u5DF2\u5199\u5165\u8865\u5145\u6280\u80FD: skills/${name}/SKILL.md`);
2019
1096
  }
2020
- const cfg = await ensureLoggedConfig();
2021
- await appendMessageContent(cfg, messageId, content);
2022
- console.log(`[apm] \u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9: ${messageId}`);
1097
+ for (const name of skipped) {
1098
+ console.log(
1099
+ `[apm] \u5DF2\u8DF3\u8FC7\u4E0E\u57FA\u7840\u6280\u80FD\u540C\u540D\u7684\u8865\u5145\u6280\u80FD: ${name}\uFF08\u4FDD\u7559\u6A21\u677F\u7248\u672C\uFF09`
1100
+ );
1101
+ }
1102
+ for (const name of removed) {
1103
+ console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
1104
+ }
1105
+ console.log(
1106
+ `[apm] \u540C\u6B65\u5B8C\u6210\uFF1A${ruleNames.length} \u4E2A\u57FA\u7840\u89C4\u5219\uFF0C${baseNames.length} \u4E2A\u57FA\u7840\u6280\u80FD\uFF0C${written.length} \u4E2A\u8865\u5145\u6280\u80FD`
1107
+ );
2023
1108
  }
2024
-
2025
- // src/commands/update-message-status.ts
2026
- var VALID_STATUSES = [
2027
- "CREATED",
2028
- "QUEUED",
2029
- "TYPING",
2030
- "SUCCESS",
2031
- "FAILED",
2032
- "CANCELLED"
2033
- ];
2034
- async function runUpdateMessageStatus(options) {
2035
- const messageId = options.id?.trim();
2036
- const status = options.status?.trim().toUpperCase();
2037
- if (!messageId) {
2038
- console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
1109
+ async function runUpdateSkills() {
1110
+ const apmDir = workspaceApmDir();
1111
+ if (!existsSync5(apmDir)) {
1112
+ console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
2039
1113
  process.exit(1);
2040
1114
  }
2041
- if (!VALID_STATUSES.includes(status)) {
2042
- console.error(
2043
- `[apm] \u65E0\u6548\u72B6\u6001: ${options.status}\uFF0C\u53EF\u9009: ${VALID_STATUSES.join(", ")}`
2044
- );
2045
- process.exit(1);
1115
+ const apmStat = statSync3(apmDir);
1116
+ if (!apmStat.isDirectory()) {
1117
+ throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
2046
1118
  }
2047
1119
  const cfg = await ensureLoggedConfig();
2048
- const api = createApmApiClient(cfg);
2049
- await api.cli.updateMessageStatus({ id: messageId, status });
2050
- console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
1120
+ await syncWorkspaceSkills(cfg, resolveWorkdirPath());
2051
1121
  }
2052
1122
 
2053
1123
  // src/commands/connect.ts
@@ -2077,57 +1147,6 @@ function validateHeartbeat(o) {
2077
1147
  }
2078
1148
  return { ok: true, data: { type: "heartbeat", userId: o.userId.trim() } };
2079
1149
  }
2080
- function validateMessagePush(o) {
2081
- if (o.type !== "message") {
2082
- return { ok: false, reason: "\u671F\u671B message" };
2083
- }
2084
- if (!nonEmptyString(o.messageId)) {
2085
- return { ok: false, reason: "message \u7F3A\u5C11 messageId" };
2086
- }
2087
- if (!nonEmptyString(o.sessionId)) {
2088
- return { ok: false, reason: "message \u7F3A\u5C11 sessionId" };
2089
- }
2090
- if (!nonEmptyString(o.content)) {
2091
- return { ok: false, reason: "message \u7F3A\u5C11 content" };
2092
- }
2093
- if (!nonEmptyString(o.model)) {
2094
- return { ok: false, reason: "message \u7F3A\u5C11 model" };
2095
- }
2096
- if (!nonEmptyString(o.apiKey)) {
2097
- return { ok: false, reason: "message \u7F3A\u5C11 apiKey" };
2098
- }
2099
- if (!nonEmptyString(o.workdir)) {
2100
- return { ok: false, reason: "message \u7F3A\u5C11 workdir" };
2101
- }
2102
- if (!nonEmptyString(o.user)) {
2103
- return { ok: false, reason: "message \u7F3A\u5C11 user" };
2104
- }
2105
- return {
2106
- ok: true,
2107
- data: {
2108
- type: "message",
2109
- messageId: o.messageId.trim(),
2110
- sessionId: o.sessionId.trim(),
2111
- content: o.content,
2112
- model: o.model.trim(),
2113
- apiKey: o.apiKey.trim(),
2114
- workdir: o.workdir.trim(),
2115
- user: o.user.trim()
2116
- }
2117
- };
2118
- }
2119
- function validateCancel(o) {
2120
- if (o.type !== "cancel") {
2121
- return { ok: false, reason: "\u671F\u671B cancel" };
2122
- }
2123
- if (!nonEmptyString(o.messageId)) {
2124
- return { ok: false, reason: "cancel \u7F3A\u5C11 messageId" };
2125
- }
2126
- return {
2127
- ok: true,
2128
- data: { type: "cancel", messageId: o.messageId.trim() }
2129
- };
2130
- }
2131
1150
  function validateDeployPush(o) {
2132
1151
  if (o.type !== "deploy") {
2133
1152
  return { ok: false, reason: "\u671F\u671B deploy" };
@@ -2152,36 +1171,56 @@ function validateDeployPush(o) {
2152
1171
  }
2153
1172
  };
2154
1173
  }
1174
+ function validateReceivedMailPush(o) {
1175
+ if (o.type !== "received_mail") {
1176
+ return { ok: false, reason: "\u671F\u671B received_mail" };
1177
+ }
1178
+ if (!nonEmptyString(o.id)) {
1179
+ return { ok: false, reason: "received_mail \u7F3A\u5C11 id" };
1180
+ }
1181
+ if (!nonEmptyString(o.taskId)) {
1182
+ return { ok: false, reason: "received_mail \u7F3A\u5C11 taskId" };
1183
+ }
1184
+ if (!nonEmptyString(o.createdAt)) {
1185
+ return { ok: false, reason: "received_mail \u7F3A\u5C11 createdAt" };
1186
+ }
1187
+ return {
1188
+ ok: true,
1189
+ data: {
1190
+ type: "received_mail",
1191
+ id: o.id.trim(),
1192
+ taskId: o.taskId.trim(),
1193
+ createdAt: o.createdAt.trim()
1194
+ }
1195
+ };
1196
+ }
2155
1197
  function validateAgentWsMessage(value, kind) {
2156
1198
  if (typeof value !== "object" || value === null) {
2157
1199
  return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
2158
1200
  }
2159
1201
  const o = value;
2160
1202
  const type = o.type;
2161
- if (type !== "heartbeat" && type !== "message" && type !== "cancel" && type !== "deploy") {
1203
+ if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail") {
2162
1204
  return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
2163
1205
  }
2164
1206
  if (kind === "heartbeat" || type === "heartbeat") {
2165
1207
  return validateHeartbeat(o);
2166
1208
  }
2167
- if (type === "cancel") {
2168
- return validateCancel(o);
2169
- }
2170
1209
  if (type === "deploy") {
2171
1210
  return validateDeployPush(o);
2172
1211
  }
2173
- return validateMessagePush(o);
1212
+ return validateReceivedMailPush(o);
2174
1213
  }
2175
1214
 
2176
1215
  // src/commands/connect/deploy-run.ts
2177
1216
  import { spawn } from "node:child_process";
2178
- import { readFileSync as readFileSync9 } from "node:fs";
2179
- import { join as join13 } from "node:path";
1217
+ import { readFileSync as readFileSync6 } from "node:fs";
1218
+ import { join as join9 } from "node:path";
2180
1219
  var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
2181
1220
  function readDeployConfig(workdir) {
2182
- const configPath = join13(workspaceApmDir(workdir), "apm.config.json");
1221
+ const configPath = join9(workspaceApmDir(workdir), "apm.config.json");
2183
1222
  try {
2184
- const raw = readFileSync9(configPath, "utf8");
1223
+ const raw = readFileSync6(configPath, "utf8");
2185
1224
  const parsed = JSON.parse(raw);
2186
1225
  return parsed.deploy;
2187
1226
  } catch {
@@ -2256,7 +1295,7 @@ function createDeployLogSyncer(api, deploymentRunId) {
2256
1295
  if (!latestLog || latestLog === lastSyncedLog) {
2257
1296
  return;
2258
1297
  }
2259
- await api.cli.syncCoordinatorDeploymentLog({
1298
+ await api.cli.syncTaskDeploymentLog({
2260
1299
  id: deploymentRunId,
2261
1300
  log: latestLog
2262
1301
  });
@@ -2287,7 +1326,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
2287
1326
  const api = createApmApiClient(cfg);
2288
1327
  const deploymentRunId = msg.deploymentRunId;
2289
1328
  if (signal.aborted) return;
2290
- await api.cli.updateCoordinatorDeploymentStatus({
1329
+ await api.cli.updateTaskDeploymentStatus({
2291
1330
  id: deploymentRunId,
2292
1331
  status: "DEPLOYING"
2293
1332
  });
@@ -2296,7 +1335,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
2296
1335
  if (!command) {
2297
1336
  const error = missingDeployCommandMessage(msg.environment);
2298
1337
  console.error(`[apm] ${error}`);
2299
- await api.cli.completeCoordinatorDeployment({
1338
+ await api.cli.completeTaskDeployment({
2300
1339
  id: deploymentRunId,
2301
1340
  status: "FAILED",
2302
1341
  log: error,
@@ -2318,7 +1357,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
2318
1357
  latestLog = log;
2319
1358
  logSyncer.updateLog(log);
2320
1359
  await logSyncer.flush();
2321
- await api.cli.completeCoordinatorDeployment({
1360
+ await api.cli.completeTaskDeployment({
2322
1361
  id: deploymentRunId,
2323
1362
  status: "SUCCESS",
2324
1363
  log
@@ -2329,7 +1368,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
2329
1368
  const log = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
2330
1369
  logSyncer.updateLog(log);
2331
1370
  await logSyncer.flush();
2332
- await api.cli.completeCoordinatorDeployment({
1371
+ await api.cli.completeTaskDeployment({
2333
1372
  id: deploymentRunId,
2334
1373
  status: "FAILED",
2335
1374
  log,
@@ -2341,67 +1380,66 @@ async function handleInboundDeploy(cfg, msg, signal) {
2341
1380
  }
2342
1381
  }
2343
1382
 
2344
- // src/commands/connect/abort-signal-debug.ts
2345
- import {
2346
- getEventListeners,
2347
- getMaxListeners,
2348
- setMaxListeners
2349
- } from "node:events";
2350
- function isAbortSignalDebugEnabled() {
2351
- const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
2352
- return v === "1" || v === "true" || v === "yes";
2353
- }
2354
- function formatAbortSignalStats(signal, label) {
2355
- if (!signal) {
2356
- return `[apm:abort-debug] ${label}: (no signal)`;
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 [];
2357
1389
  }
2358
- const listeners = getEventListeners(signal, "abort");
2359
- const max = getMaxListeners(signal);
2360
- return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
1390
+ return readdirSync4(docsDir).filter(
1391
+ (name) => name.toLowerCase().endsWith(".md")
1392
+ );
2361
1393
  }
2362
- function logAbortSignalStats(signal, label) {
2363
- if (!isAbortSignalDebugEnabled()) return;
2364
- console.log(formatAbortSignalStats(signal, label));
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
+ });
2365
1400
  }
2366
- var installed = false;
2367
- function installAbortSignalDebug() {
2368
- if (!isAbortSignalDebugEnabled() || installed) return;
2369
- installed = true;
2370
- const maxFromEnv = Number.parseInt(
2371
- process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
2372
- 10
2373
- );
2374
- if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
2375
- setMaxListeners(maxFromEnv);
2376
- console.log(
2377
- `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
2378
- );
2379
- }
2380
- process.on("warning", (warning) => {
2381
- if (warning.name !== "MaxListenersExceededWarning") return;
2382
- console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
2383
- if (warning.stack) {
2384
- console.warn(warning.stack);
2385
- }
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
2386
1408
  });
2387
- const proto = AbortSignal.prototype;
2388
- const original = proto.addEventListener;
2389
- proto.addEventListener = function(type, listener, options) {
2390
- if (type === "abort") {
2391
- const sig = this;
2392
- const before = getEventListeners(sig, "abort").length;
2393
- const max = getMaxListeners(sig);
2394
- const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
2395
- console.log(
2396
- `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
2397
- ${stack}`
2398
- );
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;
2399
1429
  }
2400
- return original.call(this, type, listener, options);
2401
- };
2402
- console.log(
2403
- "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
2404
- );
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;
2405
1443
  }
2406
1444
 
2407
1445
  // src/commands/connect/cursor-agent.ts
@@ -2411,7 +1449,7 @@ import {
2411
1449
  } from "@cursor/sdk";
2412
1450
  import { setMaxListeners as setMaxListeners2 } from "node:events";
2413
1451
 
2414
- // src/session-utils.ts
1452
+ // src/event-session.ts
2415
1453
  var EventSession = class {
2416
1454
  events = [];
2417
1455
  dirtyIndices = /* @__PURE__ */ new Set();
@@ -2458,10 +1496,10 @@ var EventSession = class {
2458
1496
  if (latestEvent?.type === formatedEvent.type) {
2459
1497
  switch (formatedEvent.type) {
2460
1498
  case "assistant":
2461
- latestEvent.content += formatedEvent.content;
1499
+ latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
2462
1500
  break;
2463
1501
  case "thinking":
2464
- latestEvent.content += formatedEvent.content;
1502
+ latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
2465
1503
  break;
2466
1504
  case "task":
2467
1505
  latestEvent.status = formatedEvent.status;
@@ -2532,55 +1570,46 @@ var EventSession = class {
2532
1570
  this.dirtyIndices.delete(index);
2533
1571
  }
2534
1572
  }
2535
- /** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
2536
1573
  getAssistantText() {
2537
1574
  return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
2538
1575
  }
2539
- resolveLogContent() {
2540
- return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
2541
- }
2542
1576
  };
2543
- function formatLogEvent(type, event) {
2544
- if (type === "input") {
2545
- return `## \u7528\u6237\u8F93\u5165
2546
-
2547
- ${String(event.content ?? "")}
2548
- `;
2549
- }
2550
- if (type === "assistant") {
2551
- return `## \u6A21\u578B\u8F93\u51FA
2552
-
2553
- ${String(event.content ?? "")}
2554
- `;
2555
- }
2556
- if (type === "thinking") {
2557
- return `## \u6A21\u578B\u601D\u8003
2558
1577
 
2559
- ${String(event.content ?? "")}
2560
- `;
2561
- }
2562
- if (type === "tool_call") {
2563
- 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)`;
2564
1591
  }
2565
- return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type}
2566
-
2567
- \`\`\`json
2568
- ${JSON.stringify(event, null, 2)}
2569
- \`\`\``;
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));
2570
1599
  }
2571
1600
 
2572
- // src/commands/connect/agent-session-registry.ts
2573
- 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";
2574
1603
  import { dirname as dirname4, resolve as resolve3 } from "node:path";
2575
- function registryPath(workdir, sessionId) {
2576
- return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
1604
+ function registryPath(workdir, taskId) {
1605
+ return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
2577
1606
  }
2578
1607
  function readRegistry(path10) {
2579
- if (!existsSync11(path10)) {
1608
+ if (!existsSync7(path10)) {
2580
1609
  return {};
2581
1610
  }
2582
1611
  try {
2583
- const parsed = JSON.parse(readFileSync10(path10, "utf8"));
1612
+ const parsed = JSON.parse(readFileSync8(path10, "utf8"));
2584
1613
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2585
1614
  const result = {};
2586
1615
  for (const [key, value] of Object.entries(
@@ -2598,21 +1627,20 @@ function readRegistry(path10) {
2598
1627
  }
2599
1628
  function writeRegistry(path10, registry) {
2600
1629
  mkdirSync5(dirname4(path10), { recursive: true });
2601
- writeFileSync10(path10, `${JSON.stringify(registry, null, 2)}
1630
+ writeFileSync7(path10, `${JSON.stringify(registry, null, 2)}
2602
1631
  `, "utf8");
2603
1632
  }
2604
- function loadSessionAgentId(workdir, sessionId, user) {
2605
- const registry = readRegistry(registryPath(workdir, sessionId));
2606
- return registry[user];
1633
+ function loadTaskAgentId(workdir, taskId, user) {
1634
+ return readRegistry(registryPath(workdir, taskId))[user];
2607
1635
  }
2608
- function saveSessionAgentId(workdir, sessionId, user, agentId) {
2609
- const path10 = registryPath(workdir, sessionId);
1636
+ function saveTaskAgentId(workdir, taskId, user, agentId) {
1637
+ const path10 = registryPath(workdir, taskId);
2610
1638
  const registry = readRegistry(path10);
2611
1639
  registry[user] = agentId;
2612
1640
  writeRegistry(path10, registry);
2613
1641
  }
2614
- function clearSessionAgentId(workdir, sessionId, user) {
2615
- const path10 = registryPath(workdir, sessionId);
1642
+ function clearTaskAgentId(workdir, taskId, user) {
1643
+ const path10 = registryPath(workdir, taskId);
2616
1644
  const registry = readRegistry(path10);
2617
1645
  if (!(user in registry)) {
2618
1646
  return;
@@ -2621,9 +1649,9 @@ function clearSessionAgentId(workdir, sessionId, user) {
2621
1649
  writeRegistry(path10, registry);
2622
1650
  }
2623
1651
 
2624
- // src/commands/connect/cursor-message-log.ts
2625
- var CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS = 2e3;
2626
- 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) {
2627
1655
  let lastRunAt = 0;
2628
1656
  let timer;
2629
1657
  let latestSession;
@@ -2635,7 +1663,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2635
1663
  }
2636
1664
  lastRunAt = Date.now();
2637
1665
  try {
2638
- await syncCursorMessageLog(cfg, ctx, events);
1666
+ await syncCursorLog(cfg, ctx, events);
2639
1667
  session.clearDirty(events.map((event) => event.index));
2640
1668
  } catch (err) {
2641
1669
  onError(err);
@@ -2654,7 +1682,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2654
1682
  latestSession = session;
2655
1683
  const now = Date.now();
2656
1684
  const elapsed = now - lastRunAt;
2657
- if (elapsed >= CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS) {
1685
+ if (elapsed >= CURSOR_LOG_SYNC_INTERVAL_MS) {
2658
1686
  if (timer) {
2659
1687
  clearTimeout(timer);
2660
1688
  timer = void 0;
@@ -2668,7 +1696,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2668
1696
  timer = setTimeout(() => {
2669
1697
  timer = void 0;
2670
1698
  enqueueSync(latestSession);
2671
- }, CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS - elapsed);
1699
+ }, CURSOR_LOG_SYNC_INTERVAL_MS - elapsed);
2672
1700
  },
2673
1701
  async flush(session) {
2674
1702
  latestSession = session;
@@ -2681,108 +1709,66 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2681
1709
  }
2682
1710
  };
2683
1711
  }
2684
- async function syncCursorMessageLog(cfg, ctx, events) {
1712
+ async function syncCursorLog(cfg, ctx, events) {
2685
1713
  const agentId = ctx.agentId.trim();
2686
1714
  if (!agentId || events.length === 0) {
2687
1715
  return;
2688
1716
  }
2689
1717
  const api = createApmApiClient(cfg);
2690
- await api.cli.upsertCursorMessageLog({
2691
- sessionId: ctx.sessionId,
2692
- messageId: ctx.messageId,
1718
+ await api.cli.upsertCursorLog({
1719
+ taskId: ctx.taskId,
1720
+ mailboxMessageId: ctx.mailboxMessageId,
2693
1721
  agentId,
2694
1722
  events
2695
1723
  });
2696
1724
  }
2697
1725
 
2698
- // src/commands/connect/append-message-tool.ts
2699
- function createAppendMessageCustomTools(cfg, messageId) {
2700
- return {
2701
- append_message: {
2702
- 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",
2703
- inputSchema: {
2704
- type: "object",
2705
- properties: {
2706
- content: {
2707
- type: "string",
2708
- description: "\u8981\u53D1\u9001\u5230\u7FA4\u91CC\u7684\u56DE\u590D\u5185\u5BB9"
2709
- }
2710
- },
2711
- required: ["content"]
2712
- },
2713
- execute: async (args) => {
2714
- const content = typeof args.content === "string" ? args.content.trim() : "";
2715
- if (!content) {
2716
- return {
2717
- content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
2718
- isError: true
2719
- };
2720
- }
2721
- try {
2722
- await appendMessageContent(cfg, messageId, content);
2723
- console.log(`[apm] append_message \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
2724
- return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
2725
- } catch (err) {
2726
- const detail = err instanceof Error ? err.message : String(err);
2727
- return {
2728
- content: [{ type: "text", text: `\u8FFD\u52A0\u6D88\u606F\u5931\u8D25: ${detail}` }],
2729
- isError: true
2730
- };
2731
- }
2732
- }
2733
- }
2734
- };
2735
- }
2736
-
2737
1726
  // src/commands/connect/cursor-agent.ts
2738
1727
  setMaxListeners2(50);
2739
- installAbortSignalDebug();
2740
- var logCtx = (ctx, agentId) => ({
2741
- sessionId: ctx.sessionId,
2742
- messageId: ctx.messageId,
2743
- agentId
2744
- });
2745
- function formatCursorRunFailure(runId, options) {
2746
- const details = [
2747
- options?.statusError?.trim(),
2748
- options?.resultText?.trim()
2749
- ].filter((value, index, arr) => {
2750
- if (!value) return false;
2751
- return arr.indexOf(value) === index;
2752
- });
2753
- if (details.length === 0) {
2754
- return `Cursor run \u5931\u8D25: ${runId}`;
1728
+ var noopRemoteLogSync = {
1729
+ schedule(_session) {
1730
+ },
1731
+ async flush(_session) {
2755
1732
  }
2756
- return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
1733
+ };
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("");
2757
1740
  }
2758
1741
  async function obtainAgent(ctx) {
2759
1742
  const agentOptions = {
2760
1743
  apiKey: ctx.apiKey,
2761
1744
  model: { id: ctx.model || "default" },
2762
1745
  local: {
2763
- cwd: ctx.cwd
1746
+ cwd: ctx.workdir,
1747
+ ...ctx.customTools ? { customTools: ctx.customTools } : {}
2764
1748
  }
2765
- // mcpServers: createPlaywrightMcpServers(),
2766
1749
  };
2767
- const savedAgentId = 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;
2768
1751
  if (savedAgentId) {
2769
1752
  try {
2770
1753
  const agent2 = await Agent.resume(savedAgentId, agentOptions);
2771
- console.log(
2772
- `[apm] \u590D\u7528\u4F1A\u8BDD Agent user=${ctx.user} agentId=${savedAgentId}`
2773
- );
1754
+ console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
1755
+ if (ctx.user) {
1756
+ saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent2.agentId);
1757
+ }
2774
1758
  return { agent: agent2, resumed: true };
2775
1759
  } catch (err) {
2776
1760
  console.warn(
2777
1761
  `[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
2778
1762
  err instanceof Error ? err.message : err
2779
1763
  );
2780
- clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
1764
+ if (ctx.user) {
1765
+ clearTaskAgentId(ctx.workdir, ctx.taskId, ctx.user);
1766
+ }
2781
1767
  }
2782
1768
  }
2783
1769
  const agent = await Agent.create(agentOptions);
2784
1770
  if (ctx.user) {
2785
- saveSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user, agent.agentId);
1771
+ saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent.agentId);
2786
1772
  }
2787
1773
  return { agent, resumed: false };
2788
1774
  }
@@ -2794,29 +1780,36 @@ async function runCursorAgent(cfg, ctx, options) {
2794
1780
  }
2795
1781
  const apiKey = ctx.apiKey.trim();
2796
1782
  if (!apiKey) {
2797
- throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
1783
+ throw new Error("\u7F3A\u5C11 Cursor API Key");
2798
1784
  }
2799
- const workdir = resolveWorkdirPath(ctx.workdir);
2800
- const prompt = ctx.prompt;
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`;
2801
1791
  console.log(
2802
- `[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}`
2803
1793
  );
2804
1794
  const { agent, resumed } = await obtainAgent({
2805
1795
  apiKey,
2806
1796
  model: ctx.model,
2807
- cwd: workdir,
2808
1797
  workdir,
2809
- sessionId: ctx.sessionId,
2810
- user: ctx.user
1798
+ taskId: ctx.taskId,
1799
+ user: ctx.user,
1800
+ customTools
2811
1801
  });
2812
1802
  const eventSession = new EventSession(prompt);
2813
- const remoteLogCtx = logCtx(ctx, agent.agentId);
2814
- const syncRemoteLog = createThrottledCursorMessageLogSync(
1803
+ const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorLogSync(
2815
1804
  cfg,
2816
- remoteLogCtx,
1805
+ {
1806
+ taskId: ctx.taskId,
1807
+ mailboxMessageId: ctx.mailId,
1808
+ agentId: agent.agentId
1809
+ },
2817
1810
  (err) => {
2818
1811
  console.warn(
2819
- "[apm] \u540C\u6B65 Cursor \u6D88\u606F\u65E5\u5FD7\u5931\u8D25:",
1812
+ `[apm] Cursor \u65E5\u5FD7\u540C\u6B65\u5931\u8D25 mailId=${ctx.mailId}:`,
2820
1813
  err instanceof Error ? err.message : err
2821
1814
  );
2822
1815
  }
@@ -2827,56 +1820,47 @@ async function runCursorAgent(cfg, ctx, options) {
2827
1820
  void activeRun.cancel().catch(() => void 0);
2828
1821
  };
2829
1822
  signal?.addEventListener("abort", abortRun, { once: true });
2830
- logAbortSignalStats(signal, "runCursorAgent:after-addListener");
1823
+ const streamEvents = [];
2831
1824
  try {
2832
1825
  const run = await agent.send(prompt, {
2833
- // mcpServers: createPlaywrightMcpServers(),
2834
- local: {
2835
- customTools: createAppendMessageCustomTools(cfg, ctx.messageId)
2836
- }
1826
+ local: { customTools }
2837
1827
  });
2838
1828
  activeRun = run;
2839
- logAbortSignalStats(signal, "runCursorAgent:after-send");
2840
1829
  console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
2841
- let lastRunErrorStatus;
2842
1830
  for await (const event of run.stream()) {
2843
1831
  if (signal?.aborted) {
2844
1832
  abortRun();
2845
1833
  throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
2846
1834
  }
2847
- if (event.type === "status" && event.status === "ERROR") {
2848
- const message = event.message?.trim();
2849
- if (message) {
2850
- lastRunErrorStatus = message;
2851
- console.error(
2852
- `[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
2853
- );
2854
- }
2855
- }
1835
+ streamEvents.push(event);
2856
1836
  eventSession.addEvent(event);
2857
1837
  syncRemoteLog.schedule(eventSession);
2858
1838
  }
2859
1839
  await syncRemoteLog.flush(eventSession);
2860
1840
  const result = await run.wait();
1841
+ const assistantText = eventSession.getAssistantText() || collectAssistantText(streamEvents);
2861
1842
  if (result.status === "error") {
2862
- const failureMessage = formatCursorRunFailure(result.id, {
2863
- statusError: lastRunErrorStatus,
2864
- resultText: result.result
2865
- });
2866
- console.error(`[apm] ${failureMessage}`);
2867
1843
  if (resumed) {
2868
- clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
1844
+ clearTaskAgentId(workdir, ctx.taskId, ctx.user);
2869
1845
  }
2870
- throw new Error(failureMessage);
1846
+ throw new Error(
1847
+ `Cursor run \u5931\u8D25: ${result.id}${result.result ? ` \u2014 ${result.result}` : ""}`
1848
+ );
2871
1849
  }
2872
1850
  if (result.status === "cancelled") {
2873
1851
  throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
2874
1852
  }
2875
- console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
1853
+ console.log(`[apm] Cursor Agent \u5B8C\u6210 mailId=${ctx.mailId}`);
1854
+ return {
1855
+ runId: result.id,
1856
+ agentId: agent.agentId,
1857
+ status: result.status,
1858
+ assistantText: assistantText || result.result || ""
1859
+ };
2876
1860
  } catch (err) {
2877
1861
  if (err instanceof CursorAgentError) {
2878
1862
  if (resumed) {
2879
- clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
1863
+ clearTaskAgentId(workdir, ctx.taskId, ctx.user);
2880
1864
  }
2881
1865
  throw new Error(
2882
1866
  `Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
@@ -2884,265 +1868,428 @@ async function runCursorAgent(cfg, ctx, options) {
2884
1868
  }
2885
1869
  throw err;
2886
1870
  } finally {
2887
- logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
2888
1871
  signal?.removeEventListener("abort", abortRun);
2889
- logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
2890
1872
  await agent[Symbol.asyncDispose]();
2891
1873
  }
2892
1874
  }
2893
1875
 
2894
- // src/commands/connect/cli-version-sync.ts
2895
- import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
2896
- import { join as join14 } from "path";
2897
- var CLI_VERSION_FILE = ".cli-version.json";
2898
- function manifestPath(apmDir) {
2899
- return join14(apmDir, CLI_VERSION_FILE);
2900
- }
2901
- function loadManifest3(apmDir) {
2902
- const path10 = toFsPath(manifestPath(apmDir));
2903
- if (!existsSync12(path10)) {
2904
- return null;
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
+ }
1926
+ },
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
+ };
1936
+ }
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";
1940
+ }
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`;
1964
+ }
1965
+ function loadManifest(rulesDir) {
1966
+ const path10 = join11(rulesDir, MANIFEST_FILE2);
1967
+ if (!existsSync8(toFsPath(path10))) {
1968
+ return { version: 1, rules: {} };
2905
1969
  }
2906
1970
  try {
2907
1971
  const parsed = JSON.parse(
2908
- readFileSync11(path10, "utf8")
1972
+ readFileSync9(toFsPath(path10), "utf8")
2909
1973
  );
2910
- if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
1974
+ if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
2911
1975
  return parsed;
2912
1976
  }
2913
1977
  } catch {
2914
1978
  }
2915
- return null;
1979
+ return { version: 1, rules: {} };
2916
1980
  }
2917
- function saveManifest3(apmDir, cliVersion) {
2918
- const manifest = { version: 1, cliVersion };
2919
- writeFileSync11(
2920
- toFsPath(manifestPath(apmDir)),
1981
+ function saveManifest(rulesDir, manifest) {
1982
+ writeFileSync8(
1983
+ toFsPath(join11(rulesDir, MANIFEST_FILE2)),
2921
1984
  `${JSON.stringify(manifest, null, 2)}
2922
1985
  `,
2923
1986
  "utf8"
2924
1987
  );
2925
1988
  }
2926
- var syncedInSession = /* @__PURE__ */ new Map();
2927
- function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
2928
- const cached = syncedInSession.get(workdir);
2929
- if (cached === currentVersion) {
2930
- return false;
2931
- }
2932
- const stored = loadManifest3(workspaceApmDir(workdir));
2933
- if (stored?.cliVersion === currentVersion) {
2934
- syncedInSession.set(workdir, currentVersion);
2935
- return false;
2936
- }
2937
- return true;
1989
+ function isBaseRuleFileName(fileName) {
1990
+ return listBaseRuleFileNames().includes(basename2(fileName));
2938
1991
  }
2939
- function markSkillsSyncedForCliVersion(workdir, cliVersion) {
2940
- saveManifest3(workspaceApmDir(workdir), cliVersion);
2941
- 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 ?? "");
2942
1999
  }
2943
-
2944
- // src/commands/connect/pre-step-cache.ts
2945
- var PULL_TTL_MS = 3e4;
2946
- function sessionWorkdirKey(sessionId, workdir) {
2947
- return `${sessionId}\0${workdir}`;
2948
- }
2949
- var lastBranchKey = null;
2950
- var lastPullAtByKey = /* @__PURE__ */ new Map();
2951
- function shouldRunBranch(sessionId, workdir) {
2952
- return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
2953
- }
2954
- function markBranchDone(sessionId, workdir) {
2955
- lastBranchKey = sessionWorkdirKey(sessionId, workdir);
2956
- }
2957
- function shouldRunPull(sessionId, workdir) {
2958
- const key = sessionWorkdirKey(sessionId, workdir);
2959
- const last = lastPullAtByKey.get(key);
2960
- if (last == null) {
2961
- 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 };
2962
2011
  }
2963
- return Date.now() - last >= PULL_TTL_MS;
2964
- }
2965
- function markPullDone(sessionId, workdir) {
2966
- lastPullAtByKey.set(sessionWorkdirKey(sessionId, workdir), Date.now());
2967
- }
2968
-
2969
- // src/commands/connect/run-slot-pool.ts
2970
- var DEFAULT_MAX_CONCURRENT = 5;
2971
- function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
2972
- let active = 0;
2973
- const waiters = [];
2974
- const acquire = () => {
2975
- if (active < maxConcurrent) {
2976
- active += 1;
2977
- 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;
2978
2029
  }
2979
- return new Promise((resolve5) => {
2980
- waiters.push(() => {
2981
- active += 1;
2982
- resolve5();
2983
- });
2984
- });
2985
- };
2986
- const release = () => {
2987
- active = Math.max(0, active - 1);
2988
- const next = waiters.shift();
2989
- if (next) {
2990
- 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 });
2991
2042
  }
2992
- };
2993
- 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 };
2994
2048
  }
2995
2049
 
2996
- // src/commands/connect.ts
2997
- var HEARTBEAT_MS = 3e4;
2998
- async function updateMessageStatus(cfg, messageId, status) {
2999
- const api = createApmApiClient(cfg);
3000
- await api.cli.updateMessageStatus({ id: messageId, status });
3001
- console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
3002
- }
3003
- async function setMessageError(cfg, messageId, error) {
3004
- const api = createApmApiClient(cfg);
3005
- await api.cli.setMessageError({ id: messageId, error });
3006
- console.log(`[apm] \u5DF2\u8BBE\u7F6E\u6D88\u606F\u9519\u8BEF: ${messageId}`);
3007
- }
3008
- var SHUTDOWN_DRAIN_MS = 3e3;
3009
- function isUserCancelled(ctx) {
3010
- return ctx.perMessageSignal.aborted && !ctx.shutdownSignal.aborted;
3011
- }
3012
- async function handleInboundMessage(cfg, msg, signal, ctx) {
3013
- if (isUserCancelled(ctx)) return;
3014
- if (signal.aborted) return;
3015
- const messageId = msg.messageId;
3016
- 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);
3017
2054
  const apmRoot = workspaceApmDir(workdir);
3018
- const runStep = async (step, fn) => {
3019
- const startedAt = Date.now();
3020
- try {
3021
- const result = await fn();
3022
- console.log(`[apm] step=${step} elapsed=${Date.now() - startedAt}ms`);
3023
- return result;
3024
- } catch (err) {
3025
- const detail = err instanceof Error ? err.message : String(err);
3026
- throw new Error(`[${step}] ${detail}`);
3027
- }
3028
- };
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;
3029
2113
  try {
3030
- if (signal.aborted) return;
3031
- const { didInit } = await runStep(
3032
- "workspace-init",
3033
- () => 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
3034
2119
  );
3035
- if (!didInit) {
3036
- assertApmGitignoredInRepo(workdir);
3037
- }
3038
- await runStep(
3039
- "status-typing",
3040
- () => 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
3041
2128
  );
3042
- if (shouldRunBranch(msg.sessionId, workdir)) {
3043
- if (signal.aborted) return;
3044
- await runStep("branch", () => runBranch(msg.sessionId, { cwd: workdir }));
3045
- markBranchDone(msg.sessionId, workdir);
3046
- } else {
3047
- console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
3048
- }
3049
- let pullRan = false;
3050
- if (shouldRunPull(msg.sessionId, workdir)) {
3051
- if (signal.aborted) return;
3052
- await runStep("pull", () => runPull(msg.sessionId, workdir));
3053
- markPullDone(msg.sessionId, workdir);
3054
- pullRan = true;
3055
- } else {
3056
- console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
3057
- }
3058
- if (pullRan) {
3059
- if (signal.aborted) return;
3060
- await runStep(
3061
- "commit-pull",
3062
- () => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
3063
- );
3064
- } else {
3065
- console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
3066
- }
3067
- const cliVersion = readCliVersion();
3068
- if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
3069
- if (signal.aborted) return;
3070
- console.log(
3071
- `[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
3072
- );
3073
- await runStep("update-skills", async () => {
3074
- await syncWorkspaceSkills(cfg, workdir);
3075
- markSkillsSyncedForCliVersion(workdir, cliVersion);
3076
- });
3077
- } else {
3078
- console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
3079
- }
3080
- if (signal.aborted) return;
3081
- if (!pullRan) {
3082
- await runStep(
3083
- "sync-project-documents-pull",
3084
- () => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
3085
- );
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);
3086
2135
  }
3087
- await runStep(
3088
- "cursor-agent",
3089
- () => runCursorAgent(
3090
- cfg,
3091
- {
3092
- messageId: msg.messageId,
3093
- sessionId: msg.sessionId,
3094
- prompt: msg.content,
3095
- model: msg.model,
3096
- apiKey: msg.apiKey,
3097
- workdir,
3098
- user: msg.user
3099
- },
3100
- { signal }
3101
- )
3102
- );
3103
- await runStep(
3104
- "sync-documents",
3105
- () => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
3106
- );
3107
- await runStep(
3108
- "sync-project-documents",
3109
- () => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
3110
- );
3111
- await runStep(
3112
- "commit-files",
3113
- () => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
3114
- );
3115
- await runStep(
3116
- "status-success",
3117
- () => updateMessageStatus(cfg, messageId, "SUCCESS")
3118
- );
2136
+ return;
2137
+ }
2138
+ try {
2139
+ await api.cli.claimMailboxMessage({ id: mail.id });
3119
2140
  } catch (err) {
3120
- if (isUserCancelled(ctx)) {
3121
- console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
3122
- return;
3123
- }
3124
2141
  console.error(
3125
- "[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
3126
- 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
3127
2144
  );
3128
- if (err instanceof Error && err.stack) {
3129
- console.error(err.stack);
3130
- }
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}`);
3131
2188
  try {
3132
- await setMessageError(
3133
- cfg,
3134
- messageId,
3135
- err instanceof Error ? err.message : String(err)
3136
- );
3137
- await updateMessageStatus(cfg, messageId, "FAILED");
3138
- } catch (statusErr) {
2189
+ await api.cli.completeMailboxMessage({
2190
+ id: detail.id,
2191
+ status: "FAILED",
2192
+ error: message
2193
+ });
2194
+ } catch (completeErr) {
3139
2195
  console.error(
3140
- "[apm] \u66F4\u65B0 FAILED \u72B6\u6001\u5931\u8D25:",
3141
- 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
3142
2198
  );
3143
2199
  }
3144
2200
  }
3145
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;
3146
2293
  function startHeartbeat(ws, clientMachineId) {
3147
2294
  const send = () => {
3148
2295
  if (ws.readyState === WebSocket.OPEN) {
@@ -3192,11 +2339,17 @@ async function runConnect(options) {
3192
2339
  let stopHeartbeat;
3193
2340
  let shuttingDown = false;
3194
2341
  const shutdownAbort = new AbortController();
3195
- const runSlots = createRunSlotPool();
3196
- const activeTasks = /* @__PURE__ */ new Set();
3197
- const activeRuns = /* @__PURE__ */ new Map();
3198
- const pendingCancels = /* @__PURE__ */ new Set();
3199
- 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) => {
3200
2353
  if (shuttingDown) return;
3201
2354
  shuttingDown = true;
3202
2355
  logAbortSignalStats(
@@ -3209,19 +2362,34 @@ async function runConnect(options) {
3209
2362
  if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
3210
2363
  ws.terminate();
3211
2364
  }
3212
- try {
3213
- await Promise.race([
3214
- Promise.all(activeTasks),
3215
- new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
3216
- ]);
3217
- } catch {
3218
- }
3219
2365
  resolve5();
3220
2366
  process.exit(code);
3221
2367
  };
3222
2368
  ws.on("open", () => {
3223
2369
  console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
3224
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
+ })();
3225
2393
  });
3226
2394
  ws.on("message", (data) => {
3227
2395
  if (shuttingDown) return;
@@ -3239,72 +2407,19 @@ async function runConnect(options) {
3239
2407
  console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
3240
2408
  return;
3241
2409
  }
3242
- if (validated.data.type === "cancel") {
3243
- const { messageId } = validated.data;
3244
- pendingCancels.add(messageId);
3245
- activeRuns.get(messageId)?.abort();
3246
- return;
3247
- }
3248
2410
  if (validated.data.type === "deploy") {
3249
- const msg2 = validated.data;
3250
- const perDeployController = new AbortController();
3251
- const signal2 = AbortSignal.any([
3252
- shutdownAbort.signal,
3253
- perDeployController.signal
3254
- ]);
3255
- const task2 = (async () => {
3256
- await runSlots.acquire();
3257
- try {
3258
- await handleInboundDeploy(cfg, msg2, signal2);
3259
- } finally {
3260
- runSlots.release();
3261
- }
3262
- })();
3263
- activeTasks.add(task2);
3264
- void task2.finally(() => {
3265
- activeTasks.delete(task2);
3266
- });
2411
+ void handleInboundDeploy(cfg, validated.data, shutdownAbort.signal);
3267
2412
  return;
3268
2413
  }
3269
- if (validated.data.type !== "message") {
3270
- return;
3271
- }
3272
- const msg = validated.data;
3273
- const perMessageController = new AbortController();
3274
- activeRuns.set(msg.messageId, perMessageController);
3275
- if (pendingCancels.has(msg.messageId)) {
3276
- activeRuns.delete(msg.messageId);
3277
- pendingCancels.delete(msg.messageId);
3278
- return;
2414
+ if (validated.data.type === "received_mail") {
2415
+ mailProcessor.receive(validated.data);
3279
2416
  }
3280
- const signal = AbortSignal.any([
3281
- shutdownAbort.signal,
3282
- perMessageController.signal
3283
- ]);
3284
- const ctx = {
3285
- shutdownSignal: shutdownAbort.signal,
3286
- perMessageSignal: perMessageController.signal
3287
- };
3288
- const task = (async () => {
3289
- await runSlots.acquire();
3290
- try {
3291
- await handleInboundMessage(cfg, msg, signal, ctx);
3292
- } finally {
3293
- runSlots.release();
3294
- activeRuns.delete(msg.messageId);
3295
- pendingCancels.delete(msg.messageId);
3296
- }
3297
- })();
3298
- activeTasks.add(task);
3299
- void task.finally(() => {
3300
- activeTasks.delete(task);
3301
- });
3302
2417
  });
3303
2418
  ws.on("close", (code, reason) => {
3304
2419
  console.log(
3305
2420
  `[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
3306
2421
  );
3307
- void shutdown();
2422
+ shutdown();
3308
2423
  });
3309
2424
  ws.on("error", (err) => {
3310
2425
  console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
@@ -3312,55 +2427,31 @@ async function runConnect(options) {
3312
2427
  });
3313
2428
  process.on("SIGINT", () => {
3314
2429
  console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
3315
- void shutdown();
2430
+ shutdown();
3316
2431
  });
3317
2432
  process.on("SIGTERM", () => {
3318
- void shutdown();
2433
+ shutdown();
3319
2434
  });
3320
2435
  });
3321
2436
  }
3322
2437
 
3323
- // src/commands/create-pr.ts
3324
- async function runCreatePr(options) {
3325
- const sessionId = options.sessionId.trim();
3326
- if (!sessionId) {
3327
- console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
3328
- process.exit(1);
3329
- }
3330
- const title = options.title.trim();
3331
- if (!title) {
3332
- console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
3333
- process.exit(1);
3334
- }
3335
- const cfg = await ensureLoggedConfig();
3336
- const api = createApmApiClient(cfg);
3337
- const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
3338
- const pr = await api.cli.createPullRequest({
3339
- sessionId,
3340
- workdir,
3341
- title,
3342
- content: options.content ?? ""
3343
- });
3344
- console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
3345
- }
3346
-
3347
2438
  // src/commands/deploy/backend.ts
3348
2439
  import path5 from "node:path";
3349
2440
 
3350
2441
  // src/commands/deploy/internal/apm-config.ts
3351
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
2442
+ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
3352
2443
  import { resolve as resolve4 } from "node:path";
3353
2444
  function loadApmConfig(options) {
3354
2445
  const p = resolve4(
3355
2446
  process.cwd(),
3356
2447
  options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
3357
2448
  );
3358
- if (!existsSync13(p)) {
2449
+ if (!existsSync9(p)) {
3359
2450
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
3360
2451
  process.exit(1);
3361
2452
  }
3362
2453
  try {
3363
- const raw = readFileSync12(p, "utf8");
2454
+ const raw = readFileSync10(p, "utf8");
3364
2455
  return JSON.parse(raw);
3365
2456
  } catch (e) {
3366
2457
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -3482,7 +2573,7 @@ import path4 from "node:path";
3482
2573
  import Docker from "dockerode";
3483
2574
 
3484
2575
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
3485
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
2576
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
3486
2577
  import path from "node:path";
3487
2578
  function asOptionalTlsBuffer(value) {
3488
2579
  if (typeof value !== "string") {
@@ -3494,8 +2585,8 @@ function asOptionalTlsBuffer(value) {
3494
2585
  if (normalized === "") {
3495
2586
  return void 0;
3496
2587
  }
3497
- if (existsSync14(normalized)) {
3498
- return readFileSync13(normalized);
2588
+ if (existsSync10(normalized)) {
2589
+ return readFileSync11(normalized);
3499
2590
  }
3500
2591
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
3501
2592
  if (looksLikePath) {
@@ -3705,7 +2796,7 @@ var DockerodeClient = class {
3705
2796
  var createDockerodeClient = (config) => new DockerodeClient(config);
3706
2797
 
3707
2798
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
3708
- 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";
3709
2800
  import path2 from "node:path";
3710
2801
  function stripSurroundingQuotes(value) {
3711
2802
  const t = value.trim();
@@ -3722,10 +2813,10 @@ function loadEnvFromFile(envFilePath) {
3722
2813
  return {};
3723
2814
  }
3724
2815
  const targetPath = path2.resolve(envFilePath);
3725
- if (!existsSync15(targetPath) || !statSync5(targetPath).isFile()) {
2816
+ if (!existsSync11(targetPath) || !statSync4(targetPath).isFile()) {
3726
2817
  return {};
3727
2818
  }
3728
- const raw = readFileSync14(targetPath, "utf-8");
2819
+ const raw = readFileSync12(targetPath, "utf-8");
3729
2820
  const result = {};
3730
2821
  for (const line of raw.split(/\r?\n/)) {
3731
2822
  const normalized = line.trim();
@@ -3896,12 +2987,12 @@ function dockerPushImage(params, cwd) {
3896
2987
  }
3897
2988
 
3898
2989
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
3899
- import { existsSync as existsSync16 } from "node:fs";
2990
+ import { existsSync as existsSync12 } from "node:fs";
3900
2991
  import path3 from "node:path";
3901
2992
  function resolveDockerBuildPaths(cwd) {
3902
2993
  const dockerfilePath = path3.join(cwd, "Dockerfile");
3903
2994
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
3904
- if (!existsSync16(dockerfilePath)) {
2995
+ if (!existsSync12(dockerfilePath)) {
3905
2996
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
3906
2997
  }
3907
2998
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -4030,14 +3121,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
4030
3121
  import path7 from "node:path";
4031
3122
 
4032
3123
  // src/commands/deploy/internal/minio.ts
4033
- import { statSync as statSync6 } from "node:fs";
3124
+ import { statSync as statSync5 } from "node:fs";
4034
3125
  import { readdir, readFile } from "node:fs/promises";
4035
3126
  import path6 from "node:path";
4036
3127
  import * as Minio from "minio";
4037
3128
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
4038
3129
  async function isDirectoryPath(dir) {
4039
3130
  try {
4040
- const st = statSync6(dir);
3131
+ const st = statSync5(dir);
4041
3132
  return st.isDirectory();
4042
3133
  } catch {
4043
3134
  return false;
@@ -4067,7 +3158,7 @@ async function collectFiles(root) {
4067
3158
  if (e.isDirectory()) {
4068
3159
  await walk(abs, rel);
4069
3160
  } else if (e.isFile()) {
4070
- const st = statSync6(abs);
3161
+ const st = statSync5(abs);
4071
3162
  out.push({
4072
3163
  absPath: abs,
4073
3164
  relativePath: rel.replace(/\\/g, "/"),
@@ -4403,14 +3494,12 @@ function buildClearRemoteDirExceptZipCommand(target) {
4403
3494
  const script = [
4404
3495
  `T=${quotedTarget}`,
4405
3496
  "S=$(mktemp -d)",
4406
- 'while IFS= read -r -d "" z; do',
4407
- 'r="${z#${T}/}"',
3497
+ 'while IFS= read -r -d "" z; do r="${z#${T}/}"',
4408
3498
  'mkdir -p "${S}/$(dirname "$r")"',
4409
3499
  'mv "$z" "${S}/${r}"',
4410
3500
  'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
4411
3501
  'rm -rf "${T}"/*',
4412
- 'while IFS= read -r -d "" r; do',
4413
- 'r="${r#./}"',
3502
+ 'while IFS= read -r -d "" r; do r="${r#./}"',
4414
3503
  'mkdir -p "${T}/$(dirname "$r")"',
4415
3504
  'mv "${S}/${r}" "${T}/${r}"',
4416
3505
  'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
@@ -4527,7 +3616,7 @@ function registerDeployCommands(program) {
4527
3616
  function buildProgram() {
4528
3617
  const program = new Command();
4529
3618
  program.name("apm").description(
4530
- `\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
4531
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`
4532
3621
  ).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
4533
3622
  program.command("login").description(
@@ -4550,63 +3639,11 @@ function buildProgram() {
4550
3639
  ).action(async () => {
4551
3640
  await runUpdateSkills();
4552
3641
  });
4553
- program.command("sync-deploy-config").description(
4554
- "\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"
4555
- ).action(async () => {
4556
- await runSyncDeployConfig();
4557
- });
4558
- program.command("sync-project-documents").description(
4559
- "\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"
4560
- ).option("--push", "\u63A8\u9001\u672C\u5730 .apm/project/ \u5230\u5E73\u53F0").option("--pull", "\u4ECE\u5E73\u53F0\u62C9\u53D6\u5230 .apm/project/").action(async (opts) => {
4561
- await runSyncProjectDocuments(opts);
4562
- });
4563
- program.command("pull").description(
4564
- "\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/"
4565
- ).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
4566
- await runPull(sessionId);
4567
- });
4568
- 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(
4569
- "--file <name>",
4570
- "\u6587\u6863\u540D\u79F0\uFF08\u5982 PRD \u6216 PRD.md\uFF09\uFF0C\u8BFB\u53D6 .apm/sessions/<sessionId>/docs/ \u4E0B\u5BF9\u5E94\u6587\u4EF6"
4571
- ).action(async (sessionId, opts) => {
4572
- await runSyncDocument(sessionId, { file: opts.file });
4573
- });
4574
- 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) => {
4575
- await runAppendMessage(opts);
4576
- });
4577
- program.command("update-message-status").description("\u66F4\u65B0\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u72B6\u6001").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption(
4578
- "--status <status>",
4579
- "CREATED | QUEUED | TYPING | SUCCESS | FAILED | CANCELLED"
4580
- ).action(async (opts) => {
4581
- await runUpdateMessageStatus(opts);
4582
- });
4583
3642
  program.command("connect").description(
4584
- "\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"
4585
3644
  ).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
4586
3645
  await runConnect(opts);
4587
3646
  });
4588
- program.command("branch").description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
4589
- "-m, --message <text>",
4590
- "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
4591
- ).action(async (sessionId, opts) => {
4592
- await runBranch(sessionId, { message: opts.message });
4593
- });
4594
- program.command("clean-branches").description(
4595
- "\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"
4596
- ).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (opts) => {
4597
- await runCleanBranches({ dryRun: opts.dryRun });
4598
- });
4599
- program.command("create-pr").description(
4600
- "\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"
4601
- ).requiredOption("--session <sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--title <title>", "PR \u6807\u9898").option("--content <content>", "PR \u6B63\u6587\uFF08Markdown\uFF09", "").action(
4602
- async (opts) => {
4603
- await runCreatePr({
4604
- sessionId: opts.session,
4605
- title: opts.title,
4606
- content: opts.content ?? ""
4607
- });
4608
- }
4609
- );
4610
3647
  registerDeployCommands(program);
4611
3648
  return program;
4612
3649
  }