ai-project-manage-cli 6.0.34 → 6.0.35

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
@@ -1012,6 +1012,7 @@ async function runAppendMessage(options) {
1012
1012
  // src/commands/update-message-status.ts
1013
1013
  var VALID_STATUSES = [
1014
1014
  "CREATED",
1015
+ "QUEUED",
1015
1016
  "TYPING",
1016
1017
  "SUCCESS",
1017
1018
  "FAILED",
@@ -1518,6 +1519,54 @@ async function runCursorAgent(cfg, ctx, options) {
1518
1519
  }
1519
1520
  }
1520
1521
 
1522
+ // src/commands/connect/pre-step-cache.ts
1523
+ var PULL_TTL_MS = 3e4;
1524
+ var lastBranchSessionId = null;
1525
+ var lastPullAtBySession = /* @__PURE__ */ new Map();
1526
+ function shouldRunBranch(sessionId) {
1527
+ return lastBranchSessionId !== sessionId;
1528
+ }
1529
+ function markBranchDone(sessionId) {
1530
+ lastBranchSessionId = sessionId;
1531
+ }
1532
+ function shouldRunPull(sessionId) {
1533
+ const last = lastPullAtBySession.get(sessionId);
1534
+ if (last == null) {
1535
+ return true;
1536
+ }
1537
+ return Date.now() - last >= PULL_TTL_MS;
1538
+ }
1539
+ function markPullDone(sessionId) {
1540
+ lastPullAtBySession.set(sessionId, Date.now());
1541
+ }
1542
+
1543
+ // src/commands/connect/run-slot-pool.ts
1544
+ var DEFAULT_MAX_CONCURRENT = 2;
1545
+ function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
1546
+ let active = 0;
1547
+ const waiters = [];
1548
+ const acquire = () => {
1549
+ if (active < maxConcurrent) {
1550
+ active += 1;
1551
+ return Promise.resolve();
1552
+ }
1553
+ return new Promise((resolve5) => {
1554
+ waiters.push(() => {
1555
+ active += 1;
1556
+ resolve5();
1557
+ });
1558
+ });
1559
+ };
1560
+ const release = () => {
1561
+ active = Math.max(0, active - 1);
1562
+ const next = waiters.shift();
1563
+ if (next) {
1564
+ next();
1565
+ }
1566
+ };
1567
+ return { acquire, release };
1568
+ }
1569
+
1521
1570
  // src/commands/connect.ts
1522
1571
  var HEARTBEAT_MS = 3e4;
