ai-project-manage-cli 6.0.64 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -7
- package/dist/index.js +807 -2236
- 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" };
|
|
1174
|
+
function validateReceivedMailPush(o) {
|
|
1175
|
+
if (o.type !== "received_mail") {
|
|
1176
|
+
return { ok: false, reason: "\u671F\u671B received_mail" };
|
|
2224
1177
|
}
|
|
2225
|
-
if (!nonEmptyString(o.
|
|
2226
|
-
return {
|
|
2227
|
-
ok: false,
|
|
2228
|
-
reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchSessionId"
|
|
2229
|
-
};
|
|
2230
|
-
}
|
|
2231
|
-
if (!validateCoordinatorDispatchMode(o.mode)) {
|
|
2232
|
-
return { ok: false, reason: "coordinator_dispatch_resume.mode \u65E0\u6548" };
|
|
2233
|
-
}
|
|
2234
|
-
if (!nonEmptyString(o.content)) {
|
|
2235
|
-
return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 content" };
|
|
2236
|
-
}
|
|
2237
|
-
if (!nonEmptyString(o.resumeAgentId)) {
|
|
2238
|
-
return {
|
|
2239
|
-
ok: false,
|
|
2240
|
-
reason: "coordinator_dispatch_resume \u7F3A\u5C11 resumeAgentId"
|
|
2241
|
-
};
|
|
1178
|
+
if (!nonEmptyString(o.id)) {
|
|
1179
|
+
return { ok: false, reason: "received_mail \u7F3A\u5C11 id" };
|
|
2242
1180
|
}
|
|
2243
|
-
if (!nonEmptyString(o.
|
|
2244
|
-
return { ok: false, reason: "
|
|
1181
|
+
if (!nonEmptyString(o.taskId)) {
|
|
1182
|
+
return { ok: false, reason: "received_mail \u7F3A\u5C11 taskId" };
|
|
2245
1183
|
}
|
|
2246
|
-
if (!nonEmptyString(o.
|
|
2247
|
-
return { ok: false, reason: "
|
|
2248
|
-
}
|
|
2249
|
-
if (!nonEmptyString(o.workdir)) {
|
|
2250
|
-
return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 workdir" };
|
|
2251
|
-
}
|
|
2252
|
-
if (!nonEmptyString(o.user)) {
|
|
2253
|
-
return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 user" };
|
|
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,7 @@ import {
|
|
|
2470
1449
|
} from "@cursor/sdk";
|
|
2471
1450
|
import { setMaxListeners as setMaxListeners2 } from "node:events";
|
|
2472
1451
|
|
|
2473
|
-
// src/
|
|
2474
|
-
function formatPlanMarkdown(raw) {
|
|
2475
|
-
let text = raw.trim();
|
|
2476
|
-
if (!text) {
|
|
2477
|
-
return text;
|
|
2478
|
-
}
|
|
2479
|
-
if (text.startsWith("{") && text.endsWith("}")) {
|
|
2480
|
-
try {
|
|
2481
|
-
const parsed = JSON.parse(text);
|
|
2482
|
-
if (typeof parsed.plan === "string") {
|
|
2483
|
-
return formatPlanMarkdown(parsed.plan);
|
|
2484
|
-
}
|
|
2485
|
-
} catch {
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
if (!text.includes("\n") && text.includes("\\n")) {
|
|
2489
|
-
text = text.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
2490
|
-
}
|
|
2491
|
-
return text.replace(/\r\n/g, "\n").trimEnd() + "\n";
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
// src/session-utils.ts
|
|
1452
|
+
// src/event-session.ts
|
|
2495
1453
|
var EventSession = class {
|
|
2496
1454
|
events = [];
|
|
2497
1455
|
dirtyIndices = /* @__PURE__ */ new Set();
|
|
@@ -2538,10 +1496,10 @@ var EventSession = class {
|
|
|
2538
1496
|
if (latestEvent?.type === formatedEvent.type) {
|
|
2539
1497
|
switch (formatedEvent.type) {
|
|
2540
1498
|
case "assistant":
|
|
2541
|
-
latestEvent.content
|
|
1499
|
+
latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
|
|
2542
1500
|
break;
|
|
2543
1501
|
case "thinking":
|
|
2544
|
-
latestEvent.content
|
|
1502
|
+
latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
|
|
2545
1503
|
break;
|
|
2546
1504
|
case "task":
|
|
2547
1505
|
latestEvent.status = formatedEvent.status;
|
|
@@ -2612,72 +1570,46 @@ var EventSession = class {
|
|
|
2612
1570
|
this.dirtyIndices.delete(index);
|
|
2613
1571
|
}
|
|
2614
1572
|
}
|
|
2615
|
-
/** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
|
|
2616
1573
|
getAssistantText() {
|
|
2617
1574
|
return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
|
|
2618
1575
|
}
|
|
2619
|
-
/** plan 模式下 createPlan 工具 completed 时的 plan 字段(取最后一次) */
|
|
2620
|
-
getCreatePlanContent() {
|
|
2621
|
-
for (let i = this.events.length - 1; i >= 0; i--) {
|
|
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");
|
|
2638
|
-
}
|
|
2639
1576
|
};
|
|
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
1577
|
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
1578
|
+
// src/commands/connect/abort-signal-debug.ts
|
|
1579
|
+
import {
|
|
1580
|
+
getEventListeners,
|
|
1581
|
+
getMaxListeners,
|
|
1582
|
+
setMaxListeners
|
|
1583
|
+
} from "node:events";
|
|
1584
|
+
function isAbortSignalDebugEnabled() {
|
|
1585
|
+
const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
|
|
1586
|
+
return v === "1" || v === "true" || v === "yes";
|
|
1587
|
+
}
|
|
1588
|
+
function formatAbortSignalStats(signal, label) {
|
|
1589
|
+
if (!signal) {
|
|
1590
|
+
return `[apm:abort-debug] ${label}: (no signal)`;
|
|
2661
1591
|
}
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
1592
|
+
const listeners = getEventListeners(signal, "abort");
|
|
1593
|
+
const max = getMaxListeners(signal);
|
|
1594
|
+
return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
|
|
1595
|
+
}
|
|
1596
|
+
function logAbortSignalStats(signal, label) {
|
|
1597
|
+
if (!isAbortSignalDebugEnabled()) return;
|
|
1598
|
+
console.log(formatAbortSignalStats(signal, label));
|
|
2667
1599
|
}
|
|
2668
1600
|
|
|
2669
|
-
// src/commands/connect/agent-
|
|
2670
|
-
import { existsSync as
|
|
1601
|
+
// src/commands/connect/agent-task-registry.ts
|
|
1602
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
2671
1603
|
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
2672
|
-
function registryPath(workdir,
|
|
2673
|
-
return resolve3(workdir, ".apm", "
|
|
1604
|
+
function registryPath(workdir, taskId) {
|
|
1605
|
+
return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
|
|
2674
1606
|
}
|
|
2675
1607
|
function readRegistry(path10) {
|
|
2676
|
-
if (!
|
|
1608
|
+
if (!existsSync7(path10)) {
|
|
2677
1609
|
return {};
|
|
2678
1610
|
}
|
|
2679
1611
|
try {
|
|
2680
|
-
const parsed = JSON.parse(
|
|
1612
|
+
const parsed = JSON.parse(readFileSync8(path10, "utf8"));
|
|
2681
1613
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2682
1614
|
const result = {};
|
|
2683
1615
|
for (const [key, value] of Object.entries(
|
|
@@ -2695,21 +1627,20 @@ function readRegistry(path10) {
|
|
|
2695
1627
|
}
|
|
2696
1628
|
function writeRegistry(path10, registry) {
|
|
2697
1629
|
mkdirSync5(dirname4(path10), { recursive: true });
|
|
2698
|
-
|
|
1630
|
+
writeFileSync7(path10, `${JSON.stringify(registry, null, 2)}
|
|
2699
1631
|
`, "utf8");
|
|
2700
1632
|
}
|
|
2701
|
-
function
|
|
2702
|
-
|
|
2703
|
-
return registry[user];
|
|
1633
|
+
function loadTaskAgentId(workdir, taskId, user) {
|
|
1634
|
+
return readRegistry(registryPath(workdir, taskId))[user];
|
|
2704
1635
|
}
|
|
2705
|
-
function
|
|
2706
|
-
const path10 = registryPath(workdir,
|
|
1636
|
+
function saveTaskAgentId(workdir, taskId, user, agentId) {
|
|
1637
|
+
const path10 = registryPath(workdir, taskId);
|
|
2707
1638
|
const registry = readRegistry(path10);
|
|
2708
1639
|
registry[user] = agentId;
|
|
2709
1640
|
writeRegistry(path10, registry);
|
|
2710
1641
|
}
|
|
2711
|
-
function
|
|
2712
|
-
const path10 = registryPath(workdir,
|
|
1642
|
+
function clearTaskAgentId(workdir, taskId, user) {
|
|
1643
|
+
const path10 = registryPath(workdir, taskId);
|
|
2713
1644
|
const registry = readRegistry(path10);
|
|
2714
1645
|
if (!(user in registry)) {
|
|
2715
1646
|
return;
|
|
@@ -2718,9 +1649,9 @@ function clearSessionAgentId(workdir, sessionId, user) {
|
|
|
2718
1649
|
writeRegistry(path10, registry);
|
|
2719
1650
|
}
|
|
2720
1651
|
|
|
2721
|
-
// src/commands/connect/cursor-
|
|
2722
|
-
var
|
|
2723
|
-
function
|
|
1652
|
+
// src/commands/connect/cursor-log.ts
|
|
1653
|
+
var CURSOR_LOG_SYNC_INTERVAL_MS = 2e3;
|
|
1654
|
+
function createThrottledCursorLogSync(cfg, ctx, onError) {
|
|
2724
1655
|
let lastRunAt = 0;
|
|
2725
1656
|
let timer;
|
|
2726
1657
|
let latestSession;
|
|
@@ -2732,7 +1663,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2732
1663
|
}
|
|
2733
1664
|
lastRunAt = Date.now();
|
|
2734
1665
|
try {
|
|
2735
|
-
await
|
|
1666
|
+
await syncCursorLog(cfg, ctx, events);
|
|
2736
1667
|
session.clearDirty(events.map((event) => event.index));
|
|
2737
1668
|
} catch (err) {
|
|
2738
1669
|
onError(err);
|
|
@@ -2751,7 +1682,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2751
1682
|
latestSession = session;
|
|
2752
1683
|
const now = Date.now();
|
|
2753
1684
|
const elapsed = now - lastRunAt;
|
|
2754
|
-
if (elapsed >=
|
|
1685
|
+
if (elapsed >= CURSOR_LOG_SYNC_INTERVAL_MS) {
|
|
2755
1686
|
if (timer) {
|
|
2756
1687
|
clearTimeout(timer);
|
|
2757
1688
|
timer = void 0;
|
|
@@ -2765,7 +1696,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2765
1696
|
timer = setTimeout(() => {
|
|
2766
1697
|
timer = void 0;
|
|
2767
1698
|
enqueueSync(latestSession);
|
|
2768
|
-
},
|
|
1699
|
+
}, CURSOR_LOG_SYNC_INTERVAL_MS - elapsed);
|
|
2769
1700
|
},
|
|
2770
1701
|
async flush(session) {
|
|
2771
1702
|
latestSession = session;
|
|
@@ -2778,336 +1709,51 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2778
1709
|
}
|
|
2779
1710
|
};
|
|
2780
1711
|
}
|
|
2781
|
-
async function
|
|
1712
|
+
async function syncCursorLog(cfg, ctx, events) {
|
|
2782
1713
|
const agentId = ctx.agentId.trim();
|
|
2783
1714
|
if (!agentId || events.length === 0) {
|
|
2784
1715
|
return;
|
|
2785
1716
|
}
|
|
2786
1717
|
const api = createApmApiClient(cfg);
|
|
2787
|
-
await api.cli.
|
|
2788
|
-
|
|
2789
|
-
|
|
1718
|
+
await api.cli.upsertCursorLog({
|
|
1719
|
+
taskId: ctx.taskId,
|
|
1720
|
+
mailboxMessageId: ctx.mailboxMessageId,
|
|
2790
1721
|
agentId,
|
|
2791
1722
|
events
|
|
2792
1723
|
});
|
|
2793
1724
|
}
|
|
2794
1725
|
|
|
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
1726
|
// src/commands/connect/cursor-agent.ts
|
|
3064
1727
|
setMaxListeners2(50);
|
|
3065
|
-
installAbortSignalDebug();
|
|
3066
1728
|
var noopRemoteLogSync = {
|
|
3067
1729
|
schedule(_session) {
|
|
3068
1730
|
},
|
|
3069
1731
|
async flush(_session) {
|
|
3070
1732
|
}
|
|
3071
1733
|
};
|
|
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")}`;
|
|
1734
|
+
function collectAssistantText(events) {
|
|
1735
|
+
return events.flatMap((event) => {
|
|
1736
|
+
if (event.type !== "assistant") return [];
|
|
1737
|
+
const text = "text" in event ? String(event.text ?? "") : "";
|
|
1738
|
+
return text ? [text] : [];
|
|
1739
|
+
}).join("");
|
|
3089
1740
|
}
|
|
3090
1741
|
async function obtainAgent(ctx) {
|
|
3091
1742
|
const agentOptions = {
|
|
3092
1743
|
apiKey: ctx.apiKey,
|
|
3093
1744
|
model: { id: ctx.model || "default" },
|
|
3094
1745
|
local: {
|
|
3095
|
-
cwd: ctx.
|
|
1746
|
+
cwd: ctx.workdir,
|
|
3096
1747
|
...ctx.customTools ? { customTools: ctx.customTools } : {}
|
|
3097
|
-
}
|
|
3098
|
-
...ctx.mode ? { mode: ctx.mode } : {}
|
|
3099
|
-
// mcpServers: createPlaywrightMcpServers(),
|
|
1748
|
+
}
|
|
3100
1749
|
};
|
|
3101
|
-
const
|
|
3102
|
-
const savedAgentId = explicitAgentId || (ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0);
|
|
1750
|
+
const savedAgentId = ctx.user ? loadTaskAgentId(ctx.workdir, ctx.taskId, ctx.user) : void 0;
|
|
3103
1751
|
if (savedAgentId) {
|
|
3104
1752
|
try {
|
|
3105
1753
|
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
|
-
);
|
|
1754
|
+
console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
|
|
3109
1755
|
if (ctx.user) {
|
|
3110
|
-
|
|
1756
|
+
saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent2.agentId);
|
|
3111
1757
|
}
|
|
3112
1758
|
return { agent: agent2, resumed: true };
|
|
3113
1759
|
} catch (err) {
|
|
@@ -3115,14 +1761,14 @@ async function obtainAgent(ctx) {
|
|
|
3115
1761
|
`[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
|
|
3116
1762
|
err instanceof Error ? err.message : err
|
|
3117
1763
|
);
|
|
3118
|
-
if (
|
|
3119
|
-
|
|
1764
|
+
if (ctx.user) {
|
|
1765
|
+
clearTaskAgentId(ctx.workdir, ctx.taskId, ctx.user);
|
|
3120
1766
|
}
|
|
3121
1767
|
}
|
|
3122
1768
|
}
|
|
3123
1769
|
const agent = await Agent.create(agentOptions);
|
|
3124
1770
|
if (ctx.user) {
|
|
3125
|
-
|
|
1771
|
+
saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent.agentId);
|
|
3126
1772
|
}
|
|
3127
1773
|
return { agent, resumed: false };
|
|
3128
1774
|
}
|
|
@@ -3134,34 +1780,36 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
3134
1780
|
}
|
|
3135
1781
|
const apiKey = ctx.apiKey.trim();
|
|
3136
1782
|
if (!apiKey) {
|
|
3137
|
-
throw new Error("\u7F3A\u5C11
|
|
1783
|
+
throw new Error("\u7F3A\u5C11 Cursor API Key");
|
|
3138
1784
|
}
|
|
3139
|
-
const workdir =
|
|
3140
|
-
const customTools = options?.customTools ??
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
1785
|
+
const workdir = requireExistingWorkdir(ctx.workdir);
|
|
1786
|
+
const customTools = options?.customTools ?? {};
|
|
1787
|
+
const prompt = `${ctx.prompt.trim()}
|
|
1788
|
+
|
|
1789
|
+
---
|
|
1790
|
+
\u8BF7\u5B8C\u6210\u4E0A\u8FF0\u4EFB\u52A1\u3002\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BF7\u7528 append_mail_reply \u589E\u91CF\u66F4\u65B0\u7ED9\u53D1\u4FE1\u4EBA\u7684\u56DE\u590D\uFF08\u53EF\u5148\u7B80\u77ED\u786E\u8BA4\uFF0C\u6709\u8FDB\u5C55\u7EE7\u7EED\u8FFD\u52A0\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\u3002\u5982\u9700\u7F16\u5199\u6587\u6863\uFF0C\u4FDD\u5B58\u5230 \`.apm/tasks/${ctx.taskId}/docs/\` \u76EE\u5F55\u3002`;
|
|
3144
1791
|
console.log(
|
|
3145
|
-
`[apm] Cursor Agent \u5F00\u59CB
|
|
1792
|
+
`[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} cwd=${workdir}`
|
|
3146
1793
|
);
|
|
3147
1794
|
const { agent, resumed } = await obtainAgent({
|
|
3148
1795
|
apiKey,
|
|
3149
1796
|
model: ctx.model,
|
|
3150
|
-
cwd: workdir,
|
|
3151
1797
|
workdir,
|
|
3152
|
-
|
|
1798
|
+
taskId: ctx.taskId,
|
|
3153
1799
|
user: ctx.user,
|
|
3154
|
-
mode: ctx.mode,
|
|
3155
|
-
resumeAgentId: ctx.resumeAgentId,
|
|
3156
1800
|
customTools
|
|
3157
1801
|
});
|
|
3158
1802
|
const eventSession = new EventSession(prompt);
|
|
3159
|
-
const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync :
|
|
1803
|
+
const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorLogSync(
|
|
3160
1804
|
cfg,
|
|
3161
|
-
|
|
1805
|
+
{
|
|
1806
|
+
taskId: ctx.taskId,
|
|
1807
|
+
mailboxMessageId: ctx.mailId,
|
|
1808
|
+
agentId: agent.agentId
|
|
1809
|
+
},
|
|
3162
1810
|
(err) => {
|
|
3163
1811
|
console.warn(
|
|
3164
|
-
|
|
1812
|
+
`[apm] Cursor \u65E5\u5FD7\u540C\u6B65\u5931\u8D25 mailId=${ctx.mailId}:`,
|
|
3165
1813
|
err instanceof Error ? err.message : err
|
|
3166
1814
|
);
|
|
3167
1815
|
}
|
|
@@ -3172,85 +1820,47 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
3172
1820
|
void activeRun.cancel().catch(() => void 0);
|
|
3173
1821
|
};
|
|
3174
1822
|
signal?.addEventListener("abort", abortRun, { once: true });
|
|
3175
|
-
|
|
1823
|
+
const streamEvents = [];
|
|
3176
1824
|
try {
|
|
3177
1825
|
const run = await agent.send(prompt, {
|
|
3178
|
-
|
|
3179
|
-
// mcpServers: createPlaywrightMcpServers(),
|
|
3180
|
-
local: {
|
|
3181
|
-
...options?.forceSend ? { force: true } : {},
|
|
3182
|
-
customTools
|
|
3183
|
-
}
|
|
1826
|
+
local: { customTools }
|
|
3184
1827
|
});
|
|
3185
1828
|
activeRun = run;
|
|
3186
|
-
logAbortSignalStats(signal, "runCursorAgent:after-send");
|
|
3187
1829
|
console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
|
|
3188
|
-
let lastRunErrorStatus;
|
|
3189
1830
|
for await (const event of run.stream()) {
|
|
3190
1831
|
if (signal?.aborted) {
|
|
3191
1832
|
abortRun();
|
|
3192
1833
|
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
3193
1834
|
}
|
|
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);
|
|
1835
|
+
streamEvents.push(event);
|
|
3204
1836
|
eventSession.addEvent(event);
|
|
3205
1837
|
syncRemoteLog.schedule(eventSession);
|
|
3206
1838
|
}
|
|
3207
1839
|
await syncRemoteLog.flush(eventSession);
|
|
3208
1840
|
const result = await run.wait();
|
|
1841
|
+
const assistantText = eventSession.getAssistantText() || collectAssistantText(streamEvents);
|
|
3209
1842
|
if (result.status === "error") {
|
|
3210
|
-
const failureMessage = formatCursorRunFailure(result.id, {
|
|
3211
|
-
statusError: lastRunErrorStatus,
|
|
3212
|
-
resultText: result.result
|
|
3213
|
-
});
|
|
3214
|
-
console.error(`[apm] ${failureMessage}`);
|
|
3215
1843
|
if (resumed) {
|
|
3216
|
-
|
|
1844
|
+
clearTaskAgentId(workdir, ctx.taskId, ctx.user);
|
|
3217
1845
|
}
|
|
3218
|
-
throw new Error(
|
|
1846
|
+
throw new Error(
|
|
1847
|
+
`Cursor run \u5931\u8D25: ${result.id}${result.result ? ` \u2014 ${result.result}` : ""}`
|
|
1848
|
+
);
|
|
3219
1849
|
}
|
|
3220
1850
|
if (result.status === "cancelled") {
|
|
3221
1851
|
throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
|
|
3222
1852
|
}
|
|
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
|
-
}
|
|
1853
|
+
console.log(`[apm] Cursor Agent \u5B8C\u6210 mailId=${ctx.mailId}`);
|
|
3239
1854
|
return {
|
|
3240
1855
|
runId: result.id,
|
|
3241
1856
|
agentId: agent.agentId,
|
|
3242
1857
|
status: result.status,
|
|
3243
|
-
|
|
3244
|
-
durationMs: result.durationMs,
|
|
3245
|
-
assistantText: eventSession.getAssistantText(),
|
|
3246
|
-
createPlan: eventSession.getCreatePlanContent(),
|
|
3247
|
-
artifacts,
|
|
3248
|
-
artifactDocuments
|
|
1858
|
+
assistantText: assistantText || result.result || ""
|
|
3249
1859
|
};
|
|
3250
1860
|
} catch (err) {
|
|
3251
1861
|
if (err instanceof CursorAgentError) {
|
|
3252
1862
|
if (resumed) {
|
|
3253
|
-
|
|
1863
|
+
clearTaskAgentId(workdir, ctx.taskId, ctx.user);
|
|
3254
1864
|
}
|
|
3255
1865
|
throw new Error(
|
|
3256
1866
|
`Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
|
|
@@ -3258,338 +1868,428 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
3258
1868
|
}
|
|
3259
1869
|
throw err;
|
|
3260
1870
|
} finally {
|
|
3261
|
-
logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
|
|
3262
1871
|
signal?.removeEventListener("abort", abortRun);
|
|
3263
|
-
logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
|
|
3264
1872
|
await agent[Symbol.asyncDispose]();
|
|
3265
1873
|
}
|
|
3266
1874
|
}
|
|
3267
1875
|
|
|
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
|
-
|
|
1876
|
+
// src/commands/connect/mail-store.ts
|
|
1877
|
+
var pendingMails = [];
|
|
1878
|
+
var seenMailIds = /* @__PURE__ */ new Set();
|
|
1879
|
+
var repliedMailIds = /* @__PURE__ */ new Set();
|
|
1880
|
+
function hasMailReplied(mailId) {
|
|
1881
|
+
return repliedMailIds.has(mailId);
|
|
1882
|
+
}
|
|
1883
|
+
function markMailReplied(mailId) {
|
|
1884
|
+
repliedMailIds.add(mailId);
|
|
1885
|
+
seenMailIds.add(mailId);
|
|
1886
|
+
removeMailById(mailId);
|
|
1887
|
+
}
|
|
1888
|
+
function enqueueReceivedMail(mail) {
|
|
1889
|
+
if (seenMailIds.has(mail.id)) {
|
|
1890
|
+
return false;
|
|
1891
|
+
}
|
|
1892
|
+
seenMailIds.add(mail.id);
|
|
1893
|
+
pendingMails.push(mail);
|
|
1894
|
+
console.log(
|
|
1895
|
+
`[apm] \u6536\u5230\u4FE1\u4EF6 id=${mail.id} taskId=${mail.taskId} createdAt=${mail.createdAt}`
|
|
1896
|
+
);
|
|
1897
|
+
return true;
|
|
1898
|
+
}
|
|
1899
|
+
function dequeueNextMail() {
|
|
1900
|
+
return pendingMails.shift();
|
|
1901
|
+
}
|
|
1902
|
+
function hasPendingMail() {
|
|
1903
|
+
return pendingMails.length > 0;
|
|
1904
|
+
}
|
|
1905
|
+
function removeMailById(mailId) {
|
|
1906
|
+
const index = pendingMails.findIndex((mail) => mail.id === mailId);
|
|
1907
|
+
if (index < 0) {
|
|
1908
|
+
return false;
|
|
1909
|
+
}
|
|
1910
|
+
pendingMails.splice(index, 1);
|
|
1911
|
+
return true;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
// src/commands/connect/reply-mail-tool.ts
|
|
1915
|
+
function createMailReplyDraft() {
|
|
1916
|
+
const parts = [];
|
|
1917
|
+
const tool = {
|
|
1918
|
+
description: "\u5411\u53D1\u4FE1\u4EBA\u589E\u91CF\u66F4\u65B0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8FFD\u52A0\u8FDB\u5C55\uFF08\u5982\u5148\u786E\u8BA4\u6536\u5230\u3001\u518D\u6C47\u62A5\u7ED3\u679C\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\uFF0C\u65E0\u9700\u53E6\u884C\u6536\u5C3E\u3002",
|
|
1919
|
+
inputSchema: {
|
|
1920
|
+
type: "object",
|
|
1921
|
+
properties: {
|
|
1922
|
+
content: {
|
|
1923
|
+
type: "string",
|
|
1924
|
+
description: "\u672C\u6B21\u8FFD\u52A0\u7684\u56DE\u590D\u7247\u6BB5"
|
|
1925
|
+
}
|
|
3292
1926
|
},
|
|
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
|
-
});
|
|
1927
|
+
required: ["content"]
|
|
1928
|
+
},
|
|
1929
|
+
execute: async (args) => {
|
|
1930
|
+
const content = typeof args.content === "string" ? args.content.trim() : "";
|
|
1931
|
+
if (!content) {
|
|
1932
|
+
return {
|
|
1933
|
+
content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
1934
|
+
isError: true
|
|
1935
|
+
};
|
|
3311
1936
|
}
|
|
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
|
-
});
|
|
1937
|
+
parts.push(content);
|
|
1938
|
+
console.log(`[apm] \u589E\u91CF\u66F4\u65B0\u56DE\u4FE1\uFF08\u7B2C ${parts.length} \u6BB5\uFF09`);
|
|
1939
|
+
return "\u5DF2\u8FFD\u52A0\u5230\u56DE\u4FE1\u8349\u7A3F\uFF0CAgent \u7ED3\u675F\u540E\u5C06\u4E00\u5E76\u63D0\u4EA4\u3002";
|
|
3337
1940
|
}
|
|
3338
|
-
}
|
|
1941
|
+
};
|
|
1942
|
+
return {
|
|
1943
|
+
tool,
|
|
1944
|
+
getReplyContent: () => parts.join("\n\n"),
|
|
1945
|
+
hasReplyContent: () => parts.some((part) => part.trim().length > 0)
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// src/commands/connect/task-pull.ts
|
|
1950
|
+
import { writeFileSync as writeFileSync9 } from "fs";
|
|
1951
|
+
import { join as join12 } from "path";
|
|
1952
|
+
import { stringify as yamlStringify } from "yaml";
|
|
1953
|
+
|
|
1954
|
+
// src/rules-sync.ts
|
|
1955
|
+
import { basename as basename2, extname, join as join11 } from "path";
|
|
1956
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
1957
|
+
var MANIFEST_FILE2 = ".rules-sync-manifest.json";
|
|
1958
|
+
function ruleLocalFileName(ruleName) {
|
|
1959
|
+
const trimmed = ruleName.trim();
|
|
1960
|
+
if (!trimmed) return "rule.md";
|
|
1961
|
+
const sanitized = trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
1962
|
+
if (extname(sanitized).toLowerCase() === ".md") return sanitized;
|
|
1963
|
+
return `${sanitized}.md`;
|
|
3339
1964
|
}
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
var CLI_VERSION_FILE = ".cli-version.json";
|
|
3345
|
-
function manifestPath(apmDir) {
|
|
3346
|
-
return join14(apmDir, CLI_VERSION_FILE);
|
|
3347
|
-
}
|
|
3348
|
-
function loadManifest3(apmDir) {
|
|
3349
|
-
const path10 = toFsPath(manifestPath(apmDir));
|
|
3350
|
-
if (!existsSync12(path10)) {
|
|
3351
|
-
return null;
|
|
1965
|
+
function loadManifest(rulesDir) {
|
|
1966
|
+
const path10 = join11(rulesDir, MANIFEST_FILE2);
|
|
1967
|
+
if (!existsSync8(toFsPath(path10))) {
|
|
1968
|
+
return { version: 1, rules: {} };
|
|
3352
1969
|
}
|
|
3353
1970
|
try {
|
|
3354
1971
|
const parsed = JSON.parse(
|
|
3355
|
-
|
|
1972
|
+
readFileSync9(toFsPath(path10), "utf8")
|
|
3356
1973
|
);
|
|
3357
|
-
if (parsed?.version === 1 && typeof parsed.
|
|
1974
|
+
if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
|
|
3358
1975
|
return parsed;
|
|
3359
1976
|
}
|
|
3360
1977
|
} catch {
|
|
3361
1978
|
}
|
|
3362
|
-
return
|
|
1979
|
+
return { version: 1, rules: {} };
|
|
3363
1980
|
}
|
|
3364
|
-
function
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
toFsPath(manifestPath(apmDir)),
|
|
1981
|
+
function saveManifest(rulesDir, manifest) {
|
|
1982
|
+
writeFileSync8(
|
|
1983
|
+
toFsPath(join11(rulesDir, MANIFEST_FILE2)),
|
|
3368
1984
|
`${JSON.stringify(manifest, null, 2)}
|
|
3369
1985
|
`,
|
|
3370
1986
|
"utf8"
|
|
3371
1987
|
);
|
|
3372
1988
|
}
|
|
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;
|
|
1989
|
+
function isBaseRuleFileName(fileName) {
|
|
1990
|
+
return listBaseRuleFileNames().includes(basename2(fileName));
|
|
3385
1991
|
}
|
|
3386
|
-
function
|
|
3387
|
-
|
|
3388
|
-
|
|
1992
|
+
function isRuleUpToDate(entry, rule, dest) {
|
|
1993
|
+
if (!entry || !existsSync8(toFsPath(dest))) return false;
|
|
1994
|
+
if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
|
|
1995
|
+
const updatedAt = rule.updatedAt ?? "";
|
|
1996
|
+
if (entry.updatedAt !== updatedAt) return false;
|
|
1997
|
+
const localContent = readFileSync9(toFsPath(dest), "utf8");
|
|
1998
|
+
return localContent === (rule.content ?? "");
|
|
3389
1999
|
}
|
|
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;
|
|
2000
|
+
async function syncPlatformRules(cfg, workdirPath, apmRoot) {
|
|
2001
|
+
const api = createApmApiClient(cfg);
|
|
2002
|
+
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
2003
|
+
const repositoryId = baseline.repositoryId;
|
|
2004
|
+
const rulesDir = join11(apmRoot ?? workspaceApmDir(workdirPath), "rules");
|
|
2005
|
+
await ensureDirExists(rulesDir);
|
|
2006
|
+
if (!repositoryId) {
|
|
2007
|
+
console.log(
|
|
2008
|
+
`[apm] \u672A\u5339\u914D\u5230\u7ED1\u5B9A\u4ED3\u5E93\u7684\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u8DF3\u8FC7\u5E73\u53F0\u89C4\u5219\u540C\u6B65\uFF08\u8DEF\u5F84\uFF1A${workdirPath}\uFF09`
|
|
2009
|
+
);
|
|
2010
|
+
return { written: [], skipped: [], removed: [], repositoryId: null };
|
|
3409
2011
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
if (
|
|
3423
|
-
|
|
3424
|
-
|
|
2012
|
+
const { list } = await api.cli.listRules({ repositoryId });
|
|
2013
|
+
const manifest = loadManifest(rulesDir);
|
|
2014
|
+
const nextManifest = { version: 1, rules: {} };
|
|
2015
|
+
const remoteIds = /* @__PURE__ */ new Set();
|
|
2016
|
+
const written = [];
|
|
2017
|
+
const skipped = [];
|
|
2018
|
+
for (const rule of list) {
|
|
2019
|
+
remoteIds.add(rule.id);
|
|
2020
|
+
const fileName = ruleLocalFileName(rule.name);
|
|
2021
|
+
const dest = join11(rulesDir, fileName);
|
|
2022
|
+
const entry = manifest.rules[rule.id];
|
|
2023
|
+
const updatedAt = rule.updatedAt ?? "";
|
|
2024
|
+
if (isRuleUpToDate(entry, rule, dest)) {
|
|
2025
|
+
nextManifest.rules[rule.id] = entry;
|
|
2026
|
+
skipped.push(fileName);
|
|
2027
|
+
console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
|
|
2028
|
+
continue;
|
|
3425
2029
|
}
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
const
|
|
3436
|
-
if (
|
|
3437
|
-
|
|
2030
|
+
writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
|
|
2031
|
+
nextManifest.rules[rule.id] = { fileName, updatedAt };
|
|
2032
|
+
written.push(fileName);
|
|
2033
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
|
|
2034
|
+
}
|
|
2035
|
+
const removed = [];
|
|
2036
|
+
for (const [ruleId, entry] of Object.entries(manifest.rules)) {
|
|
2037
|
+
if (remoteIds.has(ruleId)) continue;
|
|
2038
|
+
if (isBaseRuleFileName(entry.fileName)) continue;
|
|
2039
|
+
const dest = join11(rulesDir, entry.fileName);
|
|
2040
|
+
if (existsSync8(toFsPath(dest))) {
|
|
2041
|
+
rmSync3(toFsPath(dest), { force: true });
|
|
3438
2042
|
}
|
|
3439
|
-
|
|
3440
|
-
|
|
2043
|
+
removed.push(entry.fileName);
|
|
2044
|
+
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
|
|
2045
|
+
}
|
|
2046
|
+
saveManifest(rulesDir, nextManifest);
|
|
2047
|
+
return { written, skipped, removed, repositoryId };
|
|
3441
2048
|
}
|
|
3442
2049
|
|
|
3443
|
-
// src/commands/connect.ts
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
await api.cli.updateMessageStatus({ id: messageId, status });
|
|
3448
|
-
console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
|
|
3449
|
-
}
|
|
3450
|
-
async function setMessageError(cfg, messageId, error) {
|
|
3451
|
-
const api = createApmApiClient(cfg);
|
|
3452
|
-
await api.cli.setMessageError({ id: messageId, error });
|
|
3453
|
-
console.log(`[apm] \u5DF2\u8BBE\u7F6E\u6D88\u606F\u9519\u8BEF: ${messageId}`);
|
|
3454
|
-
}
|
|
3455
|
-
var SHUTDOWN_DRAIN_MS = 3e3;
|
|
3456
|
-
function isUserCancelled(ctx) {
|
|
3457
|
-
return ctx.perMessageSignal.aborted && !ctx.shutdownSignal.aborted;
|
|
3458
|
-
}
|
|
3459
|
-
async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
3460
|
-
if (isUserCancelled(ctx)) return;
|
|
3461
|
-
if (signal.aborted) return;
|
|
3462
|
-
const messageId = msg.messageId;
|
|
3463
|
-
const workdir = requireRemoteWorkdir(msg.workdir);
|
|
2050
|
+
// src/commands/connect/task-pull.ts
|
|
2051
|
+
async function runTaskPull(cfg, detail) {
|
|
2052
|
+
const { taskId, workdir } = detail;
|
|
2053
|
+
assertWorkspaceApmDirExists(workdir);
|
|
3464
2054
|
const apmRoot = workspaceApmDir(workdir);
|
|
3465
|
-
const
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
2055
|
+
const dir = taskDir(taskId, apmRoot, workdir);
|
|
2056
|
+
const docsDir = taskDocsDir(taskId, apmRoot, workdir);
|
|
2057
|
+
await ensureDirExists(docsDir);
|
|
2058
|
+
writeFileSync9(taskRulePath(taskId, apmRoot, workdir), "", "utf8");
|
|
2059
|
+
writeFileSync9(
|
|
2060
|
+
taskTaskPath(taskId, apmRoot, workdir),
|
|
2061
|
+
detail.task.description ?? "",
|
|
2062
|
+
"utf8"
|
|
2063
|
+
);
|
|
2064
|
+
for (const doc of detail.documents) {
|
|
2065
|
+
const fileName = documentLocalFileName(doc.name);
|
|
2066
|
+
writeFileSync9(join12(docsDir, fileName), doc.content ?? "", "utf8");
|
|
2067
|
+
}
|
|
2068
|
+
const members = detail.studio?.members ?? [];
|
|
2069
|
+
const taskYaml = yamlStringify(
|
|
2070
|
+
{
|
|
2071
|
+
name: detail.studio?.title ?? detail.task.title,
|
|
2072
|
+
phase: detail.studio?.phase ?? null,
|
|
2073
|
+
task: "./TASK.md",
|
|
2074
|
+
members: members.map((member) => ({
|
|
2075
|
+
name: member.displayName,
|
|
2076
|
+
oxcAgent: member.oxcAgent?.name ?? "",
|
|
2077
|
+
description: member.oxcAgent?.description ?? "",
|
|
2078
|
+
isLead: member.isLead
|
|
2079
|
+
})),
|
|
2080
|
+
attachments: detail.attachments.map((item) => ({ name: item.name }))
|
|
2081
|
+
},
|
|
2082
|
+
{ lineWidth: 0 }
|
|
2083
|
+
);
|
|
2084
|
+
writeFileSync9(
|
|
2085
|
+
taskYamlPath(taskId, apmRoot, workdir),
|
|
2086
|
+
taskYaml.endsWith("\n") ? taskYaml : `${taskYaml}
|
|
2087
|
+
`,
|
|
2088
|
+
"utf8"
|
|
2089
|
+
);
|
|
2090
|
+
await syncPlatformRules(cfg, workdir, apmRoot);
|
|
2091
|
+
await syncRemoteDeploymentConfig(workdir, apmRoot);
|
|
2092
|
+
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
2093
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u4EFB\u52A1\u5DE5\u4F5C\u533A: ${toFsPath(dir)}`);
|
|
2094
|
+
return dir;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// src/commands/connect/mail-processor.ts
|
|
2098
|
+
async function processMail(mail, options) {
|
|
2099
|
+
const api = createApmApiClient(options.cfg);
|
|
2100
|
+
console.log(`[apm] \u5F00\u59CB\u5904\u7406\u4FE1\u4EF6 id=${mail.id}`);
|
|
2101
|
+
if (hasMailReplied(mail.id)) {
|
|
2102
|
+
console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u5DF2\u56DE\u590D\u8FC7\uFF0C\u8DF3\u8FC7`);
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
const cursorApiKey = options.getCursorApiKey().trim();
|
|
2106
|
+
if (!cursorApiKey) {
|
|
2107
|
+
options.onFatalError(
|
|
2108
|
+
"[apm] \u5BA2\u6237\u673A\u672A\u914D\u7F6E Cursor API Key\uFF0C\u65E0\u6CD5\u5904\u7406\u4FE1\u4EF6\uFF0C\u65AD\u5F00\u8FDE\u63A5"
|
|
2109
|
+
);
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
let detail;
|
|
3476
2113
|
try {
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
2114
|
+
detail = await api.cli.getMailboxMessageDetail({ id: mail.id });
|
|
2115
|
+
} catch (err) {
|
|
2116
|
+
console.error(
|
|
2117
|
+
`[apm] \u83B7\u53D6\u4FE1\u4EF6\u8BE6\u60C5\u5931\u8D25 id=${mail.id}:`,
|
|
2118
|
+
err instanceof Error ? err.message : err
|
|
3481
2119
|
);
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
await
|
|
3486
|
-
|
|
3487
|
-
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
try {
|
|
2123
|
+
await runTaskPull(options.cfg, detail);
|
|
2124
|
+
} catch (err) {
|
|
2125
|
+
console.error(
|
|
2126
|
+
`[apm] \u540C\u6B65\u4EFB\u52A1\u5DE5\u4F5C\u533A\u5931\u8D25 taskId=${detail.taskId}:`,
|
|
2127
|
+
err instanceof Error ? err.message : err
|
|
3488
2128
|
);
|
|
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
|
-
);
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
if (detail.status !== "PENDING") {
|
|
2132
|
+
console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u72B6\u6001\u4E3A ${detail.status}\uFF0C\u8DF3\u8FC7\u5904\u7406`);
|
|
2133
|
+
if (detail.status === "SUCCEEDED" || detail.status === "FAILED") {
|
|
2134
|
+
markMailReplied(mail.id);
|
|
3533
2135
|
}
|
|
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
|
-
);
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
try {
|
|
2139
|
+
await api.cli.claimMailboxMessage({ id: mail.id });
|
|
3566
2140
|
} catch (err) {
|
|
3567
|
-
if (isUserCancelled(ctx)) {
|
|
3568
|
-
console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
|
|
3569
|
-
return;
|
|
3570
|
-
}
|
|
3571
2141
|
console.error(
|
|
3572
|
-
|
|
3573
|
-
err instanceof Error ? err.message :
|
|
2142
|
+
`[apm] \u8BA4\u9886\u4FE1\u4EF6\u5931\u8D25 id=${mail.id}:`,
|
|
2143
|
+
err instanceof Error ? err.message : err
|
|
3574
2144
|
);
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
removeMailById(mail.id);
|
|
2148
|
+
const {
|
|
2149
|
+
tool: replyTool,
|
|
2150
|
+
getReplyContent,
|
|
2151
|
+
hasReplyContent
|
|
2152
|
+
} = createMailReplyDraft();
|
|
2153
|
+
try {
|
|
2154
|
+
const result = await runCursorAgent(
|
|
2155
|
+
options.cfg,
|
|
2156
|
+
{
|
|
2157
|
+
mailId: detail.id,
|
|
2158
|
+
taskId: detail.taskId,
|
|
2159
|
+
prompt: detail.content,
|
|
2160
|
+
model: detail.recipient.model?.trim() || "default",
|
|
2161
|
+
apiKey: cursorApiKey,
|
|
2162
|
+
workdir: detail.workdir,
|
|
2163
|
+
user: detail.recipient.displayName
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
signal: options.signal,
|
|
2167
|
+
customTools: { append_mail_reply: replyTool }
|
|
2168
|
+
}
|
|
2169
|
+
);
|
|
2170
|
+
const replyContent = (hasReplyContent() ? getReplyContent() : result.assistantText).trim();
|
|
2171
|
+
if (!replyContent) {
|
|
2172
|
+
throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
|
|
2173
|
+
}
|
|
2174
|
+
await api.cli.completeMailboxMessage({
|
|
2175
|
+
id: detail.id,
|
|
2176
|
+
status: "SUCCEEDED",
|
|
2177
|
+
replyContent
|
|
2178
|
+
});
|
|
2179
|
+
markMailReplied(detail.id);
|
|
2180
|
+
await syncTaskDocuments(options.cfg, detail.taskId, detail.workdir, {
|
|
2181
|
+
api,
|
|
2182
|
+
remoteDocuments: detail.documents
|
|
2183
|
+
});
|
|
2184
|
+
console.log(`[apm] \u4FE1\u4EF6\u5904\u7406\u5B8C\u6210 id=${mail.id}`);
|
|
2185
|
+
} catch (err) {
|
|
2186
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2187
|
+
console.error(`[apm] \u4FE1\u4EF6\u5904\u7406\u5931\u8D25 id=${mail.id}: ${message}`);
|
|
3578
2188
|
try {
|
|
3579
|
-
await
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
);
|
|
3584
|
-
|
|
3585
|
-
} catch (statusErr) {
|
|
2189
|
+
await api.cli.completeMailboxMessage({
|
|
2190
|
+
id: detail.id,
|
|
2191
|
+
status: "FAILED",
|
|
2192
|
+
error: message
|
|
2193
|
+
});
|
|
2194
|
+
} catch (completeErr) {
|
|
3586
2195
|
console.error(
|
|
3587
|
-
|
|
3588
|
-
|
|
2196
|
+
`[apm] \u6807\u8BB0\u4FE1\u4EF6\u5931\u8D25\u72B6\u6001\u65F6\u51FA\u9519 id=${mail.id}:`,
|
|
2197
|
+
completeErr instanceof Error ? completeErr.message : completeErr
|
|
3589
2198
|
);
|
|
3590
2199
|
}
|
|
3591
2200
|
}
|
|
3592
2201
|
}
|
|
2202
|
+
function createMailProcessor(options) {
|
|
2203
|
+
let running = false;
|
|
2204
|
+
const pump = () => {
|
|
2205
|
+
if (running || options.signal.aborted) {
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
running = true;
|
|
2209
|
+
void (async () => {
|
|
2210
|
+
try {
|
|
2211
|
+
while (!options.signal.aborted) {
|
|
2212
|
+
const mail = dequeueNextMail();
|
|
2213
|
+
if (!mail) {
|
|
2214
|
+
break;
|
|
2215
|
+
}
|
|
2216
|
+
await processMail(mail, options);
|
|
2217
|
+
}
|
|
2218
|
+
} finally {
|
|
2219
|
+
running = false;
|
|
2220
|
+
if (hasPendingMail() && !options.signal.aborted) {
|
|
2221
|
+
pump();
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
})();
|
|
2225
|
+
};
|
|
2226
|
+
const ingest = (mail) => {
|
|
2227
|
+
if (enqueueReceivedMail(mail)) {
|
|
2228
|
+
pump();
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
return {
|
|
2232
|
+
receive(msg) {
|
|
2233
|
+
ingest({
|
|
2234
|
+
id: msg.id,
|
|
2235
|
+
taskId: msg.taskId,
|
|
2236
|
+
createdAt: msg.createdAt
|
|
2237
|
+
});
|
|
2238
|
+
},
|
|
2239
|
+
syncPendingMails(mails) {
|
|
2240
|
+
let added = 0;
|
|
2241
|
+
for (const mail of mails) {
|
|
2242
|
+
if (enqueueReceivedMail(mail)) {
|
|
2243
|
+
added++;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
if (added > 0) {
|
|
2247
|
+
console.log(`[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 ${added} \u5C01`);
|
|
2248
|
+
} else if (mails.length > 0) {
|
|
2249
|
+
console.log(
|
|
2250
|
+
`[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 0 \u5C01\uFF08${mails.length} \u5C01\u5DF2\u5728\u961F\u5217\u4E2D\uFF09`
|
|
2251
|
+
);
|
|
2252
|
+
} else {
|
|
2253
|
+
console.log("[apm] \u540C\u6B65\u5F85\u5904\u7406\u4FE1\u4EF6 0 \u5C01");
|
|
2254
|
+
}
|
|
2255
|
+
pump();
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// src/commands/connect/mail-sync.ts
|
|
2261
|
+
function parsePendingMail(value) {
|
|
2262
|
+
if (typeof value !== "object" || value === null) {
|
|
2263
|
+
return null;
|
|
2264
|
+
}
|
|
2265
|
+
const row = value;
|
|
2266
|
+
if (typeof row.id !== "string" || typeof row.taskId !== "string") {
|
|
2267
|
+
return null;
|
|
2268
|
+
}
|
|
2269
|
+
const createdAt = typeof row.createdAt === "string" ? row.createdAt : row.createdAt instanceof Date ? row.createdAt.toISOString() : "";
|
|
2270
|
+
if (!createdAt) {
|
|
2271
|
+
return null;
|
|
2272
|
+
}
|
|
2273
|
+
return {
|
|
2274
|
+
id: row.id,
|
|
2275
|
+
taskId: row.taskId,
|
|
2276
|
+
createdAt
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
async function fetchPendingMails(cfg) {
|
|
2280
|
+
const api = createApmApiClient(cfg);
|
|
2281
|
+
const list = await api.cli.listPendingMailboxMessages({});
|
|
2282
|
+
if (!Array.isArray(list)) {
|
|
2283
|
+
return [];
|
|
2284
|
+
}
|
|
2285
|
+
return list.flatMap((item) => {
|
|
2286
|
+
const mail = parsePendingMail(item);
|
|
2287
|
+
return mail ? [mail] : [];
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
// src/commands/connect.ts
|
|
2292
|
+
var HEARTBEAT_MS = 3e4;
|
|
3593
2293
|
function startHeartbeat(ws, clientMachineId) {
|
|
3594
2294
|
const send = () => {
|
|
3595
2295
|
if (ws.readyState === WebSocket.OPEN) {
|
|
@@ -3639,11 +2339,17 @@ async function runConnect(options) {
|
|
|
3639
2339
|
let stopHeartbeat;
|
|
3640
2340
|
let shuttingDown = false;
|
|
3641
2341
|
const shutdownAbort = new AbortController();
|
|
3642
|
-
|
|
3643
|
-
const
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
2342
|
+
let cursorApiKey = "";
|
|
2343
|
+
const mailProcessor = createMailProcessor({
|
|
2344
|
+
cfg,
|
|
2345
|
+
signal: shutdownAbort.signal,
|
|
2346
|
+
getCursorApiKey: () => cursorApiKey,
|
|
2347
|
+
onFatalError: (message) => {
|
|
2348
|
+
console.error(message);
|
|
2349
|
+
shutdown(1);
|
|
2350
|
+
}
|
|
2351
|
+
});
|
|
2352
|
+
const shutdown = (code = 0) => {
|
|
3647
2353
|
if (shuttingDown) return;
|
|
3648
2354
|
shuttingDown = true;
|
|
3649
2355
|
logAbortSignalStats(
|
|
@@ -3656,19 +2362,34 @@ async function runConnect(options) {
|
|
|
3656
2362
|
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
3657
2363
|
ws.terminate();
|
|
3658
2364
|
}
|
|
3659
|
-
try {
|
|
3660
|
-
await Promise.race([
|
|
3661
|
-
Promise.all(activeTasks),
|
|
3662
|
-
new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
|
|
3663
|
-
]);
|
|
3664
|
-
} catch {
|
|
3665
|
-
}
|
|
3666
2365
|
resolve5();
|
|
3667
2366
|
process.exit(code);
|
|
3668
2367
|
};
|
|
3669
2368
|
ws.on("open", () => {
|
|
3670
2369
|
console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
|
|
3671
2370
|
stopHeartbeat = startHeartbeat(ws, clientMachineId);
|
|
2371
|
+
void (async () => {
|
|
2372
|
+
try {
|
|
2373
|
+
const api = createApmApiClient(cfg);
|
|
2374
|
+
const me = await api.cli.me({});
|
|
2375
|
+
cursorApiKey = me.cursorApiKey?.trim() ?? "";
|
|
2376
|
+
if (!cursorApiKey) {
|
|
2377
|
+
console.error(
|
|
2378
|
+
"[apm] \u5BA2\u6237\u673A\u672A\u914D\u7F6E Cursor API Key\uFF0C\u8BF7\u5728\u5E73\u53F0\u5BA2\u6237\u673A\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u540E\u91CD\u8BD5"
|
|
2379
|
+
);
|
|
2380
|
+
shutdown(1);
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
const mails = await fetchPendingMails(cfg);
|
|
2384
|
+
mailProcessor.syncPendingMails(mails);
|
|
2385
|
+
} catch (err) {
|
|
2386
|
+
console.error(
|
|
2387
|
+
"[apm] \u8FDE\u63A5\u540E\u521D\u59CB\u5316\u5931\u8D25:",
|
|
2388
|
+
err instanceof Error ? err.message : err
|
|
2389
|
+
);
|
|
2390
|
+
shutdown(1);
|
|
2391
|
+
}
|
|
2392
|
+
})();
|
|
3672
2393
|
});
|
|
3673
2394
|
ws.on("message", (data) => {
|
|
3674
2395
|
if (shuttingDown) return;
|
|
@@ -3686,93 +2407,19 @@ async function runConnect(options) {
|
|
|
3686
2407
|
console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
|
|
3687
2408
|
return;
|
|
3688
2409
|
}
|
|
3689
|
-
if (validated.data.type === "cancel") {
|
|
3690
|
-
const { messageId } = validated.data;
|
|
3691
|
-
pendingCancels.add(messageId);
|
|
3692
|
-
activeRuns.get(messageId)?.abort();
|
|
3693
|
-
return;
|
|
3694
|
-
}
|
|
3695
2410
|
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
|
-
});
|
|
2411
|
+
void handleInboundDeploy(cfg, validated.data, shutdownAbort.signal);
|
|
3735
2412
|
return;
|
|
3736
2413
|
}
|
|
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;
|
|
2414
|
+
if (validated.data.type === "received_mail") {
|
|
2415
|
+
mailProcessor.receive(validated.data);
|
|
3747
2416
|
}
|
|
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
2417
|
});
|
|
3771
2418
|
ws.on("close", (code, reason) => {
|
|
3772
2419
|
console.log(
|
|
3773
2420
|
`[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
|
|
3774
2421
|
);
|
|
3775
|
-
|
|
2422
|
+
shutdown();
|
|
3776
2423
|
});
|
|
3777
2424
|
ws.on("error", (err) => {
|
|
3778
2425
|
console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
|
|
@@ -3780,55 +2427,31 @@ async function runConnect(options) {
|
|
|
3780
2427
|
});
|
|
3781
2428
|
process.on("SIGINT", () => {
|
|
3782
2429
|
console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
|
|
3783
|
-
|
|
2430
|
+
shutdown();
|
|
3784
2431
|
});
|
|
3785
2432
|
process.on("SIGTERM", () => {
|
|
3786
|
-
|
|
2433
|
+
shutdown();
|
|
3787
2434
|
});
|
|
3788
2435
|
});
|
|
3789
2436
|
}
|
|
3790
2437
|
|
|
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
2438
|
// src/commands/deploy/backend.ts
|
|
3816
2439
|
import path5 from "node:path";
|
|
3817
2440
|
|
|
3818
2441
|
// src/commands/deploy/internal/apm-config.ts
|
|
3819
|
-
import { existsSync as
|
|
2442
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
3820
2443
|
import { resolve as resolve4 } from "node:path";
|
|
3821
2444
|
function loadApmConfig(options) {
|
|
3822
2445
|
const p = resolve4(
|
|
3823
2446
|
process.cwd(),
|
|
3824
2447
|
options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
|
|
3825
2448
|
);
|
|
3826
|
-
if (!
|
|
2449
|
+
if (!existsSync9(p)) {
|
|
3827
2450
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
3828
2451
|
process.exit(1);
|
|
3829
2452
|
}
|
|
3830
2453
|
try {
|
|
3831
|
-
const raw =
|
|
2454
|
+
const raw = readFileSync10(p, "utf8");
|
|
3832
2455
|
return JSON.parse(raw);
|
|
3833
2456
|
} catch (e) {
|
|
3834
2457
|
console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
|
|
@@ -3950,7 +2573,7 @@ import path4 from "node:path";
|
|
|
3950
2573
|
import Docker from "dockerode";
|
|
3951
2574
|
|
|
3952
2575
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
3953
|
-
import { existsSync as
|
|
2576
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
|
|
3954
2577
|
import path from "node:path";
|
|
3955
2578
|
function asOptionalTlsBuffer(value) {
|
|
3956
2579
|
if (typeof value !== "string") {
|
|
@@ -3962,8 +2585,8 @@ function asOptionalTlsBuffer(value) {
|
|
|
3962
2585
|
if (normalized === "") {
|
|
3963
2586
|
return void 0;
|
|
3964
2587
|
}
|
|
3965
|
-
if (
|
|
3966
|
-
return
|
|
2588
|
+
if (existsSync10(normalized)) {
|
|
2589
|
+
return readFileSync11(normalized);
|
|
3967
2590
|
}
|
|
3968
2591
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
3969
2592
|
if (looksLikePath) {
|
|
@@ -4173,7 +2796,7 @@ var DockerodeClient = class {
|
|
|
4173
2796
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
4174
2797
|
|
|
4175
2798
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
4176
|
-
import { existsSync as
|
|
2799
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
|
|
4177
2800
|
import path2 from "node:path";
|
|
4178
2801
|
function stripSurroundingQuotes(value) {
|
|
4179
2802
|
const t = value.trim();
|
|
@@ -4190,10 +2813,10 @@ function loadEnvFromFile(envFilePath) {
|
|
|
4190
2813
|
return {};
|
|
4191
2814
|
}
|
|
4192
2815
|
const targetPath = path2.resolve(envFilePath);
|
|
4193
|
-
if (!
|
|
2816
|
+
if (!existsSync11(targetPath) || !statSync4(targetPath).isFile()) {
|
|
4194
2817
|
return {};
|
|
4195
2818
|
}
|
|
4196
|
-
const raw =
|
|
2819
|
+
const raw = readFileSync12(targetPath, "utf-8");
|
|
4197
2820
|
const result = {};
|
|
4198
2821
|
for (const line of raw.split(/\r?\n/)) {
|
|
4199
2822
|
const normalized = line.trim();
|
|
@@ -4364,12 +2987,12 @@ function dockerPushImage(params, cwd) {
|
|
|
4364
2987
|
}
|
|
4365
2988
|
|
|
4366
2989
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
4367
|
-
import { existsSync as
|
|
2990
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
4368
2991
|
import path3 from "node:path";
|
|
4369
2992
|
function resolveDockerBuildPaths(cwd) {
|
|
4370
2993
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
4371
2994
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
4372
|
-
if (!
|
|
2995
|
+
if (!existsSync12(dockerfilePath)) {
|
|
4373
2996
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
4374
2997
|
}
|
|
4375
2998
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
|
@@ -4498,14 +3121,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
|
|
|
4498
3121
|
import path7 from "node:path";
|
|
4499
3122
|
|
|
4500
3123
|
// src/commands/deploy/internal/minio.ts
|
|
4501
|
-
import { statSync as
|
|
3124
|
+
import { statSync as statSync5 } from "node:fs";
|
|
4502
3125
|
import { readdir, readFile } from "node:fs/promises";
|
|
4503
3126
|
import path6 from "node:path";
|
|
4504
3127
|
import * as Minio from "minio";
|
|
4505
3128
|
var DEFAULT_MAX_FILE_SIZE_MB = 50;
|
|
4506
3129
|
async function isDirectoryPath(dir) {
|
|
4507
3130
|
try {
|
|
4508
|
-
const st =
|
|
3131
|
+
const st = statSync5(dir);
|
|
4509
3132
|
return st.isDirectory();
|
|
4510
3133
|
} catch {
|
|
4511
3134
|
return false;
|
|
@@ -4535,7 +3158,7 @@ async function collectFiles(root) {
|
|
|
4535
3158
|
if (e.isDirectory()) {
|
|
4536
3159
|
await walk(abs, rel);
|
|
4537
3160
|
} else if (e.isFile()) {
|
|
4538
|
-
const st =
|
|
3161
|
+
const st = statSync5(abs);
|
|
4539
3162
|
out.push({
|
|
4540
3163
|
absPath: abs,
|
|
4541
3164
|
relativePath: rel.replace(/\\/g, "/"),
|
|
@@ -4993,7 +3616,7 @@ function registerDeployCommands(program) {
|
|
|
4993
3616
|
function buildProgram() {
|
|
4994
3617
|
const program = new Command();
|
|
4995
3618
|
program.name("apm").description(
|
|
4996
|
-
`\u6BD4\u90BB\u661F\u56FE\u547D\u4EE4\u884C\uFF08\
|
|
3619
|
+
`\u6BD4\u90BB\u661F\u56FE\u547D\u4EE4\u884C\uFF08\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
|
|
4997
3620
|
\u672A\u4F20 --server \u65F6\u4F18\u5148\u4F7F\u7528\u73AF\u5883\u53D8\u91CF AI_PM_SERVER\uFF0C\u5426\u5219\u9ED8\u8BA4 ${DEFAULT_BASE_URL}\u3002`
|
|
4998
3621
|
).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
|
|
4999
3622
|
program.command("login").description(
|
|
@@ -5016,63 +3639,11 @@ function buildProgram() {
|
|
|
5016
3639
|
).action(async () => {
|
|
5017
3640
|
await runUpdateSkills();
|
|
5018
3641
|
});
|
|
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
3642
|
program.command("connect").description(
|
|
5050
|
-
"\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u4E0B\u884C
|
|
3643
|
+
"\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u90E8\u7F72\u4E0B\u884C\u901A\u77E5\uFF1B\u542F\u52A8\u524D\u81EA\u52A8 apm update \u5230\u6700\u65B0\u7248"
|
|
5051
3644
|
).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
|
|
5052
3645
|
await runConnect(opts);
|
|
5053
3646
|
});
|
|
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
3647
|
registerDeployCommands(program);
|
|
5077
3648
|
return program;
|
|
5078
3649
|
}
|