ai-project-manage-cli 6.0.32 → 6.0.34
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
|
@@ -500,11 +500,14 @@ import { join as join5 } from "path";
|
|
|
500
500
|
import { stringify as yamlStringify } from "yaml";
|
|
501
501
|
|
|
502
502
|
// src/session-messages-xml.ts
|
|
503
|
+
function asXmlText(value) {
|
|
504
|
+
return value ?? "";
|
|
505
|
+
}
|
|
503
506
|
function escapeXmlAttr(value) {
|
|
504
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
507
|
+
return asXmlText(value).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
505
508
|
}
|
|
506
509
|
function wrapCdata(value) {
|
|
507
|
-
return `<![CDATA[${value.replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
|
|
510
|
+
return `<![CDATA[${asXmlText(value).replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
|
|
508
511
|
}
|
|
509
512
|
function formatSessionMessagesXml(sessionId, messages) {
|
|
510
513
|
const lines = [
|
|
@@ -1011,7 +1014,8 @@ var VALID_STATUSES = [
|
|
|
1011
1014
|
"CREATED",
|
|
1012
1015
|
"TYPING",
|
|
1013
1016
|
"SUCCESS",
|
|
1014
|
-
"FAILED"
|
|
1017
|
+
"FAILED",
|
|
1018
|
+
"CANCELLED"
|
|
1015
1019
|
];
|
|
1016
1020
|
async function runUpdateMessageStatus(options) {
|
|
1017
1021
|
const messageId = options.id?.trim();
|
|
@@ -1049,25 +1053,17 @@ function parseAgentWsMessage(raw) {
|
|
|
1049
1053
|
function serializeAgentWsMessage(msg) {
|
|
1050
1054
|
return JSON.stringify(msg);
|
|
1051
1055
|
}
|
|
1052
|
-
function
|
|
1053
|
-
if (
|
|
1054
|
-
return { ok: false, reason: "\
|
|
1056
|
+
function validateHeartbeat(o) {
|
|
1057
|
+
if (o.type !== "heartbeat") {
|
|
1058
|
+
return { ok: false, reason: "\u671F\u671B heartbeat" };
|
|
1055
1059
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
if (type !== "heartbeat" && type !== "message") {
|
|
1059
|
-
return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
|
|
1060
|
+
if (!nonEmptyString(o.userId)) {
|
|
1061
|
+
return { ok: false, reason: "heartbeat \u7F3A\u5C11 userId" };
|
|
1060
1062
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
if (!nonEmptyString(o.userId)) {
|
|
1066
|
-
return { ok: false, reason: "heartbeat \u7F3A\u5C11 userId" };
|
|
1067
|
-
}
|
|
1068
|
-
return { ok: true, data: { type: "heartbeat", userId: o.userId.trim() } };
|
|
1069
|
-
}
|
|
1070
|
-
if (type !== "message") {
|
|
1063
|
+
return { ok: true, data: { type: "heartbeat", userId: o.userId.trim() } };
|
|
1064
|
+
}
|
|
1065
|
+
function validateMessagePush(o) {
|
|
1066
|
+
if (o.type !== "message") {
|
|
1071
1067
|
return { ok: false, reason: "\u671F\u671B message" };
|
|
1072
1068
|
}
|
|
1073
1069
|
if (!nonEmptyString(o.messageId)) {
|
|
@@ -1105,6 +1101,35 @@ function validateAgentWsMessage(value, kind) {
|
|
|
1105
1101
|
}
|
|
1106
1102
|
};
|
|
1107
1103
|
}
|
|
1104
|
+
function validateCancel(o) {
|
|
1105
|
+
if (o.type !== "cancel") {
|
|
1106
|
+
return { ok: false, reason: "\u671F\u671B cancel" };
|
|
1107
|
+
}
|
|
1108
|
+
if (!nonEmptyString(o.messageId)) {
|
|
1109
|
+
return { ok: false, reason: "cancel \u7F3A\u5C11 messageId" };
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
ok: true,
|
|
1113
|
+
data: { type: "cancel", messageId: o.messageId.trim() }
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function validateAgentWsMessage(value, kind) {
|
|
1117
|
+
if (typeof value !== "object" || value === null) {
|
|
1118
|
+
return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
|
|
1119
|
+
}
|
|
1120
|
+
const o = value;
|
|
1121
|
+
const type = o.type;
|
|
1122
|
+
if (type !== "heartbeat" && type !== "message" && type !== "cancel") {
|
|
1123
|
+
return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
|
|
1124
|
+
}
|
|
1125
|
+
if (kind === "heartbeat" || type === "heartbeat") {
|
|
1126
|
+
return validateHeartbeat(o);
|
|
1127
|
+
}
|
|
1128
|
+
if (type === "cancel") {
|
|
1129
|
+
return validateCancel(o);
|
|
1130
|
+
}
|
|
1131
|
+
return validateMessagePush(o);
|
|
1132
|
+
}
|
|
1108
1133
|
|
|
1109
1134
|
// src/commands/connect/abort-signal-debug.ts
|
|
1110
1135
|
import {
|
|
@@ -1506,62 +1531,84 @@ async function setMessageError(cfg, messageId, error) {
|
|
|
1506
1531
|
console.log(`[apm] \u5DF2\u8BBE\u7F6E\u6D88\u606F\u9519\u8BEF: ${messageId}`);
|
|
1507
1532
|
}
|
|
1508
1533
|
var SHUTDOWN_DRAIN_MS = 3e3;
|
|
1509
|
-
|
|
1534
|
+
function isUserCancelled(ctx) {
|
|
1535
|
+
return ctx.perMessageSignal.aborted && !ctx.shutdownSignal.aborted;
|
|
1536
|
+
}
|
|
1537
|
+
async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
1538
|
+
if (isUserCancelled(ctx)) return;
|
|
1510
1539
|
if (signal.aborted) return;
|
|
1511
|
-
const parsed = parseAgentWsMessage(raw);
|
|
1512
|
-
if (parsed === null) {
|
|
1513
|
-
console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1516
|
-
if (typeof parsed === "object" && parsed !== null && parsed.type === "heartbeat") {
|
|
1517
|
-
return;
|
|
1518
|
-
}
|
|
1519
|
-
const validated = validateAgentWsMessage(parsed, "outbound");
|
|
1520
|
-
if (!validated.ok) {
|
|
1521
|
-
console.error(`[apm] \u6536\u5230\u65E0\u6548 message: ${validated.reason}`);
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
if (validated.data.type !== "message") {
|
|
1525
|
-
return;
|
|
1526
|
-
}
|
|
1527
|
-
const msg = validated.data;
|
|
1528
1540
|
const messageId = msg.messageId;
|
|
1541
|
+
const runStep = async (step, fn) => {
|
|
1542
|
+
try {
|
|
1543
|
+
return await fn();
|
|
1544
|
+
} catch (err) {
|
|
1545
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1546
|
+
throw new Error(`[${step}] ${detail}`);
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1529
1549
|
try {
|
|
1530
1550
|
if (signal.aborted) return;
|
|
1531
|
-
await
|
|
1551
|
+
await runStep(
|
|
1552
|
+
"branch",
|
|
1553
|
+
() => runBranch(msg.sessionId, { cwd: msg.workdir })
|
|
1554
|
+
);
|
|
1532
1555
|
if (signal.aborted) return;
|
|
1533
|
-
await
|
|
1556
|
+
await runStep(
|
|
1557
|
+
"pull",
|
|
1558
|
+
() => runPull(msg.sessionId, workspaceApmDir(msg.workdir))
|
|
1559
|
+
);
|
|
1534
1560
|
if (signal.aborted) return;
|
|
1535
|
-
await
|
|
1561
|
+
await runStep(
|
|
1562
|
+
"commit-pull",
|
|
1563
|
+
() => commitWorkingTreeIfDirty(msg.workdir, "fix: apm pull")
|
|
1564
|
+
);
|
|
1536
1565
|
if (signal.aborted) return;
|
|
1537
|
-
await
|
|
1538
|
-
|
|
1539
|
-
cfg,
|
|
1540
|
-
{
|
|
1541
|
-
messageId: msg.messageId,
|
|
1542
|
-
sessionId: msg.sessionId,
|
|
1543
|
-
prompt: msg.content,
|
|
1544
|
-
model: msg.model,
|
|
1545
|
-
apiKey: msg.apiKey,
|
|
1546
|
-
workdir: msg.workdir
|
|
1547
|
-
},
|
|
1548
|
-
{ signal }
|
|
1566
|
+
await runStep(
|
|
1567
|
+
"status-typing",
|
|
1568
|
+
() => updateMessageStatus(cfg, messageId, "TYPING")
|
|
1549
1569
|
);
|
|
1550
|
-
await
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1570
|
+
await runStep(
|
|
1571
|
+
"cursor-agent",
|
|
1572
|
+
() => runCursorAgent(
|
|
1573
|
+
cfg,
|
|
1574
|
+
{
|
|
1575
|
+
messageId: msg.messageId,
|
|
1576
|
+
sessionId: msg.sessionId,
|
|
1577
|
+
prompt: msg.content,
|
|
1578
|
+
model: msg.model,
|
|
1579
|
+
apiKey: msg.apiKey,
|
|
1580
|
+
workdir: msg.workdir
|
|
1581
|
+
},
|
|
1582
|
+
{ signal }
|
|
1583
|
+
)
|
|
1554
1584
|
);
|
|
1555
|
-
await
|
|
1556
|
-
|
|
1557
|
-
|
|
1585
|
+
await runStep(
|
|
1586
|
+
"sync-documents",
|
|
1587
|
+
() => syncSessionDocuments(cfg, msg.sessionId, workspaceApmDir(msg.workdir))
|
|
1588
|
+
);
|
|
1589
|
+
await runStep(
|
|
1590
|
+
"commit-documents",
|
|
1591
|
+
() => commitWorkingTreeIfDirty(
|
|
1592
|
+
msg.workdir,
|
|
1593
|
+
"chore(apm): sync session documents"
|
|
1594
|
+
)
|
|
1595
|
+
);
|
|
1596
|
+
await runStep(
|
|
1597
|
+
"status-success",
|
|
1598
|
+
() => updateMessageStatus(cfg, messageId, "SUCCESS")
|
|
1558
1599
|
);
|
|
1559
|
-
await updateMessageStatus(cfg, messageId, "SUCCESS");
|
|
1560
1600
|
} catch (err) {
|
|
1601
|
+
if (isUserCancelled(ctx)) {
|
|
1602
|
+
console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1561
1605
|
console.error(
|
|
1562
1606
|
"[apm] \u5904\u7406\u6D88\u606F\u5931\u8D25:",
|
|
1563
1607
|
err instanceof Error ? err.message : err
|
|
1564
1608
|
);
|
|
1609
|
+
if (err instanceof Error && err.stack) {
|
|
1610
|
+
console.error(err.stack);
|
|
1611
|
+
}
|
|
1565
1612
|
try {
|
|
1566
1613
|
await setMessageError(
|
|
1567
1614
|
cfg,
|
|
@@ -1610,6 +1657,8 @@ async function runConnect(options) {
|
|
|
1610
1657
|
let shuttingDown = false;
|
|
1611
1658
|
const shutdownAbort = new AbortController();
|
|
1612
1659
|
let inboundQueue = Promise.resolve();
|
|
1660
|
+
const activeRuns = /* @__PURE__ */ new Map();
|
|
1661
|
+
const pendingCancels = /* @__PURE__ */ new Set();
|
|
1613
1662
|
const shutdown = async (code = 0) => {
|
|
1614
1663
|
if (shuttingDown) return;
|
|
1615
1664
|
shuttingDown = true;
|
|
@@ -1640,9 +1689,48 @@ async function runConnect(options) {
|
|
|
1640
1689
|
ws.on("message", (data) => {
|
|
1641
1690
|
if (shuttingDown) return;
|
|
1642
1691
|
const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1692
|
+
const parsed = parseAgentWsMessage(text);
|
|
1693
|
+
if (parsed === null) {
|
|
1694
|
+
console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
if (typeof parsed === "object" && parsed !== null && parsed.type === "heartbeat") {
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
const validated = validateAgentWsMessage(parsed, "outbound");
|
|
1701
|
+
if (!validated.ok) {
|
|
1702
|
+
console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
if (validated.data.type === "cancel") {
|
|
1706
|
+
const { messageId } = validated.data;
|
|
1707
|
+
pendingCancels.add(messageId);
|
|
1708
|
+
activeRuns.get(messageId)?.abort();
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (validated.data.type !== "message") {
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const msg = validated.data;
|
|
1715
|
+
const perMessageController = new AbortController();
|
|
1716
|
+
activeRuns.set(msg.messageId, perMessageController);
|
|
1717
|
+
if (pendingCancels.has(msg.messageId)) {
|
|
1718
|
+
activeRuns.delete(msg.messageId);
|
|
1719
|
+
pendingCancels.delete(msg.messageId);
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
const signal = AbortSignal.any([
|
|
1723
|
+
shutdownAbort.signal,
|
|
1724
|
+
perMessageController.signal
|
|
1725
|
+
]);
|
|
1726
|
+
const ctx = {
|
|
1727
|
+
shutdownSignal: shutdownAbort.signal,
|
|
1728
|
+
perMessageSignal: perMessageController.signal
|
|
1729
|
+
};
|
|
1730
|
+
inboundQueue = inboundQueue.then(() => handleInboundMessage(cfg, msg, signal, ctx)).finally(() => {
|
|
1731
|
+
activeRuns.delete(msg.messageId);
|
|
1732
|
+
pendingCancels.delete(msg.messageId);
|
|
1733
|
+
});
|
|
1646
1734
|
});
|
|
1647
1735
|
ws.on("close", (code, reason) => {
|
|
1648
1736
|
console.log(
|
package/package.json
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
内容要求如下:
|
|
12
12
|
**不管是第几版需求,都要当成第一版来看,禁止有历史版本或者修订版本或者第几版更新的字样**
|
|
13
13
|
|
|
14
|
+
**可读性**:信息完整保留,但避免整段只靠长句堆砌。对流程分支、状态条件、按钮对照、范围边界等多步骤/多条件内容,按模板中的「图示原则」**适当补充 Mermaid 图或简表**(每个需求点 0 ~ 1 张);图示用于快速扫读,**可验收细节仍以 bullet 为准**,不得因配图而删减文字要求。
|
|
15
|
+
|
|
14
16
|
### 步骤 3: 同步 PRD 到远程
|
|
15
17
|
|
|
16
18
|
执行命令:`apm sync-document <会话 ID> --file=PRD.md`
|
|
@@ -1,17 +1,43 @@
|
|
|
1
|
-
|
|
2
1
|
## 需求模板
|
|
2
|
+
|
|
3
|
+
### 图示原则
|
|
4
|
+
|
|
5
|
+
信息量保持完整,**文字与图示互补**:bullet 写清可验收细节,图示帮助读者快速建立整体印象。优先用 **Mermaid**(Markdown 内嵌,无需额外图片文件)。
|
|
6
|
+
|
|
7
|
+
| 场景 | 推荐图示 | 作用 |
|
|
8
|
+
| ------------------------------------------- | -------------------------------- | -------------------------- |
|
|
9
|
+
| 多步骤操作流程(审批、暂存/提交、办理路径) | `flowchart` | 一眼看清先后与分支 |
|
|
10
|
+
| 字段展示/必填随状态变化 | `flowchart` 或 `stateDiagram-v2` | 替代「当 A 且 B 时…」长句 |
|
|
11
|
+
| 弹窗/页面内区块与按钮布局 | `block-beta` 或 ASCII 框图 | 标出按钮位置与对照关系 |
|
|
12
|
+
| 多需求点关系、范围边界 | `mindmap` 或简表 | 开篇总览,再展开 bullet |
|
|
13
|
+
| 角色与系统交互时序 | `sequenceDiagram` | 仅步骤多、文字难扫读时使用 |
|
|
14
|
+
|
|
15
|
+
**图示写法**:
|
|
16
|
+
|
|
17
|
+
- 每个需求点 **0 ~ 1 张** 图即可;一张图只表达一个核心概念,节点用业务语言(如「一级审批」「暂存」)。
|
|
18
|
+
- 图 **不替代** bullet:图后仍保留 2 ~ 5 条可验收 bullet,图中未写清的细节(文案、边界、校验时机)必须在文字中写明。
|
|
19
|
+
- 图为辅助:若单条 bullet 已足够清楚,**不必强行配图**。
|
|
20
|
+
|
|
3
21
|
### 正文骨架
|
|
22
|
+
|
|
4
23
|
```markdown
|
|
5
24
|
## 背景
|
|
25
|
+
|
|
6
26
|
(1 ~ 3 句:业务背景、要解决的问题、预期效果,如果没有可空着,不要强行编)
|
|
7
27
|
|
|
8
28
|
## 范围
|
|
29
|
+
|
|
9
30
|
- **包含**:…
|
|
10
31
|
- **不包含**:…
|
|
11
32
|
|
|
33
|
+
(可选:范围较大时,用 mindmap 或简表做一页总览)
|
|
34
|
+
|
|
12
35
|
## 需求说明
|
|
13
36
|
|
|
14
37
|
### 需求点 1:[简短名称]
|
|
38
|
+
|
|
39
|
+
(可选:流程/状态/布局类需求,在此放一张 Mermaid 图)
|
|
40
|
+
|
|
15
41
|
- …
|
|
16
42
|
- …
|
|
17
43
|
|
|
@@ -42,14 +68,67 @@
|
|
|
42
68
|
|
|
43
69
|
### 需求点 3:「科室医德考评小组人员名单」字段
|
|
44
70
|
|
|
71
|
+
(本需求点适合在 bullet 前加「字段展示与校验」流程图,见下方「图示示例」)
|
|
72
|
+
|
|
45
73
|
- 原字段展示名 **「科室医德考评人员名单」** 统一改为 **「科室医德考评小组人员名单」**;凡界面、提示、校验文案等涉及该名称处**一并修改**。
|
|
46
74
|
- 当流程处于 **一级审批** 且该字段**展示**时,须校验为**必填**(在展示场景下触发,而非所有保存入口一律校验)。
|
|
47
75
|
```
|
|
48
76
|
|
|
77
|
+
### 图示示例(按需选用)
|
|
78
|
+
|
|
79
|
+
**字段展示与校验**(对应上文需求点 3):
|
|
80
|
+
|
|
81
|
+
```mermaid
|
|
82
|
+
flowchart LR
|
|
83
|
+
A[进入表单] --> B{流程处于一级审批?}
|
|
84
|
+
B -->|是| C[展示该字段]
|
|
85
|
+
B -->|否| D[按原规则展示/隐藏]
|
|
86
|
+
C --> E{用户操作}
|
|
87
|
+
E -->|暂存或提交| F[校验必填]
|
|
88
|
+
E -->|仅浏览未保存| G[不触发必填校验]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**流程分支**(暂存 vs 提交、审批节点):
|
|
92
|
+
|
|
93
|
+
```mermaid
|
|
94
|
+
flowchart TD
|
|
95
|
+
A[用户打开新增/编辑弹窗] --> B[填写表单]
|
|
96
|
+
B --> C{点击底部按钮}
|
|
97
|
+
C -->|暂存| D[沿用原「确定」逻辑:校验 + 保存,不推进流程]
|
|
98
|
+
C -->|提交| E[沿用原「办理」逻辑:校验 + 保存并推进流程]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**弹窗底部按钮布局**(对照既有按钮):
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
┌─────────────────────────────────────┐
|
|
105
|
+
│ 新增 / 编辑弹窗 │
|
|
106
|
+
│ …表单字段… │
|
|
107
|
+
├─────────────────────────────────────┤
|
|
108
|
+
│ [ 取消 ] [ 暂存 ] │ ← 暂存:原「确定」逻辑,仅改文案
|
|
109
|
+
└─────────────────────────────────────┘
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**范围总览**(需求点较多时,放在「范围」之后):
|
|
113
|
+
|
|
114
|
+
```mermaid
|
|
115
|
+
mindmap
|
|
116
|
+
root((本需求))
|
|
117
|
+
包含
|
|
118
|
+
弹窗底部操作
|
|
119
|
+
字段更名与校验
|
|
120
|
+
不包含
|
|
121
|
+
历史数据迁移
|
|
122
|
+
导出能力
|
|
123
|
+
```
|
|
124
|
+
|
|
49
125
|
### 自检列表
|
|
126
|
+
|
|
50
127
|
- [ ] 含 **背景与目标、范围、需求说明** 三章,叙述完整
|
|
51
128
|
- [ ] 2 分钟内可通读;每个需求点 2 ~ 5 条 bullet,一层列表
|
|
129
|
+
- [ ] 流程/状态/多分支类需求点有 **图示或简表** 辅助,且 bullet 仍覆盖图中未写清的细节
|
|
130
|
+
- [ ] 图示节点使用业务语言,与正文术语一致;无仅看图无法验收的遗漏
|
|
52
131
|
- [ ] 场景、按钮行为、校验时机表述清楚,前后一致
|
|
53
132
|
- [ ] 「范围」与需求点中的 **「不考虑」** 口径一致
|
|
54
133
|
- [ ] 全文使用业务语言,术语统一
|
|
55
|
-
- [ ] 仅材料明确涉及时才有「非功能」「待确认」
|
|
134
|
+
- [ ] 仅材料明确涉及时才有「非功能」「待确认」
|