1523
1572
  async function updateMessageStatus(cfg, messageId, status) {
@@ -1539,34 +1588,54 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
1539
1588
  if (signal.aborted) return;
1540
1589
  const messageId = msg.messageId;
1541
1590
  const runStep = async (step, fn) => {
1591
+ const startedAt = Date.now();
1542
1592
  try {
1543
- return await fn();
1593
+ const result = await fn();
1594
+ console.log(`[apm] step=${step} elapsed=${Date.now() - startedAt}ms`);
1595
+ return result;
1544
1596
  } catch (err) {
1545
1597
  const detail = err instanceof Error ? err.message : String(err);
1546
1598
  throw new Error(`[${step}] ${detail}`);
1547
1599
  }
1548
1600
  };
1549
1601
  try {
1550
- if (signal.aborted) return;
1551
- await runStep(
1552
- "branch",
1553
- () => runBranch(msg.sessionId, { cwd: msg.workdir })
1554
- );
1555
- if (signal.aborted) return;
1556
- await runStep(
1557
- "pull",
1558
- () => runPull(msg.sessionId, workspaceApmDir(msg.workdir))
1559
- );
1560
- if (signal.aborted) return;
1561
- await runStep(
1562
- "commit-pull",
1563
- () => commitWorkingTreeIfDirty(msg.workdir, "fix: apm pull")
1564
- );
1565
1602
  if (signal.aborted) return;
1566
1603
  await runStep(
1567
1604
  "status-typing",
1568
1605
  () => updateMessageStatus(cfg, messageId, "TYPING")
1569
1606
  );
1607
+ if (shouldRunBranch(msg.sessionId)) {
1608
+ if (signal.aborted) return;
1609
+ await runStep(
1610
+ "branch",
1611
+ () => runBranch(msg.sessionId, { cwd: msg.workdir })
1612
+ );
1613
+ markBranchDone(msg.sessionId);
1614
+ } else {
1615
+ console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
1616
+ }
1617
+ let pullRan = false;
1618
+ if (shouldRunPull(msg.sessionId)) {
1619
+ if (signal.aborted) return;
1620
+ await runStep(
1621
+ "pull",
1622
+ () => runPull(msg.sessionId, workspaceApmDir(msg.workdir))
1623
+ );
1624
+ markPullDone(msg.sessionId);
1625
+ pullRan = true;
1626
+ } else {
1627
+ console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
1628
+ }
1629
+ if (pullRan) {
1630
+ if (signal.aborted) return;
1631
+ await runStep(
1632
+ "commit-pull",
1633
+ () => commitWorkingTreeIfDirty(msg.workdir, "fix: apm pull")
1634
+ );
1635
+ } else {
1636
+ console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
1637
+ }
1638
+ if (signal.aborted) return;
1570
1639
  await runStep(
1571
1640
  "cursor-agent",
1572
1641
  () => runCursorAgent(
@@ -1604,7 +1673,7 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
1604
1673
  }
1605
1674
  console.error(
1606
1675
  "[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
1607
- err instanceof Error ? err.message : err
1676
+ err instanceof Error ? err.message : String(err)
1608
1677
  );
1609
1678
  if (err instanceof Error && err.stack) {
1610
1679
  console.error(err.stack);
@@ -1656,7 +1725,8 @@ async function runConnect(options) {
1656
1725
  let stopHeartbeat;
1657
1726
  let shuttingDown = false;
1658
1727
  const shutdownAbort = new AbortController();
1659
- let inboundQueue = Promise.resolve();
1728
+ const runSlots = createRunSlotPool();
1729
+ const activeTasks = /* @__PURE__ */ new Set();
1660
1730
  const activeRuns = /* @__PURE__ */ new Map();
1661
1731
  const pendingCancels = /* @__PURE__ */ new Set();
1662
1732
  const shutdown = async (code = 0) => {
@@ -1674,7 +1744,7 @@ async function runConnect(options) {
1674
1744
  }
1675
1745
  try {
1676
1746
  await Promise.race([
1677
- inboundQueue,
1747
+ Promise.all(activeTasks),
1678
1748
  new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
1679
1749
  ]);
1680
1750
  } catch {
@@ -1727,9 +1797,19 @@ async function runConnect(options) {
1727
1797
  shutdownSignal: shutdownAbort.signal,
1728
1798
  perMessageSignal: perMessageController.signal
1729
1799
  };
1730
- inboundQueue = inboundQueue.then(() => handleInboundMessage(cfg, msg, signal, ctx)).finally(() => {
1731
- activeRuns.delete(msg.messageId);
1732
- pendingCancels.delete(msg.messageId);
1800
+ const task = (async () => {
1801
+ await runSlots.acquire();
1802
+ try {
1803
+ await handleInboundMessage(cfg, msg, signal, ctx);
1804
+ } finally {
1805
+ runSlots.release();
1806
+ activeRuns.delete(msg.messageId);
1807
+ pendingCancels.delete(msg.messageId);
1808
+ }
1809
+ })();
1810
+ activeTasks.add(task);
1811
+ void task.finally(() => {
1812
+ activeTasks.delete(task);
1733
1813
  });
1734
1814
  });
1735
1815
  ws.on("close", (code, reason) => {
@@ -2945,7 +3025,10 @@ function buildProgram() {
2945
3025
  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) => {
2946
3026
  await runAppendMessage(opts);
2947
3027
  });
2948
- program.command("update-message-status").description("\u66F4\u65B0\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u72B6\u6001").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption("--status <status>", "CREATED | TYPING | SUCCESS | FAILED").action(async (opts) => {
3028
+ program.command("update-message-status").description("\u66F4\u65B0\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u72B6\u6001").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption(
3029
+ "--status <status>",
3030
+ "CREATED | QUEUED | TYPING | SUCCESS | FAILED | CANCELLED"
3031
+ ).action(async (opts) => {
2949
3032
  await runUpdateMessageStatus(opts);
2950
3033
  });
2951
3034
  program.command("connect").description(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.34",
3
+ "version": "6.0.35",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -2,11 +2,24 @@
2
2
 
3
3
  ### 工作流程
4
4
 
5
- 1. 读取 `.apm/sessions/<会话ID>/session.yaml`,必要时读取`messages.xml` 了解当前会话状态与历史消息
5
+ 按任务轻重选择路径:
6
+
7
+ #### 轻量回复(被 @ 询问、确认、同步进展)
8
+
9
+ 1. 读取 `.apm/sessions/<会话ID>/session.yaml`
10
+ 2. 读取 `.apm/rules/reply.md`
11
+ 3. **立即**用 `apm append-message` 回复(可先简短确认,再补充)
12
+ 4. 按需阅读 `docs/` 下的文档,有进展继续 append-message
13
+
14
+ #### 重任务(开发、写方案、评审、部署)
15
+
16
+ 1. 读取 `.apm/sessions/<会话ID>/session.yaml`,必要时读取 `messages.xml` 了解历史
6
17
  2. 根据你的名字从 `session.yaml` 的 `members` 中找到你对应的 **description**(智能体描述,非人设提示词)
7
18
  3. 根据角色描述完成用户指定的任务
8
- 4. 任务有阶段行进展或者任务完成后必须回复消息(具体见规则 `reply.md`)
9
- 5. 写工作日志(具体见规则 `write_doc.md`)
19
+ 4. 任务有阶段性进展或者任务完成后必须回复消息(具体见规则 `reply.md`)
20
+ 5. 写工作日志(具体见规则 `write_doc.md`)
21
+
22
+ **禁止**在未回复前先读完所有 docs。有进展就先 append-message。
10
23
 
11
24
  ### 目录指引
12
25
 
@@ -23,5 +36,5 @@
23
36
  - 附件列表: `attachments/*`,当有文档中提及附件时,从这里查找
24
37
  - 协作规则: `RULE.md`,在这里可以看到不同成员的协作规则,从而找其他成员协助你一起解决问题,按需阅读
25
38
  - 协作 TODO: `TODO.md`,跨轮次任务清单(待办与已完成项),按需阅读
26
- - 历史消息记录: `message.xml`,历史消息列表,数据量很大,按需阅读
39
+ - 历史消息记录: `messages.xml`,历史消息列表,数据量很大,按需阅读
27
40
  - 初始目标: `TASK.md`,最初的目标,不一定具体,团队成员会有人负责让这个任务变得具体,仅供参考。如果你是相关的角色,则你需要先读取这个目标,然后规划接下来的动作。
@@ -2,14 +2,14 @@
2
2
 
3
3
  1. 如果回复的内容包含文档地址,不要把前缀也写出来,直接用文档名称即可。例如: 我已经把待处理的内容更新到了 `PRD.md` 中,请查阅。
4
4
  2. 回复的内容并非对你工作的总结,要尽可能简洁,不要长篇大论,如果同步的内容确实较多,你可以先写文档,然后指引用户去阅读你写的文档。
5
- 3. 只要你有任何进展都想都可以调用下面的命令进行回复,你可以一直补充内容。
5
+ 3. 只要你有任何进展都可以调用下面的命令进行回复,你可以一直补充内容。**被 @ 提及时,收到后必须先回复一句简短确认(不超过 50 字),再开始读文档或执行任务。**
6
6
  4. 如果有需要指定相关人,必须在消息内容中@指定出来
7
7
 
8
8
  ## 执行回复的方法
9
9
 
10
10
  命令: `apm append-message --id=<消息ID> --content=<你要回复的消息内容>`
11
11
 
12
- 示例: apm append-message --id=xxxxx --content="我已经把待处理的内容更新到了 `PRD.md`,请查阅"
12
+ 示例: apm append-message --id=xxxxx --content="收到,正在分析后端改动范围。"
13
13
 
14
14
  ## 写文档的方法
15
15