ai-project-manage-cli 7.1.13 → 7.1.16
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/README.md +1 -1
- package/dist/index.js +152 -83
- package/dist/webide-message-worker.js +253 -69
- package/package.json +1 -1
- package/template/AGENTS.md +7 -3
- package/template/rules/webide_terminal.md +13 -11
package/README.md
CHANGED
|
@@ -57,6 +57,6 @@ apm daemon start -f
|
|
|
57
57
|
|
|
58
58
|
使用全局 PM2(未安装时会自动执行 `npm install -g pm2`);同一时刻仅允许一个 connect。启动时会自动检测 CLI 更新并刷新守护配置。
|
|
59
59
|
|
|
60
|
-
`apm pull`
|
|
60
|
+
`apm pull` 会自动将平台登记的**项目文档**同步到 `.apm/docs/`(含 `manifest.json`)。Agent 修改 `.apm/docs/` 下文件后,`apm connect` 处理完消息会自动推回平台。
|
|
61
61
|
|
|
62
62
|
详见仓库根目录 [docs/CLI.md](../../docs/CLI.md)。
|
package/dist/index.js
CHANGED
|
@@ -184,6 +184,10 @@ var init_request_config = __esm({
|
|
|
184
184
|
method: "GET",
|
|
185
185
|
path: "/cli/webide/assumptions"
|
|
186
186
|
}),
|
|
187
|
+
webideListAttachments: defineEndpoint({
|
|
188
|
+
method: "GET",
|
|
189
|
+
path: "/cli/webide/attachments"
|
|
190
|
+
}),
|
|
187
191
|
webideRecommendPlan: defineEndpoint({
|
|
188
192
|
method: "PUT",
|
|
189
193
|
path: "/cli/webide/plan-recommendation"
|
|
@@ -212,10 +216,20 @@ var init_request_config = __esm({
|
|
|
212
216
|
method: "PUT",
|
|
213
217
|
path: "/cli/webide/terminals/meta"
|
|
214
218
|
}),
|
|
219
|
+
acquireSteelBrowser: defineEndpoint({
|
|
220
|
+
method: "POST",
|
|
221
|
+
path: "/cli/steel-browser/acquire"
|
|
222
|
+
}),
|
|
215
223
|
branchBaseline: defineEndpoint({
|
|
216
224
|
method: "GET",
|
|
217
225
|
path: "/cli/tasks/branch-baseline"
|
|
218
226
|
}),
|
|
227
|
+
projectBaseBranch: defineEndpoint(
|
|
228
|
+
{
|
|
229
|
+
method: "GET",
|
|
230
|
+
path: "/cli/tasks/project-base-branch"
|
|
231
|
+
}
|
|
232
|
+
),
|
|
219
233
|
listSessionsForBranchCleanup: defineEndpoint({
|
|
220
234
|
method: "GET",
|
|
221
235
|
path: "/cli/sessions/branch-cleanup"
|
|
@@ -252,21 +266,21 @@ var init_request_config = __esm({
|
|
|
252
266
|
method: "POST",
|
|
253
267
|
path: "/cli/webide/pull-requests/merge"
|
|
254
268
|
}),
|
|
255
|
-
|
|
269
|
+
getProjectDocumentManifest: defineEndpoint({
|
|
256
270
|
method: "GET",
|
|
257
|
-
path: "/cli/
|
|
271
|
+
path: "/cli/project-documents/manifest"
|
|
258
272
|
}),
|
|
259
|
-
|
|
273
|
+
listProjectDocuments: defineEndpoint({
|
|
260
274
|
method: "GET",
|
|
261
|
-
path: "/cli/
|
|
275
|
+
path: "/cli/project-documents"
|
|
262
276
|
}),
|
|
263
|
-
|
|
277
|
+
upsertProjectDocument: defineEndpoint({
|
|
264
278
|
method: "PUT",
|
|
265
|
-
path: "/cli/
|
|
279
|
+
path: "/cli/project-documents/upsert"
|
|
266
280
|
}),
|
|
267
|
-
|
|
281
|
+
removeProjectDocument: defineEndpoint({
|
|
268
282
|
method: "DELETE",
|
|
269
|
-
path: "/cli/
|
|
283
|
+
path: "/cli/project-documents"
|
|
270
284
|
}),
|
|
271
285
|
createTaskDeployment: defineEndpoint({
|
|
272
286
|
method: "POST",
|
|
@@ -648,11 +662,33 @@ function forceKillProcessTree(pid) {
|
|
|
648
662
|
}
|
|
649
663
|
}
|
|
650
664
|
}
|
|
665
|
+
async function listProcessTreePids(rootPid) {
|
|
666
|
+
const pids = [rootPid];
|
|
667
|
+
const queue = [rootPid];
|
|
668
|
+
while (queue.length > 0) {
|
|
669
|
+
const parent = queue.shift();
|
|
670
|
+
try {
|
|
671
|
+
const { stdout } = await execFileAsync3("pgrep", ["-P", String(parent)], {
|
|
672
|
+
timeout: 2e3
|
|
673
|
+
});
|
|
674
|
+
for (const line of stdout.split("\n")) {
|
|
675
|
+
const child = Number(line.trim());
|
|
676
|
+
if (child > 0 && !pids.includes(child)) {
|
|
677
|
+
pids.push(child);
|
|
678
|
+
queue.push(child);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
} catch {
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return pids;
|
|
685
|
+
}
|
|
651
686
|
async function probeListenPorts(pid) {
|
|
652
687
|
try {
|
|
688
|
+
const treePids = await listProcessTreePids(pid);
|
|
653
689
|
const { stdout } = await execFileAsync3(
|
|
654
690
|
"lsof",
|
|
655
|
-
["-nP", `-p${
|
|
691
|
+
["-nP", "-a", `-p${treePids.join(",")}`, "-iTCP", "-sTCP:LISTEN"],
|
|
656
692
|
{ timeout: 3e3, maxBuffer: 1024 * 1024 }
|
|
657
693
|
);
|
|
658
694
|
const ports = /* @__PURE__ */ new Set();
|
|
@@ -944,23 +980,16 @@ var init_webide_terminal_registry = __esm({
|
|
|
944
980
|
if (!session.pid) return;
|
|
945
981
|
const ports = await probeListenPorts(session.pid);
|
|
946
982
|
if (ports.length === 0) return;
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
}
|
|
983
|
+
const same = ports.length === session.ports.length && ports.every((p, i) => p === session.ports[i]);
|
|
984
|
+
if (same) return;
|
|
985
|
+
session.ports = ports;
|
|
986
|
+
if (!session.primaryUrl && session.ports[0]) {
|
|
987
|
+
session.primaryUrl = `http://127.0.0.1:${session.ports[0]}`;
|
|
953
988
|
}
|
|
954
|
-
if (
|
|
955
|
-
session.
|
|
956
|
-
if (!session.primaryUrl && session.ports[0]) {
|
|
957
|
-
session.primaryUrl = `http://127.0.0.1:${session.ports[0]}`;
|
|
958
|
-
}
|
|
959
|
-
if (session.status === "RUNNING" || session.status === "STARTING") {
|
|
960
|
-
session.status = "READY";
|
|
961
|
-
}
|
|
962
|
-
void this.syncStatus(session);
|
|
989
|
+
if (session.status === "RUNNING" || session.status === "STARTING") {
|
|
990
|
+
session.status = "READY";
|
|
963
991
|
}
|
|
992
|
+
void this.syncStatus(session);
|
|
964
993
|
}
|
|
965
994
|
async syncStatus(session) {
|
|
966
995
|
const api = createApmApiClient(this.cfg);
|
|
@@ -1629,7 +1658,7 @@ ${diagnostic ?? ""}
|
|
|
1629
1658
|
return { synced: true, repositoryId, configName: config.name };
|
|
1630
1659
|
}
|
|
1631
1660
|
|
|
1632
|
-
// src/
|
|
1661
|
+
// src/project-documents-sync.ts
|
|
1633
1662
|
init_client();
|
|
1634
1663
|
init_config();
|
|
1635
1664
|
import {
|
|
@@ -1643,7 +1672,7 @@ import { createHash } from "crypto";
|
|
|
1643
1672
|
import { dirname as dirname2, join as join4, relative, sep } from "path";
|
|
1644
1673
|
var MANIFEST_FILE = "manifest.json";
|
|
1645
1674
|
function projectDocumentsDir(apmRoot) {
|
|
1646
|
-
return join4(apmRoot ?? workspaceApmDir(), "
|
|
1675
|
+
return join4(apmRoot ?? workspaceApmDir(), "docs");
|
|
1647
1676
|
}
|
|
1648
1677
|
function projectDocumentLocalPath(apmRoot, documentPath) {
|
|
1649
1678
|
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
@@ -1720,41 +1749,65 @@ function diffManifestPaths(remote, local) {
|
|
|
1720
1749
|
}
|
|
1721
1750
|
return { download, deleteLocal };
|
|
1722
1751
|
}
|
|
1723
|
-
async function
|
|
1752
|
+
async function resolveProjectIdForSync(api, workdirPath) {
|
|
1753
|
+
try {
|
|
1754
|
+
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
1755
|
+
const projectId = baseline.projectId?.trim() || null;
|
|
1756
|
+
if (projectId) {
|
|
1757
|
+
return { projectId, diagnostic: null };
|
|
1758
|
+
}
|
|
1759
|
+
const projectIds = baseline.projectIds ?? [];
|
|
1760
|
+
if (projectIds.length === 0) {
|
|
1761
|
+
return {
|
|
1762
|
+
projectId: null,
|
|
1763
|
+
diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
|
|
1764
|
+
\u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
return {
|
|
1768
|
+
projectId: null,
|
|
1769
|
+
diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
|
|
1770
|
+
\u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
|
|
1771
|
+
};
|
|
1772
|
+
} catch (err) {
|
|
1773
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1774
|
+
return {
|
|
1775
|
+
projectId: null,
|
|
1776
|
+
diagnostic: detail.replace(/^\[apm\]\s*/, "")
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
async function syncProjectDocumentsPull(workdirPath, apmDir) {
|
|
1724
1781
|
const empty = {
|
|
1725
1782
|
synced: false,
|
|
1726
|
-
|
|
1783
|
+
projectId: null,
|
|
1727
1784
|
downloaded: 0,
|
|
1728
1785
|
deleted: 0
|
|
1729
1786
|
};
|
|
1730
1787
|
const cfg = await tryReadApmConfig();
|
|
1731
1788
|
if (!cfg || !resolveApiKey(cfg)) {
|
|
1732
1789
|
console.log(
|
|
1733
|
-
"[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\
|
|
1790
|
+
"[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
|
|
1734
1791
|
);
|
|
1735
1792
|
return empty;
|
|
1736
1793
|
}
|
|
1737
1794
|
const api = createApmApiClient(cfg);
|
|
1738
|
-
const {
|
|
1795
|
+
const { projectId, diagnostic } = await resolveProjectIdForSync(
|
|
1739
1796
|
api,
|
|
1740
1797
|
workdirPath
|
|
1741
1798
|
);
|
|
1742
|
-
if (!
|
|
1743
|
-
console.log(
|
|
1744
|
-
|
|
1745
|
-
${diagnostic ?? ""}`
|
|
1746
|
-
);
|
|
1799
|
+
if (!projectId) {
|
|
1800
|
+
console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
|
|
1801
|
+
${diagnostic ?? ""}`);
|
|
1747
1802
|
return empty;
|
|
1748
1803
|
}
|
|
1749
1804
|
const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
|
|
1750
|
-
const
|
|
1751
|
-
await ensureDirExists(
|
|
1752
|
-
const { manifest: remoteManifest } = await api.cli.
|
|
1805
|
+
const docsDir = projectDocumentsDir(targetApmDir);
|
|
1806
|
+
await ensureDirExists(docsDir);
|
|
1807
|
+
const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest({ projectId });
|
|
1753
1808
|
if (!remoteManifest) {
|
|
1754
|
-
console.log(
|
|
1755
|
-
|
|
1756
|
-
);
|
|
1757
|
-
return { ...empty, repositoryId };
|
|
1809
|
+
console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
|
|
1810
|
+
return { ...empty, projectId };
|
|
1758
1811
|
}
|
|
1759
1812
|
const localManifest = readLocalManifest(targetApmDir);
|
|
1760
1813
|
const { download, deleteLocal } = diffManifestPaths(
|
|
@@ -1763,12 +1816,14 @@ ${diagnostic ?? ""}`
|
|
|
1763
1816
|
);
|
|
1764
1817
|
let downloaded = 0;
|
|
1765
1818
|
if (download.length > 0) {
|
|
1766
|
-
const { list } = await api.cli.
|
|
1767
|
-
|
|
1819
|
+
const { list } = await api.cli.listProjectDocuments({
|
|
1820
|
+
projectId,
|
|
1768
1821
|
paths: download.join(",")
|
|
1769
1822
|
});
|
|
1770
1823
|
for (const doc of list) {
|
|
1771
|
-
const absPath = toFsPath(
|
|
1824
|
+
const absPath = toFsPath(
|
|
1825
|
+
projectDocumentLocalPath(targetApmDir, doc.path)
|
|
1826
|
+
);
|
|
1772
1827
|
await ensureDirExists(dirname2(absPath));
|
|
1773
1828
|
writeFileSync4(absPath, doc.content, "utf8");
|
|
1774
1829
|
downloaded += 1;
|
|
@@ -1783,34 +1838,34 @@ ${diagnostic ?? ""}`
|
|
|
1783
1838
|
}
|
|
1784
1839
|
}
|
|
1785
1840
|
writeFileSync4(
|
|
1786
|
-
toFsPath(join4(
|
|
1841
|
+
toFsPath(join4(docsDir, MANIFEST_FILE)),
|
|
1787
1842
|
`${JSON.stringify(remoteManifest, null, 2)}
|
|
1788
1843
|
`,
|
|
1789
1844
|
"utf8"
|
|
1790
1845
|
);
|
|
1791
1846
|
console.log(
|
|
1792
|
-
`[apm] \u5DF2\u540C\u6B65\
|
|
1847
|
+
`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted}`
|
|
1793
1848
|
);
|
|
1794
1849
|
return {
|
|
1795
1850
|
synced: true,
|
|
1796
|
-
|
|
1851
|
+
projectId,
|
|
1797
1852
|
downloaded,
|
|
1798
1853
|
deleted
|
|
1799
1854
|
};
|
|
1800
1855
|
}
|
|
1801
|
-
async function
|
|
1856
|
+
async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot) {
|
|
1802
1857
|
const api = createApmApiClient(cfg);
|
|
1803
|
-
const {
|
|
1804
|
-
if (!
|
|
1858
|
+
const { projectId } = await resolveProjectIdForSync(api, workdirPath);
|
|
1859
|
+
if (!projectId) {
|
|
1805
1860
|
return 0;
|
|
1806
1861
|
}
|
|
1807
1862
|
const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
|
|
1808
1863
|
const localPaths = listLocalDocumentPaths(targetApmDir);
|
|
1809
1864
|
if (localPaths.length === 0) {
|
|
1810
|
-
console.log("[apm] \
|
|
1865
|
+
console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1811
1866
|
return 0;
|
|
1812
1867
|
}
|
|
1813
|
-
const remoteManifest = (await api.cli.
|
|
1868
|
+
const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
|
|
1814
1869
|
const remoteHashByPath = new Map(
|
|
1815
1870
|
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1816
1871
|
);
|
|
@@ -1828,17 +1883,17 @@ async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
|
|
|
1828
1883
|
if (remoteHashByPath.get(path19) === contentHash) {
|
|
1829
1884
|
continue;
|
|
1830
1885
|
}
|
|
1831
|
-
await api.cli.
|
|
1832
|
-
|
|
1886
|
+
await api.cli.upsertProjectDocument({
|
|
1887
|
+
projectId,
|
|
1833
1888
|
path: path19,
|
|
1834
1889
|
content,
|
|
1835
1890
|
description: remoteDescriptionByPath.get(path19) ?? void 0
|
|
1836
1891
|
});
|
|
1837
1892
|
synced += 1;
|
|
1838
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\
|
|
1893
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path19}`);
|
|
1839
1894
|
}
|
|
1840
1895
|
if (synced === 0) {
|
|
1841
|
-
console.log("[apm] \
|
|
1896
|
+
console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1842
1897
|
}
|
|
1843
1898
|
return synced;
|
|
1844
1899
|
}
|
|
@@ -1859,7 +1914,7 @@ async function ensureWorkspaceInitialized(workdir, options) {
|
|
|
1859
1914
|
const apmDir = workspaceApmDir(workdir);
|
|
1860
1915
|
await copyTemplateFiles(apmDir, workdir);
|
|
1861
1916
|
const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
1862
|
-
await
|
|
1917
|
+
await syncProjectDocumentsPull(workdir, apmDir);
|
|
1863
1918
|
const trimmedName = options?.name?.trim();
|
|
1864
1919
|
if (trimmedName) {
|
|
1865
1920
|
const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
|
|
@@ -2449,7 +2504,9 @@ import { join as join6 } from "path";
|
|
|
2449
2504
|
var MANIFEST_FILE2 = ".sync-manifest.json";
|
|
2450
2505
|
async function downloadAttachment(cfg, attachmentId) {
|
|
2451
2506
|
const base = cfg.baseUrl.trim().replace(/\/+$/, "");
|
|
2452
|
-
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
|
|
2507
|
+
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
|
|
2508
|
+
attachmentId
|
|
2509
|
+
})}`;
|
|
2453
2510
|
const res = await fetch(url);
|
|
2454
2511
|
if (!res.ok) {
|
|
2455
2512
|
throw new Error(
|
|
@@ -2488,24 +2545,23 @@ function isAttachmentUpToDate(entry, item, dest) {
|
|
|
2488
2545
|
const createdAt = item.createdAt ?? "";
|
|
2489
2546
|
return entry.createdAt === createdAt;
|
|
2490
2547
|
}
|
|
2491
|
-
async function
|
|
2492
|
-
const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
|
|
2548
|
+
async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
|
|
2493
2549
|
await ensureDirExists(dir);
|
|
2494
2550
|
if (attachments.length === 0) {
|
|
2495
2551
|
saveManifest(dir, { version: 1, attachments: {} });
|
|
2496
|
-
return;
|
|
2552
|
+
return [];
|
|
2497
2553
|
}
|
|
2498
2554
|
const manifest = loadManifest(dir);
|
|
2499
2555
|
const nextManifest = { version: 1, attachments: {} };
|
|
2556
|
+
const names = [];
|
|
2500
2557
|
for (const item of attachments) {
|
|
2501
2558
|
const dest = join6(dir, item.name);
|
|
2502
2559
|
const entry = manifest.attachments[item.id];
|
|
2503
2560
|
const createdAt = item.createdAt ?? "";
|
|
2561
|
+
names.push(item.name);
|
|
2504
2562
|
if (isAttachmentUpToDate(entry, item, dest)) {
|
|
2505
2563
|
nextManifest.attachments[item.id] = entry;
|
|
2506
|
-
console.log(
|
|
2507
|
-
`[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
|
|
2508
|
-
);
|
|
2564
|
+
console.log(`[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${logLabel}/${item.name}`);
|
|
2509
2565
|
continue;
|
|
2510
2566
|
}
|
|
2511
2567
|
const buffer = await downloadAttachment(cfg, item.id);
|
|
@@ -2514,9 +2570,19 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
|
2514
2570
|
name: item.name,
|
|
2515
2571
|
createdAt
|
|
2516
2572
|
};
|
|
2517
|
-
console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${
|
|
2573
|
+
console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${logLabel}/${item.name}`);
|
|
2518
2574
|
}
|
|
2519
2575
|
saveManifest(dir, nextManifest);
|
|
2576
|
+
return names;
|
|
2577
|
+
}
|
|
2578
|
+
async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
2579
|
+
const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
|
|
2580
|
+
return syncAttachmentsToDirectory(
|
|
2581
|
+
cfg,
|
|
2582
|
+
attachments,
|
|
2583
|
+
dir,
|
|
2584
|
+
SESSION_ATTACHMENTS_SUBDIR
|
|
2585
|
+
);
|
|
2520
2586
|
}
|
|
2521
2587
|
|
|
2522
2588
|
// src/rules-sync.ts
|
|
@@ -2773,7 +2839,7 @@ async function runPull(sessionId, remoteWorkdir) {
|
|
|
2773
2839
|
await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
|
|
2774
2840
|
await syncPlatformRules(cfg, trimmedId, workdir, apmRoot);
|
|
2775
2841
|
await syncRemoteDeploymentConfig(workdir, apmRoot);
|
|
2776
|
-
await
|
|
2842
|
+
await syncProjectDocumentsPull(workdir, apmRoot);
|
|
2777
2843
|
console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
|
|
2778
2844
|
return dir;
|
|
2779
2845
|
}
|
|
@@ -3026,17 +3092,21 @@ async function runSyncDeployConfig() {
|
|
|
3026
3092
|
}
|
|
3027
3093
|
|
|
3028
3094
|
// src/commands/sync-project-documents.ts
|
|
3095
|
+
init_config();
|
|
3029
3096
|
async function runSyncProjectDocuments(options) {
|
|
3030
|
-
const pull = options?.pull ?? !options?.push;
|
|
3031
|
-
const push = options?.push ?? false;
|
|
3032
3097
|
const workdir = resolveWorkdirPath();
|
|
3033
3098
|
const apmRoot = workspaceApmDir(workdir);
|
|
3034
|
-
|
|
3035
|
-
|
|
3099
|
+
const doPush = Boolean(options.push);
|
|
3100
|
+
const doPull = Boolean(options.pull) || !doPush;
|
|
3101
|
+
if (doPull) {
|
|
3102
|
+
await syncProjectDocumentsPull(workdir, apmRoot);
|
|
3036
3103
|
}
|
|
3037
|
-
if (
|
|
3038
|
-
const cfg = await
|
|
3039
|
-
|
|
3104
|
+
if (doPush) {
|
|
3105
|
+
const cfg = await tryReadApmConfig();
|
|
3106
|
+
if (!cfg) {
|
|
3107
|
+
throw new Error("[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8BF7\u5148\u6267\u884C apm login");
|
|
3108
|
+
}
|
|
3109
|
+
await syncProjectDocumentsPush(cfg, workdir, apmRoot);
|
|
3040
3110
|
}
|
|
3041
3111
|
}
|
|
3042
3112
|
|
|
@@ -7491,7 +7561,7 @@ function createRecommendPlanTool(options) {
|
|
|
7491
7561
|
function createUpsertWebIdePlanTool(options) {
|
|
7492
7562
|
const { cfg, taskId } = options;
|
|
7493
7563
|
return {
|
|
7494
|
-
description: "Persist the implementation plan markdown for this WebIDE task and mark
|
|
7564
|
+
description: "Persist or replace the implementation plan markdown for this WebIDE task and mark it ready for human confirmation. Call with the full plan document (not a diff). Use after generating or revising the plan.",
|
|
7495
7565
|
inputSchema: {
|
|
7496
7566
|
type: "object",
|
|
7497
7567
|
properties: {
|
|
@@ -7878,8 +7948,8 @@ async function obtainAgent(ctx) {
|
|
|
7878
7948
|
settingSources: ["project"]
|
|
7879
7949
|
} : {}
|
|
7880
7950
|
},
|
|
7881
|
-
...ctx.mode ? { mode: ctx.mode } : {}
|
|
7882
|
-
|
|
7951
|
+
...ctx.mode ? { mode: ctx.mode } : {},
|
|
7952
|
+
...ctx.mcpServers ? { mcpServers: ctx.mcpServers } : {}
|
|
7883
7953
|
};
|
|
7884
7954
|
const explicitAgentId = ctx.resumeAgentId?.trim();
|
|
7885
7955
|
const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
|
|
@@ -7976,7 +8046,6 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
7976
8046
|
try {
|
|
7977
8047
|
const run = await agent.send(prompt, {
|
|
7978
8048
|
...ctx.mode ? { mode: ctx.mode } : {},
|
|
7979
|
-
// mcpServers: createPlaywrightMcpServers(),
|
|
7980
8049
|
local: {
|
|
7981
8050
|
...options?.forceSend ? { force: true } : {},
|
|
7982
8051
|
customTools
|
|
@@ -8409,7 +8478,7 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
8409
8478
|
if (!pullRan) {
|
|
8410
8479
|
await runStep(
|
|
8411
8480
|
"sync-project-documents-pull",
|
|
8412
|
-
() =>
|
|
8481
|
+
() => syncProjectDocumentsPull(workdir, apmRoot)
|
|
8413
8482
|
);
|
|
8414
8483
|
}
|
|
8415
8484
|
const agentResult = await runStep(
|
|
@@ -8442,7 +8511,7 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
8442
8511
|
);
|
|
8443
8512
|
await runStep(
|
|
8444
8513
|
"sync-project-documents",
|
|
8445
|
-
() =>
|
|
8514
|
+
() => syncProjectDocumentsPush(cfg, workdir, apmRoot)
|
|
8446
8515
|
);
|
|
8447
8516
|
await runStep(
|
|
8448
8517
|
"commit-files",
|
|
@@ -9979,12 +10048,12 @@ function buildProgram() {
|
|
|
9979
10048
|
await runSyncDeployConfig();
|
|
9980
10049
|
});
|
|
9981
10050
|
program.command("sync-project-documents").description(
|
|
9982
|
-
"\u540C\u6B65\
|
|
9983
|
-
).option("--push", "\u63A8\u9001\u672C\u5730 .apm/
|
|
10051
|
+
"\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF1A\u9ED8\u8BA4 pull \u5230 .apm/docs/\uFF1B\u52A0 --push \u5C06\u672C\u5730\u53D8\u66F4\u63A8\u9001\u5230\u5E73\u53F0"
|
|
10052
|
+
).option("--push", "\u63A8\u9001\u672C\u5730 .apm/docs/ \u5230\u5E73\u53F0").option("--pull", "\u4ECE\u5E73\u53F0\u62C9\u53D6\u5230 .apm/docs/").action(async (opts) => {
|
|
9984
10053
|
await runSyncProjectDocuments(opts);
|
|
9985
10054
|
});
|
|
9986
10055
|
program.command("pull").description(
|
|
9987
|
-
"\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF0C\u5E76\u540C\u6B65\u90E8\u7F72\u914D\u7F6E\u4E0E\
|
|
10056
|
+
"\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF0C\u5E76\u540C\u6B65\u90E8\u7F72\u914D\u7F6E\u4E0E\u9879\u76EE\u6587\u6863\u5230 .apm/docs/"
|
|
9988
10057
|
).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
|
|
9989
10058
|
await runPull(sessionId);
|
|
9990
10059
|
});
|
|
@@ -88,6 +88,10 @@ var requestConfig = {
|
|
|
88
88
|
method: "GET",
|
|
89
89
|
path: "/cli/webide/assumptions"
|
|
90
90
|
}),
|
|
91
|
+
webideListAttachments: defineEndpoint({
|
|
92
|
+
method: "GET",
|
|
93
|
+
path: "/cli/webide/attachments"
|
|
94
|
+
}),
|
|
91
95
|
webideRecommendPlan: defineEndpoint({
|
|
92
96
|
method: "PUT",
|
|
93
97
|
path: "/cli/webide/plan-recommendation"
|
|
@@ -116,10 +120,20 @@ var requestConfig = {
|
|
|
116
120
|
method: "PUT",
|
|
117
121
|
path: "/cli/webide/terminals/meta"
|
|
118
122
|
}),
|
|
123
|
+
acquireSteelBrowser: defineEndpoint({
|
|
124
|
+
method: "POST",
|
|
125
|
+
path: "/cli/steel-browser/acquire"
|
|
126
|
+
}),
|
|
119
127
|
branchBaseline: defineEndpoint({
|
|
120
128
|
method: "GET",
|
|
121
129
|
path: "/cli/tasks/branch-baseline"
|
|
122
130
|
}),
|
|
131
|
+
projectBaseBranch: defineEndpoint(
|
|
132
|
+
{
|
|
133
|
+
method: "GET",
|
|
134
|
+
path: "/cli/tasks/project-base-branch"
|
|
135
|
+
}
|
|
136
|
+
),
|
|
123
137
|
listSessionsForBranchCleanup: defineEndpoint({
|
|
124
138
|
method: "GET",
|
|
125
139
|
path: "/cli/sessions/branch-cleanup"
|
|
@@ -156,21 +170,21 @@ var requestConfig = {
|
|
|
156
170
|
method: "POST",
|
|
157
171
|
path: "/cli/webide/pull-requests/merge"
|
|
158
172
|
}),
|
|
159
|
-
|
|
173
|
+
getProjectDocumentManifest: defineEndpoint({
|
|
160
174
|
method: "GET",
|
|
161
|
-
path: "/cli/
|
|
175
|
+
path: "/cli/project-documents/manifest"
|
|
162
176
|
}),
|
|
163
|
-
|
|
177
|
+
listProjectDocuments: defineEndpoint({
|
|
164
178
|
method: "GET",
|
|
165
|
-
path: "/cli/
|
|
179
|
+
path: "/cli/project-documents"
|
|
166
180
|
}),
|
|
167
|
-
|
|
181
|
+
upsertProjectDocument: defineEndpoint({
|
|
168
182
|
method: "PUT",
|
|
169
|
-
path: "/cli/
|
|
183
|
+
path: "/cli/project-documents/upsert"
|
|
170
184
|
}),
|
|
171
|
-
|
|
185
|
+
removeProjectDocument: defineEndpoint({
|
|
172
186
|
method: "DELETE",
|
|
173
|
-
path: "/cli/
|
|
187
|
+
path: "/cli/project-documents"
|
|
174
188
|
}),
|
|
175
189
|
createTaskDeployment: defineEndpoint({
|
|
176
190
|
method: "POST",
|
|
@@ -207,6 +221,7 @@ import { homedir } from "os";
|
|
|
207
221
|
import { join } from "path";
|
|
208
222
|
var APM_CONFIG_DIR = join(homedir(), ".config", "apm");
|
|
209
223
|
var APM_CONFIG_PATH = join(APM_CONFIG_DIR, "config.json");
|
|
224
|
+
var DEFAULT_BASE_URL = "http://127.0.0.1:3000";
|
|
210
225
|
function resolveClientMachineId(cfg) {
|
|
211
226
|
return (cfg.clientMachineId ?? cfg.userId ?? "").trim();
|
|
212
227
|
}
|
|
@@ -237,6 +252,33 @@ async function tryReadApmConfig() {
|
|
|
237
252
|
return null;
|
|
238
253
|
}
|
|
239
254
|
}
|
|
255
|
+
function defaultApmConfig() {
|
|
256
|
+
return {
|
|
257
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
258
|
+
clientMachineId: "",
|
|
259
|
+
apiKey: ""
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async function ensureApmConfig() {
|
|
263
|
+
const existing = await tryReadApmConfig();
|
|
264
|
+
if (existing) return existing;
|
|
265
|
+
const defaults = defaultApmConfig();
|
|
266
|
+
await writeApmConfig(defaults);
|
|
267
|
+
return defaults;
|
|
268
|
+
}
|
|
269
|
+
async function writeApmConfig(cfg) {
|
|
270
|
+
const clientMachineId = resolveClientMachineId(cfg);
|
|
271
|
+
const apiKey = resolveApiKey(cfg);
|
|
272
|
+
const normalized = {
|
|
273
|
+
baseUrl: cfg.baseUrl.trim().replace(/\/+$/, ""),
|
|
274
|
+
apiKey,
|
|
275
|
+
...clientMachineId ? { clientMachineId } : {}
|
|
276
|
+
};
|
|
277
|
+
mkdirSync(APM_CONFIG_DIR, { recursive: true });
|
|
278
|
+
writeFileSync(APM_CONFIG_PATH, JSON.stringify(normalized, null, 2) + "\n", {
|
|
279
|
+
mode: 384
|
|
280
|
+
});
|
|
281
|
+
}
|
|
240
282
|
|
|
241
283
|
// src/api/client.ts
|
|
242
284
|
function createApmApiClient(cfg) {
|
|
@@ -375,24 +417,6 @@ async function ensureRemoteBaselineBranch(cwd, baselineBranch) {
|
|
|
375
417
|
`[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
|
|
376
418
|
);
|
|
377
419
|
}
|
|
378
|
-
async function resolveDefaultRemoteBranch(cwd) {
|
|
379
|
-
try {
|
|
380
|
-
const ref = (await execGit(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], true)).trim();
|
|
381
|
-
const match = ref.match(/^refs\/remotes\/origin\/(.+)$/);
|
|
382
|
-
if (match?.[1]) {
|
|
383
|
-
return match[1];
|
|
384
|
-
}
|
|
385
|
-
} catch {
|
|
386
|
-
}
|
|
387
|
-
for (const candidate of ["main", "master"]) {
|
|
388
|
-
if (await remoteBranchExists(cwd, candidate)) {
|
|
389
|
-
return candidate;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
throw new Error(
|
|
393
|
-
"[apm] \u65E0\u6CD5\u786E\u5B9A\u8FDC\u7A0B\u9ED8\u8BA4\u5206\u652F\uFF08origin/HEAD\u3001main\u3001master \u5747\u4E0D\u53EF\u7528\uFF09"
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
420
|
async function hasUpstream(cwd) {
|
|
397
421
|
try {
|
|
398
422
|
await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
|
|
@@ -573,6 +597,24 @@ function assertApmGitignoredInRepo(workdir) {
|
|
|
573
597
|
);
|
|
574
598
|
}
|
|
575
599
|
}
|
|
600
|
+
var SESSION_ATTACHMENTS_SUBDIR = "attachments";
|
|
601
|
+
var WEBIDE_SUBDIR = "webide";
|
|
602
|
+
function webideTaskDir(taskId, workdir) {
|
|
603
|
+
return join2(workdir, ".apm", WEBIDE_SUBDIR, taskId);
|
|
604
|
+
}
|
|
605
|
+
function webideAttachmentsDir(taskId, workdir) {
|
|
606
|
+
return join2(webideTaskDir(taskId, workdir), SESSION_ATTACHMENTS_SUBDIR);
|
|
607
|
+
}
|
|
608
|
+
async function ensureLoggedConfig() {
|
|
609
|
+
const cfg = await ensureApmConfig();
|
|
610
|
+
if (!resolveApiKey(cfg)) {
|
|
611
|
+
console.error(
|
|
612
|
+
"[apm] \u672A\u68C0\u6D4B\u5230 API Key\uFF0C\u8BF7\u5148\u6267\u884C: apm login --api-key cm_xxx"
|
|
613
|
+
);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
return cfg;
|
|
617
|
+
}
|
|
576
618
|
async function ensureDirExists(dir) {
|
|
577
619
|
mkdirSync2(dir, { recursive: true });
|
|
578
620
|
}
|
|
@@ -977,6 +1019,17 @@ async function runTaskBranch(taskId, options = {}) {
|
|
|
977
1019
|
if (!trimmedTaskId) {
|
|
978
1020
|
throw new Error("[apm] taskId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
979
1021
|
}
|
|
1022
|
+
const cfg = await ensureLoggedConfig();
|
|
1023
|
+
const api = createApmApiClient(cfg);
|
|
1024
|
+
const projectBaseline = await api.cli.projectBaseBranch({
|
|
1025
|
+
taskId: trimmedTaskId
|
|
1026
|
+
});
|
|
1027
|
+
const baselineBranch = projectBaseline.baseBranch?.trim() ?? "";
|
|
1028
|
+
if (!baselineBranch) {
|
|
1029
|
+
throw new Error(
|
|
1030
|
+
"[apm] \u9879\u76EE\u672A\u914D\u7F6E\u57FA\u7840\u5206\u652F\uFF0C\u65E0\u6CD5\u521B\u5EFA WebIDE \u7279\u6027\u5206\u652F"
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
980
1033
|
const cwd = options.cwd ?? process.cwd();
|
|
981
1034
|
const workdirPath = resolveWorkdirPath(cwd);
|
|
982
1035
|
const manifest = loadWorkspaceReposCache(workdirPath);
|
|
@@ -984,9 +1037,8 @@ async function runTaskBranch(taskId, options = {}) {
|
|
|
984
1037
|
const branch = branchNameForTask(trimmedTaskId);
|
|
985
1038
|
for (const gitRoot of repoRoots) {
|
|
986
1039
|
const label = formatRepoLabel(workdirPath, gitRoot);
|
|
987
|
-
const baselineBranch = await resolveDefaultRemoteBranch(gitRoot);
|
|
988
1040
|
console.log(
|
|
989
|
-
`[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (baseline=${baselineBranch})`
|
|
1041
|
+
`[apm] \u4EFB\u52A1\u5206\u652F ${branch} @ ${label} (project baseline=${baselineBranch})`
|
|
990
1042
|
);
|
|
991
1043
|
await ensureFeatureBranch(branch, baselineBranch, {
|
|
992
1044
|
...options,
|
|
@@ -1643,7 +1695,7 @@ function createRecommendPlanTool(options) {
|
|
|
1643
1695
|
function createUpsertWebIdePlanTool(options) {
|
|
1644
1696
|
const { cfg, taskId } = options;
|
|
1645
1697
|
return {
|
|
1646
|
-
description: "Persist the implementation plan markdown for this WebIDE task and mark
|
|
1698
|
+
description: "Persist or replace the implementation plan markdown for this WebIDE task and mark it ready for human confirmation. Call with the full plan document (not a diff). Use after generating or revising the plan.",
|
|
1647
1699
|
inputSchema: {
|
|
1648
1700
|
type: "object",
|
|
1649
1701
|
properties: {
|
|
@@ -2028,8 +2080,8 @@ async function obtainAgent(ctx) {
|
|
|
2028
2080
|
settingSources: ["project"]
|
|
2029
2081
|
} : {}
|
|
2030
2082
|
},
|
|
2031
|
-
...ctx.mode ? { mode: ctx.mode } : {}
|
|
2032
|
-
|
|
2083
|
+
...ctx.mode ? { mode: ctx.mode } : {},
|
|
2084
|
+
...ctx.mcpServers ? { mcpServers: ctx.mcpServers } : {}
|
|
2033
2085
|
};
|
|
2034
2086
|
const explicitAgentId = ctx.resumeAgentId?.trim();
|
|
2035
2087
|
const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
|
|
@@ -2126,7 +2178,6 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
2126
2178
|
try {
|
|
2127
2179
|
const run = await agent.send(prompt, {
|
|
2128
2180
|
...ctx.mode ? { mode: ctx.mode } : {},
|
|
2129
|
-
// mcpServers: createPlaywrightMcpServers(),
|
|
2130
2181
|
local: {
|
|
2131
2182
|
...options?.forceSend ? { force: true } : {},
|
|
2132
2183
|
customTools
|
|
@@ -2622,7 +2673,7 @@ ${diagnostic ?? ""}
|
|
|
2622
2673
|
return { synced: true, repositoryId, configName: config.name };
|
|
2623
2674
|
}
|
|
2624
2675
|
|
|
2625
|
-
// src/
|
|
2676
|
+
// src/project-documents-sync.ts
|
|
2626
2677
|
import {
|
|
2627
2678
|
existsSync as existsSync5,
|
|
2628
2679
|
readdirSync as readdirSync3,
|
|
@@ -2633,7 +2684,7 @@ import {
|
|
|
2633
2684
|
import { dirname as dirname4, join as join6, relative as relative2, sep } from "path";
|
|
2634
2685
|
var MANIFEST_FILE = "manifest.json";
|
|
2635
2686
|
function projectDocumentsDir(apmRoot) {
|
|
2636
|
-
return join6(apmRoot ?? workspaceApmDir(), "
|
|
2687
|
+
return join6(apmRoot ?? workspaceApmDir(), "docs");
|
|
2637
2688
|
}
|
|
2638
2689
|
function projectDocumentLocalPath(apmRoot, documentPath) {
|
|
2639
2690
|
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
@@ -2684,41 +2735,65 @@ function diffManifestPaths(remote, local) {
|
|
|
2684
2735
|
}
|
|
2685
2736
|
return { download, deleteLocal };
|
|
2686
2737
|
}
|
|
2687
|
-
async function
|
|
2738
|
+
async function resolveProjectIdForSync(api, workdirPath) {
|
|
2739
|
+
try {
|
|
2740
|
+
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
2741
|
+
const projectId = baseline.projectId?.trim() || null;
|
|
2742
|
+
if (projectId) {
|
|
2743
|
+
return { projectId, diagnostic: null };
|
|
2744
|
+
}
|
|
2745
|
+
const projectIds = baseline.projectIds ?? [];
|
|
2746
|
+
if (projectIds.length === 0) {
|
|
2747
|
+
return {
|
|
2748
|
+
projectId: null,
|
|
2749
|
+
diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
|
|
2750
|
+
\u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
return {
|
|
2754
|
+
projectId: null,
|
|
2755
|
+
diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
|
|
2756
|
+
\u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
|
|
2757
|
+
};
|
|
2758
|
+
} catch (err) {
|
|
2759
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
2760
|
+
return {
|
|
2761
|
+
projectId: null,
|
|
2762
|
+
diagnostic: detail.replace(/^\[apm\]\s*/, "")
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
async function syncProjectDocumentsPull(workdirPath, apmDir) {
|
|
2688
2767
|
const empty = {
|
|
2689
2768
|
synced: false,
|
|
2690
|
-
|
|
2769
|
+
projectId: null,
|
|
2691
2770
|
downloaded: 0,
|
|
2692
2771
|
deleted: 0
|
|
2693
2772
|
};
|
|
2694
2773
|
const cfg = await tryReadApmConfig();
|
|
2695
2774
|
if (!cfg || !resolveApiKey(cfg)) {
|
|
2696
2775
|
console.log(
|
|
2697
|
-
"[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\
|
|
2776
|
+
"[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
|
|
2698
2777
|
);
|
|
2699
2778
|
return empty;
|
|
2700
2779
|
}
|
|
2701
2780
|
const api = createApmApiClient(cfg);
|
|
2702
|
-
const {
|
|
2781
|
+
const { projectId, diagnostic } = await resolveProjectIdForSync(
|
|
2703
2782
|
api,
|
|
2704
2783
|
workdirPath
|
|
2705
2784
|
);
|
|
2706
|
-
if (!
|
|
2707
|
-
console.log(
|
|
2708
|
-
|
|
2709
|
-
${diagnostic ?? ""}`
|
|
2710
|
-
);
|
|
2785
|
+
if (!projectId) {
|
|
2786
|
+
console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
|
|
2787
|
+
${diagnostic ?? ""}`);
|
|
2711
2788
|
return empty;
|
|
2712
2789
|
}
|
|
2713
2790
|
const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
|
|
2714
|
-
const
|
|
2715
|
-
await ensureDirExists(
|
|
2716
|
-
const { manifest: remoteManifest } = await api.cli.
|
|
2791
|
+
const docsDir = projectDocumentsDir(targetApmDir);
|
|
2792
|
+
await ensureDirExists(docsDir);
|
|
2793
|
+
const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest({ projectId });
|
|
2717
2794
|
if (!remoteManifest) {
|
|
2718
|
-
console.log(
|
|
2719
|
-
|
|
2720
|
-
);
|
|
2721
|
-
return { ...empty, repositoryId };
|
|
2795
|
+
console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
|
|
2796
|
+
return { ...empty, projectId };
|
|
2722
2797
|
}
|
|
2723
2798
|
const localManifest = readLocalManifest(targetApmDir);
|
|
2724
2799
|
const { download, deleteLocal } = diffManifestPaths(
|
|
@@ -2727,12 +2802,14 @@ ${diagnostic ?? ""}`
|
|
|
2727
2802
|
);
|
|
2728
2803
|
let downloaded = 0;
|
|
2729
2804
|
if (download.length > 0) {
|
|
2730
|
-
const { list } = await api.cli.
|
|
2731
|
-
|
|
2805
|
+
const { list } = await api.cli.listProjectDocuments({
|
|
2806
|
+
projectId,
|
|
2732
2807
|
paths: download.join(",")
|
|
2733
2808
|
});
|
|
2734
2809
|
for (const doc of list) {
|
|
2735
|
-
const absPath = toFsPath(
|
|
2810
|
+
const absPath = toFsPath(
|
|
2811
|
+
projectDocumentLocalPath(targetApmDir, doc.path)
|
|
2812
|
+
);
|
|
2736
2813
|
await ensureDirExists(dirname4(absPath));
|
|
2737
2814
|
writeFileSync7(absPath, doc.content, "utf8");
|
|
2738
2815
|
downloaded += 1;
|
|
@@ -2747,17 +2824,17 @@ ${diagnostic ?? ""}`
|
|
|
2747
2824
|
}
|
|
2748
2825
|
}
|
|
2749
2826
|
writeFileSync7(
|
|
2750
|
-
toFsPath(join6(
|
|
2827
|
+
toFsPath(join6(docsDir, MANIFEST_FILE)),
|
|
2751
2828
|
`${JSON.stringify(remoteManifest, null, 2)}
|
|
2752
2829
|
`,
|
|
2753
2830
|
"utf8"
|
|
2754
2831
|
);
|
|
2755
2832
|
console.log(
|
|
2756
|
-
`[apm] \u5DF2\u540C\u6B65\
|
|
2833
|
+
`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted}`
|
|
2757
2834
|
);
|
|
2758
2835
|
return {
|
|
2759
2836
|
synced: true,
|
|
2760
|
-
|
|
2837
|
+
projectId,
|
|
2761
2838
|
downloaded,
|
|
2762
2839
|
deleted
|
|
2763
2840
|
};
|
|
@@ -2779,7 +2856,7 @@ async function ensureWorkspaceInitialized(workdir, options) {
|
|
|
2779
2856
|
const apmDir = workspaceApmDir(workdir);
|
|
2780
2857
|
await copyTemplateFiles(apmDir, workdir);
|
|
2781
2858
|
const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
2782
|
-
await
|
|
2859
|
+
await syncProjectDocumentsPull(workdir, apmDir);
|
|
2783
2860
|
const trimmedName = options?.name?.trim();
|
|
2784
2861
|
if (trimmedName) {
|
|
2785
2862
|
const apmConfigPath = toFsPath(join7(apmDir, "apm.config.json"));
|
|
@@ -3024,21 +3101,108 @@ async function syncWorkspaceSkills(cfg, workdir) {
|
|
|
3024
3101
|
);
|
|
3025
3102
|
}
|
|
3026
3103
|
|
|
3027
|
-
// src/commands/
|
|
3104
|
+
// src/commands/sync-session-attachments.ts
|
|
3028
3105
|
import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
3029
3106
|
import { join as join11 } from "path";
|
|
3107
|
+
var MANIFEST_FILE2 = ".sync-manifest.json";
|
|
3108
|
+
async function downloadAttachment(cfg, attachmentId) {
|
|
3109
|
+
const base = cfg.baseUrl.trim().replace(/\/+$/, "");
|
|
3110
|
+
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
|
|
3111
|
+
attachmentId
|
|
3112
|
+
})}`;
|
|
3113
|
+
const res = await fetch(url);
|
|
3114
|
+
if (!res.ok) {
|
|
3115
|
+
throw new Error(
|
|
3116
|
+
`[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
|
|
3117
|
+
);
|
|
3118
|
+
}
|
|
3119
|
+
return Buffer.from(await res.arrayBuffer());
|
|
3120
|
+
}
|
|
3121
|
+
function loadManifest(dir) {
|
|
3122
|
+
const path = join11(dir, MANIFEST_FILE2);
|
|
3123
|
+
if (!existsSync8(path)) {
|
|
3124
|
+
return { version: 1, attachments: {} };
|
|
3125
|
+
}
|
|
3126
|
+
try {
|
|
3127
|
+
const parsed = JSON.parse(
|
|
3128
|
+
readFileSync9(path, "utf8")
|
|
3129
|
+
);
|
|
3130
|
+
if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
|
|
3131
|
+
return parsed;
|
|
3132
|
+
}
|
|
3133
|
+
} catch {
|
|
3134
|
+
}
|
|
3135
|
+
return { version: 1, attachments: {} };
|
|
3136
|
+
}
|
|
3137
|
+
function saveManifest(dir, manifest) {
|
|
3138
|
+
writeFileSync10(
|
|
3139
|
+
join11(dir, MANIFEST_FILE2),
|
|
3140
|
+
`${JSON.stringify(manifest, null, 2)}
|
|
3141
|
+
`,
|
|
3142
|
+
"utf8"
|
|
3143
|
+
);
|
|
3144
|
+
}
|
|
3145
|
+
function isAttachmentUpToDate(entry, item, dest) {
|
|
3146
|
+
if (!entry || !existsSync8(dest)) return false;
|
|
3147
|
+
if (entry.name !== item.name) return false;
|
|
3148
|
+
const createdAt = item.createdAt ?? "";
|
|
3149
|
+
return entry.createdAt === createdAt;
|
|
3150
|
+
}
|
|
3151
|
+
async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
|
|
3152
|
+
await ensureDirExists(dir);
|
|
3153
|
+
if (attachments.length === 0) {
|
|
3154
|
+
saveManifest(dir, { version: 1, attachments: {} });
|
|
3155
|
+
return [];
|
|
3156
|
+
}
|
|
3157
|
+
const manifest = loadManifest(dir);
|
|
3158
|
+
const nextManifest = { version: 1, attachments: {} };
|
|
3159
|
+
const names = [];
|
|
3160
|
+
for (const item of attachments) {
|
|
3161
|
+
const dest = join11(dir, item.name);
|
|
3162
|
+
const entry = manifest.attachments[item.id];
|
|
3163
|
+
const createdAt = item.createdAt ?? "";
|
|
3164
|
+
names.push(item.name);
|
|
3165
|
+
if (isAttachmentUpToDate(entry, item, dest)) {
|
|
3166
|
+
nextManifest.attachments[item.id] = entry;
|
|
3167
|
+
console.log(`[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${logLabel}/${item.name}`);
|
|
3168
|
+
continue;
|
|
3169
|
+
}
|
|
3170
|
+
const buffer = await downloadAttachment(cfg, item.id);
|
|
3171
|
+
writeFileSync10(dest, buffer);
|
|
3172
|
+
nextManifest.attachments[item.id] = {
|
|
3173
|
+
name: item.name,
|
|
3174
|
+
createdAt
|
|
3175
|
+
};
|
|
3176
|
+
console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${logLabel}/${item.name}`);
|
|
3177
|
+
}
|
|
3178
|
+
saveManifest(dir, nextManifest);
|
|
3179
|
+
return names;
|
|
3180
|
+
}
|
|
3181
|
+
async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
|
|
3182
|
+
const dir = webideAttachmentsDir(taskId, workdir);
|
|
3183
|
+
return syncAttachmentsToDirectory(
|
|
3184
|
+
cfg,
|
|
3185
|
+
attachments,
|
|
3186
|
+
dir,
|
|
3187
|
+
`.apm/webide/${taskId}/attachments`
|
|
3188
|
+
);
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
// src/commands/connect/cli-version-sync.ts
|
|
3192
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "fs";
|
|
3193
|
+
import { join as join12 } from "path";
|
|
3030
3194
|
var CLI_VERSION_FILE = ".cli-version.json";
|
|
3031
3195
|
function manifestPath2(apmDir) {
|
|
3032
|
-
return
|
|
3196
|
+
return join12(apmDir, CLI_VERSION_FILE);
|
|
3033
3197
|
}
|
|
3034
|
-
function
|
|
3198
|
+
function loadManifest2(apmDir) {
|
|
3035
3199
|
const path = toFsPath(manifestPath2(apmDir));
|
|
3036
|
-
if (!
|
|
3200
|
+
if (!existsSync9(path)) {
|
|
3037
3201
|
return null;
|
|
3038
3202
|
}
|
|
3039
3203
|
try {
|
|
3040
3204
|
const parsed = JSON.parse(
|
|
3041
|
-
|
|
3205
|
+
readFileSync10(path, "utf8")
|
|
3042
3206
|
);
|
|
3043
3207
|
if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
|
|
3044
3208
|
return parsed;
|
|
@@ -3047,9 +3211,9 @@ function loadManifest(apmDir) {
|
|
|
3047
3211
|
}
|
|
3048
3212
|
return null;
|
|
3049
3213
|
}
|
|
3050
|
-
function
|
|
3214
|
+
function saveManifest2(apmDir, cliVersion) {
|
|
3051
3215
|
const manifest = { version: 1, cliVersion };
|
|
3052
|
-
|
|
3216
|
+
writeFileSync11(
|
|
3053
3217
|
toFsPath(manifestPath2(apmDir)),
|
|
3054
3218
|
`${JSON.stringify(manifest, null, 2)}
|
|
3055
3219
|
`,
|
|
@@ -3062,7 +3226,7 @@ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
|
|
|
3062
3226
|
if (cached === currentVersion) {
|
|
3063
3227
|
return false;
|
|
3064
3228
|
}
|
|
3065
|
-
const stored =
|
|
3229
|
+
const stored = loadManifest2(workspaceApmDir(workdir));
|
|
3066
3230
|
if (stored?.cliVersion === currentVersion) {
|
|
3067
3231
|
syncedInSession.set(workdir, currentVersion);
|
|
3068
3232
|
return false;
|
|
@@ -3070,7 +3234,7 @@ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
|
|
|
3070
3234
|
return true;
|
|
3071
3235
|
}
|
|
3072
3236
|
function markSkillsSyncedForCliVersion(workdir, cliVersion) {
|
|
3073
|
-
|
|
3237
|
+
saveManifest2(workspaceApmDir(workdir), cliVersion);
|
|
3074
3238
|
syncedInSession.set(workdir, cliVersion);
|
|
3075
3239
|
}
|
|
3076
3240
|
|
|
@@ -3143,6 +3307,27 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
|
|
|
3143
3307
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3144
3308
|
await ensureWebIdePullRequests(cfg, taskId, workdir);
|
|
3145
3309
|
}
|
|
3310
|
+
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3311
|
+
const api = createApmApiClient(cfg);
|
|
3312
|
+
try {
|
|
3313
|
+
const attachments = await api.cli.webideListAttachments({ taskId });
|
|
3314
|
+
const names = await syncWebIdeAttachments(
|
|
3315
|
+
cfg,
|
|
3316
|
+
taskId,
|
|
3317
|
+
workdir,
|
|
3318
|
+
attachments ?? []
|
|
3319
|
+
);
|
|
3320
|
+
if (names.length > 0) {
|
|
3321
|
+
console.log(
|
|
3322
|
+
`[apm] webide \u9644\u4EF6\u5DF2\u5C31\u7EEA count=${names.length} dir=.apm/webide/${taskId}/attachments`
|
|
3323
|
+
);
|
|
3324
|
+
}
|
|
3325
|
+
} catch (err) {
|
|
3326
|
+
console.warn(
|
|
3327
|
+
"[apm] WebIDE \u9644\u4EF6\u540C\u6B65\u5931\u8D25:",
|
|
3328
|
+
err instanceof Error ? err.message : err
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3146
3331
|
const savedAgentId = loadWebIdeAgentId(workdir, taskId);
|
|
3147
3332
|
const logSyncRef = { current: null };
|
|
3148
3333
|
const outcome = await runCursorAgent(
|
|
@@ -3209,12 +3394,11 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
|
|
|
3209
3394
|
result: outcome.result,
|
|
3210
3395
|
createPlan: outcome.createPlan
|
|
3211
3396
|
});
|
|
3212
|
-
const api = createApmApiClient(cfg);
|
|
3213
3397
|
await api.cli.webideEnsureMessageContent({
|
|
3214
3398
|
id: messageId,
|
|
3215
3399
|
content: fallback
|
|
3216
3400
|
});
|
|
3217
|
-
if (msg.action === "write-plan" && outcome.createPlan && outcome.createPlan.trim()) {
|
|
3401
|
+
if ((msg.action === "write-plan" || msg.action === "revise-plan") && outcome.createPlan && outcome.createPlan.trim()) {
|
|
3218
3402
|
try {
|
|
3219
3403
|
await api.cli.webideUpsertPlan({
|
|
3220
3404
|
taskId,
|
|
@@ -3222,7 +3406,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
|
|
|
3222
3406
|
});
|
|
3223
3407
|
} catch (err) {
|
|
3224
3408
|
console.warn(
|
|
3225
|
-
|
|
3409
|
+
`[apm] ${msg.action} \u8865\u5199\u8BA1\u5212\u5931\u8D25:`,
|
|
3226
3410
|
err instanceof Error ? err.message : err
|
|
3227
3411
|
);
|
|
3228
3412
|
}
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -33,13 +33,14 @@
|
|
|
33
33
|
- webide_testcase.md:WebIDE 生成测试用例时读取,按规范编写并提交用例
|
|
34
34
|
- webide_merge.md:WebIDE 验收通过后读取;直接调用 MergeWebIdePullRequests 合并代码
|
|
35
35
|
|
|
36
|
-
-
|
|
36
|
+
- 项目文档(`.apm/docs/`):
|
|
37
37
|
|
|
38
|
-
- 索引: `.apm/
|
|
39
|
-
- 文档文件: `.apm/
|
|
38
|
+
- 索引: `.apm/docs/manifest.json` — 本项目在平台登记的文档列表
|
|
39
|
+
- 文档文件: `.apm/docs/{path}` — 与 manifest 中 path 对应;`apm pull` 自动同步,`apm connect` 消息结束后自动推回平台
|
|
40
40
|
- 任务涉及菜单名、路由、业务术语等时,**先 Read manifest 与相关文档**,禁止猜测路径
|
|
41
41
|
|
|
42
42
|
- 本轮任务需要关注的文档指引(.apm/sessions/<会话 ID>/\*):
|
|
43
|
+
|
|
43
44
|
- 本轮会话状态: `session.yaml`,从这里可以看到每个成员的信息,找到可以协助你一起解决问题的人,可以结合 `RULE.md`一起看
|
|
44
45
|
- 群文档: `docs/xxxx.md`,当需要相关上下文可以在这里查找,按需阅读
|
|
45
46
|
- 附件列表: `attachments/*`,当有文档中提及附件时,从这里查找
|
|
@@ -47,3 +48,6 @@
|
|
|
47
48
|
- 协作 TODO: `TODO.md`,跨轮次任务清单(待办与已完成项),按需阅读
|
|
48
49
|
- 历史消息记录: `messages.xml`,历史消息列表,数据量很大,按需阅读
|
|
49
50
|
- 初始目标: `TASK.md`,最初的目标,不一定具体,团队成员会有人负责让这个任务变得具体,仅供参考。如果你是相关的角色,则你需要先读取这个目标,然后规划接下来的动作。
|
|
51
|
+
|
|
52
|
+
- WebIDE 任务附件(`.apm/webide/<taskId>/attachments/`):
|
|
53
|
+
- `apm connect` 处理 webide-message 时会自动同步;prompt 中若出现「任务附件」指引,请按路径查看相关文件
|
|
@@ -13,23 +13,25 @@
|
|
|
13
13
|
|
|
14
14
|
## 启动命令来源
|
|
15
15
|
|
|
16
|
-
必须 Read `.apm/
|
|
16
|
+
必须 Read `.apm/docs/deploy.md`,按文档中的 cwd / command / 服务划分调用 `StartWebIdeTerminal`。
|
|
17
17
|
|
|
18
18
|
- 文档不存在或未写明启动方式 → 用 AppendMessage 说明无法启动本地环境,**禁止猜测命令**。
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## 时机(打开本地 dev)
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
固定顺序:
|
|
23
|
+
|
|
24
|
+
1. **`ListWebIdeTerminals`**
|
|
25
|
+
2. 若无活终端 → Read `deploy.md` → 按前后端分别 `StartWebIdeTerminal`;已有活终端则复用
|
|
26
|
+
3. 等待 READY / 端口即可
|
|
27
|
+
4. AppendMessage:可在「终端」面板观察本地日志;「浏览器」面板由平台打开本客户机外网地址(勿粘贴整段 stdout;无需 Agent 打开浏览器)
|
|
28
|
+
|
|
29
|
+
后续改码(热更新):有活终端则**复用,不重起**。
|
|
30
|
+
|
|
31
|
+
显式重跑:`StopAllWebIdeTerminals` → 再 Read `deploy.md` → 重新 Start。
|
|
30
32
|
|
|
31
33
|
## 规范
|
|
32
34
|
|
|
33
35
|
1. 禁止用 Shell / 后台进程方式起 `dev` / `serve` 等长驻命令。
|
|
34
|
-
2. 观察日志请用户看 WebIDE
|
|
36
|
+
2. 观察日志请用户看 WebIDE「终端」面板;页面请用户看「浏览器」面板(客户机外网地址)。
|
|
35
37
|
3. 任务结束或不再需要时可 Stop;不要留下孤儿进程。
|