ai-project-manage-cli 6.0.64 → 7.0.2
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 +3 -7
- package/dist/index.js +903 -2223
- package/package.json +1 -1
- package/template/AGENTS.md +10 -21
- package/template/rules/write_doc.md +4 -4
- package/template/skills/apm-apply-change/SKILL.md +9 -11
- package/template/skills/apm-dev/SKILL.md +12 -13
- package/template/skills/apm-diff-review/SKILL.md +5 -5
- package/template/skills/apm-propose/SKILL.md +4 -4
- package/template/skills/apm-recap/SKILL.md +15 -22
- package/template/skills/apm-recap/recap-template.md +7 -7
- package/template/skills/apm-review/SKILL.md +1 -1
- package/template/skills/apm-write-checklist/SKILL.md +4 -4
- package/template/skills/apm-write-frontend-plan/SKILL.md +3 -3
- package/template/skills/apm-write-plan/SKILL.md +13 -13
- package/template/skills/apm-write-prd/SKILL.md +2 -2
- package/template/sessions/.gitkeep +0 -0
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 ??
|
|
14
|
+
return (cfg.clientMachineId ?? "").trim();
|
|
15
15
|
}
|
|
16
16
|
function resolveApiKey(cfg) {
|
|
17
|
-
return
|
|
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
|
|
27
|
-
if (typeof
|
|
26
|
+
const cfg = v;
|
|
27
|
+
if (typeof cfg.baseUrl !== "string" || typeof cfg.apiKey !== "string") {
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
|
-
const apiKey =
|
|
30
|
+
const apiKey = cfg.apiKey.trim();
|
|
31
31
|
if (!apiKey) return null;
|
|
32
|
-
const
|
|
33
|
-
|
|
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 {
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
-
"
|
|
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
|
-
"
|
|
297
|
+
"project"
|
|
327
298
|
];
|
|
328
299
|
for (const item of required) {
|
|
329
300
|
const path10 = join2(apmDir, item);
|
|
330
|
-
if (!
|
|
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 (
|
|
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 (!
|
|
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/
|
|
387
|
+
path: "/cli/me"
|
|
435
388
|
}),
|
|
436
389
|
workspaceBaseline: defineEndpoint({
|
|
437
390
|
method: "GET",
|
|
@@ -453,10 +406,6 @@ var requestConfig = {
|
|
|
453
406
|
method: "GET",
|
|
454
407
|
path: "/cli/rules"
|
|
455
408
|
}),
|
|
456
|
-
createPullRequest: defineEndpoint({
|
|
457
|
-
method: "POST",
|
|
458
|
-
path: "/cli/pull-requests"
|
|
459
|
-
}),
|
|
460
409
|
getRepositoryProjectDocumentManifest: defineEndpoint({
|
|
461
410
|
method: "GET",
|
|
462
411
|
path: "/cli/repository-project-documents/manifest"
|
|
@@ -473,33 +422,47 @@ var requestConfig = {
|
|
|
473
422
|
method: "DELETE",
|
|
474
423
|
path: "/cli/repository-project-documents"
|
|
475
424
|
}),
|
|
476
|
-
|
|
425
|
+
updateTaskDeploymentStatus: defineEndpoint({
|
|
477
426
|
method: "PUT",
|
|
478
|
-
path: "/cli/
|
|
427
|
+
path: "/cli/task-deployments/status"
|
|
479
428
|
}),
|
|
480
|
-
|
|
429
|
+
syncTaskDeploymentLog: defineEndpoint(
|
|
430
|
+
{
|
|
431
|
+
method: "PUT",
|
|
432
|
+
path: "/cli/task-deployments/log"
|
|
433
|
+
}
|
|
434
|
+
),
|
|
435
|
+
completeTaskDeployment: defineEndpoint({
|
|
481
436
|
method: "PUT",
|
|
482
|
-
path: "/cli/
|
|
437
|
+
path: "/cli/task-deployments/complete"
|
|
483
438
|
}),
|
|
484
|
-
|
|
485
|
-
method: "
|
|
486
|
-
path: "/cli/
|
|
439
|
+
listPendingMailboxMessages: defineEndpoint({
|
|
440
|
+
method: "GET",
|
|
441
|
+
path: "/cli/mailbox-messages/pending"
|
|
442
|
+
}),
|
|
443
|
+
getMailboxMessageDetail: defineEndpoint({
|
|
444
|
+
method: "GET",
|
|
445
|
+
path: "/cli/mailbox-messages/detail"
|
|
446
|
+
}),
|
|
447
|
+
listDocuments: defineEndpoint({
|
|
448
|
+
method: "GET",
|
|
449
|
+
path: "/cli/documents"
|
|
487
450
|
}),
|
|
488
|
-
|
|
451
|
+
upsertDocument: defineEndpoint({
|
|
489
452
|
method: "PUT",
|
|
490
|
-
path: "/cli/
|
|
453
|
+
path: "/cli/documents/upsert"
|
|
491
454
|
}),
|
|
492
|
-
|
|
455
|
+
claimMailboxMessage: defineEndpoint({
|
|
493
456
|
method: "PUT",
|
|
494
|
-
path: "/cli/
|
|
457
|
+
path: "/cli/mailbox-messages/claim"
|
|
495
458
|
}),
|
|
496
|
-
|
|
459
|
+
completeMailboxMessage: defineEndpoint({
|
|
497
460
|
method: "PUT",
|
|
498
|
-
path: "/cli/
|
|
461
|
+
path: "/cli/mailbox-messages/complete"
|
|
499
462
|
}),
|
|
500
|
-
|
|
501
|
-
method: "
|
|
502
|
-
path: "/cli/
|
|
463
|
+
upsertCursorLog: defineEndpoint({
|
|
464
|
+
method: "PUT",
|
|
465
|
+
path: "/cli/cursor-logs"
|
|
503
466
|
})
|
|
504
467
|
}
|
|
505
468
|
};
|
|
@@ -535,7 +498,7 @@ async function tryReadGitOriginUrl(cwd) {
|
|
|
535
498
|
|
|
536
499
|
// src/deployment-config-sync.ts
|
|
537
500
|
var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json \u4E0E .apm/deploy/README.md";
|
|
538
|
-
var SYNC_HINT = "\u767B\u8BB0\u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u3001\u7ED1\u5B9A\u4ED3\u5E93\u540E\uFF0C\
|
|
501
|
+
var SYNC_HINT = "\u767B\u8BB0\u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u3001\u7ED1\u5B9A\u4ED3\u5E93\u540E\uFF0C\u91CD\u65B0 apm init \u53EF\u540C\u6B65\u90E8\u7F72\u914D\u7F6E";
|
|
539
502
|
async function resolveRepositoryIdForSync(api, workdirPath) {
|
|
540
503
|
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
541
504
|
if (baseline.repositoryId) {
|
|
@@ -559,7 +522,7 @@ async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
|
|
|
559
522
|
if (!cfg || !resolveApiKey(cfg)) {
|
|
560
523
|
console.log(
|
|
561
524
|
`[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E\u540C\u6B65\uFF08${TEMPLATE_HINT}\uFF09\u3002
|
|
562
|
-
[apm] \u8BF7\u5148\u6267\u884C apm login\uFF0C\u518D\
|
|
525
|
+
[apm] \u8BF7\u5148\u6267\u884C apm login\uFF0C\u518D\u91CD\u65B0 apm init \u62C9\u53D6\u6700\u65B0\u914D\u7F6E\u3002`
|
|
563
526
|
);
|
|
564
527
|
return { synced: false, repositoryId: null };
|
|
565
528
|
}
|
|
@@ -580,7 +543,7 @@ ${diagnostic ?? ""}
|
|
|
580
543
|
if (!config) {
|
|
581
544
|
console.log(
|
|
582
545
|
`[apm] \u672A\u627E\u5230\u5173\u8054\u4ED3\u5E93\u7684\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF0CrepositoryId\uFF1A${repositoryId}\uFF09\u3002
|
|
583
|
-
[apm] \u8BF7\u5728\u5E73\u53F0\u300C\u90E8\u7F72\u914D\u7F6E\u300D\u4E2D\u521B\u5EFA\u914D\u7F6E\u5E76\u5173\u8054\u8BE5\u4ED3\u5E93\uFF0C\u7136\u540E\
|
|
546
|
+
[apm] \u8BF7\u5728\u5E73\u53F0\u300C\u90E8\u7F72\u914D\u7F6E\u300D\u4E2D\u521B\u5EFA\u914D\u7F6E\u5E76\u5173\u8054\u8BE5\u4ED3\u5E93\uFF0C\u7136\u540E\u91CD\u65B0 apm init \u540C\u6B65\u3002`
|
|
584
547
|
);
|
|
585
548
|
return { synced: false, repositoryId };
|
|
586
549
|
}
|
|
@@ -611,13 +574,12 @@ ${diagnostic ?? ""}
|
|
|
611
574
|
|
|
612
575
|
// src/repository-project-documents-sync.ts
|
|
613
576
|
import {
|
|
614
|
-
existsSync as
|
|
577
|
+
existsSync as existsSync3,
|
|
615
578
|
readdirSync as readdirSync2,
|
|
616
579
|
readFileSync as readFileSync3,
|
|
617
580
|
rmSync,
|
|
618
581
|
writeFileSync as writeFileSync4
|
|
619
582
|
} from "fs";
|
|
620
|
-
import { createHash } from "crypto";
|
|
621
583
|
import { dirname as dirname2, join as join4, relative, sep } from "path";
|
|
622
584
|
var MANIFEST_FILE = "manifest.json";
|
|
623
585
|
function projectDocumentsDir(apmRoot) {
|
|
@@ -638,45 +600,19 @@ function normalizeLocalDocumentPath(path10) {
|
|
|
638
600
|
}
|
|
639
601
|
return segments.join("/");
|
|
640
602
|
}
|
|
641
|
-
function hashLocalFileContent(content) {
|
|
642
|
-
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
643
|
-
}
|
|
644
603
|
function readLocalManifest(apmRoot) {
|
|
645
|
-
const
|
|
646
|
-
if (!
|
|
604
|
+
const manifestPath = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
|
|
605
|
+
if (!existsSync3(manifestPath)) {
|
|
647
606
|
return null;
|
|
648
607
|
}
|
|
649
608
|
try {
|
|
650
609
|
return JSON.parse(
|
|
651
|
-
readFileSync3(
|
|
610
|
+
readFileSync3(manifestPath, "utf8")
|
|
652
611
|
);
|
|
653
612
|
} catch {
|
|
654
613
|
return null;
|
|
655
614
|
}
|
|
656
615
|
}
|
|
657
|
-
function listLocalDocumentPaths(apmRoot) {
|
|
658
|
-
const root = projectDocumentsDir(apmRoot);
|
|
659
|
-
if (!existsSync2(root)) {
|
|
660
|
-
return [];
|
|
661
|
-
}
|
|
662
|
-
const paths = [];
|
|
663
|
-
const walk = (dir) => {
|
|
664
|
-
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
665
|
-
const abs = join4(dir, entry.name);
|
|
666
|
-
if (entry.isDirectory()) {
|
|
667
|
-
walk(abs);
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
if (entry.isFile() && entry.name === MANIFEST_FILE) {
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
|
-
const rel = relative(root, abs).split(sep).join("/");
|
|
674
|
-
paths.push(rel);
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
|
-
walk(root);
|
|
678
|
-
return paths.sort();
|
|
679
|
-
}
|
|
680
616
|
function diffManifestPaths(remote, local) {
|
|
681
617
|
const remoteMap = new Map(
|
|
682
618
|
(remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
@@ -755,7 +691,7 @@ ${diagnostic ?? ""}`
|
|
|
755
691
|
let deleted = 0;
|
|
756
692
|
for (const path10 of deleteLocal) {
|
|
757
693
|
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
758
|
-
if (
|
|
694
|
+
if (existsSync3(absPath)) {
|
|
759
695
|
rmSync(absPath, { force: true });
|
|
760
696
|
deleted += 1;
|
|
761
697
|
}
|
|
@@ -776,50 +712,6 @@ ${diagnostic ?? ""}`
|
|
|
776
712
|
deleted
|
|
777
713
|
};
|
|
778
714
|
}
|
|
779
|
-
async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
|
|
780
|
-
const api = createApmApiClient(cfg);
|
|
781
|
-
const { repositoryId } = await resolveRepositoryIdForSync(api, workdirPath);
|
|
782
|
-
if (!repositoryId) {
|
|
783
|
-
return 0;
|
|
784
|
-
}
|
|
785
|
-
const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
|
|
786
|
-
const localPaths = listLocalDocumentPaths(targetApmDir);
|
|
787
|
-
if (localPaths.length === 0) {
|
|
788
|
-
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
789
|
-
return 0;
|
|
790
|
-
}
|
|
791
|
-
const remoteManifest = (await api.cli.getRepositoryProjectDocumentManifest({ repositoryId })).manifest ?? null;
|
|
792
|
-
const remoteHashByPath = new Map(
|
|
793
|
-
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
794
|
-
);
|
|
795
|
-
const remoteDescriptionByPath = new Map(
|
|
796
|
-
(remoteManifest?.documents ?? []).map((doc) => [
|
|
797
|
-
doc.path,
|
|
798
|
-
doc.description
|
|
799
|
-
])
|
|
800
|
-
);
|
|
801
|
-
let synced = 0;
|
|
802
|
-
for (const path10 of localPaths) {
|
|
803
|
-
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
804
|
-
const content = readFileSync3(absPath, "utf8");
|
|
805
|
-
const contentHash = hashLocalFileContent(content);
|
|
806
|
-
if (remoteHashByPath.get(path10) === contentHash) {
|
|
807
|
-
continue;
|
|
808
|
-
}
|
|
809
|
-
await api.cli.upsertRepositoryProjectDocument({
|
|
810
|
-
repositoryId,
|
|
811
|
-
path: path10,
|
|
812
|
-
content,
|
|
813
|
-
description: remoteDescriptionByPath.get(path10) ?? void 0
|
|
814
|
-
});
|
|
815
|
-
synced += 1;
|
|
816
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: ${path10}`);
|
|
817
|
-
}
|
|
818
|
-
if (synced === 0) {
|
|
819
|
-
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
820
|
-
}
|
|
821
|
-
return synced;
|
|
822
|
-
}
|
|
823
715
|
|
|
824
716
|
// src/git-utils.ts
|
|
825
717
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -929,14 +821,13 @@ async function runInit(name) {
|
|
|
929
821
|
console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
|
|
930
822
|
if (syncResult && !syncResult.synced) {
|
|
931
823
|
console.log(
|
|
932
|
-
"[apm] \u5F53\u524D .apm/apm.config.json \u4E0E .apm/deploy/README.md \u4E3A\u6A21\u677F\u9ED8\u8BA4\u503C\uFF1B\u5B8C\u6210\u5E73\u53F0\u767B\u8BB0\u540E\
|
|
824
|
+
"[apm] \u5F53\u524D .apm/apm.config.json \u4E0E .apm/deploy/README.md \u4E3A\u6A21\u677F\u9ED8\u8BA4\u503C\uFF1B\u5B8C\u6210\u5E73\u53F0\u767B\u8BB0\u540E\u91CD\u65B0 apm init \u53EF\u540C\u6B65"
|
|
933
825
|
);
|
|
934
826
|
}
|
|
935
827
|
console.log("[apm] \u8BF7\u5728\u5E73\u53F0\u300C\u63A5\u5165\u7BA1\u7406 \u2192 \u5DE5\u4F5C\u7A7A\u95F4\u300D\u767B\u8BB0\u4E0A\u8FF0\u76EE\u5F55\u8DEF\u5F84");
|
|
936
828
|
}
|
|
937
829
|
|
|
938
830
|
// src/commands/login.ts
|
|
939
|
-
import { existsSync as existsSync3 } from "fs";
|
|
940
831
|
import { ApiError } from "listpage-http";
|
|
941
832
|
async function runLogin(opts) {
|
|
942
833
|
const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
@@ -989,500 +880,106 @@ async function runLogin(opts) {
|
|
|
989
880
|
2
|
|
990
881
|
)
|
|
991
882
|
);
|
|
992
|
-
const workdir = resolveWorkdirPath();
|
|
993
|
-
const apmDir = workspaceApmDir(workdir);
|
|
994
|
-
if (existsSync3(apmDir)) {
|
|
995
|
-
await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
996
|
-
}
|
|
997
883
|
}
|
|
998
884
|
|
|
999
|
-
// src/commands/
|
|
1000
|
-
import {
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
}
|
|
1009
|
-
if (/[\s/\\]/.test(id)) {
|
|
1010
|
-
throw new Error(
|
|
1011
|
-
"[apm] \u4F1A\u8BDD ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
|
|
1012
|
-
);
|
|
1013
|
-
}
|
|
1014
|
-
return `${SESSION_BRANCH_PREFIX}${id}`;
|
|
1015
|
-
}
|
|
1016
|
-
function sessionIdFromBranchName(branch) {
|
|
1017
|
-
const name = branch.trim().replace(/^origin\//, "");
|
|
1018
|
-
if (!name.startsWith(SESSION_BRANCH_PREFIX)) {
|
|
1019
|
-
return null;
|
|
1020
|
-
}
|
|
1021
|
-
const sessionId = name.slice(SESSION_BRANCH_PREFIX.length).trim();
|
|
1022
|
-
return sessionId || null;
|
|
1023
|
-
}
|
|
1024
|
-
async function execGit2(cwd, args, quiet) {
|
|
885
|
+
// src/commands/update.ts
|
|
886
|
+
import { spawnSync } from "child_process";
|
|
887
|
+
|
|
888
|
+
// src/version.ts
|
|
889
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
890
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
891
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
892
|
+
var CLI_PACKAGE_NAME = "ai-project-manage-cli";
|
|
893
|
+
function readCliVersion() {
|
|
1025
894
|
try {
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
process.stderr.write(stderr);
|
|
1033
|
-
}
|
|
1034
|
-
return stdout;
|
|
1035
|
-
} catch (err) {
|
|
1036
|
-
const e = err;
|
|
1037
|
-
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
1038
|
-
throw new Error(
|
|
1039
|
-
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
1040
|
-
);
|
|
895
|
+
const dir = dirname3(fileURLToPath2(import.meta.url));
|
|
896
|
+
const pkgPath = join6(dir, "..", "package.json");
|
|
897
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
898
|
+
return pkg.version ?? "0.0.0";
|
|
899
|
+
} catch {
|
|
900
|
+
return "0.0.0";
|
|
1041
901
|
}
|
|
1042
902
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const out = await execGit2(cwd, ["status", "--porcelain"], true);
|
|
1052
|
-
return out.trim().length > 0;
|
|
903
|
+
|
|
904
|
+
// src/commands/update.ts
|
|
905
|
+
var useNpmShell = process.platform === "win32";
|
|
906
|
+
function runNpm(args, options = {}) {
|
|
907
|
+
return spawnSync(useNpmShell ? "npm.cmd" : "npm", args, {
|
|
908
|
+
...options,
|
|
909
|
+
shell: useNpmShell
|
|
910
|
+
});
|
|
1053
911
|
}
|
|
1054
|
-
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
["ls-remote", "--heads", "origin", branch],
|
|
1058
|
-
true
|
|
1059
|
-
);
|
|
1060
|
-
return out.trim().length > 0;
|
|
912
|
+
function registryBaseUrl() {
|
|
913
|
+
const fromEnv = process.env.npm_config_registry?.trim() || process.env.NPM_CONFIG_REGISTRY?.trim();
|
|
914
|
+
return (fromEnv || "https://registry.npmjs.org").replace(/\/+$/, "");
|
|
1061
915
|
}
|
|
1062
|
-
async function
|
|
916
|
+
async function fetchLatestPublishedVersion() {
|
|
917
|
+
const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
|
|
1063
918
|
try {
|
|
1064
|
-
await
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
);
|
|
1069
|
-
return true;
|
|
919
|
+
const res = await fetch(url);
|
|
920
|
+
if (!res.ok) return null;
|
|
921
|
+
const data = await res.json();
|
|
922
|
+
return data.version?.trim() || null;
|
|
1070
923
|
} catch {
|
|
1071
|
-
return
|
|
924
|
+
return null;
|
|
1072
925
|
}
|
|
1073
926
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
return false;
|
|
1078
|
-
}
|
|
1079
|
-
const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
|
|
1080
|
-
await execGit2(cwd, ["add", "-A"]);
|
|
1081
|
-
await execGit2(cwd, ["commit", "-m", commitMessage]);
|
|
1082
|
-
console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
|
|
1083
|
-
return true;
|
|
927
|
+
function npmAvailable() {
|
|
928
|
+
const r = runNpm(["--version"], { encoding: "utf8" });
|
|
929
|
+
return !r.error && r.status === 0;
|
|
1084
930
|
}
|
|
1085
|
-
async function
|
|
1086
|
-
const
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
`[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F ${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
|
|
1092
|
-
);
|
|
1093
|
-
}
|
|
1094
|
-
const current = await getCurrentBranch(cwd);
|
|
1095
|
-
const dirty = await isWorkingTreeDirty(cwd);
|
|
1096
|
-
if (dirty) {
|
|
1097
|
-
if (current === branch) {
|
|
1098
|
-
await commitWorkingTreeIfDirty(cwd, commitMessage);
|
|
1099
|
-
} else {
|
|
1100
|
-
await execGit2(cwd, [
|
|
1101
|
-
"stash",
|
|
1102
|
-
"push",
|
|
1103
|
-
"-u",
|
|
1104
|
-
"-m",
|
|
1105
|
-
`apm: switch to ${branch}`
|
|
1106
|
-
]);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
const onTargetBranch = await getCurrentBranch(cwd) === branch;
|
|
1110
|
-
if (onTargetBranch) {
|
|
1111
|
-
await execGit2(cwd, ["fetch", "origin", baselineBranch]);
|
|
1112
|
-
await execGit2(cwd, ["merge", `origin/${baselineBranch}`, "--no-edit"]);
|
|
1113
|
-
} else {
|
|
1114
|
-
const remoteExists = await remoteHeadBranchExists(cwd, branch);
|
|
1115
|
-
if (remoteExists) {
|
|
1116
|
-
await execGit2(cwd, ["fetch", "origin", branch]);
|
|
1117
|
-
await execGit2(cwd, ["checkout", "-B", branch, `origin/${branch}`]);
|
|
1118
|
-
} else if (await localBranchExists(cwd, branch)) {
|
|
1119
|
-
if (await getCurrentBranch(cwd) !== branch) {
|
|
1120
|
-
await execGit2(cwd, ["checkout", branch]);
|
|
1121
|
-
}
|
|
1122
|
-
console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
|
|
1123
|
-
} else {
|
|
1124
|
-
await execGit2(cwd, ["fetch", "origin", baselineBranch]);
|
|
1125
|
-
try {
|
|
1126
|
-
await execGit2(cwd, [
|
|
1127
|
-
"checkout",
|
|
1128
|
-
"-b",
|
|
1129
|
-
branch,
|
|
1130
|
-
`origin/${baselineBranch}`
|
|
1131
|
-
]);
|
|
1132
|
-
await execGit2(cwd, ["push", "-u", "origin", branch]);
|
|
1133
|
-
} catch (err) {
|
|
1134
|
-
if (await localBranchExists(cwd, branch)) {
|
|
1135
|
-
if (await getCurrentBranch(cwd) !== branch) {
|
|
1136
|
-
await execGit2(cwd, ["checkout", branch]);
|
|
1137
|
-
}
|
|
1138
|
-
console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
|
|
1139
|
-
} else {
|
|
1140
|
-
throw err;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
931
|
+
async function runUpdate() {
|
|
932
|
+
const current = readCliVersion();
|
|
933
|
+
const latest = await fetchLatestPublishedVersion();
|
|
934
|
+
if (latest && current === latest) {
|
|
935
|
+
console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
|
|
936
|
+
return { didUpdate: false };
|
|
1144
937
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
const trimmedSessionId = sessionId.trim();
|
|
1150
|
-
if (!trimmedSessionId) {
|
|
1151
|
-
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
938
|
+
if (!npmAvailable()) {
|
|
939
|
+
console.error(
|
|
940
|
+
`[apm] \u672A\u627E\u5230 npm\u3002\u8BF7\u5B89\u88C5 Node.js \u540E\u6267\u884C\uFF1Anpm install -g ${CLI_PACKAGE_NAME}@latest`
|
|
941
|
+
);
|
|
1152
942
|
process.exit(1);
|
|
1153
943
|
}
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
workdirPath
|
|
944
|
+
const targetLabel = latest ?? "latest";
|
|
945
|
+
console.error(
|
|
946
|
+
`[apm] \u5F53\u524D\u7248\u672C ${current}\uFF0C\u6B63\u5728\u5B89\u88C5 ${CLI_PACKAGE_NAME}@${targetLabel} \u2026`
|
|
947
|
+
);
|
|
948
|
+
const install = runNpm(["install", "-g", `${CLI_PACKAGE_NAME}@latest`], {
|
|
949
|
+
stdio: "inherit"
|
|
1161
950
|
});
|
|
1162
|
-
if (
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
951
|
+
if (install.error) {
|
|
952
|
+
console.error("[apm] \u66F4\u65B0\u5931\u8D25:", install.error.message);
|
|
953
|
+
process.exit(1);
|
|
954
|
+
}
|
|
955
|
+
if (install.status !== 0) {
|
|
956
|
+
process.exit(install.status ?? 1);
|
|
1166
957
|
}
|
|
1167
|
-
const
|
|
1168
|
-
if (
|
|
1169
|
-
|
|
958
|
+
const after = readCliVersion();
|
|
959
|
+
if (latest && after === latest) {
|
|
960
|
+
console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
|
|
961
|
+
} else {
|
|
962
|
+
console.log(
|
|
963
|
+
`[apm] \u66F4\u65B0\u5B8C\u6210\u3002\u82E5\u7248\u672C\u53F7\u672A\u53D8\u5316\uFF0C\u8BF7\u5728\u65B0\u7EC8\u7AEF\u6267\u884C apm -V \u786E\u8BA4\uFF08\u5168\u5C40\u5B89\u88C5\u8DEF\u5F84\u53EF\u80FD\u672A\u5237\u65B0\uFF09`
|
|
964
|
+
);
|
|
1170
965
|
}
|
|
1171
|
-
|
|
1172
|
-
return ensureFeatureBranch(branch, baselineBranch, options);
|
|
966
|
+
return { didUpdate: true };
|
|
1173
967
|
}
|
|
1174
968
|
|
|
1175
|
-
// src/commands/
|
|
1176
|
-
import {
|
|
1177
|
-
import {
|
|
1178
|
-
var execFileAsync4 = promisify4(execFile4);
|
|
1179
|
-
async function execGit3(cwd, args, quiet) {
|
|
1180
|
-
try {
|
|
1181
|
-
const { stdout, stderr } = await execFileAsync4("git", args, {
|
|
1182
|
-
cwd,
|
|
1183
|
-
encoding: "utf8",
|
|
1184
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1185
|
-
});
|
|
1186
|
-
if (!quiet && stderr.trim()) {
|
|
1187
|
-
process.stderr.write(stderr);
|
|
1188
|
-
}
|
|
1189
|
-
return stdout;
|
|
1190
|
-
} catch (err) {
|
|
1191
|
-
const e = err;
|
|
1192
|
-
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
1193
|
-
throw new Error(
|
|
1194
|
-
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
1195
|
-
);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
async function ensureGitRepo2(cwd) {
|
|
1199
|
-
await execGit3(cwd, ["rev-parse", "--git-dir"], true);
|
|
1200
|
-
}
|
|
1201
|
-
async function getCurrentBranch2(cwd) {
|
|
1202
|
-
return (await execGit3(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
1203
|
-
}
|
|
1204
|
-
async function resolveDefaultBranch(cwd) {
|
|
1205
|
-
try {
|
|
1206
|
-
const ref = (await execGit3(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], true)).trim();
|
|
1207
|
-
const match = ref.match(/^refs\/remotes\/origin\/(.+)$/);
|
|
1208
|
-
if (match?.[1]) {
|
|
1209
|
-
return match[1];
|
|
1210
|
-
}
|
|
1211
|
-
} catch {
|
|
1212
|
-
}
|
|
1213
|
-
const out = await execGit3(cwd, ["remote", "show", "origin"], true);
|
|
1214
|
-
const headLine = out.split(/\r?\n/).find((line) => line.includes("HEAD branch"));
|
|
1215
|
-
const branch = headLine?.split(":").pop()?.trim();
|
|
1216
|
-
if (branch) {
|
|
1217
|
-
return branch;
|
|
1218
|
-
}
|
|
1219
|
-
throw new Error("[apm] \u65E0\u6CD5\u89E3\u6790 origin \u9ED8\u8BA4\u5206\u652F\uFF0C\u8BF7\u5148\u6267\u884C git fetch origin");
|
|
1220
|
-
}
|
|
1221
|
-
async function listLocalSessionBranches(cwd) {
|
|
1222
|
-
const out = await execGit3(
|
|
1223
|
-
cwd,
|
|
1224
|
-
[
|
|
1225
|
-
"for-each-ref",
|
|
1226
|
-
"--format=%(refname:short)",
|
|
1227
|
-
"refs/heads/",
|
|
1228
|
-
SESSION_BRANCH_PREFIX + "*"
|
|
1229
|
-
],
|
|
1230
|
-
true
|
|
1231
|
-
);
|
|
1232
|
-
return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1233
|
-
}
|
|
1234
|
-
async function listRemoteSessionBranches(cwd) {
|
|
1235
|
-
const out = await execGit3(
|
|
1236
|
-
cwd,
|
|
1237
|
-
[
|
|
1238
|
-
"for-each-ref",
|
|
1239
|
-
"--format=%(refname:short)",
|
|
1240
|
-
"refs/remotes/origin/",
|
|
1241
|
-
SESSION_BRANCH_PREFIX + "*"
|
|
1242
|
-
],
|
|
1243
|
-
true
|
|
1244
|
-
);
|
|
1245
|
-
return out.split(/\r?\n/).map((line) => line.trim().replace(/^origin\//, "")).filter(Boolean);
|
|
1246
|
-
}
|
|
1247
|
-
async function localBranchExists2(cwd, branch) {
|
|
1248
|
-
try {
|
|
1249
|
-
await execGit3(
|
|
1250
|
-
cwd,
|
|
1251
|
-
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
1252
|
-
true
|
|
1253
|
-
);
|
|
1254
|
-
return true;
|
|
1255
|
-
} catch {
|
|
1256
|
-
return false;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
async function remoteBranchExists(cwd, branch) {
|
|
1260
|
-
const out = await execGit3(
|
|
1261
|
-
cwd,
|
|
1262
|
-
["ls-remote", "--heads", "origin", branch],
|
|
1263
|
-
true
|
|
1264
|
-
);
|
|
1265
|
-
return out.trim().length > 0;
|
|
1266
|
-
}
|
|
1267
|
-
function reasonForCleanup(sessionId, sessionStatusById) {
|
|
1268
|
-
if (!sessionStatusById.has(sessionId)) {
|
|
1269
|
-
return "\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D";
|
|
1270
|
-
}
|
|
1271
|
-
if (sessionStatusById.get(sessionId) === "COMPLETED") {
|
|
1272
|
-
return "\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210";
|
|
1273
|
-
}
|
|
1274
|
-
return "\u4FDD\u7559";
|
|
1275
|
-
}
|
|
1276
|
-
async function isBranchMergedIntoDefault(cwd, branch, defaultBranch) {
|
|
1277
|
-
const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
|
|
1278
|
-
try {
|
|
1279
|
-
await execGit3(
|
|
1280
|
-
cwd,
|
|
1281
|
-
["merge-base", "--is-ancestor", ref, `origin/${defaultBranch}`],
|
|
1282
|
-
true
|
|
1283
|
-
);
|
|
1284
|
-
return true;
|
|
1285
|
-
} catch {
|
|
1286
|
-
return false;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
async function runCleanBranches(options = {}) {
|
|
1290
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1291
|
-
const dryRun = options.dryRun ?? false;
|
|
1292
|
-
await ensureGitRepo2(cwd);
|
|
1293
|
-
await execGit3(cwd, ["fetch", "--prune", "origin"], true);
|
|
1294
|
-
const cfg = await ensureLoggedConfig();
|
|
1295
|
-
const api = createApmApiClient(cfg);
|
|
1296
|
-
const { sessions } = await api.cli.listSessionsForBranchCleanup({});
|
|
1297
|
-
const sessionStatusById = new Map(
|
|
1298
|
-
sessions.map((item) => [item.sessionId, item.taskStatus])
|
|
1299
|
-
);
|
|
1300
|
-
const branchNames = /* @__PURE__ */ new Set([
|
|
1301
|
-
...await listLocalSessionBranches(cwd),
|
|
1302
|
-
...await listRemoteSessionBranches(cwd)
|
|
1303
|
-
]);
|
|
1304
|
-
if (branchNames.size === 0) {
|
|
1305
|
-
console.log("[apm] \u672A\u53D1\u73B0 feat/session-* \u5206\u652F");
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
const toDelete = [...branchNames].map((branch) => {
|
|
1309
|
-
const sessionId = sessionIdFromBranchName(branch);
|
|
1310
|
-
if (!sessionId) {
|
|
1311
|
-
return null;
|
|
1312
|
-
}
|
|
1313
|
-
const reason = reasonForCleanup(sessionId, sessionStatusById);
|
|
1314
|
-
if (reason === "\u4FDD\u7559") {
|
|
1315
|
-
return null;
|
|
1316
|
-
}
|
|
1317
|
-
return { branch, sessionId, reason };
|
|
1318
|
-
}).filter((item) => item != null).sort((a, b) => a.branch.localeCompare(b.branch));
|
|
1319
|
-
if (toDelete.length === 0) {
|
|
1320
|
-
console.log("[apm] \u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684 feat/session-* \u5206\u652F");
|
|
1321
|
-
return;
|
|
1322
|
-
}
|
|
1323
|
-
let currentBranch = await getCurrentBranch2(cwd);
|
|
1324
|
-
let defaultBranch = null;
|
|
1325
|
-
if (dryRun) {
|
|
1326
|
-
defaultBranch = await resolveDefaultBranch(cwd);
|
|
1327
|
-
}
|
|
1328
|
-
for (const item of toDelete) {
|
|
1329
|
-
const { branch, sessionId, reason } = item;
|
|
1330
|
-
const label = `${branch} (${sessionId}: ${reason})`;
|
|
1331
|
-
if (dryRun) {
|
|
1332
|
-
const merged = await isBranchMergedIntoDefault(
|
|
1333
|
-
cwd,
|
|
1334
|
-
branch,
|
|
1335
|
-
defaultBranch
|
|
1336
|
-
);
|
|
1337
|
-
const mergeTag = merged ? "\u5DF2\u5408\u5E76" : "\u672A\u5408\u5E76";
|
|
1338
|
-
console.log(`[apm] [dry-run] \u5C06\u5220\u9664 ${label} [${mergeTag}]`);
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
if (currentBranch === branch) {
|
|
1342
|
-
defaultBranch ??= await resolveDefaultBranch(cwd);
|
|
1343
|
-
await execGit3(cwd, ["checkout", defaultBranch], true);
|
|
1344
|
-
currentBranch = defaultBranch;
|
|
1345
|
-
}
|
|
1346
|
-
if (await localBranchExists2(cwd, branch)) {
|
|
1347
|
-
await execGit3(cwd, ["branch", "-D", branch], true);
|
|
1348
|
-
console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
|
|
1349
|
-
}
|
|
1350
|
-
if (await remoteBranchExists(cwd, branch)) {
|
|
1351
|
-
await execGit3(cwd, ["push", "origin", "--delete", branch], true);
|
|
1352
|
-
console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
if (dryRun) {
|
|
1356
|
-
console.log(`[apm] [dry-run] \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`);
|
|
1357
|
-
} else {
|
|
1358
|
-
console.log(`[apm] \u5DF2\u6E05\u7406 ${toDelete.length} \u4E2A feat/session-* \u5206\u652F`);
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
// src/commands/pull.ts
|
|
1363
|
-
import { writeFileSync as writeFileSync9 } from "fs";
|
|
1364
|
-
import { join as join9 } from "path";
|
|
1365
|
-
import { stringify as yamlStringify } from "yaml";
|
|
1366
|
-
|
|
1367
|
-
// src/session-messages-xml.ts
|
|
1368
|
-
function asXmlText(value) {
|
|
1369
|
-
return value ?? "";
|
|
1370
|
-
}
|
|
1371
|
-
function escapeXmlAttr(value) {
|
|
1372
|
-
return asXmlText(value).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
1373
|
-
}
|
|
1374
|
-
function wrapCdata(value) {
|
|
1375
|
-
return `<![CDATA[${asXmlText(value).replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
|
|
1376
|
-
}
|
|
1377
|
-
function formatSessionMessagesXml(sessionId, messages) {
|
|
1378
|
-
const lines = [
|
|
1379
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
1380
|
-
`<messages sessionId="${escapeXmlAttr(sessionId)}">`
|
|
1381
|
-
];
|
|
1382
|
-
for (const message of messages) {
|
|
1383
|
-
const roundAttr = message.round != null && message.round > 0 ? ` round="${message.round}"` : "";
|
|
1384
|
-
lines.push(
|
|
1385
|
-
` <message id="${escapeXmlAttr(message.id)}" name="${escapeXmlAttr(
|
|
1386
|
-
message.name
|
|
1387
|
-
)}" agent="${escapeXmlAttr(message.oxcAgent)}"${roundAttr}>`,
|
|
1388
|
-
` <content>${wrapCdata(message.content)}</content>`,
|
|
1389
|
-
" </message>"
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
lines.push("</messages>", "");
|
|
1393
|
-
return lines.join("\n");
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
// src/commands/sync-session-attachments.ts
|
|
1397
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
1398
|
-
import { join as join6 } from "path";
|
|
1399
|
-
var MANIFEST_FILE2 = ".sync-manifest.json";
|
|
1400
|
-
async function downloadAttachment(cfg, attachmentId) {
|
|
1401
|
-
const base = cfg.baseUrl.trim().replace(/\/+$/, "");
|
|
1402
|
-
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({ attachmentId })}`;
|
|
1403
|
-
const res = await fetch(url);
|
|
1404
|
-
if (!res.ok) {
|
|
1405
|
-
throw new Error(
|
|
1406
|
-
`[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
|
|
1407
|
-
);
|
|
1408
|
-
}
|
|
1409
|
-
return Buffer.from(await res.arrayBuffer());
|
|
1410
|
-
}
|
|
1411
|
-
function loadManifest(dir) {
|
|
1412
|
-
const path10 = join6(dir, MANIFEST_FILE2);
|
|
1413
|
-
if (!existsSync4(path10)) {
|
|
1414
|
-
return { version: 1, attachments: {} };
|
|
1415
|
-
}
|
|
1416
|
-
try {
|
|
1417
|
-
const parsed = JSON.parse(
|
|
1418
|
-
readFileSync5(path10, "utf8")
|
|
1419
|
-
);
|
|
1420
|
-
if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
|
|
1421
|
-
return parsed;
|
|
1422
|
-
}
|
|
1423
|
-
} catch {
|
|
1424
|
-
}
|
|
1425
|
-
return { version: 1, attachments: {} };
|
|
1426
|
-
}
|
|
1427
|
-
function saveManifest(dir, manifest) {
|
|
1428
|
-
writeFileSync6(
|
|
1429
|
-
join6(dir, MANIFEST_FILE2),
|
|
1430
|
-
`${JSON.stringify(manifest, null, 2)}
|
|
1431
|
-
`,
|
|
1432
|
-
"utf8"
|
|
1433
|
-
);
|
|
1434
|
-
}
|
|
1435
|
-
function isAttachmentUpToDate(entry, item, dest) {
|
|
1436
|
-
if (!entry || !existsSync4(dest)) return false;
|
|
1437
|
-
if (entry.name !== item.name) return false;
|
|
1438
|
-
const createdAt = item.createdAt ?? "";
|
|
1439
|
-
return entry.createdAt === createdAt;
|
|
1440
|
-
}
|
|
1441
|
-
async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
1442
|
-
const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
|
|
1443
|
-
await ensureDirExists(dir);
|
|
1444
|
-
if (attachments.length === 0) {
|
|
1445
|
-
saveManifest(dir, { version: 1, attachments: {} });
|
|
1446
|
-
return;
|
|
1447
|
-
}
|
|
1448
|
-
const manifest = loadManifest(dir);
|
|
1449
|
-
const nextManifest = { version: 1, attachments: {} };
|
|
1450
|
-
for (const item of attachments) {
|
|
1451
|
-
const dest = join6(dir, item.name);
|
|
1452
|
-
const entry = manifest.attachments[item.id];
|
|
1453
|
-
const createdAt = item.createdAt ?? "";
|
|
1454
|
-
if (isAttachmentUpToDate(entry, item, dest)) {
|
|
1455
|
-
nextManifest.attachments[item.id] = entry;
|
|
1456
|
-
console.log(
|
|
1457
|
-
`[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
|
|
1458
|
-
);
|
|
1459
|
-
continue;
|
|
1460
|
-
}
|
|
1461
|
-
const buffer = await downloadAttachment(cfg, item.id);
|
|
1462
|
-
writeFileSync6(dest, buffer);
|
|
1463
|
-
nextManifest.attachments[item.id] = {
|
|
1464
|
-
name: item.name,
|
|
1465
|
-
createdAt
|
|
1466
|
-
};
|
|
1467
|
-
console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`);
|
|
1468
|
-
}
|
|
1469
|
-
saveManifest(dir, nextManifest);
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
// src/rules-sync.ts
|
|
1473
|
-
import { basename as basename2, extname as extname2, join as join8 } from "path";
|
|
1474
|
-
import { existsSync as existsSync6, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
969
|
+
// src/commands/update-skills.ts
|
|
970
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
|
|
971
|
+
import { join as join8 } from "path";
|
|
1475
972
|
|
|
1476
973
|
// src/skills-sync.ts
|
|
1477
974
|
import {
|
|
1478
975
|
copyFileSync as copyFileSync2,
|
|
1479
976
|
cpSync,
|
|
1480
|
-
existsSync as
|
|
977
|
+
existsSync as existsSync4,
|
|
1481
978
|
mkdirSync as mkdirSync3,
|
|
1482
979
|
readdirSync as readdirSync3,
|
|
1483
980
|
rmSync as rmSync2,
|
|
1484
981
|
statSync as statSync2,
|
|
1485
|
-
writeFileSync as
|
|
982
|
+
writeFileSync as writeFileSync6
|
|
1486
983
|
} from "fs";
|
|
1487
984
|
import { join as join7 } from "path";
|
|
1488
985
|
var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
|
|
@@ -1494,20 +991,20 @@ function sanitizeSkillDirName(name) {
|
|
|
1494
991
|
return trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
1495
992
|
}
|
|
1496
993
|
function listBaseSkillDirNames() {
|
|
1497
|
-
if (!
|
|
994
|
+
if (!existsSync4(BASE_SKILLS_TEMPLATE_DIR)) return [];
|
|
1498
995
|
return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
|
|
1499
996
|
const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
|
|
1500
997
|
return statSync2(path10).isDirectory();
|
|
1501
998
|
});
|
|
1502
999
|
}
|
|
1503
1000
|
function syncAgentsGuide(apmDir) {
|
|
1504
|
-
if (!
|
|
1001
|
+
if (!existsSync4(AGENTS_TEMPLATE_PATH)) return false;
|
|
1505
1002
|
mkdirSync3(apmDir, { recursive: true });
|
|
1506
1003
|
copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
|
|
1507
1004
|
return true;
|
|
1508
1005
|
}
|
|
1509
1006
|
function listBaseRuleFileNames() {
|
|
1510
|
-
if (!
|
|
1007
|
+
if (!existsSync4(BASE_RULES_TEMPLATE_DIR)) return [];
|
|
1511
1008
|
return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
|
|
1512
1009
|
const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
|
|
1513
1010
|
return statSync2(path10).isFile();
|
|
@@ -1547,11 +1044,11 @@ function syncSupplementarySkills(skillsDir, list) {
|
|
|
1547
1044
|
}
|
|
1548
1045
|
const skillDir = join7(skillsDir, dirName);
|
|
1549
1046
|
mkdirSync3(skillDir, { recursive: true });
|
|
1550
|
-
|
|
1047
|
+
writeFileSync6(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
|
|
1551
1048
|
written.push(dirName);
|
|
1552
1049
|
}
|
|
1553
1050
|
const removed = [];
|
|
1554
|
-
if (!
|
|
1051
|
+
if (!existsSync4(skillsDir)) return { written, skipped, removed };
|
|
1555
1052
|
for (const entry of readdirSync3(skillsDir)) {
|
|
1556
1053
|
const full = join7(skillsDir, entry);
|
|
1557
1054
|
if (!statSync2(full).isDirectory()) continue;
|
|
@@ -1563,263 +1060,11 @@ function syncSupplementarySkills(skillsDir, list) {
|
|
|
1563
1060
|
return { written, skipped, removed };
|
|
1564
1061
|
}
|
|
1565
1062
|
|
|
1566
|
-
// src/rules-sync.ts
|
|
1567
|
-
var MANIFEST_FILE3 = ".rules-sync-manifest.json";
|
|
1568
|
-
function ruleLocalFileName(ruleName) {
|
|
1569
|
-
const trimmed = ruleName.trim();
|
|
1570
|
-
if (!trimmed) return "rule.md";
|
|
1571
|
-
const sanitized = trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
1572
|
-
if (extname2(sanitized).toLowerCase() === ".md") return sanitized;
|
|
1573
|
-
return `${sanitized}.md`;
|
|
1574
|
-
}
|
|
1575
|
-
function loadManifest2(rulesDir) {
|
|
1576
|
-
const path10 = join8(rulesDir, MANIFEST_FILE3);
|
|
1577
|
-
if (!existsSync6(toFsPath(path10))) {
|
|
1578
|
-
return { version: 1, rules: {} };
|
|
1579
|
-
}
|
|
1580
|
-
try {
|
|
1581
|
-
const parsed = JSON.parse(
|
|
1582
|
-
readFileSync6(toFsPath(path10), "utf8")
|
|
1583
|
-
);
|
|
1584
|
-
if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
|
|
1585
|
-
return parsed;
|
|
1586
|
-
}
|
|
1587
|
-
} catch {
|
|
1588
|
-
}
|
|
1589
|
-
return { version: 1, rules: {} };
|
|
1590
|
-
}
|
|
1591
|
-
function saveManifest2(rulesDir, manifest) {
|
|
1592
|
-
writeFileSync8(
|
|
1593
|
-
toFsPath(join8(rulesDir, MANIFEST_FILE3)),
|
|
1594
|
-
`${JSON.stringify(manifest, null, 2)}
|
|
1595
|
-
`,
|
|
1596
|
-
"utf8"
|
|
1597
|
-
);
|
|
1598
|
-
}
|
|
1599
|
-
function isBaseRuleFileName(fileName) {
|
|
1600
|
-
return listBaseRuleFileNames().includes(basename2(fileName));
|
|
1601
|
-
}
|
|
1602
|
-
function isRuleUpToDate(entry, rule, dest) {
|
|
1603
|
-
if (!entry || !existsSync6(toFsPath(dest))) return false;
|
|
1604
|
-
if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
|
|
1605
|
-
const updatedAt = rule.updatedAt ?? "";
|
|
1606
|
-
if (entry.updatedAt !== updatedAt) return false;
|
|
1607
|
-
const localContent = readFileSync6(toFsPath(dest), "utf8");
|
|
1608
|
-
return localContent === (rule.content ?? "");
|
|
1609
|
-
}
|
|
1610
|
-
async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
1611
|
-
const api = createApmApiClient(cfg);
|
|
1612
|
-
const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
|
|
1613
|
-
const repositoryId = baseline.repositoryId;
|
|
1614
|
-
const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
|
|
1615
|
-
await ensureDirExists(rulesDir);
|
|
1616
|
-
if (!repositoryId) {
|
|
1617
|
-
console.log(
|
|
1618
|
-
`[apm] \u672A\u5339\u914D\u5230\u7ED1\u5B9A\u4ED3\u5E93\u7684\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u8DF3\u8FC7\u5E73\u53F0\u89C4\u5219\u540C\u6B65\uFF08\u8DEF\u5F84\uFF1A${workdirPath}\uFF09`
|
|
1619
|
-
);
|
|
1620
|
-
return { written: [], skipped: [], removed: [], repositoryId: null };
|
|
1621
|
-
}
|
|
1622
|
-
const { list } = await api.cli.listRules({ repositoryId });
|
|
1623
|
-
const manifest = loadManifest2(rulesDir);
|
|
1624
|
-
const nextManifest = { version: 1, rules: {} };
|
|
1625
|
-
const remoteIds = /* @__PURE__ */ new Set();
|
|
1626
|
-
const written = [];
|
|
1627
|
-
const skipped = [];
|
|
1628
|
-
for (const rule of list) {
|
|
1629
|
-
remoteIds.add(rule.id);
|
|
1630
|
-
const fileName = ruleLocalFileName(rule.name);
|
|
1631
|
-
const dest = join8(rulesDir, fileName);
|
|
1632
|
-
const entry = manifest.rules[rule.id];
|
|
1633
|
-
const updatedAt = rule.updatedAt ?? "";
|
|
1634
|
-
if (isRuleUpToDate(entry, rule, dest)) {
|
|
1635
|
-
nextManifest.rules[rule.id] = entry;
|
|
1636
|
-
skipped.push(fileName);
|
|
1637
|
-
console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
|
|
1638
|
-
continue;
|
|
1639
|
-
}
|
|
1640
|
-
writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
|
|
1641
|
-
nextManifest.rules[rule.id] = { fileName, updatedAt };
|
|
1642
|
-
written.push(fileName);
|
|
1643
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
|
|
1644
|
-
}
|
|
1645
|
-
const removed = [];
|
|
1646
|
-
for (const [ruleId, entry] of Object.entries(manifest.rules)) {
|
|
1647
|
-
if (remoteIds.has(ruleId)) continue;
|
|
1648
|
-
if (isBaseRuleFileName(entry.fileName)) continue;
|
|
1649
|
-
const dest = join8(rulesDir, entry.fileName);
|
|
1650
|
-
if (existsSync6(toFsPath(dest))) {
|
|
1651
|
-
rmSync3(toFsPath(dest), { force: true });
|
|
1652
|
-
}
|
|
1653
|
-
removed.push(entry.fileName);
|
|
1654
|
-
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
|
|
1655
|
-
}
|
|
1656
|
-
saveManifest2(rulesDir, nextManifest);
|
|
1657
|
-
return { written, skipped, removed, repositoryId };
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// src/commands/pull.ts
|
|
1661
|
-
async function runPull(sessionId, remoteWorkdir) {
|
|
1662
|
-
const trimmedId = sessionId.trim();
|
|
1663
|
-
if (!trimmedId) {
|
|
1664
|
-
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1665
|
-
process.exit(1);
|
|
1666
|
-
}
|
|
1667
|
-
const cfg = await ensureLoggedConfig();
|
|
1668
|
-
const api = createApmApiClient(cfg);
|
|
1669
|
-
const workdir = remoteWorkdir === void 0 ? resolveWorkdirPath() : requireRemoteWorkdir(remoteWorkdir);
|
|
1670
|
-
const apmRoot = workspaceApmDir(workdir);
|
|
1671
|
-
const [detail, members, documents, attachments, messages] = await Promise.all(
|
|
1672
|
-
[
|
|
1673
|
-
api.cli.sessionDetail({ sessionId: trimmedId }),
|
|
1674
|
-
api.cli.sessionMembers({ sessionId: trimmedId }),
|
|
1675
|
-
api.cli.listDocuments({ sessionId: trimmedId }),
|
|
1676
|
-
api.cli.listAttachments({ sessionId: trimmedId }),
|
|
1677
|
-
api.cli.listSessionMessages({ sessionId: trimmedId })
|
|
1678
|
-
]
|
|
1679
|
-
);
|
|
1680
|
-
const dir = sessionDir(trimmedId, apmRoot);
|
|
1681
|
-
const docsDir = sessionDocsDir(trimmedId, apmRoot);
|
|
1682
|
-
await ensureDirExists(docsDir);
|
|
1683
|
-
writeFileSync9(
|
|
1684
|
-
sessionRulePath(trimmedId, apmRoot),
|
|
1685
|
-
detail.description ?? "",
|
|
1686
|
-
"utf8"
|
|
1687
|
-
);
|
|
1688
|
-
writeFileSync9(
|
|
1689
|
-
sessionTaskPath(trimmedId, apmRoot),
|
|
1690
|
-
detail.task.description ?? "",
|
|
1691
|
-
"utf8"
|
|
1692
|
-
);
|
|
1693
|
-
writeFileSync9(sessionTodoPath(trimmedId, apmRoot), detail.todo ?? "", "utf8");
|
|
1694
|
-
for (const doc of documents) {
|
|
1695
|
-
const fileName = documentLocalFileName(doc.name);
|
|
1696
|
-
writeFileSync9(join9(docsDir, fileName), doc.content ?? "", "utf8");
|
|
1697
|
-
}
|
|
1698
|
-
const sessionYaml = yamlStringify(
|
|
1699
|
-
{
|
|
1700
|
-
name: detail.title,
|
|
1701
|
-
task: "./TASK.md",
|
|
1702
|
-
todo: "./TODO.md",
|
|
1703
|
-
rule: "./RULE.md",
|
|
1704
|
-
members: members.map((m) => ({
|
|
1705
|
-
name: m.displayName,
|
|
1706
|
-
oxcAgent: m.oxcAgent,
|
|
1707
|
-
description: m.description ?? ""
|
|
1708
|
-
})),
|
|
1709
|
-
attachments: attachments.map((a) => ({ name: a.name }))
|
|
1710
|
-
},
|
|
1711
|
-
{ lineWidth: 0 }
|
|
1712
|
-
);
|
|
1713
|
-
writeFileSync9(
|
|
1714
|
-
sessionYamlPath(trimmedId, apmRoot),
|
|
1715
|
-
sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
|
|
1716
|
-
`,
|
|
1717
|
-
"utf8"
|
|
1718
|
-
);
|
|
1719
|
-
writeFileSync9(
|
|
1720
|
-
sessionMessagesXmlPath(trimmedId, apmRoot),
|
|
1721
|
-
formatSessionMessagesXml(trimmedId, messages),
|
|
1722
|
-
"utf8"
|
|
1723
|
-
);
|
|
1724
|
-
await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
|
|
1725
|
-
await syncPlatformRules(cfg, trimmedId, workdir, apmRoot);
|
|
1726
|
-
await syncRemoteDeploymentConfig(workdir, apmRoot);
|
|
1727
|
-
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
1728
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
|
|
1729
|
-
return dir;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
// src/commands/update.ts
|
|
1733
|
-
import { spawnSync } from "child_process";
|
|
1734
|
-
|
|
1735
|
-
// src/version.ts
|
|
1736
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
1737
|
-
import { dirname as dirname3, join as join10 } from "path";
|
|
1738
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1739
|
-
var CLI_PACKAGE_NAME = "ai-project-manage-cli";
|
|
1740
|
-
function readCliVersion() {
|
|
1741
|
-
try {
|
|
1742
|
-
const dir = dirname3(fileURLToPath2(import.meta.url));
|
|
1743
|
-
const pkgPath = join10(dir, "..", "package.json");
|
|
1744
|
-
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
1745
|
-
return pkg.version ?? "0.0.0";
|
|
1746
|
-
} catch {
|
|
1747
|
-
return "0.0.0";
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
// src/commands/update.ts
|
|
1752
|
-
var useNpmShell = process.platform === "win32";
|
|
1753
|
-
function runNpm(args, options = {}) {
|
|
1754
|
-
return spawnSync(useNpmShell ? "npm.cmd" : "npm", args, {
|
|
1755
|
-
...options,
|
|
1756
|
-
shell: useNpmShell
|
|
1757
|
-
});
|
|
1758
|
-
}
|
|
1759
|
-
function registryBaseUrl() {
|
|
1760
|
-
const fromEnv = process.env.npm_config_registry?.trim() || process.env.NPM_CONFIG_REGISTRY?.trim();
|
|
1761
|
-
return (fromEnv || "https://registry.npmjs.org").replace(/\/+$/, "");
|
|
1762
|
-
}
|
|
1763
|
-
async function fetchLatestPublishedVersion() {
|
|
1764
|
-
const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
|
|
1765
|
-
try {
|
|
1766
|
-
const res = await fetch(url);
|
|
1767
|
-
if (!res.ok) return null;
|
|
1768
|
-
const data = await res.json();
|
|
1769
|
-
return data.version?.trim() || null;
|
|
1770
|
-
} catch {
|
|
1771
|
-
return null;
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
function npmAvailable() {
|
|
1775
|
-
const r = runNpm(["--version"], { encoding: "utf8" });
|
|
1776
|
-
return !r.error && r.status === 0;
|
|
1777
|
-
}
|
|
1778
|
-
async function runUpdate() {
|
|
1779
|
-
const current = readCliVersion();
|
|
1780
|
-
const latest = await fetchLatestPublishedVersion();
|
|
1781
|
-
if (latest && current === latest) {
|
|
1782
|
-
console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
|
|
1783
|
-
return { didUpdate: false };
|
|
1784
|
-
}
|
|
1785
|
-
if (!npmAvailable()) {
|
|
1786
|
-
console.error(
|
|
1787
|
-
`[apm] \u672A\u627E\u5230 npm\u3002\u8BF7\u5B89\u88C5 Node.js \u540E\u6267\u884C\uFF1Anpm install -g ${CLI_PACKAGE_NAME}@latest`
|
|
1788
|
-
);
|
|
1789
|
-
process.exit(1);
|
|
1790
|
-
}
|
|
1791
|
-
const targetLabel = latest ?? "latest";
|
|
1792
|
-
console.error(
|
|
1793
|
-
`[apm] \u5F53\u524D\u7248\u672C ${current}\uFF0C\u6B63\u5728\u5B89\u88C5 ${CLI_PACKAGE_NAME}@${targetLabel} \u2026`
|
|
1794
|
-
);
|
|
1795
|
-
const install = runNpm(["install", "-g", `${CLI_PACKAGE_NAME}@latest`], {
|
|
1796
|
-
stdio: "inherit"
|
|
1797
|
-
});
|
|
1798
|
-
if (install.error) {
|
|
1799
|
-
console.error("[apm] \u66F4\u65B0\u5931\u8D25:", install.error.message);
|
|
1800
|
-
process.exit(1);
|
|
1801
|
-
}
|
|
1802
|
-
if (install.status !== 0) {
|
|
1803
|
-
process.exit(install.status ?? 1);
|
|
1804
|
-
}
|
|
1805
|
-
const after = readCliVersion();
|
|
1806
|
-
if (latest && after === latest) {
|
|
1807
|
-
console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
|
|
1808
|
-
} else {
|
|
1809
|
-
console.log(
|
|
1810
|
-
`[apm] \u66F4\u65B0\u5B8C\u6210\u3002\u82E5\u7248\u672C\u53F7\u672A\u53D8\u5316\uFF0C\u8BF7\u5728\u65B0\u7EC8\u7AEF\u6267\u884C apm -V \u786E\u8BA4\uFF08\u5168\u5C40\u5B89\u88C5\u8DEF\u5F84\u53EF\u80FD\u672A\u5237\u65B0\uFF09`
|
|
1811
|
-
);
|
|
1812
|
-
}
|
|
1813
|
-
return { didUpdate: true };
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
1063
|
// src/commands/update-skills.ts
|
|
1817
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
|
|
1818
|
-
import { join as join11 } from "path";
|
|
1819
1064
|
async function syncWorkspaceSkills(cfg, workdir) {
|
|
1820
1065
|
const apmDir = workspaceApmDir(workdir);
|
|
1821
1066
|
const fsApmDir = toFsPath(apmDir);
|
|
1822
|
-
if (!
|
|
1067
|
+
if (!existsSync5(fsApmDir)) {
|
|
1823
1068
|
throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1824
1069
|
}
|
|
1825
1070
|
const apmStat = statSync3(fsApmDir);
|
|
@@ -1831,12 +1076,12 @@ async function syncWorkspaceSkills(cfg, workdir) {
|
|
|
1831
1076
|
if (syncAgentsGuide(apmDir)) {
|
|
1832
1077
|
console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
|
|
1833
1078
|
}
|
|
1834
|
-
const rulesDir =
|
|
1079
|
+
const rulesDir = join8(apmDir, "rules");
|
|
1835
1080
|
const ruleNames = syncBaseRules(rulesDir);
|
|
1836
1081
|
for (const name of ruleNames) {
|
|
1837
1082
|
console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
|
|
1838
1083
|
}
|
|
1839
|
-
const skillsDir =
|
|
1084
|
+
const skillsDir = join8(apmDir, "skills");
|
|
1840
1085
|
mkdirSync4(toFsPath(skillsDir), { recursive: true });
|
|
1841
1086
|
const baseNames = syncBaseSkills(skillsDir);
|
|
1842
1087
|
for (const name of baseNames) {
|
|
@@ -1863,7 +1108,7 @@ async function syncWorkspaceSkills(cfg, workdir) {
|
|
|
1863
1108
|
}
|
|
1864
1109
|
async function runUpdateSkills() {
|
|
1865
1110
|
const apmDir = workspaceApmDir();
|
|
1866
|
-
if (!
|
|
1111
|
+
if (!existsSync5(apmDir)) {
|
|
1867
1112
|
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1868
1113
|
process.exit(1);
|
|
1869
1114
|
}
|
|
@@ -1875,197 +1120,6 @@ async function runUpdateSkills() {
|
|
|
1875
1120
|
await syncWorkspaceSkills(cfg, resolveWorkdirPath());
|
|
1876
1121
|
}
|
|
1877
1122
|
|
|
1878
|
-
// src/commands/sync-deploy-config.ts
|
|
1879
|
-
import { existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
1880
|
-
async function runSyncDeployConfig() {
|
|
1881
|
-
const workdir = resolveWorkdirPath();
|
|
1882
|
-
const apmDir = workspaceApmDir(workdir);
|
|
1883
|
-
if (!existsSync8(apmDir)) {
|
|
1884
|
-
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1885
|
-
process.exit(1);
|
|
1886
|
-
}
|
|
1887
|
-
const apmStat = statSync4(apmDir);
|
|
1888
|
-
if (!apmStat.isDirectory()) {
|
|
1889
|
-
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
1890
|
-
}
|
|
1891
|
-
await ensureLoggedConfig();
|
|
1892
|
-
const result = await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
1893
|
-
if (!result.synced) {
|
|
1894
|
-
process.exit(1);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
// src/commands/sync-project-documents.ts
|
|
1899
|
-
async function runSyncProjectDocuments(options) {
|
|
1900
|
-
const pull = options?.pull ?? !options?.push;
|
|
1901
|
-
const push = options?.push ?? false;
|
|
1902
|
-
const workdir = resolveWorkdirPath();
|
|
1903
|
-
const apmRoot = workspaceApmDir(workdir);
|
|
1904
|
-
if (pull) {
|
|
1905
|
-
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
1906
|
-
}
|
|
1907
|
-
if (push) {
|
|
1908
|
-
const cfg = await ensureLoggedConfig();
|
|
1909
|
-
await syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot);
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
// src/commands/sync-document.ts
|
|
1914
|
-
import { existsSync as existsSync10 } from "fs";
|
|
1915
|
-
import { basename as basename3 } from "path";
|
|
1916
|
-
|
|
1917
|
-
// src/commands/sync-session-documents.ts
|
|
1918
|
-
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
1919
|
-
import { join as join12 } from "path";
|
|
1920
|
-
function listLocalMarkdownFiles(docsDir) {
|
|
1921
|
-
if (!existsSync9(docsDir)) {
|
|
1922
|
-
return [];
|
|
1923
|
-
}
|
|
1924
|
-
return readdirSync4(docsDir).filter(
|
|
1925
|
-
(name) => name.toLowerCase().endsWith(".md")
|
|
1926
|
-
);
|
|
1927
|
-
}
|
|
1928
|
-
function remoteDocumentByLocalName(remoteDocuments, localFileName) {
|
|
1929
|
-
const platformName = documentPlatformName(localFileName);
|
|
1930
|
-
return remoteDocuments.find((doc) => {
|
|
1931
|
-
const remoteLocalName = documentLocalFileName(doc.name);
|
|
1932
|
-
return remoteLocalName === localFileName || documentPlatformName(doc.name) === platformName;
|
|
1933
|
-
});
|
|
1934
|
-
}
|
|
1935
|
-
async function upsertLocalDocumentFile(api, sessionId, docsDir, fileName) {
|
|
1936
|
-
const absPath = join12(docsDir, fileName);
|
|
1937
|
-
const content = readFileSync8(absPath, "utf8");
|
|
1938
|
-
const name = documentPlatformName(absPath);
|
|
1939
|
-
return api.cli.upsertDocument({
|
|
1940
|
-
sessionId,
|
|
1941
|
-
name,
|
|
1942
|
-
content
|
|
1943
|
-
});
|
|
1944
|
-
}
|
|
1945
|
-
async function syncSessionDocuments(cfg, sessionId, apmRoot, options) {
|
|
1946
|
-
const trimmedSessionId = sessionId.trim();
|
|
1947
|
-
if (!trimmedSessionId) {
|
|
1948
|
-
return 0;
|
|
1949
|
-
}
|
|
1950
|
-
const docsDir = sessionDocsDir(trimmedSessionId, apmRoot);
|
|
1951
|
-
const localFiles = listLocalMarkdownFiles(docsDir);
|
|
1952
|
-
if (localFiles.length === 0) {
|
|
1953
|
-
return 0;
|
|
1954
|
-
}
|
|
1955
|
-
const api = options?.api ?? createApmApiClient(cfg);
|
|
1956
|
-
const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ sessionId: trimmedSessionId });
|
|
1957
|
-
let synced = 0;
|
|
1958
|
-
for (const fileName of localFiles) {
|
|
1959
|
-
const absPath = join12(docsDir, fileName);
|
|
1960
|
-
const content = readFileSync8(absPath, "utf8");
|
|
1961
|
-
const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
|
|
1962
|
-
if (remote && remote.content === content) {
|
|
1963
|
-
continue;
|
|
1964
|
-
}
|
|
1965
|
-
const doc = await upsertLocalDocumentFile(
|
|
1966
|
-
api,
|
|
1967
|
-
trimmedSessionId,
|
|
1968
|
-
docsDir,
|
|
1969
|
-
fileName
|
|
1970
|
-
);
|
|
1971
|
-
synced += 1;
|
|
1972
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
|
|
1973
|
-
}
|
|
1974
|
-
if (synced === 0) {
|
|
1975
|
-
console.log("[apm] \u4F1A\u8BDD\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1976
|
-
}
|
|
1977
|
-
return synced;
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
// src/commands/sync-document.ts
|
|
1981
|
-
async function runSyncDocument(sessionId, options) {
|
|
1982
|
-
const trimmedSessionId = sessionId.trim();
|
|
1983
|
-
if (!trimmedSessionId) {
|
|
1984
|
-
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1985
|
-
process.exit(1);
|
|
1986
|
-
}
|
|
1987
|
-
const fileArg = options.file?.trim();
|
|
1988
|
-
if (!fileArg) {
|
|
1989
|
-
console.error("[apm] \u8BF7\u6307\u5B9A --file <\u6587\u6863\u540D\u79F0>");
|
|
1990
|
-
process.exit(1);
|
|
1991
|
-
}
|
|
1992
|
-
const absPath = resolveSessionDocumentPath(trimmedSessionId, fileArg);
|
|
1993
|
-
if (!existsSync10(absPath)) {
|
|
1994
|
-
const docsDir2 = sessionDocsDir(trimmedSessionId);
|
|
1995
|
-
console.error(
|
|
1996
|
-
`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
|
|
1997
|
-
[apm] \u8BF7\u786E\u8BA4\u5DF2 pull\uFF0C\u4E14 ${docsDir2} \u4E0B\u5B58\u5728\u5BF9\u5E94\u6587\u4EF6`
|
|
1998
|
-
);
|
|
1999
|
-
process.exit(1);
|
|
2000
|
-
}
|
|
2001
|
-
const cfg = await ensureLoggedConfig();
|
|
2002
|
-
const api = createApmApiClient(cfg);
|
|
2003
|
-
const docsDir = sessionDocsDir(trimmedSessionId);
|
|
2004
|
-
const doc = await upsertLocalDocumentFile(
|
|
2005
|
-
api,
|
|
2006
|
-
trimmedSessionId,
|
|
2007
|
-
docsDir,
|
|
2008
|
-
basename3(absPath)
|
|
2009
|
-
);
|
|
2010
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
// src/commands/append-message.ts
|
|
2014
|
-
async function appendMessageContent(cfg, messageId, content) {
|
|
2015
|
-
const trimmedId = messageId.trim();
|
|
2016
|
-
if (!trimmedId) {
|
|
2017
|
-
throw new Error("messageId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2018
|
-
}
|
|
2019
|
-
if (!content) {
|
|
2020
|
-
throw new Error("content \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2021
|
-
}
|
|
2022
|
-
const api = createApmApiClient(cfg);
|
|
2023
|
-
await api.cli.appendMessageContent({ id: trimmedId, content });
|
|
2024
|
-
}
|
|
2025
|
-
async function runAppendMessage(options) {
|
|
2026
|
-
const messageId = options.id?.trim();
|
|
2027
|
-
if (!messageId) {
|
|
2028
|
-
console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
|
|
2029
|
-
process.exit(1);
|
|
2030
|
-
}
|
|
2031
|
-
const content = options.content ?? "";
|
|
2032
|
-
if (!content) {
|
|
2033
|
-
console.error("[apm] \u8BF7\u6307\u5B9A --content <\u5185\u5BB9>");
|
|
2034
|
-
process.exit(1);
|
|
2035
|
-
}
|
|
2036
|
-
const cfg = await ensureLoggedConfig();
|
|
2037
|
-
await appendMessageContent(cfg, messageId, content);
|
|
2038
|
-
console.log(`[apm] \u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9: ${messageId}`);
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
// src/commands/update-message-status.ts
|
|
2042
|
-
var VALID_STATUSES = [
|
|
2043
|
-
"CREATED",
|
|
2044
|
-
"QUEUED",
|
|
2045
|
-
"TYPING",
|
|
2046
|
-
"SUCCESS",
|
|
2047
|
-
"FAILED",
|
|
2048
|
-
"CANCELLED"
|
|
2049
|
-
];
|
|
2050
|
-
async function runUpdateMessageStatus(options) {
|
|
2051
|
-
const messageId = options.id?.trim();
|
|
2052
|
-
const status = options.status?.trim().toUpperCase();
|
|
2053
|
-
if (!messageId) {
|
|
2054
|
-
console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
|
|
2055
|
-
process.exit(1);
|
|
2056
|
-
}
|
|
2057
|
-
if (!VALID_STATUSES.includes(status)) {
|
|
2058
|
-
console.error(
|
|
2059
|
-
`[apm] \u65E0\u6548\u72B6\u6001: ${options.status}\uFF0C\u53EF\u9009: ${VALID_STATUSES.join(", ")}`
|
|
2060
|
-
);
|
|
2061
|
-
process.exit(1);
|
|
2062
|
-
}
|
|
2063
|
-
const cfg = await ensureLoggedConfig();
|
|
2064
|
-
const api = createApmApiClient(cfg);
|
|
2065
|
-
await api.cli.updateMessageStatus({ id: messageId, status });
|
|
2066
|
-
console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
1123
|
// src/commands/connect.ts
|
|
2070
1124
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2071
1125
|
import WebSocket from "ws";
|
|
@@ -2093,57 +1147,6 @@ function validateHeartbeat(o) {
|
|
|
2093
1147
|
}
|
|
2094
1148
|
return { ok: true, data: { type: "heartbeat", userId: o.userId.trim() } };
|
|
2095
1149
|
}
|
|
2096
|
-
function validateMessagePush(o) {
|
|
2097
|
-
if (o.type !== "message") {
|
|
2098
|
-
return { ok: false, reason: "\u671F\u671B message" };
|
|
2099
|
-
}
|
|
2100
|
-
if (!nonEmptyString(o.messageId)) {
|
|
2101
|
-
return { ok: false, reason: "message \u7F3A\u5C11 messageId" };
|
|
2102
|
-
}
|
|
2103
|
-
if (!nonEmptyString(o.sessionId)) {
|
|
2104
|
-
return { ok: false, reason: "message \u7F3A\u5C11 sessionId" };
|
|
2105
|
-
}
|
|
2106
|
-
if (!nonEmptyString(o.content)) {
|
|
2107
|
-
return { ok: false, reason: "message \u7F3A\u5C11 content" };
|
|
2108
|
-
}
|
|
2109
|
-
if (!nonEmptyString(o.model)) {
|
|
2110
|
-
return { ok: false, reason: "message \u7F3A\u5C11 model" };
|
|
2111
|
-
}
|
|
2112
|
-
if (!nonEmptyString(o.apiKey)) {
|
|
2113
|
-
return { ok: false, reason: "message \u7F3A\u5C11 apiKey" };
|
|
2114
|
-
}
|
|
2115
|
-
if (!nonEmptyString(o.workdir)) {
|
|
2116
|
-
return { ok: false, reason: "message \u7F3A\u5C11 workdir" };
|
|
2117
|
-
}
|
|
2118
|
-
if (!nonEmptyString(o.user)) {
|
|
2119
|
-
return { ok: false, reason: "message \u7F3A\u5C11 user" };
|
|
2120
|
-
}
|
|
2121
|
-
return {
|
|
2122
|
-
ok: true,
|
|
2123
|
-
data: {
|
|
2124
|
-
type: "message",
|
|
2125
|
-
messageId: o.messageId.trim(),
|
|
2126
|
-
sessionId: o.sessionId.trim(),
|
|
2127
|
-
content: o.content,
|
|
2128
|
-
model: o.model.trim(),
|
|
2129
|
-
apiKey: o.apiKey.trim(),
|
|
2130
|
-
workdir: o.workdir.trim(),
|
|
2131
|
-
user: o.user.trim()
|
|
2132
|
-
}
|
|
2133
|
-
};
|
|
2134
|
-
}
|
|
2135
|
-
function validateCancel(o) {
|
|
2136
|
-
if (o.type !== "cancel") {
|
|
2137
|
-
return { ok: false, reason: "\u671F\u671B cancel" };
|
|
2138
|
-
}
|
|
2139
|
-
if (!nonEmptyString(o.messageId)) {
|
|
2140
|
-
return { ok: false, reason: "cancel \u7F3A\u5C11 messageId" };
|
|
2141
|
-
}
|
|
2142
|
-
return {
|
|
2143
|
-
ok: true,
|
|
2144
|
-
data: { type: "cancel", messageId: o.messageId.trim() }
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
1150
|
function validateDeployPush(o) {
|
|
2148
1151
|
if (o.type !== "deploy") {
|
|
2149
1152
|
return { ok: false, reason: "\u671F\u671B deploy" };
|
|
@@ -2168,103 +1171,26 @@ function validateDeployPush(o) {
|
|
|
2168
1171
|
}
|
|
2169
1172
|
};
|
|
2170
1173
|
}
|
|
2171
|
-
function
|
|
2172
|
-
|
|
2173
|
-
}
|
|
2174
|
-
function validateCoordinatorDispatchPush(o) {
|
|
2175
|
-
if (o.type !== "coordinator_dispatch") {
|
|
2176
|
-
return { ok: false, reason: "\u671F\u671B coordinator_dispatch" };
|
|
2177
|
-
}
|
|
2178
|
-
if (!nonEmptyString(o.dispatchId)) {
|
|
2179
|
-
return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 dispatchId" };
|
|
2180
|
-
}
|
|
2181
|
-
if (!nonEmptyString(o.dispatchSessionId)) {
|
|
2182
|
-
return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 dispatchSessionId" };
|
|
2183
|
-
}
|
|
2184
|
-
if (!validateCoordinatorDispatchMode(o.mode)) {
|
|
2185
|
-
return { ok: false, reason: "coordinator_dispatch.mode \u65E0\u6548" };
|
|
2186
|
-
}
|
|
2187
|
-
if (!nonEmptyString(o.content)) {
|
|
2188
|
-
return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 content" };
|
|
2189
|
-
}
|
|
2190
|
-
if (!nonEmptyString(o.model)) {
|
|
2191
|
-
return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 model" };
|
|
2192
|
-
}
|
|
2193
|
-
if (!nonEmptyString(o.apiKey)) {
|
|
2194
|
-
return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 apiKey" };
|
|
2195
|
-
}
|
|
2196
|
-
if (!nonEmptyString(o.workdir)) {
|
|
2197
|
-
return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 workdir" };
|
|
2198
|
-
}
|
|
2199
|
-
if (!nonEmptyString(o.user)) {
|
|
2200
|
-
return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 user" };
|
|
2201
|
-
}
|
|
2202
|
-
const data = {
|
|
2203
|
-
type: "coordinator_dispatch",
|
|
2204
|
-
dispatchId: o.dispatchId.trim(),
|
|
2205
|
-
dispatchSessionId: o.dispatchSessionId.trim(),
|
|
2206
|
-
mode: o.mode,
|
|
2207
|
-
content: o.content,
|
|
2208
|
-
model: o.model.trim(),
|
|
2209
|
-
apiKey: o.apiKey.trim(),
|
|
2210
|
-
workdir: o.workdir.trim(),
|
|
2211
|
-
user: o.user.trim()
|
|
2212
|
-
};
|
|
2213
|
-
if (nonEmptyString(o.resumeAgentId)) {
|
|
2214
|
-
data.resumeAgentId = o.resumeAgentId.trim();
|
|
2215
|
-
}
|
|
2216
|
-
return { ok: true, data };
|
|
2217
|
-
}
|
|
2218
|
-
function validateCoordinatorDispatchResumePush(o) {
|
|
2219
|
-
if (o.type !== "coordinator_dispatch_resume") {
|
|
2220
|
-
return { ok: false, reason: "\u671F\u671B coordinator_dispatch_resume" };
|
|
2221
|
-
}
|
|
2222
|
-
if (!nonEmptyString(o.dispatchId)) {
|
|
2223
|
-
return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchId" };
|
|
2224
|
-
}
|
|
2225
|
-
if (!nonEmptyString(o.dispatchSessionId)) {
|
|
2226
|
-
return {
|
|
2227
|
-
ok: false,
|
|
2228
|
-
reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchSessionId"
|
|
2229
|
-
};
|
|
1174
|
+
function validateReceivedMailPush(o) {
|
|
1175
|
+
if (o.type !== "received_mail") {
|
|
1176
|
+
return { ok: false, reason: "\u671F\u671B received_mail" };
|
|
2230
1177
|
}
|
|
2231
|
-
if (!
|
|
2232
|
-
return { ok: false, reason: "
|
|
1178
|
+
if (!nonEmptyString(o.id)) {
|
|
1179
|
+
return { ok: false, reason: "received_mail \u7F3A\u5C11 id" };
|
|
2233
1180
|
}
|
|
2234
|
-
if (!nonEmptyString(o.
|
|
2235
|
-
return { ok: false, reason: "
|
|
2236
|
-
}
|
|
2237
|
-
if (!nonEmptyString(o.resumeAgentId)) {
|
|
2238
|
-
return {
|
|
2239
|
-
ok: false,
|
|
2240
|
-
reason: "coordinator_dispatch_resume \u7F3A\u5C11 resumeAgentId"
|
|
2241
|
-
};
|
|
2242
|
-
}
|
|
2243
|
-
if (!nonEmptyString(o.model)) {
|
|
2244
|
-
return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 model" };
|
|
2245
|
-
}
|
|
2246
|
-
if (!nonEmptyString(o.apiKey)) {
|
|
2247
|
-
return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 apiKey" };
|
|
2248
|
-
}
|
|
2249
|
-
if (!nonEmptyString(o.workdir)) {
|
|
2250
|
-
return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 workdir" };
|
|
1181
|
+
if (!nonEmptyString(o.taskId)) {
|
|
1182
|
+
return { ok: false, reason: "received_mail \u7F3A\u5C11 taskId" };
|
|
2251
1183
|
}
|
|
2252
|
-
if (!nonEmptyString(o.
|
|
2253
|
-
return { ok: false, reason: "
|
|
1184
|
+
if (!nonEmptyString(o.createdAt)) {
|
|
1185
|
+
return { ok: false, reason: "received_mail \u7F3A\u5C11 createdAt" };
|
|
2254
1186
|
}
|
|
2255
1187
|
return {
|
|
2256
1188
|
ok: true,
|
|
2257
1189
|
data: {
|
|
2258
|
-
type: "
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
content: o.content,
|
|
2263
|
-
resumeAgentId: o.resumeAgentId.trim(),
|
|
2264
|
-
model: o.model.trim(),
|
|
2265
|
-
apiKey: o.apiKey.trim(),
|
|
2266
|
-
workdir: o.workdir.trim(),
|
|
2267
|
-
user: o.user.trim()
|
|
1190
|
+
type: "received_mail",
|
|
1191
|
+
id: o.id.trim(),
|
|
1192
|
+
taskId: o.taskId.trim(),
|
|
1193
|
+
createdAt: o.createdAt.trim()
|
|
2268
1194
|
}
|
|
2269
1195
|
};
|
|
2270
1196
|
}
|
|
@@ -2274,36 +1200,27 @@ function validateAgentWsMessage(value, kind) {
|
|
|
2274
1200
|
}
|
|
2275
1201
|
const o = value;
|
|
2276
1202
|
const type = o.type;
|
|
2277
|
-
if (type !== "heartbeat" && type !== "
|
|
1203
|
+
if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail") {
|
|
2278
1204
|
return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
|
|
2279
1205
|
}
|
|
2280
1206
|
if (kind === "heartbeat" || type === "heartbeat") {
|
|
2281
1207
|
return validateHeartbeat(o);
|
|
2282
1208
|
}
|
|
2283
|
-
if (type === "cancel") {
|
|
2284
|
-
return validateCancel(o);
|
|
2285
|
-
}
|
|
2286
1209
|
if (type === "deploy") {
|
|
2287
1210
|
return validateDeployPush(o);
|
|
2288
1211
|
}
|
|
2289
|
-
|
|
2290
|
-
return validateCoordinatorDispatchPush(o);
|
|
2291
|
-
}
|
|
2292
|
-
if (type === "coordinator_dispatch_resume") {
|
|
2293
|
-
return validateCoordinatorDispatchResumePush(o);
|
|
2294
|
-
}
|
|
2295
|
-
return validateMessagePush(o);
|
|
1212
|
+
return validateReceivedMailPush(o);
|
|
2296
1213
|
}
|
|
2297
1214
|
|
|
2298
1215
|
// src/commands/connect/deploy-run.ts
|
|
2299
1216
|
import { spawn } from "node:child_process";
|
|
2300
|
-
import { readFileSync as
|
|
2301
|
-
import { join as
|
|
1217
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1218
|
+
import { join as join9 } from "node:path";
|
|
2302
1219
|
var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
|
|
2303
1220
|
function readDeployConfig(workdir) {
|
|
2304
|
-
const configPath =
|
|
1221
|
+
const configPath = join9(workspaceApmDir(workdir), "apm.config.json");
|
|
2305
1222
|
try {
|
|
2306
|
-
const raw =
|
|
1223
|
+
const raw = readFileSync6(configPath, "utf8");
|
|
2307
1224
|
const parsed = JSON.parse(raw);
|
|
2308
1225
|
return parsed.deploy;
|
|
2309
1226
|
} catch {
|
|
@@ -2378,7 +1295,7 @@ function createDeployLogSyncer(api, deploymentRunId) {
|
|
|
2378
1295
|
if (!latestLog || latestLog === lastSyncedLog) {
|
|
2379
1296
|
return;
|
|
2380
1297
|
}
|
|
2381
|
-
await api.cli.
|
|
1298
|
+
await api.cli.syncTaskDeploymentLog({
|
|
2382
1299
|
id: deploymentRunId,
|
|
2383
1300
|
log: latestLog
|
|
2384
1301
|
});
|
|
@@ -2409,7 +1326,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2409
1326
|
const api = createApmApiClient(cfg);
|
|
2410
1327
|
const deploymentRunId = msg.deploymentRunId;
|
|
2411
1328
|
if (signal.aborted) return;
|
|
2412
|
-
await api.cli.
|
|
1329
|
+
await api.cli.updateTaskDeploymentStatus({
|
|
2413
1330
|
id: deploymentRunId,
|
|
2414
1331
|
status: "DEPLOYING"
|
|
2415
1332
|
});
|
|
@@ -2418,7 +1335,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2418
1335
|
if (!command) {
|
|
2419
1336
|
const error = missingDeployCommandMessage(msg.environment);
|
|
2420
1337
|
console.error(`[apm] ${error}`);
|
|
2421
|
-
await api.cli.
|
|
1338
|
+
await api.cli.completeTaskDeployment({
|
|
2422
1339
|
id: deploymentRunId,
|
|
2423
1340
|
status: "FAILED",
|
|
2424
1341
|
log: error,
|
|
@@ -2440,7 +1357,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2440
1357
|
latestLog = log;
|
|
2441
1358
|
logSyncer.updateLog(log);
|
|
2442
1359
|
await logSyncer.flush();
|
|
2443
|
-
await api.cli.
|
|
1360
|
+
await api.cli.completeTaskDeployment({
|
|
2444
1361
|
id: deploymentRunId,
|
|
2445
1362
|
status: "SUCCESS",
|
|
2446
1363
|
log
|
|
@@ -2451,7 +1368,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2451
1368
|
const log = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
|
|
2452
1369
|
logSyncer.updateLog(log);
|
|
2453
1370
|
await logSyncer.flush();
|
|
2454
|
-
await api.cli.
|
|
1371
|
+
await api.cli.completeTaskDeployment({
|
|
2455
1372
|
id: deploymentRunId,
|
|
2456
1373
|
status: "FAILED",
|
|
2457
1374
|
log,
|
|
@@ -2463,6 +1380,68 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2463
1380
|
}
|
|
2464
1381
|
}
|
|
2465
1382
|
|
|
1383
|
+
// src/commands/sync-task-documents.ts
|
|
1384
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
|
|
1385
|
+
import { join as join10 } from "path";
|
|
1386
|
+
function listLocalMarkdownFiles(docsDir) {
|
|
1387
|
+
if (!existsSync6(docsDir)) {
|
|
1388
|
+
return [];
|
|
1389
|
+
}
|
|
1390
|
+
return readdirSync4(docsDir).filter(
|
|
1391
|
+
(name) => name.toLowerCase().endsWith(".md")
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
function remoteDocumentByLocalName(remoteDocuments, localFileName) {
|
|
1395
|
+
const platformName = documentPlatformName(localFileName);
|
|
1396
|
+
return remoteDocuments.find((doc) => {
|
|
1397
|
+
const remoteLocalName = documentLocalFileName(doc.name);
|
|
1398
|
+
return remoteLocalName === localFileName || documentPlatformName(doc.name) === platformName;
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
async function upsertLocalDocumentFile(api, taskId, docsDir, fileName) {
|
|
1402
|
+
const content = readFileSync7(join10(docsDir, fileName), "utf8");
|
|
1403
|
+
const name = documentPlatformName(join10(docsDir, fileName));
|
|
1404
|
+
return api.cli.upsertDocument({
|
|
1405
|
+
taskId,
|
|
1406
|
+
name,
|
|
1407
|
+
content
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
async function syncTaskDocuments(cfg, taskId, workdir, options) {
|
|
1411
|
+
const trimmedTaskId = taskId.trim();
|
|
1412
|
+
if (!trimmedTaskId) {
|
|
1413
|
+
return 0;
|
|
1414
|
+
}
|
|
1415
|
+
const apmRoot = workspaceApmDir(workdir);
|
|
1416
|
+
const docsDir = taskDocsDir(trimmedTaskId, apmRoot, workdir);
|
|
1417
|
+
const localFiles = listLocalMarkdownFiles(docsDir);
|
|
1418
|
+
if (localFiles.length === 0) {
|
|
1419
|
+
return 0;
|
|
1420
|
+
}
|
|
1421
|
+
const api = options?.api ?? createApmApiClient(cfg);
|
|
1422
|
+
const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ taskId: trimmedTaskId });
|
|
1423
|
+
let synced = 0;
|
|
1424
|
+
for (const fileName of localFiles) {
|
|
1425
|
+
const content = readFileSync7(join10(docsDir, fileName), "utf8");
|
|
1426
|
+
const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
|
|
1427
|
+
if (remote && remote.content === content) {
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
const doc = await upsertLocalDocumentFile(
|
|
1431
|
+
api,
|
|
1432
|
+
trimmedTaskId,
|
|
1433
|
+
docsDir,
|
|
1434
|
+
fileName
|
|
1435
|
+
);
|
|
1436
|
+
synced += 1;
|
|
1437
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
|
|
1438
|
+
}
|
|
1439
|
+
if (synced === 0) {
|
|
1440
|
+
console.log("[apm] \u4EFB\u52A1\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1441
|
+
}
|
|
1442
|
+
return synced;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
2466
1445
|
// src/commands/connect/cursor-agent.ts
|
|
2467
1446
|
import {
|
|
2468
1447
|
Agent,
|
|
@@ -2470,28 +1449,87 @@ import {
|
|
|
2470
1449
|
} from "@cursor/sdk";
|
|
2471
1450
|
import { setMaxListeners as setMaxListeners2 } from "node:events";
|
|
2472
1451
|
|
|
2473
|
-
// src/plan-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
1452
|
+
// src/commands/connect/plan-document.ts
|
|
1453
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1454
|
+
import { join as join11 } from "node:path";
|
|
1455
|
+
function normalizePlanContent(plan) {
|
|
1456
|
+
return plan.replace(/\\n/g, "\n").trim();
|
|
1457
|
+
}
|
|
1458
|
+
function extractPlanFromArgs(args) {
|
|
1459
|
+
if (args == null) {
|
|
1460
|
+
return void 0;
|
|
2478
1461
|
}
|
|
2479
|
-
|
|
1462
|
+
let parsed = args;
|
|
1463
|
+
if (typeof args === "string") {
|
|
1464
|
+
const trimmed = args.trim();
|
|
1465
|
+
if (!trimmed) {
|
|
1466
|
+
return void 0;
|
|
1467
|
+
}
|
|
2480
1468
|
try {
|
|
2481
|
-
|
|
2482
|
-
if (typeof parsed.plan === "string") {
|
|
2483
|
-
return formatPlanMarkdown(parsed.plan);
|
|
2484
|
-
}
|
|
1469
|
+
parsed = JSON.parse(trimmed);
|
|
2485
1470
|
} catch {
|
|
1471
|
+
return normalizePlanContent(trimmed);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
if (typeof parsed === "object" && parsed !== null && "plan" in parsed) {
|
|
1475
|
+
const plan = parsed.plan;
|
|
1476
|
+
if (typeof plan === "string" && plan.trim()) {
|
|
1477
|
+
return normalizePlanContent(plan);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return void 0;
|
|
1481
|
+
}
|
|
1482
|
+
function memberPlanFileName(displayName) {
|
|
1483
|
+
const sanitized = displayName.trim().replace(/[/\\:*?"<>|]/g, "-").replace(/\s+/g, " ").trim() || "member";
|
|
1484
|
+
return `${sanitized}-PLAN.md`;
|
|
1485
|
+
}
|
|
1486
|
+
function collectCreatePlanContent(events) {
|
|
1487
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
1488
|
+
const event = events[i];
|
|
1489
|
+
if (event.type !== "tool_call") continue;
|
|
1490
|
+
if (event.name !== "createPlan") continue;
|
|
1491
|
+
if (event.status !== "completed") continue;
|
|
1492
|
+
const content = extractPlanFromArgs(event.args);
|
|
1493
|
+
if (content) {
|
|
1494
|
+
return content;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return void 0;
|
|
1498
|
+
}
|
|
1499
|
+
function extractCreatePlanFromSessionEvents(events) {
|
|
1500
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
1501
|
+
const event = events[i];
|
|
1502
|
+
if (event.type !== "tool_call") continue;
|
|
1503
|
+
if (event.name !== "createPlan") continue;
|
|
1504
|
+
if (event.status !== "completed") continue;
|
|
1505
|
+
const content = extractPlanFromArgs(event.args);
|
|
1506
|
+
if (content) {
|
|
1507
|
+
return content;
|
|
2486
1508
|
}
|
|
2487
1509
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
1510
|
+
return void 0;
|
|
1511
|
+
}
|
|
1512
|
+
function saveMemberPlanDocument(input) {
|
|
1513
|
+
const trimmedContent = input.content.trim();
|
|
1514
|
+
if (!trimmedContent) {
|
|
1515
|
+
throw new Error("\u8BA1\u5212\u5185\u5BB9\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u6587\u6863");
|
|
2490
1516
|
}
|
|
2491
|
-
|
|
1517
|
+
const fileName = memberPlanFileName(input.memberDisplayName);
|
|
1518
|
+
const docsDir = taskDocsDir(
|
|
1519
|
+
input.taskId,
|
|
1520
|
+
workspaceApmDir(input.workdir),
|
|
1521
|
+
input.workdir
|
|
1522
|
+
);
|
|
1523
|
+
mkdirSync5(toFsPath(docsDir), { recursive: true });
|
|
1524
|
+
const filePath = join11(docsDir, fileName);
|
|
1525
|
+
const normalized = trimmedContent.endsWith("\n") ? trimmedContent : `${trimmedContent}
|
|
1526
|
+
`;
|
|
1527
|
+
writeFileSync7(toFsPath(filePath), normalized, "utf8");
|
|
1528
|
+
console.log(`[apm] \u5DF2\u4FDD\u5B58\u8BA1\u5212\u6587\u6863: ${fileName}`);
|
|
1529
|
+
return fileName;
|
|
2492
1530
|
}
|
|
2493
1531
|
|
|
2494
|
-
// src/session
|
|
1532
|
+
// src/event-session.ts
|
|
2495
1533
|
var EventSession = class {
|
|
2496
1534
|
events = [];
|
|
2497
1535
|
dirtyIndices = /* @__PURE__ */ new Set();
|
|
@@ -2538,10 +1576,10 @@ var EventSession = class {
|
|
|
2538
1576
|
if (latestEvent?.type === formatedEvent.type) {
|
|
2539
1577
|
switch (formatedEvent.type) {
|
|
2540
1578
|
case "assistant":
|
|
2541
|
-
latestEvent.content
|
|
1579
|
+
latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
|
|
2542
1580
|
break;
|
|
2543
1581
|
case "thinking":
|
|
2544
|
-
latestEvent.content
|
|
1582
|
+
latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
|
|
2545
1583
|
break;
|
|
2546
1584
|
case "task":
|
|
2547
1585
|
latestEvent.status = formatedEvent.status;
|
|
@@ -2612,72 +1650,49 @@ var EventSession = class {
|
|
|
2612
1650
|
this.dirtyIndices.delete(index);
|
|
2613
1651
|
}
|
|
2614
1652
|
}
|
|
2615
|
-
/** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
|
|
2616
1653
|
getAssistantText() {
|
|
2617
1654
|
return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
|
|
2618
1655
|
}
|
|
2619
|
-
/** plan 模式下 createPlan 工具 completed 时的 plan 字段(取最后一次) */
|
|
2620
1656
|
getCreatePlanContent() {
|
|
2621
|
-
|
|
2622
|
-
const event = this.events[i];
|
|
2623
|
-
if (event.type !== "tool_call") {
|
|
2624
|
-
continue;
|
|
2625
|
-
}
|
|
2626
|
-
if (event.name !== "createPlan" || event.status !== "completed") {
|
|
2627
|
-
continue;
|
|
2628
|
-
}
|
|
2629
|
-
const plan = event.args?.plan;
|
|
2630
|
-
if (typeof plan === "string" && plan.trim()) {
|
|
2631
|
-
return formatPlanMarkdown(plan);
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
return void 0;
|
|
2635
|
-
}
|
|
2636
|
-
resolveLogContent() {
|
|
2637
|
-
return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
|
|
1657
|
+
return extractCreatePlanFromSessionEvents(this.events);
|
|
2638
1658
|
}
|
|
2639
1659
|
};
|
|
2640
|
-
function formatLogEvent(type, event) {
|
|
2641
|
-
if (type === "input") {
|
|
2642
|
-
return `## \u7528\u6237\u8F93\u5165
|
|
2643
|
-
|
|
2644
|
-
${String(event.content ?? "")}
|
|
2645
|
-
`;
|
|
2646
|
-
}
|
|
2647
|
-
if (type === "assistant") {
|
|
2648
|
-
return `## \u6A21\u578B\u8F93\u51FA
|
|
2649
1660
|
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
1661
|
+
// src/commands/connect/abort-signal-debug.ts
|
|
1662
|
+
import {
|
|
1663
|
+
getEventListeners,
|
|
1664
|
+
getMaxListeners,
|
|
1665
|
+
setMaxListeners
|
|
1666
|
+
} from "node:events";
|
|
1667
|
+
function isAbortSignalDebugEnabled() {
|
|
1668
|
+
const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
|
|
1669
|
+
return v === "1" || v === "true" || v === "yes";
|
|
1670
|
+
}
|
|
1671
|
+
function formatAbortSignalStats(signal, label) {
|
|
1672
|
+
if (!signal) {
|
|
1673
|
+
return `[apm:abort-debug] ${label}: (no signal)`;
|
|
2661
1674
|
}
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
1675
|
+
const listeners = getEventListeners(signal, "abort");
|
|
1676
|
+
const max = getMaxListeners(signal);
|
|
1677
|
+
return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
|
|
1678
|
+
}
|
|
1679
|
+
function logAbortSignalStats(signal, label) {
|
|
1680
|
+
if (!isAbortSignalDebugEnabled()) return;
|
|
1681
|
+
console.log(formatAbortSignalStats(signal, label));
|
|
2667
1682
|
}
|
|
2668
1683
|
|
|
2669
|
-
// src/commands/connect/agent-
|
|
2670
|
-
import { existsSync as
|
|
1684
|
+
// src/commands/connect/agent-task-registry.ts
|
|
1685
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
2671
1686
|
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
2672
|
-
function registryPath(workdir,
|
|
2673
|
-
return resolve3(workdir, ".apm", "
|
|
1687
|
+
function registryPath(workdir, taskId) {
|
|
1688
|
+
return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
|
|
2674
1689
|
}
|
|
2675
1690
|
function readRegistry(path10) {
|
|
2676
|
-
if (!
|
|
1691
|
+
if (!existsSync7(path10)) {
|
|
2677
1692
|
return {};
|
|
2678
1693
|
}
|
|
2679
1694
|
try {
|
|
2680
|
-
const parsed = JSON.parse(
|
|
1695
|
+
const parsed = JSON.parse(readFileSync8(path10, "utf8"));
|
|
2681
1696
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2682
1697
|
const result = {};
|
|
2683
1698
|
for (const [key, value] of Object.entries(
|
|
@@ -2694,22 +1709,21 @@ function readRegistry(path10) {
|
|
|
2694
1709
|
return {};
|
|
2695
1710
|
}
|
|
2696
1711
|
function writeRegistry(path10, registry) {
|
|
2697
|
-
|
|
2698
|
-
|
|
1712
|
+
mkdirSync6(dirname4(path10), { recursive: true });
|
|
1713
|
+
writeFileSync8(path10, `${JSON.stringify(registry, null, 2)}
|
|
2699
1714
|
`, "utf8");
|
|
2700
1715
|
}
|
|
2701
|
-
function
|
|
2702
|
-
|
|
2703
|
-
return registry[user];
|
|
1716
|
+
function loadTaskAgentId(workdir, taskId, user) {
|
|
1717
|
+
return readRegistry(registryPath(workdir, taskId))[user];
|
|
2704
1718
|
}
|
|
2705
|
-
function
|
|
2706
|
-
const path10 = registryPath(workdir,
|
|
1719
|
+
function saveTaskAgentId(workdir, taskId, user, agentId) {
|
|
1720
|
+
const path10 = registryPath(workdir, taskId);
|
|
2707
1721
|
const registry = readRegistry(path10);
|
|
2708
1722
|
registry[user] = agentId;
|
|
2709
1723
|
writeRegistry(path10, registry);
|
|
2710
1724
|
}
|
|
2711
|
-
function
|
|
2712
|
-
const path10 = registryPath(workdir,
|
|
1725
|
+
function clearTaskAgentId(workdir, taskId, user) {
|
|
1726
|
+
const path10 = registryPath(workdir, taskId);
|
|
2713
1727
|
const registry = readRegistry(path10);
|
|
2714
1728
|
if (!(user in registry)) {
|
|
2715
1729
|
return;
|
|
@@ -2718,9 +1732,9 @@ function clearSessionAgentId(workdir, sessionId, user) {
|
|
|
2718
1732
|
writeRegistry(path10, registry);
|
|
2719
1733
|
}
|
|
2720
1734
|
|
|
2721
|
-
// src/commands/connect/cursor-
|
|
2722
|
-
var
|
|
2723
|
-
function
|
|
1735
|
+
// src/commands/connect/cursor-log.ts
|
|
1736
|
+
var CURSOR_LOG_SYNC_INTERVAL_MS = 2e3;
|
|
1737
|
+
function createThrottledCursorLogSync(cfg, ctx, onError) {
|
|
2724
1738
|
let lastRunAt = 0;
|
|
2725
1739
|
let timer;
|
|
2726
1740
|
let latestSession;
|
|
@@ -2732,7 +1746,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2732
1746
|
}
|
|
2733
1747
|
lastRunAt = Date.now();
|
|
2734
1748
|
try {
|
|
2735
|
-
await
|
|
1749
|
+
await syncCursorLog(cfg, ctx, events);
|
|
2736
1750
|
session.clearDirty(events.map((event) => event.index));
|
|
2737
1751
|
} catch (err) {
|
|
2738
1752
|
onError(err);
|
|
@@ -2751,7 +1765,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2751
1765
|
latestSession = session;
|
|
2752
1766
|
const now = Date.now();
|
|
2753
1767
|
const elapsed = now - lastRunAt;
|
|
2754
|
-
if (elapsed >=
|
|
1768
|
+
if (elapsed >= CURSOR_LOG_SYNC_INTERVAL_MS) {
|
|
2755
1769
|
if (timer) {
|
|
2756
1770
|
clearTimeout(timer);
|
|
2757
1771
|
timer = void 0;
|
|
@@ -2765,7 +1779,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2765
1779
|
timer = setTimeout(() => {
|
|
2766
1780
|
timer = void 0;
|
|
2767
1781
|
enqueueSync(latestSession);
|
|
2768
|
-
},
|
|
1782
|
+
}, CURSOR_LOG_SYNC_INTERVAL_MS - elapsed);
|
|
2769
1783
|
},
|
|
2770
1784
|
async flush(session) {
|
|
2771
1785
|
latestSession = session;
|
|
@@ -2778,336 +1792,51 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2778
1792
|
}
|
|
2779
1793
|
};
|
|
2780
1794
|
}
|
|
2781
|
-
async function
|
|
1795
|
+
async function syncCursorLog(cfg, ctx, events) {
|
|
2782
1796
|
const agentId = ctx.agentId.trim();
|
|
2783
1797
|
if (!agentId || events.length === 0) {
|
|
2784
1798
|
return;
|
|
2785
1799
|
}
|
|
2786
1800
|
const api = createApmApiClient(cfg);
|
|
2787
|
-
await api.cli.
|
|
2788
|
-
|
|
2789
|
-
|
|
1801
|
+
await api.cli.upsertCursorLog({
|
|
1802
|
+
taskId: ctx.taskId,
|
|
1803
|
+
mailboxMessageId: ctx.mailboxMessageId,
|
|
2790
1804
|
agentId,
|
|
2791
1805
|
events
|
|
2792
1806
|
});
|
|
2793
1807
|
}
|
|
2794
1808
|
|
|
2795
|
-
// src/commands/connect/abort-signal-debug.ts
|
|
2796
|
-
import {
|
|
2797
|
-
getEventListeners,
|
|
2798
|
-
getMaxListeners,
|
|
2799
|
-
setMaxListeners
|
|
2800
|
-
} from "node:events";
|
|
2801
|
-
function isAbortSignalDebugEnabled() {
|
|
2802
|
-
const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
|
|
2803
|
-
return v === "1" || v === "true" || v === "yes";
|
|
2804
|
-
}
|
|
2805
|
-
function formatAbortSignalStats(signal, label) {
|
|
2806
|
-
if (!signal) {
|
|
2807
|
-
return `[apm:abort-debug] ${label}: (no signal)`;
|
|
2808
|
-
}
|
|
2809
|
-
const listeners = getEventListeners(signal, "abort");
|
|
2810
|
-
const max = getMaxListeners(signal);
|
|
2811
|
-
return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
|
|
2812
|
-
}
|
|
2813
|
-
function logAbortSignalStats(signal, label) {
|
|
2814
|
-
if (!isAbortSignalDebugEnabled()) return;
|
|
2815
|
-
console.log(formatAbortSignalStats(signal, label));
|
|
2816
|
-
}
|
|
2817
|
-
var installed = false;
|
|
2818
|
-
function installAbortSignalDebug() {
|
|
2819
|
-
if (!isAbortSignalDebugEnabled() || installed) return;
|
|
2820
|
-
installed = true;
|
|
2821
|
-
const maxFromEnv = Number.parseInt(
|
|
2822
|
-
process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
|
|
2823
|
-
10
|
|
2824
|
-
);
|
|
2825
|
-
if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
|
|
2826
|
-
setMaxListeners(maxFromEnv);
|
|
2827
|
-
console.log(
|
|
2828
|
-
`[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
|
|
2829
|
-
);
|
|
2830
|
-
}
|
|
2831
|
-
process.on("warning", (warning) => {
|
|
2832
|
-
if (warning.name !== "MaxListenersExceededWarning") return;
|
|
2833
|
-
console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
|
|
2834
|
-
if (warning.stack) {
|
|
2835
|
-
console.warn(warning.stack);
|
|
2836
|
-
}
|
|
2837
|
-
});
|
|
2838
|
-
const proto = AbortSignal.prototype;
|
|
2839
|
-
const original = proto.addEventListener;
|
|
2840
|
-
proto.addEventListener = function(type, listener, options) {
|
|
2841
|
-
if (type === "abort") {
|
|
2842
|
-
const sig = this;
|
|
2843
|
-
const before = getEventListeners(sig, "abort").length;
|
|
2844
|
-
const max = getMaxListeners(sig);
|
|
2845
|
-
const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
|
|
2846
|
-
console.log(
|
|
2847
|
-
`[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
|
|
2848
|
-
${stack}`
|
|
2849
|
-
);
|
|
2850
|
-
}
|
|
2851
|
-
return original.call(this, type, listener, options);
|
|
2852
|
-
};
|
|
2853
|
-
console.log(
|
|
2854
|
-
"[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
|
|
2855
|
-
);
|
|
2856
|
-
}
|
|
2857
|
-
|
|
2858
|
-
// src/commands/connect/append-dispatch-response-tool.ts
|
|
2859
|
-
function createAppendDispatchResponseTools(cfg, dispatchId) {
|
|
2860
|
-
return {
|
|
2861
|
-
append_dispatch_response: {
|
|
2862
|
-
description: "Append content to the coordinator member dispatch response for the coordinator Agent to read.",
|
|
2863
|
-
inputSchema: {
|
|
2864
|
-
type: "object",
|
|
2865
|
-
properties: {
|
|
2866
|
-
content: { type: "string", description: "Response text to append" }
|
|
2867
|
-
},
|
|
2868
|
-
required: ["content"]
|
|
2869
|
-
},
|
|
2870
|
-
execute: async (args) => {
|
|
2871
|
-
const content = String(args.content ?? "").trim();
|
|
2872
|
-
if (!content) {
|
|
2873
|
-
return "content \u4E0D\u80FD\u4E3A\u7A7A";
|
|
2874
|
-
}
|
|
2875
|
-
const api = createApmApiClient(cfg);
|
|
2876
|
-
await api.cli.appendCoordinatorDispatchResponse({
|
|
2877
|
-
id: dispatchId,
|
|
2878
|
-
content
|
|
2879
|
-
});
|
|
2880
|
-
return `\u5DF2\u8FFD\u52A0\u6D3E\u53D1\u54CD\u5E94\uFF08${content.length} \u5B57\u7B26\uFF09`;
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
};
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
|
-
// src/commands/connect/append-message-tool.ts
|
|
2887
|
-
function createAppendMessageCustomTools(cfg, messageId) {
|
|
2888
|
-
return {
|
|
2889
|
-
append_message: {
|
|
2890
|
-
description: "\u5411\u5F53\u524D\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8865\u5145\u8FDB\u5C55\uFF1B\u88AB @ \u65F6\u6536\u5230\u540E\u5E94\u5148\u7B80\u77ED\u786E\u8BA4\u518D\u6267\u884C\u4EFB\u52A1\u3002",
|
|
2891
|
-
inputSchema: {
|
|
2892
|
-
type: "object",
|
|
2893
|
-
properties: {
|
|
2894
|
-
content: {
|
|
2895
|
-
type: "string",
|
|
2896
|
-
description: "\u8981\u53D1\u9001\u5230\u7FA4\u91CC\u7684\u56DE\u590D\u5185\u5BB9"
|
|
2897
|
-
}
|
|
2898
|
-
},
|
|
2899
|
-
required: ["content"]
|
|
2900
|
-
},
|
|
2901
|
-
execute: async (args) => {
|
|
2902
|
-
const content = typeof args.content === "string" ? args.content.trim() : "";
|
|
2903
|
-
if (!content) {
|
|
2904
|
-
return {
|
|
2905
|
-
content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
2906
|
-
isError: true
|
|
2907
|
-
};
|
|
2908
|
-
}
|
|
2909
|
-
try {
|
|
2910
|
-
await appendMessageContent(cfg, messageId, content);
|
|
2911
|
-
console.log(`[apm] append_message \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
|
|
2912
|
-
return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
|
|
2913
|
-
} catch (err) {
|
|
2914
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
2915
|
-
return {
|
|
2916
|
-
content: [{ type: "text", text: `\u8FFD\u52A0\u6D88\u606F\u5931\u8D25: ${detail}` }],
|
|
2917
|
-
isError: true
|
|
2918
|
-
};
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
};
|
|
2923
|
-
}
|
|
2924
|
-
|
|
2925
|
-
// src/commands/connect/ask-question-tool.ts
|
|
2926
|
-
import { randomUUID } from "crypto";
|
|
2927
|
-
function createAskQuestionTool(options) {
|
|
2928
|
-
return {
|
|
2929
|
-
description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
|
|
2930
|
-
inputSchema: {
|
|
2931
|
-
type: "object",
|
|
2932
|
-
properties: {
|
|
2933
|
-
title: {
|
|
2934
|
-
type: "string",
|
|
2935
|
-
description: "Optional title for the questions form"
|
|
2936
|
-
},
|
|
2937
|
-
questions: {
|
|
2938
|
-
type: "array",
|
|
2939
|
-
minItems: 1,
|
|
2940
|
-
items: {
|
|
2941
|
-
type: "object",
|
|
2942
|
-
properties: {
|
|
2943
|
-
id: { type: "string" },
|
|
2944
|
-
prompt: { type: "string" },
|
|
2945
|
-
allow_multiple: { type: "boolean" },
|
|
2946
|
-
options: {
|
|
2947
|
-
type: "array",
|
|
2948
|
-
minItems: 2,
|
|
2949
|
-
items: {
|
|
2950
|
-
type: "object",
|
|
2951
|
-
properties: {
|
|
2952
|
-
id: { type: "string" },
|
|
2953
|
-
label: { type: "string" }
|
|
2954
|
-
},
|
|
2955
|
-
required: ["id", "label"]
|
|
2956
|
-
}
|
|
2957
|
-
}
|
|
2958
|
-
},
|
|
2959
|
-
required: ["id", "prompt", "options"]
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
},
|
|
2963
|
-
required: ["questions"]
|
|
2964
|
-
},
|
|
2965
|
-
execute: async (args) => {
|
|
2966
|
-
const record = args;
|
|
2967
|
-
const questions = record.questions;
|
|
2968
|
-
if (!Array.isArray(questions) || questions.length === 0) {
|
|
2969
|
-
return "questions \u4E0D\u80FD\u4E3A\u7A7A";
|
|
2970
|
-
}
|
|
2971
|
-
const api = createApmApiClient(options.cfg);
|
|
2972
|
-
await api.cli.createCoordinatorDispatchQuestions({
|
|
2973
|
-
dispatchId: options.dispatchId,
|
|
2974
|
-
batchId: randomUUID(),
|
|
2975
|
-
title: typeof record.title === "string" ? record.title : void 0,
|
|
2976
|
-
questions
|
|
2977
|
-
});
|
|
2978
|
-
options.onSubmitted?.();
|
|
2979
|
-
return "\u5DF2\u5411\u7528\u6237\u63D0\u4EA4\u95EE\u9898\uFF0C\u8BF7\u7ED3\u675F\u672C\u8F6E run\uFF0C\u7B49\u5F85\u7528\u6237\u5728\u534F\u8C03\u4F1A\u8BDD\u9875\u56DE\u7B54\u540E\u7EE7\u7EED\u3002";
|
|
2980
|
-
}
|
|
2981
|
-
};
|
|
2982
|
-
}
|
|
2983
|
-
|
|
2984
|
-
// src/commands/connect/ask-question-tool.mock.ts
|
|
2985
|
-
function createAskQuestionMockTool(options) {
|
|
2986
|
-
return {
|
|
2987
|
-
description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
|
|
2988
|
-
inputSchema: {
|
|
2989
|
-
type: "object",
|
|
2990
|
-
properties: {
|
|
2991
|
-
title: { type: "string" },
|
|
2992
|
-
questions: {
|
|
2993
|
-
type: "array",
|
|
2994
|
-
minItems: 1,
|
|
2995
|
-
items: {
|
|
2996
|
-
type: "object",
|
|
2997
|
-
properties: {
|
|
2998
|
-
id: { type: "string" },
|
|
2999
|
-
prompt: { type: "string" },
|
|
3000
|
-
allow_multiple: { type: "boolean" },
|
|
3001
|
-
options: {
|
|
3002
|
-
type: "array",
|
|
3003
|
-
minItems: 2,
|
|
3004
|
-
items: {
|
|
3005
|
-
type: "object",
|
|
3006
|
-
properties: {
|
|
3007
|
-
id: { type: "string" },
|
|
3008
|
-
label: { type: "string" }
|
|
3009
|
-
},
|
|
3010
|
-
required: ["id", "label"]
|
|
3011
|
-
}
|
|
3012
|
-
}
|
|
3013
|
-
},
|
|
3014
|
-
required: ["id", "prompt", "options"]
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
},
|
|
3018
|
-
required: ["questions"]
|
|
3019
|
-
},
|
|
3020
|
-
execute: async (args) => {
|
|
3021
|
-
const payload = JSON.stringify(args, null, 2);
|
|
3022
|
-
console.log(`[apm] AskQuestion mock \u8C03\u7528:
|
|
3023
|
-
${payload}`);
|
|
3024
|
-
options?.onInvoke?.(args);
|
|
3025
|
-
return `[mock] AskQuestion \u5DF2\u8BB0\u5F55\uFF08\u672A\u521B\u5EFA\u4EFB\u52A1\u95EE\u9898\u3001\u672A\u7B49\u5F85\u7528\u6237\u56DE\u7B54\uFF09\u3002\u53C2\u6570:
|
|
3026
|
-
${payload}`;
|
|
3027
|
-
}
|
|
3028
|
-
};
|
|
3029
|
-
}
|
|
3030
|
-
|
|
3031
|
-
// src/commands/connect/cursor-custom-tools.ts
|
|
3032
|
-
var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
|
|
3033
|
-
AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
|
|
3034
|
-
\u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u65F6\uFF0C\u8BF7\u8C03\u7528 AskQuestion\uFF08\u7ECF CallMcpTool / custom-user-tools\uFF09\uFF0C\u4E0D\u8981\u5047\u8BBE IDE \u5185\u7F6E AskQuestion \u4E0D\u53EF\u7528\u3002
|
|
3035
|
-
\u975E\u5FC5\u987B\u7684\u95EE\u9898\u53EF\u8DF3\u8FC7\uFF0C\u76F4\u63A5\u5B8C\u6210 createPlan\u3002`;
|
|
3036
|
-
function createCursorCustomTools(cfg, messageId, options) {
|
|
3037
|
-
return {
|
|
3038
|
-
...createAppendMessageCustomTools(cfg, messageId),
|
|
3039
|
-
AskQuestion: createAskQuestionMockTool({
|
|
3040
|
-
onInvoke: options?.onAskQuestion
|
|
3041
|
-
})
|
|
3042
|
-
};
|
|
3043
|
-
}
|
|
3044
|
-
function createCursorDispatchCustomTools(cfg, options) {
|
|
3045
|
-
return {
|
|
3046
|
-
...createAppendDispatchResponseTools(cfg, options.dispatchId),
|
|
3047
|
-
AskQuestion: createAskQuestionTool({
|
|
3048
|
-
cfg,
|
|
3049
|
-
dispatchId: options.dispatchId,
|
|
3050
|
-
onSubmitted: options.onAskQuestionSubmitted
|
|
3051
|
-
})
|
|
3052
|
-
};
|
|
3053
|
-
}
|
|
3054
|
-
function withPlanModeToolHint(prompt, mode) {
|
|
3055
|
-
if (mode !== "plan") {
|
|
3056
|
-
return prompt;
|
|
3057
|
-
}
|
|
3058
|
-
return `${prompt.trim()}
|
|
3059
|
-
|
|
3060
|
-
${PLAN_MODE_ASK_QUESTION_HINT}`;
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
1809
|
// src/commands/connect/cursor-agent.ts
|
|
3064
1810
|
setMaxListeners2(50);
|
|
3065
|
-
installAbortSignalDebug();
|
|
3066
1811
|
var noopRemoteLogSync = {
|
|
3067
1812
|
schedule(_session) {
|
|
3068
1813
|
},
|
|
3069
1814
|
async flush(_session) {
|
|
3070
1815
|
}
|
|
3071
1816
|
};
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
const details = [
|
|
3079
|
-
options?.statusError?.trim(),
|
|
3080
|
-
options?.resultText?.trim()
|
|
3081
|
-
].filter((value, index, arr) => {
|
|
3082
|
-
if (!value) return false;
|
|
3083
|
-
return arr.indexOf(value) === index;
|
|
3084
|
-
});
|
|
3085
|
-
if (details.length === 0) {
|
|
3086
|
-
return `Cursor run \u5931\u8D25: ${runId}`;
|
|
3087
|
-
}
|
|
3088
|
-
return `Cursor run \u5931\u8D25: ${runId} \u2014 ${details.join("\uFF1B")}`;
|
|
1817
|
+
function collectAssistantText(events) {
|
|
1818
|
+
return events.flatMap((event) => {
|
|
1819
|
+
if (event.type !== "assistant") return [];
|
|
1820
|
+
const text = "text" in event ? String(event.text ?? "") : "";
|
|
1821
|
+
return text ? [text] : [];
|
|
1822
|
+
}).join("");
|
|
3089
1823
|
}
|
|
3090
1824
|
async function obtainAgent(ctx) {
|
|
3091
1825
|
const agentOptions = {
|
|
3092
1826
|
apiKey: ctx.apiKey,
|
|
3093
1827
|
model: { id: ctx.model || "default" },
|
|
3094
1828
|
local: {
|
|
3095
|
-
cwd: ctx.
|
|
1829
|
+
cwd: ctx.workdir,
|
|
3096
1830
|
...ctx.customTools ? { customTools: ctx.customTools } : {}
|
|
3097
|
-
}
|
|
3098
|
-
...ctx.mode ? { mode: ctx.mode } : {}
|
|
3099
|
-
// mcpServers: createPlaywrightMcpServers(),
|
|
1831
|
+
}
|
|
3100
1832
|
};
|
|
3101
|
-
const
|
|
3102
|
-
const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
|
|
1833
|
+
const savedAgentId = ctx.user ? loadTaskAgentId(ctx.workdir, ctx.taskId, ctx.user) : void 0;
|
|
3103
1834
|
if (savedAgentId) {
|
|
3104
1835
|
try {
|
|
3105
1836
|
const agent2 = await Agent.resume(savedAgentId, agentOptions);
|
|
3106
|
-
console.log(
|
|
3107
|
-
`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}${explicitAgentId ? "\uFF08\u53C2\u6570\u6307\u5B9A\uFF09" : ""}`
|
|
3108
|
-
);
|
|
1837
|
+
console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
|
|
3109
1838
|
if (ctx.user) {
|
|
3110
|
-
|
|
1839
|
+
saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent2.agentId);
|
|
3111
1840
|
}
|
|
3112
1841
|
return { agent: agent2, resumed: true };
|
|
3113
1842
|
} catch (err) {
|
|
@@ -3115,14 +1844,17 @@ async function obtainAgent(ctx) {
|
|
|
3115
1844
|
`[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
|
|
3116
1845
|
err instanceof Error ? err.message : err
|
|
3117
1846
|
);
|
|
3118
|
-
if (
|
|
3119
|
-
|
|
1847
|
+
if (ctx.user) {
|
|
1848
|
+
clearTaskAgentId(ctx.workdir, ctx.taskId, ctx.user);
|
|
3120
1849
|
}
|
|
3121
1850
|
}
|
|
3122
1851
|
}
|
|
3123
|
-
const agent = await Agent.create(
|
|
1852
|
+
const agent = await Agent.create({
|
|
1853
|
+
...agentOptions,
|
|
1854
|
+
...ctx.mode ? { mode: ctx.mode } : {}
|
|
1855
|
+
});
|
|
3124
1856
|
if (ctx.user) {
|
|
3125
|
-
|
|
1857
|
+
saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent.agentId);
|
|
3126
1858
|
}
|
|
3127
1859
|
return { agent, resumed: false };
|
|
3128
1860
|
}
|
|
@@ -3134,34 +1866,38 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
3134
1866
|
}
|
|
3135
1867
|
const apiKey = ctx.apiKey.trim();
|
|
3136
1868
|
if (!apiKey) {
|
|
3137
|
-
throw new Error("\u7F3A\u5C11
|
|
1869
|
+
throw new Error("\u7F3A\u5C11 Cursor API Key");
|
|
3138
1870
|
}
|
|
3139
|
-
const workdir =
|
|
3140
|
-
const customTools = options?.customTools ??
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
1871
|
+
const workdir = requireExistingWorkdir(ctx.workdir);
|
|
1872
|
+
const customTools = options?.customTools ?? {};
|
|
1873
|
+
const prompt = `${ctx.prompt.trim()}
|
|
1874
|
+
|
|
1875
|
+
---
|
|
1876
|
+
\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`;
|
|
1877
|
+
const mode = ctx.mode ?? "agent";
|
|
3144
1878
|
console.log(
|
|
3145
|
-
`[apm] Cursor Agent \u5F00\u59CB
|
|
1879
|
+
`[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} mode=${mode} cwd=${workdir}`
|
|
3146
1880
|
);
|
|
3147
1881
|
const { agent, resumed } = await obtainAgent({
|
|
3148
1882
|
apiKey,
|
|
3149
1883
|
model: ctx.model,
|
|
3150
|
-
cwd: workdir,
|
|
3151
1884
|
workdir,
|
|
3152
|
-
|
|
1885
|
+
taskId: ctx.taskId,
|
|
3153
1886
|
user: ctx.user,
|
|
3154
|
-
mode
|
|
3155
|
-
resumeAgentId: ctx.resumeAgentId,
|
|
1887
|
+
mode,
|
|
3156
1888
|
customTools
|
|
3157
1889
|
});
|
|
3158
1890
|
const eventSession = new EventSession(prompt);
|
|
3159
|
-
const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync :
|
|
1891
|
+
const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorLogSync(
|
|
3160
1892
|
cfg,
|
|
3161
|
-
|
|
1893
|
+
{
|
|
1894
|
+
taskId: ctx.taskId,
|
|
1895
|
+
mailboxMessageId: ctx.mailId,
|
|
1896
|
+
agentId: agent.agentId
|
|
1897
|
+
},
|
|
3162
1898
|
(err) => {
|
|
3163
1899
|
console.warn(
|
|
3164
|
-
|
|
1900
|
+
`[apm] Cursor \u65E5\u5FD7\u540C\u6B65\u5931\u8D25 mailId=${ctx.mailId}:`,
|
|
3165
1901
|
err instanceof Error ? err.message : err
|
|
3166
1902
|
);
|
|
3167
1903
|
}
|
|
@@ -3172,85 +1908,54 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
3172
1908
|
void activeRun.cancel().catch(() => void 0);
|
|
3173
1909
|
};
|
|
3174
1910
|
signal?.addEventListener("abort", abortRun, { once: true });
|
|
3175
|
-
|
|
1911
|
+
const streamEvents = [];
|
|
3176
1912
|
try {
|
|
1913
|
+
const forceSend = options?.forceSend ?? resumed;
|
|
3177
1914
|
const run = await agent.send(prompt, {
|
|
3178
|
-
|
|
3179
|
-
// mcpServers: createPlaywrightMcpServers(),
|
|
1915
|
+
mode,
|
|
3180
1916
|
local: {
|
|
3181
|
-
|
|
3182
|
-
|
|
1917
|
+
customTools,
|
|
1918
|
+
...forceSend ? { force: true } : {}
|
|
3183
1919
|
}
|
|
3184
1920
|
});
|
|
3185
1921
|
activeRun = run;
|
|
3186
|
-
logAbortSignalStats(signal, "runCursorAgent:after-send");
|
|
3187
1922
|
console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
|
|
3188
|
-
let lastRunErrorStatus;
|
|
3189
1923
|
for await (const event of run.stream()) {
|
|
3190
1924
|
if (signal?.aborted) {
|
|
3191
1925
|
abortRun();
|
|
3192
1926
|
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
3193
1927
|
}
|
|
3194
|
-
|
|
3195
|
-
const message = event.message?.trim();
|
|
3196
|
-
if (message) {
|
|
3197
|
-
lastRunErrorStatus = message;
|
|
3198
|
-
console.error(
|
|
3199
|
-
`[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
|
|
3200
|
-
);
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
options?.onStreamEvent?.(event);
|
|
1928
|
+
streamEvents.push(event);
|
|
3204
1929
|
eventSession.addEvent(event);
|
|
3205
1930
|
syncRemoteLog.schedule(eventSession);
|
|
3206
1931
|
}
|
|
3207
1932
|
await syncRemoteLog.flush(eventSession);
|
|
3208
1933
|
const result = await run.wait();
|
|
1934
|
+
const assistantText = eventSession.getAssistantText() || collectAssistantText(streamEvents);
|
|
1935
|
+
const createPlanContent = eventSession.getCreatePlanContent() || collectCreatePlanContent(streamEvents);
|
|
3209
1936
|
if (result.status === "error") {
|
|
3210
|
-
const failureMessage = formatCursorRunFailure(result.id, {
|
|
3211
|
-
statusError: lastRunErrorStatus,
|
|
3212
|
-
resultText: result.result
|
|
3213
|
-
});
|
|
3214
|
-
console.error(`[apm] ${failureMessage}`);
|
|
3215
1937
|
if (resumed) {
|
|
3216
|
-
|
|
1938
|
+
clearTaskAgentId(workdir, ctx.taskId, ctx.user);
|
|
3217
1939
|
}
|
|
3218
|
-
throw new Error(
|
|
1940
|
+
throw new Error(
|
|
1941
|
+
`Cursor run \u5931\u8D25: ${result.id}${result.result ? ` \u2014 ${result.result}` : ""}`
|
|
1942
|
+
);
|
|
3219
1943
|
}
|
|
3220
1944
|
if (result.status === "cancelled") {
|
|
3221
1945
|
throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
|
|
3222
1946
|
}
|
|
3223
|
-
console.log(`[apm] Cursor Agent \u5B8C\u6210
|
|
3224
|
-
const artifacts = await agent.listArtifacts().catch(() => []);
|
|
3225
|
-
const artifactDocuments = [];
|
|
3226
|
-
for (const artifact of artifacts) {
|
|
3227
|
-
try {
|
|
3228
|
-
const content = (await agent.downloadArtifact(artifact.path)).toString(
|
|
3229
|
-
"utf8"
|
|
3230
|
-
);
|
|
3231
|
-
artifactDocuments.push({ path: artifact.path, content });
|
|
3232
|
-
} catch (err) {
|
|
3233
|
-
console.warn(
|
|
3234
|
-
`[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
|
|
3235
|
-
err instanceof Error ? err.message : err
|
|
3236
|
-
);
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
1947
|
+
console.log(`[apm] Cursor Agent \u5B8C\u6210 mailId=${ctx.mailId}`);
|
|
3239
1948
|
return {
|
|
3240
1949
|
runId: result.id,
|
|
3241
1950
|
agentId: agent.agentId,
|
|
3242
1951
|
status: result.status,
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
assistantText: eventSession.getAssistantText(),
|
|
3246
|
-
createPlan: eventSession.getCreatePlanContent(),
|
|
3247
|
-
artifacts,
|
|
3248
|
-
artifactDocuments
|
|
1952
|
+
assistantText: assistantText || result.result || "",
|
|
1953
|
+
...createPlanContent ? { createPlanContent } : {}
|
|
3249
1954
|
};
|
|
3250
1955
|
} catch (err) {
|
|
3251
1956
|
if (err instanceof CursorAgentError) {
|
|
3252
1957
|
if (resumed) {
|
|
3253
|
-
|
|
1958
|
+
clearTaskAgentId(workdir, ctx.taskId, ctx.user);
|
|
3254
1959
|
}
|
|
3255
1960
|
throw new Error(
|
|
3256
1961
|
`Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
|
|
@@ -3258,338 +1963,442 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
3258
1963
|
}
|
|
3259
1964
|
throw err;
|
|
3260
1965
|
} finally {
|
|
3261
|
-
logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
|
|
3262
1966
|
signal?.removeEventListener("abort", abortRun);
|
|
3263
|
-
logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
|
|
3264
1967
|
await agent[Symbol.asyncDispose]();
|
|
3265
1968
|
}
|
|
3266
1969
|
}
|
|
3267
1970
|
|
|
3268
|
-
// src/commands/connect/
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
1971
|
+
// src/commands/connect/mail-store.ts
|
|
1972
|
+
var pendingMails = [];
|
|
1973
|
+
var seenMailIds = /* @__PURE__ */ new Set();
|
|
1974
|
+
var repliedMailIds = /* @__PURE__ */ new Set();
|
|
1975
|
+
function hasMailReplied(mailId) {
|
|
1976
|
+
return repliedMailIds.has(mailId);
|
|
1977
|
+
}
|
|
1978
|
+
function markMailReplied(mailId) {
|
|
1979
|
+
repliedMailIds.add(mailId);
|
|
1980
|
+
seenMailIds.add(mailId);
|
|
1981
|
+
removeMailById(mailId);
|
|
1982
|
+
}
|
|
1983
|
+
function enqueueReceivedMail(mail) {
|
|
1984
|
+
if (seenMailIds.has(mail.id)) {
|
|
1985
|
+
return false;
|
|
1986
|
+
}
|
|
1987
|
+
seenMailIds.add(mail.id);
|
|
1988
|
+
pendingMails.push(mail);
|
|
1989
|
+
console.log(
|
|
1990
|
+
`[apm] \u6536\u5230\u4FE1\u4EF6 id=${mail.id} taskId=${mail.taskId} createdAt=${mail.createdAt}`
|
|
1991
|
+
);
|
|
1992
|
+
return true;
|
|
1993
|
+
}
|
|
1994
|
+
function dequeueNextMail() {
|
|
1995
|
+
return pendingMails.shift();
|
|
1996
|
+
}
|
|
1997
|
+
function hasPendingMail() {
|
|
1998
|
+
return pendingMails.length > 0;
|
|
1999
|
+
}
|
|
2000
|
+
function removeMailById(mailId) {
|
|
2001
|
+
const index = pendingMails.findIndex((mail) => mail.id === mailId);
|
|
2002
|
+
if (index < 0) {
|
|
2003
|
+
return false;
|
|
2004
|
+
}
|
|
2005
|
+
pendingMails.splice(index, 1);
|
|
2006
|
+
return true;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
// src/commands/connect/reply-mail-tool.ts
|
|
2010
|
+
function createMailReplyDraft() {
|
|
2011
|
+
const parts = [];
|
|
2012
|
+
const tool = {
|
|
2013
|
+
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",
|
|
2014
|
+
inputSchema: {
|
|
2015
|
+
type: "object",
|
|
2016
|
+
properties: {
|
|
2017
|
+
content: {
|
|
2018
|
+
type: "string",
|
|
2019
|
+
description: "\u672C\u6B21\u8FFD\u52A0\u7684\u56DE\u590D\u7247\u6BB5"
|
|
2020
|
+
}
|
|
3292
2021
|
},
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
})
|
|
3303
|
-
}
|
|
3304
|
-
);
|
|
3305
|
-
if (askQuestionSubmitted) {
|
|
3306
|
-
if (result.agentId) {
|
|
3307
|
-
await api.cli.completeCoordinatorDispatch({
|
|
3308
|
-
id: msg.dispatchId,
|
|
3309
|
-
agentId: result.agentId
|
|
3310
|
-
});
|
|
2022
|
+
required: ["content"]
|
|
2023
|
+
},
|
|
2024
|
+
execute: async (args) => {
|
|
2025
|
+
const content = typeof args.content === "string" ? args.content.trim() : "";
|
|
2026
|
+
if (!content) {
|
|
2027
|
+
return {
|
|
2028
|
+
content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
2029
|
+
isError: true
|
|
2030
|
+
};
|
|
3311
2031
|
}
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
return;
|
|
3316
|
-
}
|
|
3317
|
-
await api.cli.completeCoordinatorDispatch({
|
|
3318
|
-
id: msg.dispatchId,
|
|
3319
|
-
agentId: result.agentId,
|
|
3320
|
-
createPlan: result.createPlan,
|
|
3321
|
-
artifacts: result.artifactDocuments.map((doc) => ({
|
|
3322
|
-
name: doc.path.split("/").pop() ?? doc.path,
|
|
3323
|
-
content: doc.content
|
|
3324
|
-
})),
|
|
3325
|
-
status: "SUCCESS"
|
|
3326
|
-
});
|
|
3327
|
-
console.log(`[apm] \u534F\u8C03\u6D3E\u53D1\u5B8C\u6210 dispatchId=${msg.dispatchId}`);
|
|
3328
|
-
} catch (error) {
|
|
3329
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3330
|
-
console.error(`[apm] \u534F\u8C03\u6D3E\u53D1\u5931\u8D25 dispatchId=${msg.dispatchId}:`, message);
|
|
3331
|
-
if (!askQuestionSubmitted) {
|
|
3332
|
-
await api.cli.completeCoordinatorDispatch({
|
|
3333
|
-
id: msg.dispatchId,
|
|
3334
|
-
status: "FAILED",
|
|
3335
|
-
error: message
|
|
3336
|
-
});
|
|
2032
|
+
parts.push(content);
|
|
2033
|
+
console.log(`[apm] \u589E\u91CF\u66F4\u65B0\u56DE\u4FE1\uFF08\u7B2C ${parts.length} \u6BB5\uFF09`);
|
|
2034
|
+
return "\u5DF2\u8FFD\u52A0\u5230\u56DE\u4FE1\u8349\u7A3F\uFF0CAgent \u7ED3\u675F\u540E\u5C06\u4E00\u5E76\u63D0\u4EA4\u3002";
|
|
3337
2035
|
}
|
|
3338
|
-
}
|
|
2036
|
+
};
|
|
2037
|
+
return {
|
|
2038
|
+
tool,
|
|
2039
|
+
getReplyContent: () => parts.join("\n\n"),
|
|
2040
|
+
hasReplyContent: () => parts.some((part) => part.trim().length > 0)
|
|
2041
|
+
};
|
|
3339
2042
|
}
|
|
3340
2043
|
|
|
3341
|
-
// src/commands/connect/
|
|
3342
|
-
import {
|
|
3343
|
-
import { join as
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
}
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
2044
|
+
// src/commands/connect/task-pull.ts
|
|
2045
|
+
import { writeFileSync as writeFileSync10 } from "fs";
|
|
2046
|
+
import { join as join13 } from "path";
|
|
2047
|
+
import { stringify as yamlStringify } from "yaml";
|
|
2048
|
+
|
|
2049
|
+
// src/rules-sync.ts
|
|
2050
|
+
import { basename as basename2, extname, join as join12 } from "path";
|
|
2051
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
2052
|
+
var MANIFEST_FILE2 = ".rules-sync-manifest.json";
|
|
2053
|
+
function ruleLocalFileName(ruleName) {
|
|
2054
|
+
const trimmed = ruleName.trim();
|
|
2055
|
+
if (!trimmed) return "rule.md";
|
|
2056
|
+
const sanitized = trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
2057
|
+
if (extname(sanitized).toLowerCase() === ".md") return sanitized;
|
|
2058
|
+
return `${sanitized}.md`;
|
|
2059
|
+
}
|
|
2060
|
+
function loadManifest(rulesDir) {
|
|
2061
|
+
const path10 = join12(rulesDir, MANIFEST_FILE2);
|
|
2062
|
+
if (!existsSync8(toFsPath(path10))) {
|
|
2063
|
+
return { version: 1, rules: {} };
|
|
3352
2064
|
}
|
|
3353
2065
|
try {
|
|
3354
2066
|
const parsed = JSON.parse(
|
|
3355
|
-
|
|
2067
|
+
readFileSync9(toFsPath(path10), "utf8")
|
|
3356
2068
|
);
|
|
3357
|
-
if (parsed?.version === 1 && typeof parsed.
|
|
2069
|
+
if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
|
|
3358
2070
|
return parsed;
|
|
3359
2071
|
}
|
|
3360
2072
|
} catch {
|
|
3361
2073
|
}
|
|
3362
|
-
return
|
|
2074
|
+
return { version: 1, rules: {} };
|
|
3363
2075
|
}
|
|
3364
|
-
function
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
toFsPath(manifestPath(apmDir)),
|
|
2076
|
+
function saveManifest(rulesDir, manifest) {
|
|
2077
|
+
writeFileSync9(
|
|
2078
|
+
toFsPath(join12(rulesDir, MANIFEST_FILE2)),
|
|
3368
2079
|
`${JSON.stringify(manifest, null, 2)}
|
|
3369
2080
|
`,
|
|
3370
2081
|
"utf8"
|
|
3371
2082
|
);
|
|
3372
2083
|
}
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
const cached = syncedInSession.get(workdir);
|
|
3376
|
-
if (cached === currentVersion) {
|
|
3377
|
-
return false;
|
|
3378
|
-
}
|
|
3379
|
-
const stored = loadManifest3(workspaceApmDir(workdir));
|
|
3380
|
-
if (stored?.cliVersion === currentVersion) {
|
|
3381
|
-
syncedInSession.set(workdir, currentVersion);
|
|
3382
|
-
return false;
|
|
3383
|
-
}
|
|
3384
|
-
return true;
|
|
2084
|
+
function isBaseRuleFileName(fileName) {
|
|
2085
|
+
return listBaseRuleFileNames().includes(basename2(fileName));
|
|
3385
2086
|
}
|
|
3386
|
-
function
|
|
3387
|
-
|
|
3388
|
-
|
|
2087
|
+
function isRuleUpToDate(entry, rule, dest) {
|
|
2088
|
+
if (!entry || !existsSync8(toFsPath(dest))) return false;
|
|
2089
|
+
if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
|
|
2090
|
+
const updatedAt = rule.updatedAt ?? "";
|
|
2091
|
+
if (entry.updatedAt !== updatedAt) return false;
|
|
2092
|
+
const localContent = readFileSync9(toFsPath(dest), "utf8");
|
|
2093
|
+
return localContent === (rule.content ?? "");
|
|
3389
2094
|
}
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
}
|
|
3401
|
-
function markBranchDone(sessionId, workdir) {
|
|
3402
|
-
lastBranchKey = sessionWorkdirKey(sessionId, workdir);
|
|
3403
|
-
}
|
|
3404
|
-
function shouldRunPull(sessionId, workdir) {
|
|
3405
|
-
const key = sessionWorkdirKey(sessionId, workdir);
|
|
3406
|
-
const last = lastPullAtByKey.get(key);
|
|
3407
|
-
if (last == null) {
|
|
3408
|
-
return true;
|
|
2095
|
+
async function syncPlatformRules(cfg, workdirPath, apmRoot) {
|
|
2096
|
+
const api = createApmApiClient(cfg);
|
|
2097
|
+
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
2098
|
+
const repositoryId = baseline.repositoryId;
|
|
2099
|
+
const rulesDir = join12(apmRoot ?? workspaceApmDir(workdirPath), "rules");
|
|
2100
|
+
await ensureDirExists(rulesDir);
|
|
2101
|
+
if (!repositoryId) {
|
|
2102
|
+
console.log(
|
|
2103
|
+
`[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`
|
|
2104
|
+
);
|
|
2105
|
+
return { written: [], skipped: [], removed: [], repositoryId: null };
|
|
3409
2106
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
if (
|
|
3423
|
-
|
|
3424
|
-
|
|
2107
|
+
const { list } = await api.cli.listRules({ repositoryId });
|
|
2108
|
+
const manifest = loadManifest(rulesDir);
|
|
2109
|
+
const nextManifest = { version: 1, rules: {} };
|
|
2110
|
+
const remoteIds = /* @__PURE__ */ new Set();
|
|
2111
|
+
const written = [];
|
|
2112
|
+
const skipped = [];
|
|
2113
|
+
for (const rule of list) {
|
|
2114
|
+
remoteIds.add(rule.id);
|
|
2115
|
+
const fileName = ruleLocalFileName(rule.name);
|
|
2116
|
+
const dest = join12(rulesDir, fileName);
|
|
2117
|
+
const entry = manifest.rules[rule.id];
|
|
2118
|
+
const updatedAt = rule.updatedAt ?? "";
|
|
2119
|
+
if (isRuleUpToDate(entry, rule, dest)) {
|
|
2120
|
+
nextManifest.rules[rule.id] = entry;
|
|
2121
|
+
skipped.push(fileName);
|
|
2122
|
+
console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
|
|
2123
|
+
continue;
|
|
3425
2124
|
}
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
const
|
|
3436
|
-
if (
|
|
3437
|
-
|
|
2125
|
+
writeFileSync9(toFsPath(dest), rule.content ?? "", "utf8");
|
|
2126
|
+
nextManifest.rules[rule.id] = { fileName, updatedAt };
|
|
2127
|
+
written.push(fileName);
|
|
2128
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
|
|
2129
|
+
}
|
|
2130
|
+
const removed = [];
|
|
2131
|
+
for (const [ruleId, entry] of Object.entries(manifest.rules)) {
|
|
2132
|
+
if (remoteIds.has(ruleId)) continue;
|
|
2133
|
+
if (isBaseRuleFileName(entry.fileName)) continue;
|
|
2134
|
+
const dest = join12(rulesDir, entry.fileName);
|
|
2135
|
+
if (existsSync8(toFsPath(dest))) {
|
|
2136
|
+
rmSync3(toFsPath(dest), { force: true });
|
|
3438
2137
|
}
|
|
3439
|
-
|
|
3440
|
-
|
|
2138
|
+
removed.push(entry.fileName);
|
|
2139
|
+
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
|
|
2140
|
+
}
|
|
2141
|
+
saveManifest(rulesDir, nextManifest);
|
|
2142
|
+
return { written, skipped, removed, repositoryId };
|
|
3441
2143
|
}
|
|
3442
2144
|
|
|
3443
|
-
// src/commands/connect.ts
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
2145
|
+
// src/commands/connect/task-pull.ts
|
|
2146
|
+
async function runTaskPull(cfg, detail) {
|
|
2147
|
+
const { taskId, workdir } = detail;
|
|
2148
|
+
assertWorkspaceApmDirExists(workdir);
|
|
2149
|
+
const apmRoot = workspaceApmDir(workdir);
|
|
2150
|
+
const dir = taskDir(taskId, apmRoot, workdir);
|
|
2151
|
+
const docsDir = taskDocsDir(taskId, apmRoot, workdir);
|
|
2152
|
+
await ensureDirExists(docsDir);
|
|
2153
|
+
writeFileSync10(taskRulePath(taskId, apmRoot, workdir), "", "utf8");
|
|
2154
|
+
writeFileSync10(
|
|
2155
|
+
taskTaskPath(taskId, apmRoot, workdir),
|
|
2156
|
+
detail.task.description ?? "",
|
|
2157
|
+
"utf8"
|
|
2158
|
+
);
|
|
2159
|
+
for (const doc of detail.documents) {
|
|
2160
|
+
const fileName = documentLocalFileName(doc.name);
|
|
2161
|
+
writeFileSync10(join13(docsDir, fileName), doc.content ?? "", "utf8");
|
|
2162
|
+
}
|
|
2163
|
+
const members = detail.studio?.members ?? [];
|
|
2164
|
+
const taskYaml = yamlStringify(
|
|
2165
|
+
{
|
|
2166
|
+
name: detail.studio?.title ?? detail.task.title,
|
|
2167
|
+
phase: detail.studio?.phase ?? null,
|
|
2168
|
+
task: "./TASK.md",
|
|
2169
|
+
members: members.map((member) => ({
|
|
2170
|
+
name: member.displayName,
|
|
2171
|
+
oxcAgent: member.oxcAgent?.name ?? "",
|
|
2172
|
+
description: member.oxcAgent?.description ?? "",
|
|
2173
|
+
isLead: member.isLead
|
|
2174
|
+
})),
|
|
2175
|
+
attachments: detail.attachments.map((item) => ({ name: item.name }))
|
|
2176
|
+
},
|
|
2177
|
+
{ lineWidth: 0 }
|
|
2178
|
+
);
|
|
2179
|
+
writeFileSync10(
|
|
2180
|
+
taskYamlPath(taskId, apmRoot, workdir),
|
|
2181
|
+
taskYaml.endsWith("\n") ? taskYaml : `${taskYaml}
|
|
2182
|
+
`,
|
|
2183
|
+
"utf8"
|
|
2184
|
+
);
|
|
2185
|
+
await syncPlatformRules(cfg, workdir, apmRoot);
|
|
2186
|
+
await syncRemoteDeploymentConfig(workdir, apmRoot);
|
|
2187
|
+
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
2188
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u4EFB\u52A1\u5DE5\u4F5C\u533A: ${toFsPath(dir)}`);
|
|
2189
|
+
return dir;
|
|
3454
2190
|
}
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
2191
|
+
|
|
2192
|
+
// src/commands/connect/mail-processor.ts
|
|
2193
|
+
function resolveCursorAgentMode(phase) {
|
|
2194
|
+
return phase === "PLANNING" ? "plan" : "agent";
|
|
3458
2195
|
}
|
|
3459
|
-
async function
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
}
|
|
3475
|
-
};
|
|
2196
|
+
async function processMail(mail, options) {
|
|
2197
|
+
const api = createApmApiClient(options.cfg);
|
|
2198
|
+
console.log(`[apm] \u5F00\u59CB\u5904\u7406\u4FE1\u4EF6 id=${mail.id}`);
|
|
2199
|
+
if (hasMailReplied(mail.id)) {
|
|
2200
|
+
console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u5DF2\u56DE\u590D\u8FC7\uFF0C\u8DF3\u8FC7`);
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
const cursorApiKey = options.getCursorApiKey().trim();
|
|
2204
|
+
if (!cursorApiKey) {
|
|
2205
|
+
options.onFatalError(
|
|
2206
|
+
"[apm] \u5BA2\u6237\u673A\u672A\u914D\u7F6E Cursor API Key\uFF0C\u65E0\u6CD5\u5904\u7406\u4FE1\u4EF6\uFF0C\u65AD\u5F00\u8FDE\u63A5"
|
|
2207
|
+
);
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
let detail;
|
|
3476
2211
|
try {
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
2212
|
+
detail = await api.cli.getMailboxMessageDetail({ id: mail.id });
|
|
2213
|
+
} catch (err) {
|
|
2214
|
+
console.error(
|
|
2215
|
+
`[apm] \u83B7\u53D6\u4FE1\u4EF6\u8BE6\u60C5\u5931\u8D25 id=${mail.id}:`,
|
|
2216
|
+
err instanceof Error ? err.message : err
|
|
3481
2217
|
);
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
await
|
|
3486
|
-
|
|
3487
|
-
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
try {
|
|
2221
|
+
await runTaskPull(options.cfg, detail);
|
|
2222
|
+
} catch (err) {
|
|
2223
|
+
console.error(
|
|
2224
|
+
`[apm] \u540C\u6B65\u4EFB\u52A1\u5DE5\u4F5C\u533A\u5931\u8D25 taskId=${detail.taskId}:`,
|
|
2225
|
+
err instanceof Error ? err.message : err
|
|
3488
2226
|
);
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
}
|
|
3496
|
-
let pullRan = false;
|
|
3497
|
-
if (shouldRunPull(msg.sessionId, workdir)) {
|
|
3498
|
-
if (signal.aborted) return;
|
|
3499
|
-
await runStep("pull", () => runPull(msg.sessionId, workdir));
|
|
3500
|
-
markPullDone(msg.sessionId, workdir);
|
|
3501
|
-
pullRan = true;
|
|
3502
|
-
} else {
|
|
3503
|
-
console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
|
|
3504
|
-
}
|
|
3505
|
-
if (pullRan) {
|
|
3506
|
-
if (signal.aborted) return;
|
|
3507
|
-
await runStep(
|
|
3508
|
-
"commit-pull",
|
|
3509
|
-
() => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
|
|
3510
|
-
);
|
|
3511
|
-
} else {
|
|
3512
|
-
console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
|
|
3513
|
-
}
|
|
3514
|
-
const cliVersion = readCliVersion();
|
|
3515
|
-
if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
|
|
3516
|
-
if (signal.aborted) return;
|
|
3517
|
-
console.log(
|
|
3518
|
-
`[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
|
|
3519
|
-
);
|
|
3520
|
-
await runStep("update-skills", async () => {
|
|
3521
|
-
await syncWorkspaceSkills(cfg, workdir);
|
|
3522
|
-
markSkillsSyncedForCliVersion(workdir, cliVersion);
|
|
3523
|
-
});
|
|
3524
|
-
} else {
|
|
3525
|
-
console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
|
|
3526
|
-
}
|
|
3527
|
-
if (signal.aborted) return;
|
|
3528
|
-
if (!pullRan) {
|
|
3529
|
-
await runStep(
|
|
3530
|
-
"sync-project-documents-pull",
|
|
3531
|
-
() => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
|
|
3532
|
-
);
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
if (detail.status !== "PENDING") {
|
|
2230
|
+
console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u72B6\u6001\u4E3A ${detail.status}\uFF0C\u8DF3\u8FC7\u5904\u7406`);
|
|
2231
|
+
if (detail.status === "SUCCEEDED" || detail.status === "FAILED") {
|
|
2232
|
+
markMailReplied(mail.id);
|
|
3533
2233
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
{
|
|
3539
|
-
messageId: msg.messageId,
|
|
3540
|
-
sessionId: msg.sessionId,
|
|
3541
|
-
prompt: msg.content,
|
|
3542
|
-
model: msg.model,
|
|
3543
|
-
apiKey: msg.apiKey,
|
|
3544
|
-
workdir,
|
|
3545
|
-
user: msg.user
|
|
3546
|
-
},
|
|
3547
|
-
{ signal }
|
|
3548
|
-
)
|
|
3549
|
-
);
|
|
3550
|
-
await runStep(
|
|
3551
|
-
"sync-documents",
|
|
3552
|
-
() => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
|
|
3553
|
-
);
|
|
3554
|
-
await runStep(
|
|
3555
|
-
"sync-project-documents",
|
|
3556
|
-
() => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
|
|
3557
|
-
);
|
|
3558
|
-
await runStep(
|
|
3559
|
-
"commit-files",
|
|
3560
|
-
() => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
|
|
3561
|
-
);
|
|
3562
|
-
await runStep(
|
|
3563
|
-
"status-success",
|
|
3564
|
-
() => updateMessageStatus(cfg, messageId, "SUCCESS")
|
|
3565
|
-
);
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
try {
|
|
2237
|
+
await api.cli.claimMailboxMessage({ id: mail.id });
|
|
3566
2238
|
} catch (err) {
|
|
3567
|
-
if (isUserCancelled(ctx)) {
|
|
3568
|
-
console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
|
|
3569
|
-
return;
|
|
3570
|
-
}
|
|
3571
2239
|
console.error(
|
|
3572
|
-
|
|
3573
|
-
err instanceof Error ? err.message :
|
|
2240
|
+
`[apm] \u8BA4\u9886\u4FE1\u4EF6\u5931\u8D25 id=${mail.id}:`,
|
|
2241
|
+
err instanceof Error ? err.message : err
|
|
2242
|
+
);
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
removeMailById(mail.id);
|
|
2246
|
+
const {
|
|
2247
|
+
tool: replyTool,
|
|
2248
|
+
getReplyContent,
|
|
2249
|
+
hasReplyContent
|
|
2250
|
+
} = createMailReplyDraft();
|
|
2251
|
+
try {
|
|
2252
|
+
const cursorMode = resolveCursorAgentMode(detail.studio?.phase);
|
|
2253
|
+
const result = await runCursorAgent(
|
|
2254
|
+
options.cfg,
|
|
2255
|
+
{
|
|
2256
|
+
mailId: detail.id,
|
|
2257
|
+
taskId: detail.taskId,
|
|
2258
|
+
prompt: detail.content,
|
|
2259
|
+
model: detail.recipient.model?.trim() || "default",
|
|
2260
|
+
apiKey: cursorApiKey,
|
|
2261
|
+
workdir: detail.workdir,
|
|
2262
|
+
user: detail.recipient.displayName,
|
|
2263
|
+
mode: cursorMode
|
|
2264
|
+
},
|
|
2265
|
+
{
|
|
2266
|
+
signal: options.signal,
|
|
2267
|
+
customTools: { append_mail_reply: replyTool }
|
|
2268
|
+
}
|
|
3574
2269
|
);
|
|
3575
|
-
|
|
3576
|
-
|
|
2270
|
+
const replyContent = (hasReplyContent() ? getReplyContent() : result.assistantText).trim();
|
|
2271
|
+
if (!replyContent) {
|
|
2272
|
+
throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
|
|
2273
|
+
}
|
|
2274
|
+
if (cursorMode === "plan" && result.createPlanContent) {
|
|
2275
|
+
saveMemberPlanDocument({
|
|
2276
|
+
workdir: detail.workdir,
|
|
2277
|
+
taskId: detail.taskId,
|
|
2278
|
+
memberDisplayName: detail.recipient.displayName,
|
|
2279
|
+
content: result.createPlanContent
|
|
2280
|
+
});
|
|
3577
2281
|
}
|
|
2282
|
+
await api.cli.completeMailboxMessage({
|
|
2283
|
+
id: detail.id,
|
|
2284
|
+
status: "SUCCEEDED",
|
|
2285
|
+
replyContent
|
|
2286
|
+
});
|
|
2287
|
+
markMailReplied(detail.id);
|
|
2288
|
+
await syncTaskDocuments(options.cfg, detail.taskId, detail.workdir, {
|
|
2289
|
+
api,
|
|
2290
|
+
remoteDocuments: detail.documents
|
|
2291
|
+
});
|
|
2292
|
+
console.log(`[apm] \u4FE1\u4EF6\u5904\u7406\u5B8C\u6210 id=${mail.id}`);
|
|
2293
|
+
} catch (err) {
|
|
2294
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2295
|
+
console.error(`[apm] \u4FE1\u4EF6\u5904\u7406\u5931\u8D25 id=${mail.id}: ${message}`);
|
|
3578
2296
|
try {
|
|
3579
|
-
await
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
);
|
|
3584
|
-
|
|
3585
|
-
} catch (statusErr) {
|
|
2297
|
+
await api.cli.completeMailboxMessage({
|
|
2298
|
+
id: detail.id,
|
|
2299
|
+
status: "FAILED",
|
|
2300
|
+
error: message
|
|
2301
|
+
});
|
|
2302
|
+
} catch (completeErr) {
|
|
3586
2303
|
console.error(
|
|
3587
|
-
|
|
3588
|
-
|
|
2304
|
+
`[apm] \u6807\u8BB0\u4FE1\u4EF6\u5931\u8D25\u72B6\u6001\u65F6\u51FA\u9519 id=${mail.id}:`,
|
|
2305
|
+
completeErr instanceof Error ? completeErr.message : completeErr
|
|
3589
2306
|
);
|
|
3590
2307
|
}
|
|
2308
|
+
markMailReplied(detail.id);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
function createMailProcessor(options) {
|
|
2312
|
+
let running = false;
|
|
2313
|
+
const pump = () => {
|
|
2314
|
+
if (running || options.signal.aborted) {
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
2317
|
+
running = true;
|
|
2318
|
+
void (async () => {
|
|
2319
|
+
try {
|
|
2320
|
+
while (!options.signal.aborted) {
|
|
2321
|
+
const mail = dequeueNextMail();
|
|
2322
|
+
if (!mail) {
|
|
2323
|
+
break;
|
|
2324
|
+
}
|
|
2325
|
+
await processMail(mail, options);
|
|
2326
|
+
}
|
|
2327
|
+
} finally {
|
|
2328
|
+
running = false;
|
|
2329
|
+
if (hasPendingMail() && !options.signal.aborted) {
|
|
2330
|
+
pump();
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
})();
|
|
2334
|
+
};
|
|
2335
|
+
const ingest = (mail) => {
|
|
2336
|
+
if (enqueueReceivedMail(mail)) {
|
|
2337
|
+
pump();
|
|
2338
|
+
}
|
|
2339
|
+
};
|
|
2340
|
+
return {
|
|
2341
|
+
receive(msg) {
|
|
2342
|
+
ingest({
|
|
2343
|
+
id: msg.id,
|
|
2344
|
+
taskId: msg.taskId,
|
|
2345
|
+
createdAt: msg.createdAt
|
|
2346
|
+
});
|
|
2347
|
+
},
|
|
2348
|
+
syncPendingMails(mails) {
|
|
2349
|
+
let added = 0;
|
|
2350
|
+
for (const mail of mails) {
|
|
2351
|
+
if (enqueueReceivedMail(mail)) {
|
|
2352
|
+
added++;
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
if (added > 0) {
|
|
2356
|
+
console.log(`[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 ${added} \u5C01`);
|
|
2357
|
+
} else if (mails.length > 0) {
|
|
2358
|
+
console.log(
|
|
2359
|
+
`[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 0 \u5C01\uFF08${mails.length} \u5C01\u5DF2\u5728\u961F\u5217\u4E2D\uFF09`
|
|
2360
|
+
);
|
|
2361
|
+
} else {
|
|
2362
|
+
console.log("[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 0 \u5C01");
|
|
2363
|
+
}
|
|
2364
|
+
pump();
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// src/commands/connect/mail-sync.ts
|
|
2370
|
+
function parsePendingMail(value) {
|
|
2371
|
+
if (typeof value !== "object" || value === null) {
|
|
2372
|
+
return null;
|
|
2373
|
+
}
|
|
2374
|
+
const row = value;
|
|
2375
|
+
if (typeof row.id !== "string" || typeof row.taskId !== "string") {
|
|
2376
|
+
return null;
|
|
2377
|
+
}
|
|
2378
|
+
const createdAt = typeof row.createdAt === "string" ? row.createdAt : row.createdAt instanceof Date ? row.createdAt.toISOString() : "";
|
|
2379
|
+
if (!createdAt) {
|
|
2380
|
+
return null;
|
|
2381
|
+
}
|
|
2382
|
+
return {
|
|
2383
|
+
id: row.id,
|
|
2384
|
+
taskId: row.taskId,
|
|
2385
|
+
createdAt
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
async function fetchPendingMails(cfg) {
|
|
2389
|
+
const api = createApmApiClient(cfg);
|
|
2390
|
+
const list = await api.cli.listPendingMailboxMessages({});
|
|
2391
|
+
if (!Array.isArray(list)) {
|
|
2392
|
+
return [];
|
|
3591
2393
|
}
|
|
2394
|
+
return list.flatMap((item) => {
|
|
2395
|
+
const mail = parsePendingMail(item);
|
|
2396
|
+
return mail ? [mail] : [];
|
|
2397
|
+
});
|
|
3592
2398
|
}
|
|
2399
|
+
|
|
2400
|
+
// src/commands/connect.ts
|
|
2401
|
+
var HEARTBEAT_MS = 3e4;
|
|
3593
2402
|
function startHeartbeat(ws, clientMachineId) {
|
|
3594
2403
|
const send = () => {
|
|
3595
2404
|
if (ws.readyState === WebSocket.OPEN) {
|
|
@@ -3639,11 +2448,17 @@ async function runConnect(options) {
|
|
|
3639
2448
|
let stopHeartbeat;
|
|
3640
2449
|
let shuttingDown = false;
|
|
3641
2450
|
const shutdownAbort = new AbortController();
|
|
3642
|
-
|
|
3643
|
-
const
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
2451
|
+
let cursorApiKey = "";
|
|
2452
|
+
const mailProcessor = createMailProcessor({
|
|
2453
|
+
cfg,
|
|
2454
|
+
signal: shutdownAbort.signal,
|
|
2455
|
+
getCursorApiKey: () => cursorApiKey,
|
|
2456
|
+
onFatalError: (message) => {
|
|
2457
|
+
console.error(message);
|
|
2458
|
+
shutdown(1);
|
|
2459
|
+
}
|
|
2460
|
+
});
|
|
2461
|
+
const shutdown = (code = 0) => {
|
|
3647
2462
|
if (shuttingDown) return;
|
|
3648
2463
|
shuttingDown = true;
|
|
3649
2464
|
logAbortSignalStats(
|
|
@@ -3656,19 +2471,34 @@ async function runConnect(options) {
|
|
|
3656
2471
|
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
3657
2472
|
ws.terminate();
|
|
3658
2473
|
}
|
|
3659
|
-
try {
|
|
3660
|
-
await Promise.race([
|
|
3661
|
-
Promise.all(activeTasks),
|
|
3662
|
-
new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
|
|
3663
|
-
]);
|
|
3664
|
-
} catch {
|
|
3665
|
-
}
|
|
3666
2474
|
resolve5();
|
|
3667
2475
|
process.exit(code);
|
|
3668
2476
|
};
|
|
3669
2477
|
ws.on("open", () => {
|
|
3670
2478
|
console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
|
|
3671
2479
|
stopHeartbeat = startHeartbeat(ws, clientMachineId);
|
|
2480
|
+
void (async () => {
|
|
2481
|
+
try {
|
|
2482
|
+
const api = createApmApiClient(cfg);
|
|
2483
|
+
const me = await api.cli.me({});
|
|
2484
|
+
cursorApiKey = me.cursorApiKey?.trim() ?? "";
|
|
2485
|
+
if (!cursorApiKey) {
|
|
2486
|
+
console.error(
|
|
2487
|
+
"[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"
|
|
2488
|
+
);
|
|
2489
|
+
shutdown(1);
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
const mails = await fetchPendingMails(cfg);
|
|
2493
|
+
mailProcessor.syncPendingMails(mails);
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
console.error(
|
|
2496
|
+
"[apm] \u8FDE\u63A5\u540E\u521D\u59CB\u5316\u5931\u8D25:",
|
|
2497
|
+
err instanceof Error ? err.message : err
|
|
2498
|
+
);
|
|
2499
|
+
shutdown(1);
|
|
2500
|
+
}
|
|
2501
|
+
})();
|
|
3672
2502
|
});
|
|
3673
2503
|
ws.on("message", (data) => {
|
|
3674
2504
|
if (shuttingDown) return;
|
|
@@ -3686,93 +2516,19 @@ async function runConnect(options) {
|
|
|
3686
2516
|
console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
|
|
3687
2517
|
return;
|
|
3688
2518
|
}
|
|
3689
|
-
if (validated.data.type === "cancel") {
|
|
3690
|
-
const { messageId } = validated.data;
|
|
3691
|
-
pendingCancels.add(messageId);
|
|
3692
|
-
activeRuns.get(messageId)?.abort();
|
|
3693
|
-
return;
|
|
3694
|
-
}
|
|
3695
2519
|
if (validated.data.type === "deploy") {
|
|
3696
|
-
|
|
3697
|
-
const perDeployController = new AbortController();
|
|
3698
|
-
const signal2 = AbortSignal.any([
|
|
3699
|
-
shutdownAbort.signal,
|
|
3700
|
-
perDeployController.signal
|
|
3701
|
-
]);
|
|
3702
|
-
const task2 = (async () => {
|
|
3703
|
-
await runSlots.acquire();
|
|
3704
|
-
try {
|
|
3705
|
-
await handleInboundDeploy(cfg, msg2, signal2);
|
|
3706
|
-
} finally {
|
|
3707
|
-
runSlots.release();
|
|
3708
|
-
}
|
|
3709
|
-
})();
|
|
3710
|
-
activeTasks.add(task2);
|
|
3711
|
-
void task2.finally(() => {
|
|
3712
|
-
activeTasks.delete(task2);
|
|
3713
|
-
});
|
|
3714
|
-
return;
|
|
3715
|
-
}
|
|
3716
|
-
if (validated.data.type === "coordinator_dispatch" || validated.data.type === "coordinator_dispatch_resume") {
|
|
3717
|
-
const msg2 = validated.data;
|
|
3718
|
-
const perDispatchController = new AbortController();
|
|
3719
|
-
const signal2 = AbortSignal.any([
|
|
3720
|
-
shutdownAbort.signal,
|
|
3721
|
-
perDispatchController.signal
|
|
3722
|
-
]);
|
|
3723
|
-
const task2 = (async () => {
|
|
3724
|
-
await runSlots.acquire();
|
|
3725
|
-
try {
|
|
3726
|
-
await handleInboundCoordinatorDispatch(cfg, msg2, signal2);
|
|
3727
|
-
} finally {
|
|
3728
|
-
runSlots.release();
|
|
3729
|
-
}
|
|
3730
|
-
})();
|
|
3731
|
-
activeTasks.add(task2);
|
|
3732
|
-
void task2.finally(() => {
|
|
3733
|
-
activeTasks.delete(task2);
|
|
3734
|
-
});
|
|
2520
|
+
void handleInboundDeploy(cfg, validated.data, shutdownAbort.signal);
|
|
3735
2521
|
return;
|
|
3736
2522
|
}
|
|
3737
|
-
if (validated.data.type
|
|
3738
|
-
|
|
3739
|
-
}
|
|
3740
|
-
const msg = validated.data;
|
|
3741
|
-
const perMessageController = new AbortController();
|
|
3742
|
-
activeRuns.set(msg.messageId, perMessageController);
|
|
3743
|
-
if (pendingCancels.has(msg.messageId)) {
|
|
3744
|
-
activeRuns.delete(msg.messageId);
|
|
3745
|
-
pendingCancels.delete(msg.messageId);
|
|
3746
|
-
return;
|
|
2523
|
+
if (validated.data.type === "received_mail") {
|
|
2524
|
+
mailProcessor.receive(validated.data);
|
|
3747
2525
|
}
|
|
3748
|
-
const signal = AbortSignal.any([
|
|
3749
|
-
shutdownAbort.signal,
|
|
3750
|
-
perMessageController.signal
|
|
3751
|
-
]);
|
|
3752
|
-
const ctx = {
|
|
3753
|
-
shutdownSignal: shutdownAbort.signal,
|
|
3754
|
-
perMessageSignal: perMessageController.signal
|
|
3755
|
-
};
|
|
3756
|
-
const task = (async () => {
|
|
3757
|
-
await runSlots.acquire();
|
|
3758
|
-
try {
|
|
3759
|
-
await handleInboundMessage(cfg, msg, signal, ctx);
|
|
3760
|
-
} finally {
|
|
3761
|
-
runSlots.release();
|
|
3762
|
-
activeRuns.delete(msg.messageId);
|
|
3763
|
-
pendingCancels.delete(msg.messageId);
|
|
3764
|
-
}
|
|
3765
|
-
})();
|
|
3766
|
-
activeTasks.add(task);
|
|
3767
|
-
void task.finally(() => {
|
|
3768
|
-
activeTasks.delete(task);
|
|
3769
|
-
});
|
|
3770
2526
|
});
|
|
3771
2527
|
ws.on("close", (code, reason) => {
|
|
3772
2528
|
console.log(
|
|
3773
2529
|
`[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
|
|
3774
2530
|
);
|
|
3775
|
-
|
|
2531
|
+
shutdown();
|
|
3776
2532
|
});
|
|
3777
2533
|
ws.on("error", (err) => {
|
|
3778
2534
|
console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
|
|
@@ -3780,55 +2536,31 @@ async function runConnect(options) {
|
|
|
3780
2536
|
});
|
|
3781
2537
|
process.on("SIGINT", () => {
|
|
3782
2538
|
console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
|
|
3783
|
-
|
|
2539
|
+
shutdown();
|
|
3784
2540
|
});
|
|
3785
2541
|
process.on("SIGTERM", () => {
|
|
3786
|
-
|
|
2542
|
+
shutdown();
|
|
3787
2543
|
});
|
|
3788
2544
|
});
|
|
3789
2545
|
}
|
|
3790
2546
|
|
|
3791
|
-
// src/commands/create-pr.ts
|
|
3792
|
-
async function runCreatePr(options) {
|
|
3793
|
-
const sessionId = options.sessionId.trim();
|
|
3794
|
-
if (!sessionId) {
|
|
3795
|
-
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
3796
|
-
process.exit(1);
|
|
3797
|
-
}
|
|
3798
|
-
const title = options.title.trim();
|
|
3799
|
-
if (!title) {
|
|
3800
|
-
console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
|
|
3801
|
-
process.exit(1);
|
|
3802
|
-
}
|
|
3803
|
-
const cfg = await ensureLoggedConfig();
|
|
3804
|
-
const api = createApmApiClient(cfg);
|
|
3805
|
-
const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
|
|
3806
|
-
const pr = await api.cli.createPullRequest({
|
|
3807
|
-
sessionId,
|
|
3808
|
-
workdir,
|
|
3809
|
-
title,
|
|
3810
|
-
content: options.content ?? ""
|
|
3811
|
-
});
|
|
3812
|
-
console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
|
|
3813
|
-
}
|
|
3814
|
-
|
|
3815
2547
|
// src/commands/deploy/backend.ts
|
|
3816
2548
|
import path5 from "node:path";
|
|
3817
2549
|
|
|
3818
2550
|
// src/commands/deploy/internal/apm-config.ts
|
|
3819
|
-
import { existsSync as
|
|
2551
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
3820
2552
|
import { resolve as resolve4 } from "node:path";
|
|
3821
2553
|
function loadApmConfig(options) {
|
|
3822
2554
|
const p = resolve4(
|
|
3823
2555
|
process.cwd(),
|
|
3824
2556
|
options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
|
|
3825
2557
|
);
|
|
3826
|
-
if (!
|
|
2558
|
+
if (!existsSync9(p)) {
|
|
3827
2559
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
3828
2560
|
process.exit(1);
|
|
3829
2561
|
}
|
|
3830
2562
|
try {
|
|
3831
|
-
const raw =
|
|
2563
|
+
const raw = readFileSync10(p, "utf8");
|
|
3832
2564
|
return JSON.parse(raw);
|
|
3833
2565
|
} catch (e) {
|
|
3834
2566
|
console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
|
|
@@ -3950,7 +2682,7 @@ import path4 from "node:path";
|
|
|
3950
2682
|
import Docker from "dockerode";
|
|
3951
2683
|
|
|
3952
2684
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
3953
|
-
import { existsSync as
|
|
2685
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
|
|
3954
2686
|
import path from "node:path";
|
|
3955
2687
|
function asOptionalTlsBuffer(value) {
|
|
3956
2688
|
if (typeof value !== "string") {
|
|
@@ -3962,8 +2694,8 @@ function asOptionalTlsBuffer(value) {
|
|
|
3962
2694
|
if (normalized === "") {
|
|
3963
2695
|
return void 0;
|
|
3964
2696
|
}
|
|
3965
|
-
if (
|
|
3966
|
-
return
|
|
2697
|
+
if (existsSync10(normalized)) {
|
|
2698
|
+
return readFileSync11(normalized);
|
|
3967
2699
|
}
|
|
3968
2700
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
3969
2701
|
if (looksLikePath) {
|
|
@@ -4173,7 +2905,7 @@ var DockerodeClient = class {
|
|
|
4173
2905
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
4174
2906
|
|
|
4175
2907
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
4176
|
-
import { existsSync as
|
|
2908
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
|
|
4177
2909
|
import path2 from "node:path";
|
|
4178
2910
|
function stripSurroundingQuotes(value) {
|
|
4179
2911
|
const t = value.trim();
|
|
@@ -4190,10 +2922,10 @@ function loadEnvFromFile(envFilePath) {
|
|
|
4190
2922
|
return {};
|
|
4191
2923
|
}
|
|
4192
2924
|
const targetPath = path2.resolve(envFilePath);
|
|
4193
|
-
if (!
|
|
2925
|
+
if (!existsSync11(targetPath) || !statSync4(targetPath).isFile()) {
|
|
4194
2926
|
return {};
|
|
4195
2927
|
}
|
|
4196
|
-
const raw =
|
|
2928
|
+
const raw = readFileSync12(targetPath, "utf-8");
|
|
4197
2929
|
const result = {};
|
|
4198
2930
|
for (const line of raw.split(/\r?\n/)) {
|
|
4199
2931
|
const normalized = line.trim();
|
|
@@ -4364,12 +3096,12 @@ function dockerPushImage(params, cwd) {
|
|
|
4364
3096
|
}
|
|
4365
3097
|
|
|
4366
3098
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
4367
|
-
import { existsSync as
|
|
3099
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
4368
3100
|
import path3 from "node:path";
|
|
4369
3101
|
function resolveDockerBuildPaths(cwd) {
|
|
4370
3102
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
4371
3103
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
4372
|
-
if (!
|
|
3104
|
+
if (!existsSync12(dockerfilePath)) {
|
|
4373
3105
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
4374
3106
|
}
|
|
4375
3107
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
|
@@ -4498,14 +3230,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
|
|
|
4498
3230
|
import path7 from "node:path";
|
|
4499
3231
|
|
|
4500
3232
|
// src/commands/deploy/internal/minio.ts
|
|
4501
|
-
import { statSync as
|
|
3233
|
+
import { statSync as statSync5 } from "node:fs";
|
|
4502
3234
|
import { readdir, readFile } from "node:fs/promises";
|
|
4503
3235
|
import path6 from "node:path";
|
|
4504
3236
|
import * as Minio from "minio";
|
|
4505
3237
|
var DEFAULT_MAX_FILE_SIZE_MB = 50;
|
|
4506
3238
|
async function isDirectoryPath(dir) {
|
|
4507
3239
|
try {
|
|
4508
|
-
const st =
|
|
3240
|
+
const st = statSync5(dir);
|
|
4509
3241
|
return st.isDirectory();
|
|
4510
3242
|
} catch {
|
|
4511
3243
|
return false;
|
|
@@ -4535,7 +3267,7 @@ async function collectFiles(root) {
|
|
|
4535
3267
|
if (e.isDirectory()) {
|
|
4536
3268
|
await walk(abs, rel);
|
|
4537
3269
|
} else if (e.isFile()) {
|
|
4538
|
-
const st =
|
|
3270
|
+
const st = statSync5(abs);
|
|
4539
3271
|
out.push({
|
|
4540
3272
|
absPath: abs,
|
|
4541
3273
|
relativePath: rel.replace(/\\/g, "/"),
|
|
@@ -4993,7 +3725,7 @@ function registerDeployCommands(program) {
|
|
|
4993
3725
|
function buildProgram() {
|
|
4994
3726
|
const program = new Command();
|
|
4995
3727
|
program.name("apm").description(
|
|
4996
|
-
`\u6BD4\u90BB\u661F\u56FE\u547D\u4EE4\u884C\uFF08\
|
|
3728
|
+
`\u6BD4\u90BB\u661F\u56FE\u547D\u4EE4\u884C\uFF08\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
|
|
4997
3729
|
\u672A\u4F20 --server \u65F6\u4F18\u5148\u4F7F\u7528\u73AF\u5883\u53D8\u91CF AI_PM_SERVER\uFF0C\u5426\u5219\u9ED8\u8BA4 ${DEFAULT_BASE_URL}\u3002`
|
|
4998
3730
|
).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
|
|
4999
3731
|
program.command("login").description(
|
|
@@ -5016,63 +3748,11 @@ function buildProgram() {
|
|
|
5016
3748
|
).action(async () => {
|
|
5017
3749
|
await runUpdateSkills();
|
|
5018
3750
|
});
|
|
5019
|
-
program.command("sync-deploy-config").description(
|
|
5020
|
-
"\u4ECE\u5E73\u53F0\u62C9\u53D6\u90E8\u7F72\u914D\u7F6E\uFF0C\u8986\u76D6 .apm/apm.config.json \u4E0E .apm/deploy/README.md\uFF08\u9700\u5DF2 login \u4E14\u5DE5\u4F5C\u7A7A\u95F4\u5DF2\u767B\u8BB0\u5E76\u7ED1\u5B9A\u4ED3\u5E93\uFF09"
|
|
5021
|
-
).action(async () => {
|
|
5022
|
-
await runSyncDeployConfig();
|
|
5023
|
-
});
|
|
5024
|
-
program.command("sync-project-documents").description(
|
|
5025
|
-
"\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\uFF1A\u9ED8\u8BA4 pull \u5230 .apm/project/\uFF1B\u52A0 --push \u5C06\u672C\u5730\u53D8\u66F4\u63A8\u9001\u5230\u5E73\u53F0"
|
|
5026
|
-
).option("--push", "\u63A8\u9001\u672C\u5730 .apm/project/ \u5230\u5E73\u53F0").option("--pull", "\u4ECE\u5E73\u53F0\u62C9\u53D6\u5230 .apm/project/").action(async (opts) => {
|
|
5027
|
-
await runSyncProjectDocuments(opts);
|
|
5028
|
-
});
|
|
5029
|
-
program.command("pull").description(
|
|
5030
|
-
"\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF0C\u5E76\u540C\u6B65\u90E8\u7F72\u914D\u7F6E\u4E0E\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u5230 .apm/project/"
|
|
5031
|
-
).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
|
|
5032
|
-
await runPull(sessionId);
|
|
5033
|
-
});
|
|
5034
|
-
program.command("sync-document").description("\u5C06\u672C\u5730 Markdown \u8986\u76D6\u5F0F upsert \u5230\u5E73\u53F0\u4EFB\u52A1\u6587\u6863").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption(
|
|
5035
|
-
"--file <name>",
|
|
5036
|
-
"\u6587\u6863\u540D\u79F0\uFF08\u5982 PRD \u6216 PRD.md\uFF09\uFF0C\u8BFB\u53D6 .apm/sessions/<sessionId>/docs/ \u4E0B\u5BF9\u5E94\u6587\u4EF6"
|
|
5037
|
-
).action(async (sessionId, opts) => {
|
|
5038
|
-
await runSyncDocument(sessionId, { file: opts.file });
|
|
5039
|
-
});
|
|
5040
|
-
program.command("append-message").description("\u5411\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u5185\u5BB9\uFF08PUT /api/v1/cli/messages/content\uFF09").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption("--content <content>", "\u8981\u8FFD\u52A0\u7684\u6D88\u606F\u5185\u5BB9").action(async (opts) => {
|
|
5041
|
-
await runAppendMessage(opts);
|
|
5042
|
-
});
|
|
5043
|
-
program.command("update-message-status").description("\u66F4\u65B0\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u72B6\u6001").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption(
|
|
5044
|
-
"--status <status>",
|
|
5045
|
-
"CREATED | QUEUED | TYPING | SUCCESS | FAILED | CANCELLED"
|
|
5046
|
-
).action(async (opts) => {
|
|
5047
|
-
await runUpdateMessageStatus(opts);
|
|
5048
|
-
});
|
|
5049
3751
|
program.command("connect").description(
|
|
5050
|
-
"\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u4E0B\u884C
|
|
3752
|
+
"\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u90E8\u7F72\u4E0B\u884C\u901A\u77E5\uFF1B\u542F\u52A8\u524D\u81EA\u52A8 apm update \u5230\u6700\u65B0\u7248"
|
|
5051
3753
|
).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
|
|
5052
3754
|
await runConnect(opts);
|
|
5053
3755
|
});
|
|
5054
|
-
program.command("branch").description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
|
|
5055
|
-
"-m, --message <text>",
|
|
5056
|
-
"\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
|
|
5057
|
-
).action(async (sessionId, opts) => {
|
|
5058
|
-
await runBranch(sessionId, { message: opts.message });
|
|
5059
|
-
});
|
|
5060
|
-
program.command("clean-branches").description(
|
|
5061
|
-
"\u6E05\u7406\u672C\u5730\u4E0E\u8FDC\u7A0B feat/session-* \u5206\u652F\uFF1A\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D\uFF0C\u6216\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210\u65F6\u5220\u9664"
|
|
5062
|
-
).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (opts) => {
|
|
5063
|
-
await runCleanBranches({ dryRun: opts.dryRun });
|
|
5064
|
-
});
|
|
5065
|
-
program.command("create-pr").description(
|
|
5066
|
-
"\u4E3A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u7684\u4F1A\u8BDD\u7279\u6027\u5206\u652F\u521B\u5EFA PR\uFF08\u6807\u9898\u81EA\u52A8\u52A0 [AI] \u6807\u8BC6\uFF1B\u8FDC\u7A0B\u521B\u5EFA\u5931\u8D25\u65F6\u5E73\u53F0\u6570\u636E\u56DE\u6EDA\uFF09"
|
|
5067
|
-
).requiredOption("--session <sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--title <title>", "PR \u6807\u9898").option("--content <content>", "PR \u6B63\u6587\uFF08Markdown\uFF09", "").action(
|
|
5068
|
-
async (opts) => {
|
|
5069
|
-
await runCreatePr({
|
|
5070
|
-
sessionId: opts.session,
|
|
5071
|
-
title: opts.title,
|
|
5072
|
-
content: opts.content ?? ""
|
|
5073
|
-
});
|
|
5074
|
-
}
|
|
5075
|
-
);
|
|
5076
3756
|
registerDeployCommands(program);
|
|
5077
3757
|
return program;
|
|
5078
3758
|
}
|