ai-project-manage-cli 6.0.63 → 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 +880 -1843
- package/package.json +2 -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,17 +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({
|
|
436
|
+
method: "PUT",
|
|
437
|
+
path: "/cli/task-deployments/complete"
|
|
438
|
+
}),
|
|
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"
|
|
450
|
+
}),
|
|
451
|
+
upsertDocument: defineEndpoint({
|
|
452
|
+
method: "PUT",
|
|
453
|
+
path: "/cli/documents/upsert"
|
|
454
|
+
}),
|
|
455
|
+
claimMailboxMessage: defineEndpoint({
|
|
456
|
+
method: "PUT",
|
|
457
|
+
path: "/cli/mailbox-messages/claim"
|
|
458
|
+
}),
|
|
459
|
+
completeMailboxMessage: defineEndpoint({
|
|
481
460
|
method: "PUT",
|
|
482
|
-
path: "/cli/
|
|
461
|
+
path: "/cli/mailbox-messages/complete"
|
|
483
462
|
}),
|
|
484
|
-
|
|
463
|
+
upsertCursorLog: defineEndpoint({
|
|
485
464
|
method: "PUT",
|
|
486
|
-
path: "/cli/
|
|
465
|
+
path: "/cli/cursor-logs"
|
|
487
466
|
})
|
|
488
467
|
}
|
|
489
468
|
};
|
|
@@ -519,7 +498,7 @@ async function tryReadGitOriginUrl(cwd) {
|
|
|
519
498
|
|
|
520
499
|
// src/deployment-config-sync.ts
|
|
521
500
|
var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json \u4E0E .apm/deploy/README.md";
|
|
522
|
-
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";
|
|
523
502
|
async function resolveRepositoryIdForSync(api, workdirPath) {
|
|
524
503
|
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
525
504
|
if (baseline.repositoryId) {
|
|
@@ -543,7 +522,7 @@ async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
|
|
|
543
522
|
if (!cfg || !resolveApiKey(cfg)) {
|
|
544
523
|
console.log(
|
|
545
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
|
|
546
|
-
[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`
|
|
547
526
|
);
|
|
548
527
|
return { synced: false, repositoryId: null };
|
|
549
528
|
}
|
|
@@ -564,7 +543,7 @@ ${diagnostic ?? ""}
|
|
|
564
543
|
if (!config) {
|
|
565
544
|
console.log(
|
|
566
545
|
`[apm] \u672A\u627E\u5230\u5173\u8054\u4ED3\u5E93\u7684\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF0CrepositoryId\uFF1A${repositoryId}\uFF09\u3002
|
|
567
|
-
[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`
|
|
568
547
|
);
|
|
569
548
|
return { synced: false, repositoryId };
|
|
570
549
|
}
|
|
@@ -595,13 +574,12 @@ ${diagnostic ?? ""}
|
|
|
595
574
|
|
|
596
575
|
// src/repository-project-documents-sync.ts
|
|
597
576
|
import {
|
|
598
|
-
existsSync as
|
|
577
|
+
existsSync as existsSync3,
|
|
599
578
|
readdirSync as readdirSync2,
|
|
600
579
|
readFileSync as readFileSync3,
|
|
601
580
|
rmSync,
|
|
602
581
|
writeFileSync as writeFileSync4
|
|
603
582
|
} from "fs";
|
|
604
|
-
import { createHash } from "crypto";
|
|
605
583
|
import { dirname as dirname2, join as join4, relative, sep } from "path";
|
|
606
584
|
var MANIFEST_FILE = "manifest.json";
|
|
607
585
|
function projectDocumentsDir(apmRoot) {
|
|
@@ -622,45 +600,19 @@ function normalizeLocalDocumentPath(path10) {
|
|
|
622
600
|
}
|
|
623
601
|
return segments.join("/");
|
|
624
602
|
}
|
|
625
|
-
function hashLocalFileContent(content) {
|
|
626
|
-
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
627
|
-
}
|
|
628
603
|
function readLocalManifest(apmRoot) {
|
|
629
|
-
const
|
|
630
|
-
if (!
|
|
604
|
+
const manifestPath = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
|
|
605
|
+
if (!existsSync3(manifestPath)) {
|
|
631
606
|
return null;
|
|
632
607
|
}
|
|
633
608
|
try {
|
|
634
609
|
return JSON.parse(
|
|
635
|
-
readFileSync3(
|
|
610
|
+
readFileSync3(manifestPath, "utf8")
|
|
636
611
|
);
|
|
637
612
|
} catch {
|
|
638
613
|
return null;
|
|
639
614
|
}
|
|
640
615
|
}
|
|
641
|
-
function listLocalDocumentPaths(apmRoot) {
|
|
642
|
-
const root = projectDocumentsDir(apmRoot);
|
|
643
|
-
if (!existsSync2(root)) {
|
|
644
|
-
return [];
|
|
645
|
-
}
|
|
646
|
-
const paths = [];
|
|
647
|
-
const walk = (dir) => {
|
|
648
|
-
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
649
|
-
const abs = join4(dir, entry.name);
|
|
650
|
-
if (entry.isDirectory()) {
|
|
651
|
-
walk(abs);
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
if (entry.isFile() && entry.name === MANIFEST_FILE) {
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
const rel = relative(root, abs).split(sep).join("/");
|
|
658
|
-
paths.push(rel);
|
|
659
|
-
}
|
|
660
|
-
};
|
|
661
|
-
walk(root);
|
|
662
|
-
return paths.sort();
|
|
663
|
-
}
|
|
664
616
|
function diffManifestPaths(remote, local) {
|
|
665
617
|
const remoteMap = new Map(
|
|
666
618
|
(remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
@@ -739,7 +691,7 @@ ${diagnostic ?? ""}`
|
|
|
739
691
|
let deleted = 0;
|
|
740
692
|
for (const path10 of deleteLocal) {
|
|
741
693
|
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
742
|
-
if (
|
|
694
|
+
if (existsSync3(absPath)) {
|
|
743
695
|
rmSync(absPath, { force: true });
|
|
744
696
|
deleted += 1;
|
|
745
697
|
}
|
|
@@ -760,50 +712,6 @@ ${diagnostic ?? ""}`
|
|
|
760
712
|
deleted
|
|
761
713
|
};
|
|
762
714
|
}
|
|
763
|
-
async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
|
|
764
|
-
const api = createApmApiClient(cfg);
|
|
765
|
-
const { repositoryId } = await resolveRepositoryIdForSync(api, workdirPath);
|
|
766
|
-
if (!repositoryId) {
|
|
767
|
-
return 0;
|
|
768
|
-
}
|
|
769
|
-
const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
|
|
770
|
-
const localPaths = listLocalDocumentPaths(targetApmDir);
|
|
771
|
-
if (localPaths.length === 0) {
|
|
772
|
-
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
773
|
-
return 0;
|
|
774
|
-
}
|
|
775
|
-
const remoteManifest = (await api.cli.getRepositoryProjectDocumentManifest({ repositoryId })).manifest ?? null;
|
|
776
|
-
const remoteHashByPath = new Map(
|
|
777
|
-
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
778
|
-
);
|
|
779
|
-
const remoteDescriptionByPath = new Map(
|
|
780
|
-
(remoteManifest?.documents ?? []).map((doc) => [
|
|
781
|
-
doc.path,
|
|
782
|
-
doc.description
|
|
783
|
-
])
|
|
784
|
-
);
|
|
785
|
-
let synced = 0;
|
|
786
|
-
for (const path10 of localPaths) {
|
|
787
|
-
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
788
|
-
const content = readFileSync3(absPath, "utf8");
|
|
789
|
-
const contentHash = hashLocalFileContent(content);
|
|
790
|
-
if (remoteHashByPath.get(path10) === contentHash) {
|
|
791
|
-
continue;
|
|
792
|
-
}
|
|
793
|
-
await api.cli.upsertRepositoryProjectDocument({
|
|
794
|
-
repositoryId,
|
|
795
|
-
path: path10,
|
|
796
|
-
content,
|
|
797
|
-
description: remoteDescriptionByPath.get(path10) ?? void 0
|
|
798
|
-
});
|
|
799
|
-
synced += 1;
|
|
800
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: ${path10}`);
|
|
801
|
-
}
|
|
802
|
-
if (synced === 0) {
|
|
803
|
-
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
804
|
-
}
|
|
805
|
-
return synced;
|
|
806
|
-
}
|
|
807
715
|
|
|
808
716
|
// src/git-utils.ts
|
|
809
717
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -913,14 +821,13 @@ async function runInit(name) {
|
|
|
913
821
|
console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
|
|
914
822
|
if (syncResult && !syncResult.synced) {
|
|
915
823
|
console.log(
|
|
916
|
-
"[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"
|
|
917
825
|
);
|
|
918
826
|
}
|
|
919
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");
|
|
920
828
|
}
|
|
921
829
|
|
|
922
830
|
// src/commands/login.ts
|
|
923
|
-
import { existsSync as existsSync3 } from "fs";
|
|
924
831
|
import { ApiError } from "listpage-http";
|
|
925
832
|
async function runLogin(opts) {
|
|
926
833
|
const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
@@ -973,500 +880,106 @@ async function runLogin(opts) {
|
|
|
973
880
|
2
|
|
974
881
|
)
|
|
975
882
|
);
|
|
976
|
-
const workdir = resolveWorkdirPath();
|
|
977
|
-
const apmDir = workspaceApmDir(workdir);
|
|
978
|
-
if (existsSync3(apmDir)) {
|
|
979
|
-
await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
980
|
-
}
|
|
981
883
|
}
|
|
982
884
|
|
|
983
|
-
// src/commands/
|
|
984
|
-
import {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
}
|
|
993
|
-
if (/[\s/\\]/.test(id)) {
|
|
994
|
-
throw new Error(
|
|
995
|
-
"[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"
|
|
996
|
-
);
|
|
997
|
-
}
|
|
998
|
-
return `${SESSION_BRANCH_PREFIX}${id}`;
|
|
999
|
-
}
|
|
1000
|
-
function sessionIdFromBranchName(branch) {
|
|
1001
|
-
const name = branch.trim().replace(/^origin\//, "");
|
|
1002
|
-
if (!name.startsWith(SESSION_BRANCH_PREFIX)) {
|
|
1003
|
-
return null;
|
|
1004
|
-
}
|
|
1005
|
-
const sessionId = name.slice(SESSION_BRANCH_PREFIX.length).trim();
|
|
1006
|
-
return sessionId || null;
|
|
1007
|
-
}
|
|
1008
|
-
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() {
|
|
1009
894
|
try {
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
process.stderr.write(stderr);
|
|
1017
|
-
}
|
|
1018
|
-
return stdout;
|
|
1019
|
-
} catch (err) {
|
|
1020
|
-
const e = err;
|
|
1021
|
-
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
1022
|
-
throw new Error(
|
|
1023
|
-
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
1024
|
-
);
|
|
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";
|
|
1025
901
|
}
|
|
1026
902
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
const out = await execGit2(cwd, ["status", "--porcelain"], true);
|
|
1036
|
-
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
|
+
});
|
|
1037
911
|
}
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
["ls-remote", "--heads", "origin", branch],
|
|
1042
|
-
true
|
|
1043
|
-
);
|
|
1044
|
-
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(/\/+$/, "");
|
|
1045
915
|
}
|
|
1046
|
-
async function
|
|
916
|
+
async function fetchLatestPublishedVersion() {
|
|
917
|
+
const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
|
|
1047
918
|
try {
|
|
1048
|
-
await
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
);
|
|
1053
|
-
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;
|
|
1054
923
|
} catch {
|
|
1055
|
-
return
|
|
924
|
+
return null;
|
|
1056
925
|
}
|
|
1057
926
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
return false;
|
|
1062
|
-
}
|
|
1063
|
-
const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
|
|
1064
|
-
await execGit2(cwd, ["add", "-A"]);
|
|
1065
|
-
await execGit2(cwd, ["commit", "-m", commitMessage]);
|
|
1066
|
-
console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
|
|
1067
|
-
return true;
|
|
927
|
+
function npmAvailable() {
|
|
928
|
+
const r = runNpm(["--version"], { encoding: "utf8" });
|
|
929
|
+
return !r.error && r.status === 0;
|
|
1068
930
|
}
|
|
1069
|
-
async function
|
|
1070
|
-
const
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
`[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`
|
|
1076
|
-
);
|
|
1077
|
-
}
|
|
1078
|
-
const current = await getCurrentBranch(cwd);
|
|
1079
|
-
const dirty = await isWorkingTreeDirty(cwd);
|
|
1080
|
-
if (dirty) {
|
|
1081
|
-
if (current === branch) {
|
|
1082
|
-
await commitWorkingTreeIfDirty(cwd, commitMessage);
|
|
1083
|
-
} else {
|
|
1084
|
-
await execGit2(cwd, [
|
|
1085
|
-
"stash",
|
|
1086
|
-
"push",
|
|
1087
|
-
"-u",
|
|
1088
|
-
"-m",
|
|
1089
|
-
`apm: switch to ${branch}`
|
|
1090
|
-
]);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
const onTargetBranch = await getCurrentBranch(cwd) === branch;
|
|
1094
|
-
if (onTargetBranch) {
|
|
1095
|
-
await execGit2(cwd, ["fetch", "origin", baselineBranch]);
|
|
1096
|
-
await execGit2(cwd, ["merge", `origin/${baselineBranch}`, "--no-edit"]);
|
|
1097
|
-
} else {
|
|
1098
|
-
const remoteExists = await remoteHeadBranchExists(cwd, branch);
|
|
1099
|
-
if (remoteExists) {
|
|
1100
|
-
await execGit2(cwd, ["fetch", "origin", branch]);
|
|
1101
|
-
await execGit2(cwd, ["checkout", "-B", branch, `origin/${branch}`]);
|
|
1102
|
-
} else if (await localBranchExists(cwd, branch)) {
|
|
1103
|
-
if (await getCurrentBranch(cwd) !== branch) {
|
|
1104
|
-
await execGit2(cwd, ["checkout", branch]);
|
|
1105
|
-
}
|
|
1106
|
-
console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
|
|
1107
|
-
} else {
|
|
1108
|
-
await execGit2(cwd, ["fetch", "origin", baselineBranch]);
|
|
1109
|
-
try {
|
|
1110
|
-
await execGit2(cwd, [
|
|
1111
|
-
"checkout",
|
|
1112
|
-
"-b",
|
|
1113
|
-
branch,
|
|
1114
|
-
`origin/${baselineBranch}`
|
|
1115
|
-
]);
|
|
1116
|
-
await execGit2(cwd, ["push", "-u", "origin", branch]);
|
|
1117
|
-
} catch (err) {
|
|
1118
|
-
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
|
-
throw err;
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
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 };
|
|
1128
937
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
const trimmedSessionId = sessionId.trim();
|
|
1134
|
-
if (!trimmedSessionId) {
|
|
1135
|
-
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
|
+
);
|
|
1136
942
|
process.exit(1);
|
|
1137
943
|
}
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
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"
|
|
1145
950
|
});
|
|
1146
|
-
if (
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
throw new Error(`[apm] ${detail}`);
|
|
951
|
+
if (install.error) {
|
|
952
|
+
console.error("[apm] \u66F4\u65B0\u5931\u8D25:", install.error.message);
|
|
953
|
+
process.exit(1);
|
|
1150
954
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
|
|
955
|
+
if (install.status !== 0) {
|
|
956
|
+
process.exit(install.status ?? 1);
|
|
1154
957
|
}
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// src/commands/clean-branches.ts
|
|
1160
|
-
import { execFile as execFile4 } from "child_process";
|
|
1161
|
-
import { promisify as promisify4 } from "util";
|
|
1162
|
-
var execFileAsync4 = promisify4(execFile4);
|
|
1163
|
-
async function execGit3(cwd, args, quiet) {
|
|
1164
|
-
try {
|
|
1165
|
-
const { stdout, stderr } = await execFileAsync4("git", args, {
|
|
1166
|
-
cwd,
|
|
1167
|
-
encoding: "utf8",
|
|
1168
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1169
|
-
});
|
|
1170
|
-
if (!quiet && stderr.trim()) {
|
|
1171
|
-
process.stderr.write(stderr);
|
|
1172
|
-
}
|
|
1173
|
-
return stdout;
|
|
1174
|
-
} catch (err) {
|
|
1175
|
-
const e = err;
|
|
1176
|
-
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
1177
|
-
throw new Error(
|
|
1178
|
-
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
1179
|
-
);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
async function ensureGitRepo2(cwd) {
|
|
1183
|
-
await execGit3(cwd, ["rev-parse", "--git-dir"], true);
|
|
1184
|
-
}
|
|
1185
|
-
async function getCurrentBranch2(cwd) {
|
|
1186
|
-
return (await execGit3(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
1187
|
-
}
|
|
1188
|
-
async function resolveDefaultBranch(cwd) {
|
|
1189
|
-
try {
|
|
1190
|
-
const ref = (await execGit3(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], true)).trim();
|
|
1191
|
-
const match = ref.match(/^refs\/remotes\/origin\/(.+)$/);
|
|
1192
|
-
if (match?.[1]) {
|
|
1193
|
-
return match[1];
|
|
1194
|
-
}
|
|
1195
|
-
} catch {
|
|
1196
|
-
}
|
|
1197
|
-
const out = await execGit3(cwd, ["remote", "show", "origin"], true);
|
|
1198
|
-
const headLine = out.split(/\r?\n/).find((line) => line.includes("HEAD branch"));
|
|
1199
|
-
const branch = headLine?.split(":").pop()?.trim();
|
|
1200
|
-
if (branch) {
|
|
1201
|
-
return branch;
|
|
1202
|
-
}
|
|
1203
|
-
throw new Error("[apm] \u65E0\u6CD5\u89E3\u6790 origin \u9ED8\u8BA4\u5206\u652F\uFF0C\u8BF7\u5148\u6267\u884C git fetch origin");
|
|
1204
|
-
}
|
|
1205
|
-
async function listLocalSessionBranches(cwd) {
|
|
1206
|
-
const out = await execGit3(
|
|
1207
|
-
cwd,
|
|
1208
|
-
[
|
|
1209
|
-
"for-each-ref",
|
|
1210
|
-
"--format=%(refname:short)",
|
|
1211
|
-
"refs/heads/",
|
|
1212
|
-
SESSION_BRANCH_PREFIX + "*"
|
|
1213
|
-
],
|
|
1214
|
-
true
|
|
1215
|
-
);
|
|
1216
|
-
return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1217
|
-
}
|
|
1218
|
-
async function listRemoteSessionBranches(cwd) {
|
|
1219
|
-
const out = await execGit3(
|
|
1220
|
-
cwd,
|
|
1221
|
-
[
|
|
1222
|
-
"for-each-ref",
|
|
1223
|
-
"--format=%(refname:short)",
|
|
1224
|
-
"refs/remotes/origin/",
|
|
1225
|
-
SESSION_BRANCH_PREFIX + "*"
|
|
1226
|
-
],
|
|
1227
|
-
true
|
|
1228
|
-
);
|
|
1229
|
-
return out.split(/\r?\n/).map((line) => line.trim().replace(/^origin\//, "")).filter(Boolean);
|
|
1230
|
-
}
|
|
1231
|
-
async function localBranchExists2(cwd, branch) {
|
|
1232
|
-
try {
|
|
1233
|
-
await execGit3(
|
|
1234
|
-
cwd,
|
|
1235
|
-
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
1236
|
-
true
|
|
1237
|
-
);
|
|
1238
|
-
return true;
|
|
1239
|
-
} catch {
|
|
1240
|
-
return false;
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
async function remoteBranchExists(cwd, branch) {
|
|
1244
|
-
const out = await execGit3(
|
|
1245
|
-
cwd,
|
|
1246
|
-
["ls-remote", "--heads", "origin", branch],
|
|
1247
|
-
true
|
|
1248
|
-
);
|
|
1249
|
-
return out.trim().length > 0;
|
|
1250
|
-
}
|
|
1251
|
-
function reasonForCleanup(sessionId, sessionStatusById) {
|
|
1252
|
-
if (!sessionStatusById.has(sessionId)) {
|
|
1253
|
-
return "\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D";
|
|
1254
|
-
}
|
|
1255
|
-
if (sessionStatusById.get(sessionId) === "COMPLETED") {
|
|
1256
|
-
return "\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210";
|
|
1257
|
-
}
|
|
1258
|
-
return "\u4FDD\u7559";
|
|
1259
|
-
}
|
|
1260
|
-
async function isBranchMergedIntoDefault(cwd, branch, defaultBranch) {
|
|
1261
|
-
const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
|
|
1262
|
-
try {
|
|
1263
|
-
await execGit3(
|
|
1264
|
-
cwd,
|
|
1265
|
-
["merge-base", "--is-ancestor", ref, `origin/${defaultBranch}`],
|
|
1266
|
-
true
|
|
1267
|
-
);
|
|
1268
|
-
return true;
|
|
1269
|
-
} catch {
|
|
1270
|
-
return false;
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
async function runCleanBranches(options = {}) {
|
|
1274
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1275
|
-
const dryRun = options.dryRun ?? false;
|
|
1276
|
-
await ensureGitRepo2(cwd);
|
|
1277
|
-
await execGit3(cwd, ["fetch", "--prune", "origin"], true);
|
|
1278
|
-
const cfg = await ensureLoggedConfig();
|
|
1279
|
-
const api = createApmApiClient(cfg);
|
|
1280
|
-
const { sessions } = await api.cli.listSessionsForBranchCleanup({});
|
|
1281
|
-
const sessionStatusById = new Map(
|
|
1282
|
-
sessions.map((item) => [item.sessionId, item.taskStatus])
|
|
1283
|
-
);
|
|
1284
|
-
const branchNames = /* @__PURE__ */ new Set([
|
|
1285
|
-
...await listLocalSessionBranches(cwd),
|
|
1286
|
-
...await listRemoteSessionBranches(cwd)
|
|
1287
|
-
]);
|
|
1288
|
-
if (branchNames.size === 0) {
|
|
1289
|
-
console.log("[apm] \u672A\u53D1\u73B0 feat/session-* \u5206\u652F");
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
const toDelete = [...branchNames].map((branch) => {
|
|
1293
|
-
const sessionId = sessionIdFromBranchName(branch);
|
|
1294
|
-
if (!sessionId) {
|
|
1295
|
-
return null;
|
|
1296
|
-
}
|
|
1297
|
-
const reason = reasonForCleanup(sessionId, sessionStatusById);
|
|
1298
|
-
if (reason === "\u4FDD\u7559") {
|
|
1299
|
-
return null;
|
|
1300
|
-
}
|
|
1301
|
-
return { branch, sessionId, reason };
|
|
1302
|
-
}).filter((item) => item != null).sort((a, b) => a.branch.localeCompare(b.branch));
|
|
1303
|
-
if (toDelete.length === 0) {
|
|
1304
|
-
console.log("[apm] \u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684 feat/session-* \u5206\u652F");
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
let currentBranch = await getCurrentBranch2(cwd);
|
|
1308
|
-
let defaultBranch = null;
|
|
1309
|
-
if (dryRun) {
|
|
1310
|
-
defaultBranch = await resolveDefaultBranch(cwd);
|
|
1311
|
-
}
|
|
1312
|
-
for (const item of toDelete) {
|
|
1313
|
-
const { branch, sessionId, reason } = item;
|
|
1314
|
-
const label = `${branch} (${sessionId}: ${reason})`;
|
|
1315
|
-
if (dryRun) {
|
|
1316
|
-
const merged = await isBranchMergedIntoDefault(
|
|
1317
|
-
cwd,
|
|
1318
|
-
branch,
|
|
1319
|
-
defaultBranch
|
|
1320
|
-
);
|
|
1321
|
-
const mergeTag = merged ? "\u5DF2\u5408\u5E76" : "\u672A\u5408\u5E76";
|
|
1322
|
-
console.log(`[apm] [dry-run] \u5C06\u5220\u9664 ${label} [${mergeTag}]`);
|
|
1323
|
-
continue;
|
|
1324
|
-
}
|
|
1325
|
-
if (currentBranch === branch) {
|
|
1326
|
-
defaultBranch ??= await resolveDefaultBranch(cwd);
|
|
1327
|
-
await execGit3(cwd, ["checkout", defaultBranch], true);
|
|
1328
|
-
currentBranch = defaultBranch;
|
|
1329
|
-
}
|
|
1330
|
-
if (await localBranchExists2(cwd, branch)) {
|
|
1331
|
-
await execGit3(cwd, ["branch", "-D", branch], true);
|
|
1332
|
-
console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
|
|
1333
|
-
}
|
|
1334
|
-
if (await remoteBranchExists(cwd, branch)) {
|
|
1335
|
-
await execGit3(cwd, ["push", "origin", "--delete", branch], true);
|
|
1336
|
-
console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
if (dryRun) {
|
|
1340
|
-
console.log(`[apm] [dry-run] \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`);
|
|
958
|
+
const after = readCliVersion();
|
|
959
|
+
if (latest && after === latest) {
|
|
960
|
+
console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
|
|
1341
961
|
} else {
|
|
1342
|
-
console.log(
|
|
1343
|
-
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
// src/commands/pull.ts
|
|
1347
|
-
import { writeFileSync as writeFileSync9 } from "fs";
|
|
1348
|
-
import { join as join9 } from "path";
|
|
1349
|
-
import { stringify as yamlStringify } from "yaml";
|
|
1350
|
-
|
|
1351
|
-
// src/session-messages-xml.ts
|
|
1352
|
-
function asXmlText(value) {
|
|
1353
|
-
return value ?? "";
|
|
1354
|
-
}
|
|
1355
|
-
function escapeXmlAttr(value) {
|
|
1356
|
-
return asXmlText(value).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
1357
|
-
}
|
|
1358
|
-
function wrapCdata(value) {
|
|
1359
|
-
return `<![CDATA[${asXmlText(value).replace(/]]>/g, "]]]]><![CDATA[>")}]]>`;
|
|
1360
|
-
}
|
|
1361
|
-
function formatSessionMessagesXml(sessionId, messages) {
|
|
1362
|
-
const lines = [
|
|
1363
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
1364
|
-
`<messages sessionId="${escapeXmlAttr(sessionId)}">`
|
|
1365
|
-
];
|
|
1366
|
-
for (const message of messages) {
|
|
1367
|
-
const roundAttr = message.round != null && message.round > 0 ? ` round="${message.round}"` : "";
|
|
1368
|
-
lines.push(
|
|
1369
|
-
` <message id="${escapeXmlAttr(message.id)}" name="${escapeXmlAttr(
|
|
1370
|
-
message.name
|
|
1371
|
-
)}" agent="${escapeXmlAttr(message.oxcAgent)}"${roundAttr}>`,
|
|
1372
|
-
` <content>${wrapCdata(message.content)}</content>`,
|
|
1373
|
-
" </message>"
|
|
1374
|
-
);
|
|
1375
|
-
}
|
|
1376
|
-
lines.push("</messages>", "");
|
|
1377
|
-
return lines.join("\n");
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
// src/commands/sync-session-attachments.ts
|
|
1381
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
1382
|
-
import { join as join6 } from "path";
|
|
1383
|
-
var MANIFEST_FILE2 = ".sync-manifest.json";
|
|
1384
|
-
async function downloadAttachment(cfg, attachmentId) {
|
|
1385
|
-
const base = cfg.baseUrl.trim().replace(/\/+$/, "");
|
|
1386
|
-
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({ attachmentId })}`;
|
|
1387
|
-
const res = await fetch(url);
|
|
1388
|
-
if (!res.ok) {
|
|
1389
|
-
throw new Error(
|
|
1390
|
-
`[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
|
|
1391
|
-
);
|
|
1392
|
-
}
|
|
1393
|
-
return Buffer.from(await res.arrayBuffer());
|
|
1394
|
-
}
|
|
1395
|
-
function loadManifest(dir) {
|
|
1396
|
-
const path10 = join6(dir, MANIFEST_FILE2);
|
|
1397
|
-
if (!existsSync4(path10)) {
|
|
1398
|
-
return { version: 1, attachments: {} };
|
|
1399
|
-
}
|
|
1400
|
-
try {
|
|
1401
|
-
const parsed = JSON.parse(
|
|
1402
|
-
readFileSync5(path10, "utf8")
|
|
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`
|
|
1403
964
|
);
|
|
1404
|
-
if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
|
|
1405
|
-
return parsed;
|
|
1406
|
-
}
|
|
1407
|
-
} catch {
|
|
1408
965
|
}
|
|
1409
|
-
return {
|
|
1410
|
-
}
|
|
1411
|
-
function saveManifest(dir, manifest) {
|
|
1412
|
-
writeFileSync6(
|
|
1413
|
-
join6(dir, MANIFEST_FILE2),
|
|
1414
|
-
`${JSON.stringify(manifest, null, 2)}
|
|
1415
|
-
`,
|
|
1416
|
-
"utf8"
|
|
1417
|
-
);
|
|
1418
|
-
}
|
|
1419
|
-
function isAttachmentUpToDate(entry, item, dest) {
|
|
1420
|
-
if (!entry || !existsSync4(dest)) return false;
|
|
1421
|
-
if (entry.name !== item.name) return false;
|
|
1422
|
-
const createdAt = item.createdAt ?? "";
|
|
1423
|
-
return entry.createdAt === createdAt;
|
|
1424
|
-
}
|
|
1425
|
-
async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
1426
|
-
const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
|
|
1427
|
-
await ensureDirExists(dir);
|
|
1428
|
-
if (attachments.length === 0) {
|
|
1429
|
-
saveManifest(dir, { version: 1, attachments: {} });
|
|
1430
|
-
return;
|
|
1431
|
-
}
|
|
1432
|
-
const manifest = loadManifest(dir);
|
|
1433
|
-
const nextManifest = { version: 1, attachments: {} };
|
|
1434
|
-
for (const item of attachments) {
|
|
1435
|
-
const dest = join6(dir, item.name);
|
|
1436
|
-
const entry = manifest.attachments[item.id];
|
|
1437
|
-
const createdAt = item.createdAt ?? "";
|
|
1438
|
-
if (isAttachmentUpToDate(entry, item, dest)) {
|
|
1439
|
-
nextManifest.attachments[item.id] = entry;
|
|
1440
|
-
console.log(
|
|
1441
|
-
`[apm] \u9644\u4EF6\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
|
|
1442
|
-
);
|
|
1443
|
-
continue;
|
|
1444
|
-
}
|
|
1445
|
-
const buffer = await downloadAttachment(cfg, item.id);
|
|
1446
|
-
writeFileSync6(dest, buffer);
|
|
1447
|
-
nextManifest.attachments[item.id] = {
|
|
1448
|
-
name: item.name,
|
|
1449
|
-
createdAt
|
|
1450
|
-
};
|
|
1451
|
-
console.log(`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`);
|
|
1452
|
-
}
|
|
1453
|
-
saveManifest(dir, nextManifest);
|
|
966
|
+
return { didUpdate: true };
|
|
1454
967
|
}
|
|
1455
968
|
|
|
1456
|
-
// src/
|
|
1457
|
-
import {
|
|
1458
|
-
import {
|
|
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";
|
|
1459
972
|
|
|
1460
973
|
// src/skills-sync.ts
|
|
1461
974
|
import {
|
|
1462
975
|
copyFileSync as copyFileSync2,
|
|
1463
976
|
cpSync,
|
|
1464
|
-
existsSync as
|
|
977
|
+
existsSync as existsSync4,
|
|
1465
978
|
mkdirSync as mkdirSync3,
|
|
1466
979
|
readdirSync as readdirSync3,
|
|
1467
980
|
rmSync as rmSync2,
|
|
1468
981
|
statSync as statSync2,
|
|
1469
|
-
writeFileSync as
|
|
982
|
+
writeFileSync as writeFileSync6
|
|
1470
983
|
} from "fs";
|
|
1471
984
|
import { join as join7 } from "path";
|
|
1472
985
|
var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
|
|
@@ -1478,20 +991,20 @@ function sanitizeSkillDirName(name) {
|
|
|
1478
991
|
return trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
1479
992
|
}
|
|
1480
993
|
function listBaseSkillDirNames() {
|
|
1481
|
-
if (!
|
|
994
|
+
if (!existsSync4(BASE_SKILLS_TEMPLATE_DIR)) return [];
|
|
1482
995
|
return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
|
|
1483
996
|
const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
|
|
1484
997
|
return statSync2(path10).isDirectory();
|
|
1485
998
|
});
|
|
1486
999
|
}
|
|
1487
1000
|
function syncAgentsGuide(apmDir) {
|
|
1488
|
-
if (!
|
|
1001
|
+
if (!existsSync4(AGENTS_TEMPLATE_PATH)) return false;
|
|
1489
1002
|
mkdirSync3(apmDir, { recursive: true });
|
|
1490
1003
|
copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
|
|
1491
1004
|
return true;
|
|
1492
1005
|
}
|
|
1493
1006
|
function listBaseRuleFileNames() {
|
|
1494
|
-
if (!
|
|
1007
|
+
if (!existsSync4(BASE_RULES_TEMPLATE_DIR)) return [];
|
|
1495
1008
|
return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
|
|
1496
1009
|
const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
|
|
1497
1010
|
return statSync2(path10).isFile();
|
|
@@ -1531,523 +1044,80 @@ function syncSupplementarySkills(skillsDir, list) {
|
|
|
1531
1044
|
}
|
|
1532
1045
|
const skillDir = join7(skillsDir, dirName);
|
|
1533
1046
|
mkdirSync3(skillDir, { recursive: true });
|
|
1534
|
-
|
|
1047
|
+
writeFileSync6(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
|
|
1535
1048
|
written.push(dirName);
|
|
1536
1049
|
}
|
|
1537
1050
|
const removed = [];
|
|
1538
|
-
if (!
|
|
1051
|
+
if (!existsSync4(skillsDir)) return { written, skipped, removed };
|
|
1539
1052
|
for (const entry of readdirSync3(skillsDir)) {
|
|
1540
1053
|
const full = join7(skillsDir, entry);
|
|
1541
1054
|
if (!statSync2(full).isDirectory()) continue;
|
|
1542
1055
|
if (baseNames.has(entry)) continue;
|
|
1543
|
-
if (apiDirNames.has(entry)) continue;
|
|
1544
|
-
rmSync2(full, { recursive: true, force: true });
|
|
1545
|
-
removed.push(entry);
|
|
1546
|
-
}
|
|
1547
|
-
return { written, skipped, removed };
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
// src/rules-sync.ts
|
|
1551
|
-
var MANIFEST_FILE3 = ".rules-sync-manifest.json";
|
|
1552
|
-
function ruleLocalFileName(ruleName) {
|
|
1553
|
-
const trimmed = ruleName.trim();
|
|
1554
|
-
if (!trimmed) return "rule.md";
|
|
1555
|
-
const sanitized = trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
1556
|
-
if (extname2(sanitized).toLowerCase() === ".md") return sanitized;
|
|
1557
|
-
return `${sanitized}.md`;
|
|
1558
|
-
}
|
|
1559
|
-
function loadManifest2(rulesDir) {
|
|
1560
|
-
const path10 = join8(rulesDir, MANIFEST_FILE3);
|
|
1561
|
-
if (!existsSync6(toFsPath(path10))) {
|
|
1562
|
-
return { version: 1, rules: {} };
|
|
1563
|
-
}
|
|
1564
|
-
try {
|
|
1565
|
-
const parsed = JSON.parse(
|
|
1566
|
-
readFileSync6(toFsPath(path10), "utf8")
|
|
1567
|
-
);
|
|
1568
|
-
if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
|
|
1569
|
-
return parsed;
|
|
1570
|
-
}
|
|
1571
|
-
} catch {
|
|
1572
|
-
}
|
|
1573
|
-
return { version: 1, rules: {} };
|
|
1574
|
-
}
|
|
1575
|
-
function saveManifest2(rulesDir, manifest) {
|
|
1576
|
-
writeFileSync8(
|
|
1577
|
-
toFsPath(join8(rulesDir, MANIFEST_FILE3)),
|
|
1578
|
-
`${JSON.stringify(manifest, null, 2)}
|
|
1579
|
-
`,
|
|
1580
|
-
"utf8"
|
|
1581
|
-
);
|
|
1582
|
-
}
|
|
1583
|
-
function isBaseRuleFileName(fileName) {
|
|
1584
|
-
return listBaseRuleFileNames().includes(basename2(fileName));
|
|
1585
|
-
}
|
|
1586
|
-
function isRuleUpToDate(entry, rule, dest) {
|
|
1587
|
-
if (!entry || !existsSync6(toFsPath(dest))) return false;
|
|
1588
|
-
if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
|
|
1589
|
-
const updatedAt = rule.updatedAt ?? "";
|
|
1590
|
-
if (entry.updatedAt !== updatedAt) return false;
|
|
1591
|
-
const localContent = readFileSync6(toFsPath(dest), "utf8");
|
|
1592
|
-
return localContent === (rule.content ?? "");
|
|
1593
|
-
}
|
|
1594
|
-
async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
1595
|
-
const api = createApmApiClient(cfg);
|
|
1596
|
-
const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
|
|
1597
|
-
const repositoryId = baseline.repositoryId;
|
|
1598
|
-
const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
|
|
1599
|
-
await ensureDirExists(rulesDir);
|
|
1600
|
-
if (!repositoryId) {
|
|
1601
|
-
console.log(
|
|
1602
|
-
`[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`
|
|
1603
|
-
);
|
|
1604
|
-
return { written: [], skipped: [], removed: [], repositoryId: null };
|
|
1605
|
-
}
|
|
1606
|
-
const { list } = await api.cli.listRules({ repositoryId });
|
|
1607
|
-
const manifest = loadManifest2(rulesDir);
|
|
1608
|
-
const nextManifest = { version: 1, rules: {} };
|
|
1609
|
-
const remoteIds = /* @__PURE__ */ new Set();
|
|
1610
|
-
const written = [];
|
|
1611
|
-
const skipped = [];
|
|
1612
|
-
for (const rule of list) {
|
|
1613
|
-
remoteIds.add(rule.id);
|
|
1614
|
-
const fileName = ruleLocalFileName(rule.name);
|
|
1615
|
-
const dest = join8(rulesDir, fileName);
|
|
1616
|
-
const entry = manifest.rules[rule.id];
|
|
1617
|
-
const updatedAt = rule.updatedAt ?? "";
|
|
1618
|
-
if (isRuleUpToDate(entry, rule, dest)) {
|
|
1619
|
-
nextManifest.rules[rule.id] = entry;
|
|
1620
|
-
skipped.push(fileName);
|
|
1621
|
-
console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
|
|
1622
|
-
continue;
|
|
1623
|
-
}
|
|
1624
|
-
writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
|
|
1625
|
-
nextManifest.rules[rule.id] = { fileName, updatedAt };
|
|
1626
|
-
written.push(fileName);
|
|
1627
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
|
|
1628
|
-
}
|
|
1629
|
-
const removed = [];
|
|
1630
|
-
for (const [ruleId, entry] of Object.entries(manifest.rules)) {
|
|
1631
|
-
if (remoteIds.has(ruleId)) continue;
|
|
1632
|
-
if (isBaseRuleFileName(entry.fileName)) continue;
|
|
1633
|
-
const dest = join8(rulesDir, entry.fileName);
|
|
1634
|
-
if (existsSync6(toFsPath(dest))) {
|
|
1635
|
-
rmSync3(toFsPath(dest), { force: true });
|
|
1636
|
-
}
|
|
1637
|
-
removed.push(entry.fileName);
|
|
1638
|
-
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
|
|
1639
|
-
}
|
|
1640
|
-
saveManifest2(rulesDir, nextManifest);
|
|
1641
|
-
return { written, skipped, removed, repositoryId };
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
// src/commands/pull.ts
|
|
1645
|
-
async function runPull(sessionId, remoteWorkdir) {
|
|
1646
|
-
const trimmedId = sessionId.trim();
|
|
1647
|
-
if (!trimmedId) {
|
|
1648
|
-
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1649
|
-
process.exit(1);
|
|
1650
|
-
}
|
|
1651
|
-
const cfg = await ensureLoggedConfig();
|
|
1652
|
-
const api = createApmApiClient(cfg);
|
|
1653
|
-
const workdir = remoteWorkdir === void 0 ? resolveWorkdirPath() : requireRemoteWorkdir(remoteWorkdir);
|
|
1654
|
-
const apmRoot = workspaceApmDir(workdir);
|
|
1655
|
-
const [detail, members, documents, attachments, messages] = await Promise.all(
|
|
1656
|
-
[
|
|
1657
|
-
api.cli.sessionDetail({ sessionId: trimmedId }),
|
|
1658
|
-
api.cli.sessionMembers({ sessionId: trimmedId }),
|
|
1659
|
-
api.cli.listDocuments({ sessionId: trimmedId }),
|
|
1660
|
-
api.cli.listAttachments({ sessionId: trimmedId }),
|
|
1661
|
-
api.cli.listSessionMessages({ sessionId: trimmedId })
|
|
1662
|
-
]
|
|
1663
|
-
);
|
|
1664
|
-
const dir = sessionDir(trimmedId, apmRoot);
|
|
1665
|
-
const docsDir = sessionDocsDir(trimmedId, apmRoot);
|
|
1666
|
-
await ensureDirExists(docsDir);
|
|
1667
|
-
writeFileSync9(
|
|
1668
|
-
sessionRulePath(trimmedId, apmRoot),
|
|
1669
|
-
detail.description ?? "",
|
|
1670
|
-
"utf8"
|
|
1671
|
-
);
|
|
1672
|
-
writeFileSync9(
|
|
1673
|
-
sessionTaskPath(trimmedId, apmRoot),
|
|
1674
|
-
detail.task.description ?? "",
|
|
1675
|
-
"utf8"
|
|
1676
|
-
);
|
|
1677
|
-
writeFileSync9(sessionTodoPath(trimmedId, apmRoot), detail.todo ?? "", "utf8");
|
|
1678
|
-
for (const doc of documents) {
|
|
1679
|
-
const fileName = documentLocalFileName(doc.name);
|
|
1680
|
-
writeFileSync9(join9(docsDir, fileName), doc.content ?? "", "utf8");
|
|
1681
|
-
}
|
|
1682
|
-
const sessionYaml = yamlStringify(
|
|
1683
|
-
{
|
|
1684
|
-
name: detail.title,
|
|
1685
|
-
task: "./TASK.md",
|
|
1686
|
-
todo: "./TODO.md",
|
|
1687
|
-
rule: "./RULE.md",
|
|
1688
|
-
members: members.map((m) => ({
|
|
1689
|
-
name: m.displayName,
|
|
1690
|
-
oxcAgent: m.oxcAgent,
|
|
1691
|
-
description: m.description ?? ""
|
|
1692
|
-
})),
|
|
1693
|
-
attachments: attachments.map((a) => ({ name: a.name }))
|
|
1694
|
-
},
|
|
1695
|
-
{ lineWidth: 0 }
|
|
1696
|
-
);
|
|
1697
|
-
writeFileSync9(
|
|
1698
|
-
sessionYamlPath(trimmedId, apmRoot),
|
|
1699
|
-
sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
|
|
1700
|
-
`,
|
|
1701
|
-
"utf8"
|
|
1702
|
-
);
|
|
1703
|
-
writeFileSync9(
|
|
1704
|
-
sessionMessagesXmlPath(trimmedId, apmRoot),
|
|
1705
|
-
formatSessionMessagesXml(trimmedId, messages),
|
|
1706
|
-
"utf8"
|
|
1707
|
-
);
|
|
1708
|
-
await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
|
|
1709
|
-
await syncPlatformRules(cfg, trimmedId, workdir, apmRoot);
|
|
1710
|
-
await syncRemoteDeploymentConfig(workdir, apmRoot);
|
|
1711
|
-
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
1712
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
|
|
1713
|
-
return dir;
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
// src/commands/update.ts
|
|
1717
|
-
import { spawnSync } from "child_process";
|
|
1718
|
-
|
|
1719
|
-
// src/version.ts
|
|
1720
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
1721
|
-
import { dirname as dirname3, join as join10 } from "path";
|
|
1722
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1723
|
-
var CLI_PACKAGE_NAME = "ai-project-manage-cli";
|
|
1724
|
-
function readCliVersion() {
|
|
1725
|
-
try {
|
|
1726
|
-
const dir = dirname3(fileURLToPath2(import.meta.url));
|
|
1727
|
-
const pkgPath = join10(dir, "..", "package.json");
|
|
1728
|
-
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
1729
|
-
return pkg.version ?? "0.0.0";
|
|
1730
|
-
} catch {
|
|
1731
|
-
return "0.0.0";
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
// src/commands/update.ts
|
|
1736
|
-
var useNpmShell = process.platform === "win32";
|
|
1737
|
-
function runNpm(args, options = {}) {
|
|
1738
|
-
return spawnSync(useNpmShell ? "npm.cmd" : "npm", args, {
|
|
1739
|
-
...options,
|
|
1740
|
-
shell: useNpmShell
|
|
1741
|
-
});
|
|
1742
|
-
}
|
|
1743
|
-
function registryBaseUrl() {
|
|
1744
|
-
const fromEnv = process.env.npm_config_registry?.trim() || process.env.NPM_CONFIG_REGISTRY?.trim();
|
|
1745
|
-
return (fromEnv || "https://registry.npmjs.org").replace(/\/+$/, "");
|
|
1746
|
-
}
|
|
1747
|
-
async function fetchLatestPublishedVersion() {
|
|
1748
|
-
const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
|
|
1749
|
-
try {
|
|
1750
|
-
const res = await fetch(url);
|
|
1751
|
-
if (!res.ok) return null;
|
|
1752
|
-
const data = await res.json();
|
|
1753
|
-
return data.version?.trim() || null;
|
|
1754
|
-
} catch {
|
|
1755
|
-
return null;
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
function npmAvailable() {
|
|
1759
|
-
const r = runNpm(["--version"], { encoding: "utf8" });
|
|
1760
|
-
return !r.error && r.status === 0;
|
|
1761
|
-
}
|
|
1762
|
-
async function runUpdate() {
|
|
1763
|
-
const current = readCliVersion();
|
|
1764
|
-
const latest = await fetchLatestPublishedVersion();
|
|
1765
|
-
if (latest && current === latest) {
|
|
1766
|
-
console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
|
|
1767
|
-
return { didUpdate: false };
|
|
1768
|
-
}
|
|
1769
|
-
if (!npmAvailable()) {
|
|
1770
|
-
console.error(
|
|
1771
|
-
`[apm] \u672A\u627E\u5230 npm\u3002\u8BF7\u5B89\u88C5 Node.js \u540E\u6267\u884C\uFF1Anpm install -g ${CLI_PACKAGE_NAME}@latest`
|
|
1772
|
-
);
|
|
1773
|
-
process.exit(1);
|
|
1774
|
-
}
|
|
1775
|
-
const targetLabel = latest ?? "latest";
|
|
1776
|
-
console.error(
|
|
1777
|
-
`[apm] \u5F53\u524D\u7248\u672C ${current}\uFF0C\u6B63\u5728\u5B89\u88C5 ${CLI_PACKAGE_NAME}@${targetLabel} \u2026`
|
|
1778
|
-
);
|
|
1779
|
-
const install = runNpm(["install", "-g", `${CLI_PACKAGE_NAME}@latest`], {
|
|
1780
|
-
stdio: "inherit"
|
|
1781
|
-
});
|
|
1782
|
-
if (install.error) {
|
|
1783
|
-
console.error("[apm] \u66F4\u65B0\u5931\u8D25:", install.error.message);
|
|
1784
|
-
process.exit(1);
|
|
1785
|
-
}
|
|
1786
|
-
if (install.status !== 0) {
|
|
1787
|
-
process.exit(install.status ?? 1);
|
|
1788
|
-
}
|
|
1789
|
-
const after = readCliVersion();
|
|
1790
|
-
if (latest && after === latest) {
|
|
1791
|
-
console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
|
|
1792
|
-
} else {
|
|
1793
|
-
console.log(
|
|
1794
|
-
`[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`
|
|
1795
|
-
);
|
|
1796
|
-
}
|
|
1797
|
-
return { didUpdate: true };
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
// src/commands/update-skills.ts
|
|
1801
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
|
|
1802
|
-
import { join as join11 } from "path";
|
|
1803
|
-
async function syncWorkspaceSkills(cfg, workdir) {
|
|
1804
|
-
const apmDir = workspaceApmDir(workdir);
|
|
1805
|
-
const fsApmDir = toFsPath(apmDir);
|
|
1806
|
-
if (!existsSync7(fsApmDir)) {
|
|
1807
|
-
throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1808
|
-
}
|
|
1809
|
-
const apmStat = statSync3(fsApmDir);
|
|
1810
|
-
if (!apmStat.isDirectory()) {
|
|
1811
|
-
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
1812
|
-
}
|
|
1813
|
-
const api = createApmApiClient(cfg);
|
|
1814
|
-
const { list } = await api.cli.listSkills({});
|
|
1815
|
-
if (syncAgentsGuide(apmDir)) {
|
|
1816
|
-
console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
|
|
1817
|
-
}
|
|
1818
|
-
const rulesDir = join11(apmDir, "rules");
|
|
1819
|
-
const ruleNames = syncBaseRules(rulesDir);
|
|
1820
|
-
for (const name of ruleNames) {
|
|
1821
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
|
|
1822
|
-
}
|
|
1823
|
-
const skillsDir = join11(apmDir, "skills");
|
|
1824
|
-
mkdirSync4(toFsPath(skillsDir), { recursive: true });
|
|
1825
|
-
const baseNames = syncBaseSkills(skillsDir);
|
|
1826
|
-
for (const name of baseNames) {
|
|
1827
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u6280\u80FD: skills/${name}/`);
|
|
1828
|
-
}
|
|
1829
|
-
const { written, skipped, removed } = syncSupplementarySkills(
|
|
1830
|
-
skillsDir,
|
|
1831
|
-
list
|
|
1832
|
-
);
|
|
1833
|
-
for (const name of written) {
|
|
1834
|
-
console.log(`[apm] \u5DF2\u5199\u5165\u8865\u5145\u6280\u80FD: skills/${name}/SKILL.md`);
|
|
1835
|
-
}
|
|
1836
|
-
for (const name of skipped) {
|
|
1837
|
-
console.log(
|
|
1838
|
-
`[apm] \u5DF2\u8DF3\u8FC7\u4E0E\u57FA\u7840\u6280\u80FD\u540C\u540D\u7684\u8865\u5145\u6280\u80FD: ${name}\uFF08\u4FDD\u7559\u6A21\u677F\u7248\u672C\uFF09`
|
|
1839
|
-
);
|
|
1840
|
-
}
|
|
1841
|
-
for (const name of removed) {
|
|
1842
|
-
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
|
|
1843
|
-
}
|
|
1844
|
-
console.log(
|
|
1845
|
-
`[apm] \u540C\u6B65\u5B8C\u6210\uFF1A${ruleNames.length} \u4E2A\u57FA\u7840\u89C4\u5219\uFF0C${baseNames.length} \u4E2A\u57FA\u7840\u6280\u80FD\uFF0C${written.length} \u4E2A\u8865\u5145\u6280\u80FD`
|
|
1846
|
-
);
|
|
1847
|
-
}
|
|
1848
|
-
async function runUpdateSkills() {
|
|
1849
|
-
const apmDir = workspaceApmDir();
|
|
1850
|
-
if (!existsSync7(apmDir)) {
|
|
1851
|
-
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1852
|
-
process.exit(1);
|
|
1853
|
-
}
|
|
1854
|
-
const apmStat = statSync3(apmDir);
|
|
1855
|
-
if (!apmStat.isDirectory()) {
|
|
1856
|
-
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
1857
|
-
}
|
|
1858
|
-
const cfg = await ensureLoggedConfig();
|
|
1859
|
-
await syncWorkspaceSkills(cfg, resolveWorkdirPath());
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
// src/commands/sync-deploy-config.ts
|
|
1863
|
-
import { existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
1864
|
-
async function runSyncDeployConfig() {
|
|
1865
|
-
const workdir = resolveWorkdirPath();
|
|
1866
|
-
const apmDir = workspaceApmDir(workdir);
|
|
1867
|
-
if (!existsSync8(apmDir)) {
|
|
1868
|
-
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1869
|
-
process.exit(1);
|
|
1870
|
-
}
|
|
1871
|
-
const apmStat = statSync4(apmDir);
|
|
1872
|
-
if (!apmStat.isDirectory()) {
|
|
1873
|
-
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
1874
|
-
}
|
|
1875
|
-
await ensureLoggedConfig();
|
|
1876
|
-
const result = await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
1877
|
-
if (!result.synced) {
|
|
1878
|
-
process.exit(1);
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
// src/commands/sync-project-documents.ts
|
|
1883
|
-
async function runSyncProjectDocuments(options) {
|
|
1884
|
-
const pull = options?.pull ?? !options?.push;
|
|
1885
|
-
const push = options?.push ?? false;
|
|
1886
|
-
const workdir = resolveWorkdirPath();
|
|
1887
|
-
const apmRoot = workspaceApmDir(workdir);
|
|
1888
|
-
if (pull) {
|
|
1889
|
-
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
1890
|
-
}
|
|
1891
|
-
if (push) {
|
|
1892
|
-
const cfg = await ensureLoggedConfig();
|
|
1893
|
-
await syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot);
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
// src/commands/sync-document.ts
|
|
1898
|
-
import { existsSync as existsSync10 } from "fs";
|
|
1899
|
-
import { basename as basename3 } from "path";
|
|
1900
|
-
|
|
1901
|
-
// src/commands/sync-session-documents.ts
|
|
1902
|
-
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
1903
|
-
import { join as join12 } from "path";
|
|
1904
|
-
function listLocalMarkdownFiles(docsDir) {
|
|
1905
|
-
if (!existsSync9(docsDir)) {
|
|
1906
|
-
return [];
|
|
1907
|
-
}
|
|
1908
|
-
return readdirSync4(docsDir).filter(
|
|
1909
|
-
(name) => name.toLowerCase().endsWith(".md")
|
|
1910
|
-
);
|
|
1911
|
-
}
|
|
1912
|
-
function remoteDocumentByLocalName(remoteDocuments, localFileName) {
|
|
1913
|
-
const platformName = documentPlatformName(localFileName);
|
|
1914
|
-
return remoteDocuments.find((doc) => {
|
|
1915
|
-
const remoteLocalName = documentLocalFileName(doc.name);
|
|
1916
|
-
return remoteLocalName === localFileName || documentPlatformName(doc.name) === platformName;
|
|
1917
|
-
});
|
|
1918
|
-
}
|
|
1919
|
-
async function upsertLocalDocumentFile(api, sessionId, docsDir, fileName) {
|
|
1920
|
-
const absPath = join12(docsDir, fileName);
|
|
1921
|
-
const content = readFileSync8(absPath, "utf8");
|
|
1922
|
-
const name = documentPlatformName(absPath);
|
|
1923
|
-
return api.cli.upsertDocument({
|
|
1924
|
-
sessionId,
|
|
1925
|
-
name,
|
|
1926
|
-
content
|
|
1927
|
-
});
|
|
1928
|
-
}
|
|
1929
|
-
async function syncSessionDocuments(cfg, sessionId, apmRoot, options) {
|
|
1930
|
-
const trimmedSessionId = sessionId.trim();
|
|
1931
|
-
if (!trimmedSessionId) {
|
|
1932
|
-
return 0;
|
|
1933
|
-
}
|
|
1934
|
-
const docsDir = sessionDocsDir(trimmedSessionId, apmRoot);
|
|
1935
|
-
const localFiles = listLocalMarkdownFiles(docsDir);
|
|
1936
|
-
if (localFiles.length === 0) {
|
|
1937
|
-
return 0;
|
|
1938
|
-
}
|
|
1939
|
-
const api = options?.api ?? createApmApiClient(cfg);
|
|
1940
|
-
const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ sessionId: trimmedSessionId });
|
|
1941
|
-
let synced = 0;
|
|
1942
|
-
for (const fileName of localFiles) {
|
|
1943
|
-
const absPath = join12(docsDir, fileName);
|
|
1944
|
-
const content = readFileSync8(absPath, "utf8");
|
|
1945
|
-
const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
|
|
1946
|
-
if (remote && remote.content === content) {
|
|
1947
|
-
continue;
|
|
1948
|
-
}
|
|
1949
|
-
const doc = await upsertLocalDocumentFile(
|
|
1950
|
-
api,
|
|
1951
|
-
trimmedSessionId,
|
|
1952
|
-
docsDir,
|
|
1953
|
-
fileName
|
|
1954
|
-
);
|
|
1955
|
-
synced += 1;
|
|
1956
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
|
|
1957
|
-
}
|
|
1958
|
-
if (synced === 0) {
|
|
1959
|
-
console.log("[apm] \u4F1A\u8BDD\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1056
|
+
if (apiDirNames.has(entry)) continue;
|
|
1057
|
+
rmSync2(full, { recursive: true, force: true });
|
|
1058
|
+
removed.push(entry);
|
|
1960
1059
|
}
|
|
1961
|
-
return
|
|
1060
|
+
return { written, skipped, removed };
|
|
1962
1061
|
}
|
|
1963
1062
|
|
|
1964
|
-
// src/commands/
|
|
1965
|
-
async function
|
|
1966
|
-
const
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
}
|
|
1971
|
-
const fileArg = options.file?.trim();
|
|
1972
|
-
if (!fileArg) {
|
|
1973
|
-
console.error("[apm] \u8BF7\u6307\u5B9A --file <\u6587\u6863\u540D\u79F0>");
|
|
1974
|
-
process.exit(1);
|
|
1063
|
+
// src/commands/update-skills.ts
|
|
1064
|
+
async function syncWorkspaceSkills(cfg, workdir) {
|
|
1065
|
+
const apmDir = workspaceApmDir(workdir);
|
|
1066
|
+
const fsApmDir = toFsPath(apmDir);
|
|
1067
|
+
if (!existsSync5(fsApmDir)) {
|
|
1068
|
+
throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1975
1069
|
}
|
|
1976
|
-
const
|
|
1977
|
-
if (!
|
|
1978
|
-
|
|
1979
|
-
console.error(
|
|
1980
|
-
`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
|
|
1981
|
-
[apm] \u8BF7\u786E\u8BA4\u5DF2 pull\uFF0C\u4E14 ${docsDir2} \u4E0B\u5B58\u5728\u5BF9\u5E94\u6587\u4EF6`
|
|
1982
|
-
);
|
|
1983
|
-
process.exit(1);
|
|
1070
|
+
const apmStat = statSync3(fsApmDir);
|
|
1071
|
+
if (!apmStat.isDirectory()) {
|
|
1072
|
+
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
1984
1073
|
}
|
|
1985
|
-
const cfg = await ensureLoggedConfig();
|
|
1986
1074
|
const api = createApmApiClient(cfg);
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
trimmedSessionId,
|
|
1991
|
-
docsDir,
|
|
1992
|
-
basename3(absPath)
|
|
1993
|
-
);
|
|
1994
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
// src/commands/append-message.ts
|
|
1998
|
-
async function appendMessageContent(cfg, messageId, content) {
|
|
1999
|
-
const trimmedId = messageId.trim();
|
|
2000
|
-
if (!trimmedId) {
|
|
2001
|
-
throw new Error("messageId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1075
|
+
const { list } = await api.cli.listSkills({});
|
|
1076
|
+
if (syncAgentsGuide(apmDir)) {
|
|
1077
|
+
console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
|
|
2002
1078
|
}
|
|
2003
|
-
|
|
2004
|
-
|
|
1079
|
+
const rulesDir = join8(apmDir, "rules");
|
|
1080
|
+
const ruleNames = syncBaseRules(rulesDir);
|
|
1081
|
+
for (const name of ruleNames) {
|
|
1082
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
|
|
2005
1083
|
}
|
|
2006
|
-
const
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
if (!messageId) {
|
|
2012
|
-
console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
|
|
2013
|
-
process.exit(1);
|
|
1084
|
+
const skillsDir = join8(apmDir, "skills");
|
|
1085
|
+
mkdirSync4(toFsPath(skillsDir), { recursive: true });
|
|
1086
|
+
const baseNames = syncBaseSkills(skillsDir);
|
|
1087
|
+
for (const name of baseNames) {
|
|
1088
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u6280\u80FD: skills/${name}/`);
|
|
2014
1089
|
}
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
1090
|
+
const { written, skipped, removed } = syncSupplementarySkills(
|
|
1091
|
+
skillsDir,
|
|
1092
|
+
list
|
|
1093
|
+
);
|
|
1094
|
+
for (const name of written) {
|
|
1095
|
+
console.log(`[apm] \u5DF2\u5199\u5165\u8865\u5145\u6280\u80FD: skills/${name}/SKILL.md`);
|
|
2019
1096
|
}
|
|
2020
|
-
const
|
|
2021
|
-
|
|
2022
|
-
|
|
1097
|
+
for (const name of skipped) {
|
|
1098
|
+
console.log(
|
|
1099
|
+
`[apm] \u5DF2\u8DF3\u8FC7\u4E0E\u57FA\u7840\u6280\u80FD\u540C\u540D\u7684\u8865\u5145\u6280\u80FD: ${name}\uFF08\u4FDD\u7559\u6A21\u677F\u7248\u672C\uFF09`
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
for (const name of removed) {
|
|
1103
|
+
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
|
|
1104
|
+
}
|
|
1105
|
+
console.log(
|
|
1106
|
+
`[apm] \u540C\u6B65\u5B8C\u6210\uFF1A${ruleNames.length} \u4E2A\u57FA\u7840\u89C4\u5219\uFF0C${baseNames.length} \u4E2A\u57FA\u7840\u6280\u80FD\uFF0C${written.length} \u4E2A\u8865\u5145\u6280\u80FD`
|
|
1107
|
+
);
|
|
2023
1108
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
"QUEUED",
|
|
2029
|
-
"TYPING",
|
|
2030
|
-
"SUCCESS",
|
|
2031
|
-
"FAILED",
|
|
2032
|
-
"CANCELLED"
|
|
2033
|
-
];
|
|
2034
|
-
async function runUpdateMessageStatus(options) {
|
|
2035
|
-
const messageId = options.id?.trim();
|
|
2036
|
-
const status = options.status?.trim().toUpperCase();
|
|
2037
|
-
if (!messageId) {
|
|
2038
|
-
console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
|
|
1109
|
+
async function runUpdateSkills() {
|
|
1110
|
+
const apmDir = workspaceApmDir();
|
|
1111
|
+
if (!existsSync5(apmDir)) {
|
|
1112
|
+
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
2039
1113
|
process.exit(1);
|
|
2040
1114
|
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
);
|
|
2045
|
-
process.exit(1);
|
|
1115
|
+
const apmStat = statSync3(apmDir);
|
|
1116
|
+
if (!apmStat.isDirectory()) {
|
|
1117
|
+
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
2046
1118
|
}
|
|
2047
1119
|
const cfg = await ensureLoggedConfig();
|
|
2048
|
-
|
|
2049
|
-
await api.cli.updateMessageStatus({ id: messageId, status });
|
|
2050
|
-
console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
|
|
1120
|
+
await syncWorkspaceSkills(cfg, resolveWorkdirPath());
|
|
2051
1121
|
}
|
|
2052
1122
|
|
|
2053
1123
|
// src/commands/connect.ts
|
|
@@ -2077,57 +1147,6 @@ function validateHeartbeat(o) {
|
|
|
2077
1147
|
}
|
|
2078
1148
|
return { ok: true, data: { type: "heartbeat", userId: o.userId.trim() } };
|
|
2079
1149
|
}
|
|
2080
|
-
function validateMessagePush(o) {
|
|
2081
|
-
if (o.type !== "message") {
|
|
2082
|
-
return { ok: false, reason: "\u671F\u671B message" };
|
|
2083
|
-
}
|
|
2084
|
-
if (!nonEmptyString(o.messageId)) {
|
|
2085
|
-
return { ok: false, reason: "message \u7F3A\u5C11 messageId" };
|
|
2086
|
-
}
|
|
2087
|
-
if (!nonEmptyString(o.sessionId)) {
|
|
2088
|
-
return { ok: false, reason: "message \u7F3A\u5C11 sessionId" };
|
|
2089
|
-
}
|
|
2090
|
-
if (!nonEmptyString(o.content)) {
|
|
2091
|
-
return { ok: false, reason: "message \u7F3A\u5C11 content" };
|
|
2092
|
-
}
|
|
2093
|
-
if (!nonEmptyString(o.model)) {
|
|
2094
|
-
return { ok: false, reason: "message \u7F3A\u5C11 model" };
|
|
2095
|
-
}
|
|
2096
|
-
if (!nonEmptyString(o.apiKey)) {
|
|
2097
|
-
return { ok: false, reason: "message \u7F3A\u5C11 apiKey" };
|
|
2098
|
-
}
|
|
2099
|
-
if (!nonEmptyString(o.workdir)) {
|
|
2100
|
-
return { ok: false, reason: "message \u7F3A\u5C11 workdir" };
|
|
2101
|
-
}
|
|
2102
|
-
if (!nonEmptyString(o.user)) {
|
|
2103
|
-
return { ok: false, reason: "message \u7F3A\u5C11 user" };
|
|
2104
|
-
}
|
|
2105
|
-
return {
|
|
2106
|
-
ok: true,
|
|
2107
|
-
data: {
|
|
2108
|
-
type: "message",
|
|
2109
|
-
messageId: o.messageId.trim(),
|
|
2110
|
-
sessionId: o.sessionId.trim(),
|
|
2111
|
-
content: o.content,
|
|
2112
|
-
model: o.model.trim(),
|
|
2113
|
-
apiKey: o.apiKey.trim(),
|
|
2114
|
-
workdir: o.workdir.trim(),
|
|
2115
|
-
user: o.user.trim()
|
|
2116
|
-
}
|
|
2117
|
-
};
|
|
2118
|
-
}
|
|
2119
|
-
function validateCancel(o) {
|
|
2120
|
-
if (o.type !== "cancel") {
|
|
2121
|
-
return { ok: false, reason: "\u671F\u671B cancel" };
|
|
2122
|
-
}
|
|
2123
|
-
if (!nonEmptyString(o.messageId)) {
|
|
2124
|
-
return { ok: false, reason: "cancel \u7F3A\u5C11 messageId" };
|
|
2125
|
-
}
|
|
2126
|
-
return {
|
|
2127
|
-
ok: true,
|
|
2128
|
-
data: { type: "cancel", messageId: o.messageId.trim() }
|
|
2129
|
-
};
|
|
2130
|
-
}
|
|
2131
1150
|
function validateDeployPush(o) {
|
|
2132
1151
|
if (o.type !== "deploy") {
|
|
2133
1152
|
return { ok: false, reason: "\u671F\u671B deploy" };
|
|
@@ -2152,36 +1171,56 @@ function validateDeployPush(o) {
|
|
|
2152
1171
|
}
|
|
2153
1172
|
};
|
|
2154
1173
|
}
|
|
1174
|
+
function validateReceivedMailPush(o) {
|
|
1175
|
+
if (o.type !== "received_mail") {
|
|
1176
|
+
return { ok: false, reason: "\u671F\u671B received_mail" };
|
|
1177
|
+
}
|
|
1178
|
+
if (!nonEmptyString(o.id)) {
|
|
1179
|
+
return { ok: false, reason: "received_mail \u7F3A\u5C11 id" };
|
|
1180
|
+
}
|
|
1181
|
+
if (!nonEmptyString(o.taskId)) {
|
|
1182
|
+
return { ok: false, reason: "received_mail \u7F3A\u5C11 taskId" };
|
|
1183
|
+
}
|
|
1184
|
+
if (!nonEmptyString(o.createdAt)) {
|
|
1185
|
+
return { ok: false, reason: "received_mail \u7F3A\u5C11 createdAt" };
|
|
1186
|
+
}
|
|
1187
|
+
return {
|
|
1188
|
+
ok: true,
|
|
1189
|
+
data: {
|
|
1190
|
+
type: "received_mail",
|
|
1191
|
+
id: o.id.trim(),
|
|
1192
|
+
taskId: o.taskId.trim(),
|
|
1193
|
+
createdAt: o.createdAt.trim()
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
2155
1197
|
function validateAgentWsMessage(value, kind) {
|
|
2156
1198
|
if (typeof value !== "object" || value === null) {
|
|
2157
1199
|
return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
|
|
2158
1200
|
}
|
|
2159
1201
|
const o = value;
|
|
2160
1202
|
const type = o.type;
|
|
2161
|
-
if (type !== "heartbeat" && type !== "
|
|
1203
|
+
if (type !== "heartbeat" && type !== "deploy" && type !== "received_mail") {
|
|
2162
1204
|
return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
|
|
2163
1205
|
}
|
|
2164
1206
|
if (kind === "heartbeat" || type === "heartbeat") {
|
|
2165
1207
|
return validateHeartbeat(o);
|
|
2166
1208
|
}
|
|
2167
|
-
if (type === "cancel") {
|
|
2168
|
-
return validateCancel(o);
|
|
2169
|
-
}
|
|
2170
1209
|
if (type === "deploy") {
|
|
2171
1210
|
return validateDeployPush(o);
|
|
2172
1211
|
}
|
|
2173
|
-
return
|
|
1212
|
+
return validateReceivedMailPush(o);
|
|
2174
1213
|
}
|
|
2175
1214
|
|
|
2176
1215
|
// src/commands/connect/deploy-run.ts
|
|
2177
1216
|
import { spawn } from "node:child_process";
|
|
2178
|
-
import { readFileSync as
|
|
2179
|
-
import { join as
|
|
1217
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1218
|
+
import { join as join9 } from "node:path";
|
|
2180
1219
|
var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
|
|
2181
1220
|
function readDeployConfig(workdir) {
|
|
2182
|
-
const configPath =
|
|
1221
|
+
const configPath = join9(workspaceApmDir(workdir), "apm.config.json");
|
|
2183
1222
|
try {
|
|
2184
|
-
const raw =
|
|
1223
|
+
const raw = readFileSync6(configPath, "utf8");
|
|
2185
1224
|
const parsed = JSON.parse(raw);
|
|
2186
1225
|
return parsed.deploy;
|
|
2187
1226
|
} catch {
|
|
@@ -2256,7 +1295,7 @@ function createDeployLogSyncer(api, deploymentRunId) {
|
|
|
2256
1295
|
if (!latestLog || latestLog === lastSyncedLog) {
|
|
2257
1296
|
return;
|
|
2258
1297
|
}
|
|
2259
|
-
await api.cli.
|
|
1298
|
+
await api.cli.syncTaskDeploymentLog({
|
|
2260
1299
|
id: deploymentRunId,
|
|
2261
1300
|
log: latestLog
|
|
2262
1301
|
});
|
|
@@ -2287,7 +1326,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2287
1326
|
const api = createApmApiClient(cfg);
|
|
2288
1327
|
const deploymentRunId = msg.deploymentRunId;
|
|
2289
1328
|
if (signal.aborted) return;
|
|
2290
|
-
await api.cli.
|
|
1329
|
+
await api.cli.updateTaskDeploymentStatus({
|
|
2291
1330
|
id: deploymentRunId,
|
|
2292
1331
|
status: "DEPLOYING"
|
|
2293
1332
|
});
|
|
@@ -2296,7 +1335,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2296
1335
|
if (!command) {
|
|
2297
1336
|
const error = missingDeployCommandMessage(msg.environment);
|
|
2298
1337
|
console.error(`[apm] ${error}`);
|
|
2299
|
-
await api.cli.
|
|
1338
|
+
await api.cli.completeTaskDeployment({
|
|
2300
1339
|
id: deploymentRunId,
|
|
2301
1340
|
status: "FAILED",
|
|
2302
1341
|
log: error,
|
|
@@ -2318,7 +1357,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2318
1357
|
latestLog = log;
|
|
2319
1358
|
logSyncer.updateLog(log);
|
|
2320
1359
|
await logSyncer.flush();
|
|
2321
|
-
await api.cli.
|
|
1360
|
+
await api.cli.completeTaskDeployment({
|
|
2322
1361
|
id: deploymentRunId,
|
|
2323
1362
|
status: "SUCCESS",
|
|
2324
1363
|
log
|
|
@@ -2329,7 +1368,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2329
1368
|
const log = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
|
|
2330
1369
|
logSyncer.updateLog(log);
|
|
2331
1370
|
await logSyncer.flush();
|
|
2332
|
-
await api.cli.
|
|
1371
|
+
await api.cli.completeTaskDeployment({
|
|
2333
1372
|
id: deploymentRunId,
|
|
2334
1373
|
status: "FAILED",
|
|
2335
1374
|
log,
|
|
@@ -2341,67 +1380,66 @@ async function handleInboundDeploy(cfg, msg, signal) {
|
|
|
2341
1380
|
}
|
|
2342
1381
|
}
|
|
2343
1382
|
|
|
2344
|
-
// src/commands/
|
|
2345
|
-
import {
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
function isAbortSignalDebugEnabled() {
|
|
2351
|
-
const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
|
|
2352
|
-
return v === "1" || v === "true" || v === "yes";
|
|
2353
|
-
}
|
|
2354
|
-
function formatAbortSignalStats(signal, label) {
|
|
2355
|
-
if (!signal) {
|
|
2356
|
-
return `[apm:abort-debug] ${label}: (no signal)`;
|
|
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 [];
|
|
2357
1389
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
1390
|
+
return readdirSync4(docsDir).filter(
|
|
1391
|
+
(name) => name.toLowerCase().endsWith(".md")
|
|
1392
|
+
);
|
|
2361
1393
|
}
|
|
2362
|
-
function
|
|
2363
|
-
|
|
2364
|
-
|
|
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
|
+
});
|
|
2365
1400
|
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
);
|
|
2374
|
-
if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
|
|
2375
|
-
setMaxListeners(maxFromEnv);
|
|
2376
|
-
console.log(
|
|
2377
|
-
`[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
|
|
2378
|
-
);
|
|
2379
|
-
}
|
|
2380
|
-
process.on("warning", (warning) => {
|
|
2381
|
-
if (warning.name !== "MaxListenersExceededWarning") return;
|
|
2382
|
-
console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
|
|
2383
|
-
if (warning.stack) {
|
|
2384
|
-
console.warn(warning.stack);
|
|
2385
|
-
}
|
|
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
|
|
2386
1408
|
});
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
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;
|
|
2399
1429
|
}
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
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;
|
|
2405
1443
|
}
|
|
2406
1444
|
|
|
2407
1445
|
// src/commands/connect/cursor-agent.ts
|
|
@@ -2411,7 +1449,7 @@ import {
|
|
|
2411
1449
|
} from "@cursor/sdk";
|
|
2412
1450
|
import { setMaxListeners as setMaxListeners2 } from "node:events";
|
|
2413
1451
|
|
|
2414
|
-
// src/session
|
|
1452
|
+
// src/event-session.ts
|
|
2415
1453
|
var EventSession = class {
|
|
2416
1454
|
events = [];
|
|
2417
1455
|
dirtyIndices = /* @__PURE__ */ new Set();
|
|
@@ -2458,10 +1496,10 @@ var EventSession = class {
|
|
|
2458
1496
|
if (latestEvent?.type === formatedEvent.type) {
|
|
2459
1497
|
switch (formatedEvent.type) {
|
|
2460
1498
|
case "assistant":
|
|
2461
|
-
latestEvent.content
|
|
1499
|
+
latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
|
|
2462
1500
|
break;
|
|
2463
1501
|
case "thinking":
|
|
2464
|
-
latestEvent.content
|
|
1502
|
+
latestEvent.content = String(latestEvent.content ?? "") + formatedEvent.content;
|
|
2465
1503
|
break;
|
|
2466
1504
|
case "task":
|
|
2467
1505
|
latestEvent.status = formatedEvent.status;
|
|
@@ -2532,55 +1570,46 @@ var EventSession = class {
|
|
|
2532
1570
|
this.dirtyIndices.delete(index);
|
|
2533
1571
|
}
|
|
2534
1572
|
}
|
|
2535
|
-
/** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
|
|
2536
1573
|
getAssistantText() {
|
|
2537
1574
|
return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
|
|
2538
1575
|
}
|
|
2539
|
-
resolveLogContent() {
|
|
2540
|
-
return this.events.map((event) => formatLogEvent(event.type, event)).join("\n");
|
|
2541
|
-
}
|
|
2542
1576
|
};
|
|
2543
|
-
function formatLogEvent(type, event) {
|
|
2544
|
-
if (type === "input") {
|
|
2545
|
-
return `## \u7528\u6237\u8F93\u5165
|
|
2546
|
-
|
|
2547
|
-
${String(event.content ?? "")}
|
|
2548
|
-
`;
|
|
2549
|
-
}
|
|
2550
|
-
if (type === "assistant") {
|
|
2551
|
-
return `## \u6A21\u578B\u8F93\u51FA
|
|
2552
|
-
|
|
2553
|
-
${String(event.content ?? "")}
|
|
2554
|
-
`;
|
|
2555
|
-
}
|
|
2556
|
-
if (type === "thinking") {
|
|
2557
|
-
return `## \u6A21\u578B\u601D\u8003
|
|
2558
1577
|
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
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)`;
|
|
2564
1591
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
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));
|
|
2570
1599
|
}
|
|
2571
1600
|
|
|
2572
|
-
// src/commands/connect/agent-
|
|
2573
|
-
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";
|
|
2574
1603
|
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
2575
|
-
function registryPath(workdir,
|
|
2576
|
-
return resolve3(workdir, ".apm", "
|
|
1604
|
+
function registryPath(workdir, taskId) {
|
|
1605
|
+
return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
|
|
2577
1606
|
}
|
|
2578
1607
|
function readRegistry(path10) {
|
|
2579
|
-
if (!
|
|
1608
|
+
if (!existsSync7(path10)) {
|
|
2580
1609
|
return {};
|
|
2581
1610
|
}
|
|
2582
1611
|
try {
|
|
2583
|
-
const parsed = JSON.parse(
|
|
1612
|
+
const parsed = JSON.parse(readFileSync8(path10, "utf8"));
|
|
2584
1613
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2585
1614
|
const result = {};
|
|
2586
1615
|
for (const [key, value] of Object.entries(
|
|
@@ -2598,21 +1627,20 @@ function readRegistry(path10) {
|
|
|
2598
1627
|
}
|
|
2599
1628
|
function writeRegistry(path10, registry) {
|
|
2600
1629
|
mkdirSync5(dirname4(path10), { recursive: true });
|
|
2601
|
-
|
|
1630
|
+
writeFileSync7(path10, `${JSON.stringify(registry, null, 2)}
|
|
2602
1631
|
`, "utf8");
|
|
2603
1632
|
}
|
|
2604
|
-
function
|
|
2605
|
-
|
|
2606
|
-
return registry[user];
|
|
1633
|
+
function loadTaskAgentId(workdir, taskId, user) {
|
|
1634
|
+
return readRegistry(registryPath(workdir, taskId))[user];
|
|
2607
1635
|
}
|
|
2608
|
-
function
|
|
2609
|
-
const path10 = registryPath(workdir,
|
|
1636
|
+
function saveTaskAgentId(workdir, taskId, user, agentId) {
|
|
1637
|
+
const path10 = registryPath(workdir, taskId);
|
|
2610
1638
|
const registry = readRegistry(path10);
|
|
2611
1639
|
registry[user] = agentId;
|
|
2612
1640
|
writeRegistry(path10, registry);
|
|
2613
1641
|
}
|
|
2614
|
-
function
|
|
2615
|
-
const path10 = registryPath(workdir,
|
|
1642
|
+
function clearTaskAgentId(workdir, taskId, user) {
|
|
1643
|
+
const path10 = registryPath(workdir, taskId);
|
|
2616
1644
|
const registry = readRegistry(path10);
|
|
2617
1645
|
if (!(user in registry)) {
|
|
2618
1646
|
return;
|
|
@@ -2621,9 +1649,9 @@ function clearSessionAgentId(workdir, sessionId, user) {
|
|
|
2621
1649
|
writeRegistry(path10, registry);
|
|
2622
1650
|
}
|
|
2623
1651
|
|
|
2624
|
-
// src/commands/connect/cursor-
|
|
2625
|
-
var
|
|
2626
|
-
function
|
|
1652
|
+
// src/commands/connect/cursor-log.ts
|
|
1653
|
+
var CURSOR_LOG_SYNC_INTERVAL_MS = 2e3;
|
|
1654
|
+
function createThrottledCursorLogSync(cfg, ctx, onError) {
|
|
2627
1655
|
let lastRunAt = 0;
|
|
2628
1656
|
let timer;
|
|
2629
1657
|
let latestSession;
|
|
@@ -2635,7 +1663,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2635
1663
|
}
|
|
2636
1664
|
lastRunAt = Date.now();
|
|
2637
1665
|
try {
|
|
2638
|
-
await
|
|
1666
|
+
await syncCursorLog(cfg, ctx, events);
|
|
2639
1667
|
session.clearDirty(events.map((event) => event.index));
|
|
2640
1668
|
} catch (err) {
|
|
2641
1669
|
onError(err);
|
|
@@ -2654,7 +1682,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2654
1682
|
latestSession = session;
|
|
2655
1683
|
const now = Date.now();
|
|
2656
1684
|
const elapsed = now - lastRunAt;
|
|
2657
|
-
if (elapsed >=
|
|
1685
|
+
if (elapsed >= CURSOR_LOG_SYNC_INTERVAL_MS) {
|
|
2658
1686
|
if (timer) {
|
|
2659
1687
|
clearTimeout(timer);
|
|
2660
1688
|
timer = void 0;
|
|
@@ -2668,7 +1696,7 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2668
1696
|
timer = setTimeout(() => {
|
|
2669
1697
|
timer = void 0;
|
|
2670
1698
|
enqueueSync(latestSession);
|
|
2671
|
-
},
|
|
1699
|
+
}, CURSOR_LOG_SYNC_INTERVAL_MS - elapsed);
|
|
2672
1700
|
},
|
|
2673
1701
|
async flush(session) {
|
|
2674
1702
|
latestSession = session;
|
|
@@ -2681,108 +1709,66 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
|
2681
1709
|
}
|
|
2682
1710
|
};
|
|
2683
1711
|
}
|
|
2684
|
-
async function
|
|
1712
|
+
async function syncCursorLog(cfg, ctx, events) {
|
|
2685
1713
|
const agentId = ctx.agentId.trim();
|
|
2686
1714
|
if (!agentId || events.length === 0) {
|
|
2687
1715
|
return;
|
|
2688
1716
|
}
|
|
2689
1717
|
const api = createApmApiClient(cfg);
|
|
2690
|
-
await api.cli.
|
|
2691
|
-
|
|
2692
|
-
|
|
1718
|
+
await api.cli.upsertCursorLog({
|
|
1719
|
+
taskId: ctx.taskId,
|
|
1720
|
+
mailboxMessageId: ctx.mailboxMessageId,
|
|
2693
1721
|
agentId,
|
|
2694
1722
|
events
|
|
2695
1723
|
});
|
|
2696
1724
|
}
|
|
2697
1725
|
|
|
2698
|
-
// src/commands/connect/append-message-tool.ts
|
|
2699
|
-
function createAppendMessageCustomTools(cfg, messageId) {
|
|
2700
|
-
return {
|
|
2701
|
-
append_message: {
|
|
2702
|
-
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",
|
|
2703
|
-
inputSchema: {
|
|
2704
|
-
type: "object",
|
|
2705
|
-
properties: {
|
|
2706
|
-
content: {
|
|
2707
|
-
type: "string",
|
|
2708
|
-
description: "\u8981\u53D1\u9001\u5230\u7FA4\u91CC\u7684\u56DE\u590D\u5185\u5BB9"
|
|
2709
|
-
}
|
|
2710
|
-
},
|
|
2711
|
-
required: ["content"]
|
|
2712
|
-
},
|
|
2713
|
-
execute: async (args) => {
|
|
2714
|
-
const content = typeof args.content === "string" ? args.content.trim() : "";
|
|
2715
|
-
if (!content) {
|
|
2716
|
-
return {
|
|
2717
|
-
content: [{ type: "text", text: "content \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
2718
|
-
isError: true
|
|
2719
|
-
};
|
|
2720
|
-
}
|
|
2721
|
-
try {
|
|
2722
|
-
await appendMessageContent(cfg, messageId, content);
|
|
2723
|
-
console.log(`[apm] append_message \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
|
|
2724
|
-
return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
|
|
2725
|
-
} catch (err) {
|
|
2726
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
2727
|
-
return {
|
|
2728
|
-
content: [{ type: "text", text: `\u8FFD\u52A0\u6D88\u606F\u5931\u8D25: ${detail}` }],
|
|
2729
|
-
isError: true
|
|
2730
|
-
};
|
|
2731
|
-
}
|
|
2732
|
-
}
|
|
2733
|
-
}
|
|
2734
|
-
};
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
1726
|
// src/commands/connect/cursor-agent.ts
|
|
2738
1727
|
setMaxListeners2(50);
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
agentId
|
|
2744
|
-
});
|
|
2745
|
-
function formatCursorRunFailure(runId, options) {
|
|
2746
|
-
const details = [
|
|
2747
|
-
options?.statusError?.trim(),
|
|
2748
|
-
options?.resultText?.trim()
|
|
2749
|
-
].filter((value, index, arr) => {
|
|
2750
|
-
if (!value) return false;
|
|
2751
|
-
return arr.indexOf(value) === index;
|
|
2752
|
-
});
|
|
2753
|
-
if (details.length === 0) {
|
|
2754
|
-
return `Cursor run \u5931\u8D25: ${runId}`;
|
|
1728
|
+
var noopRemoteLogSync = {
|
|
1729
|
+
schedule(_session) {
|
|
1730
|
+
},
|
|
1731
|
+
async flush(_session) {
|
|
2755
1732
|
}
|
|
2756
|
-
|
|
1733
|
+
};
|
|
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("");
|
|
2757
1740
|
}
|
|
2758
1741
|
async function obtainAgent(ctx) {
|
|
2759
1742
|
const agentOptions = {
|
|
2760
1743
|
apiKey: ctx.apiKey,
|
|
2761
1744
|
model: { id: ctx.model || "default" },
|
|
2762
1745
|
local: {
|
|
2763
|
-
cwd: ctx.
|
|
1746
|
+
cwd: ctx.workdir,
|
|
1747
|
+
...ctx.customTools ? { customTools: ctx.customTools } : {}
|
|
2764
1748
|
}
|
|
2765
|
-
// mcpServers: createPlaywrightMcpServers(),
|
|
2766
1749
|
};
|
|
2767
|
-
const savedAgentId = ctx.user ?
|
|
1750
|
+
const savedAgentId = ctx.user ? loadTaskAgentId(ctx.workdir, ctx.taskId, ctx.user) : void 0;
|
|
2768
1751
|
if (savedAgentId) {
|
|
2769
1752
|
try {
|
|
2770
1753
|
const agent2 = await Agent.resume(savedAgentId, agentOptions);
|
|
2771
|
-
console.log(
|
|
2772
|
-
|
|
2773
|
-
|
|
1754
|
+
console.log(`[apm] \u590D\u7528 Agent user=${ctx.user} agentId=${savedAgentId}`);
|
|
1755
|
+
if (ctx.user) {
|
|
1756
|
+
saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent2.agentId);
|
|
1757
|
+
}
|
|
2774
1758
|
return { agent: agent2, resumed: true };
|
|
2775
1759
|
} catch (err) {
|
|
2776
1760
|
console.warn(
|
|
2777
1761
|
`[apm] \u590D\u7528 Agent \u5931\u8D25\uFF08agentId=${savedAgentId}\uFF09\uFF0C\u56DE\u9000\u4E3A\u65B0\u5EFA:`,
|
|
2778
1762
|
err instanceof Error ? err.message : err
|
|
2779
1763
|
);
|
|
2780
|
-
|
|
1764
|
+
if (ctx.user) {
|
|
1765
|
+
clearTaskAgentId(ctx.workdir, ctx.taskId, ctx.user);
|
|
1766
|
+
}
|
|
2781
1767
|
}
|
|
2782
1768
|
}
|
|
2783
1769
|
const agent = await Agent.create(agentOptions);
|
|
2784
1770
|
if (ctx.user) {
|
|
2785
|
-
|
|
1771
|
+
saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent.agentId);
|
|
2786
1772
|
}
|
|
2787
1773
|
return { agent, resumed: false };
|
|
2788
1774
|
}
|
|
@@ -2794,29 +1780,36 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
2794
1780
|
}
|
|
2795
1781
|
const apiKey = ctx.apiKey.trim();
|
|
2796
1782
|
if (!apiKey) {
|
|
2797
|
-
throw new Error("\u7F3A\u5C11
|
|
1783
|
+
throw new Error("\u7F3A\u5C11 Cursor API Key");
|
|
2798
1784
|
}
|
|
2799
|
-
const workdir =
|
|
2800
|
-
const
|
|
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`;
|
|
2801
1791
|
console.log(
|
|
2802
|
-
`[apm] Cursor Agent \u5F00\u59CB
|
|
1792
|
+
`[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} cwd=${workdir}`
|
|
2803
1793
|
);
|
|
2804
1794
|
const { agent, resumed } = await obtainAgent({
|
|
2805
1795
|
apiKey,
|
|
2806
1796
|
model: ctx.model,
|
|
2807
|
-
cwd: workdir,
|
|
2808
1797
|
workdir,
|
|
2809
|
-
|
|
2810
|
-
user: ctx.user
|
|
1798
|
+
taskId: ctx.taskId,
|
|
1799
|
+
user: ctx.user,
|
|
1800
|
+
customTools
|
|
2811
1801
|
});
|
|
2812
1802
|
const eventSession = new EventSession(prompt);
|
|
2813
|
-
const
|
|
2814
|
-
const syncRemoteLog = createThrottledCursorMessageLogSync(
|
|
1803
|
+
const syncRemoteLog = options?.skipRemoteLogSync ? noopRemoteLogSync : createThrottledCursorLogSync(
|
|
2815
1804
|
cfg,
|
|
2816
|
-
|
|
1805
|
+
{
|
|
1806
|
+
taskId: ctx.taskId,
|
|
1807
|
+
mailboxMessageId: ctx.mailId,
|
|
1808
|
+
agentId: agent.agentId
|
|
1809
|
+
},
|
|
2817
1810
|
(err) => {
|
|
2818
1811
|
console.warn(
|
|
2819
|
-
|
|
1812
|
+
`[apm] Cursor \u65E5\u5FD7\u540C\u6B65\u5931\u8D25 mailId=${ctx.mailId}:`,
|
|
2820
1813
|
err instanceof Error ? err.message : err
|
|
2821
1814
|
);
|
|
2822
1815
|
}
|
|
@@ -2827,56 +1820,47 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
2827
1820
|
void activeRun.cancel().catch(() => void 0);
|
|
2828
1821
|
};
|
|
2829
1822
|
signal?.addEventListener("abort", abortRun, { once: true });
|
|
2830
|
-
|
|
1823
|
+
const streamEvents = [];
|
|
2831
1824
|
try {
|
|
2832
1825
|
const run = await agent.send(prompt, {
|
|
2833
|
-
|
|
2834
|
-
local: {
|
|
2835
|
-
customTools: createAppendMessageCustomTools(cfg, ctx.messageId)
|
|
2836
|
-
}
|
|
1826
|
+
local: { customTools }
|
|
2837
1827
|
});
|
|
2838
1828
|
activeRun = run;
|
|
2839
|
-
logAbortSignalStats(signal, "runCursorAgent:after-send");
|
|
2840
1829
|
console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
|
|
2841
|
-
let lastRunErrorStatus;
|
|
2842
1830
|
for await (const event of run.stream()) {
|
|
2843
1831
|
if (signal?.aborted) {
|
|
2844
1832
|
abortRun();
|
|
2845
1833
|
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
2846
1834
|
}
|
|
2847
|
-
|
|
2848
|
-
const message = event.message?.trim();
|
|
2849
|
-
if (message) {
|
|
2850
|
-
lastRunErrorStatus = message;
|
|
2851
|
-
console.error(
|
|
2852
|
-
`[apm] Cursor run status=ERROR runId=${run.id}: ${message}`
|
|
2853
|
-
);
|
|
2854
|
-
}
|
|
2855
|
-
}
|
|
1835
|
+
streamEvents.push(event);
|
|
2856
1836
|
eventSession.addEvent(event);
|
|
2857
1837
|
syncRemoteLog.schedule(eventSession);
|
|
2858
1838
|
}
|
|
2859
1839
|
await syncRemoteLog.flush(eventSession);
|
|
2860
1840
|
const result = await run.wait();
|
|
1841
|
+
const assistantText = eventSession.getAssistantText() || collectAssistantText(streamEvents);
|
|
2861
1842
|
if (result.status === "error") {
|
|
2862
|
-
const failureMessage = formatCursorRunFailure(result.id, {
|
|
2863
|
-
statusError: lastRunErrorStatus,
|
|
2864
|
-
resultText: result.result
|
|
2865
|
-
});
|
|
2866
|
-
console.error(`[apm] ${failureMessage}`);
|
|
2867
1843
|
if (resumed) {
|
|
2868
|
-
|
|
1844
|
+
clearTaskAgentId(workdir, ctx.taskId, ctx.user);
|
|
2869
1845
|
}
|
|
2870
|
-
throw new Error(
|
|
1846
|
+
throw new Error(
|
|
1847
|
+
`Cursor run \u5931\u8D25: ${result.id}${result.result ? ` \u2014 ${result.result}` : ""}`
|
|
1848
|
+
);
|
|
2871
1849
|
}
|
|
2872
1850
|
if (result.status === "cancelled") {
|
|
2873
1851
|
throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
|
|
2874
1852
|
}
|
|
2875
|
-
console.log(`[apm] Cursor Agent \u5B8C\u6210
|
|
1853
|
+
console.log(`[apm] Cursor Agent \u5B8C\u6210 mailId=${ctx.mailId}`);
|
|
1854
|
+
return {
|
|
1855
|
+
runId: result.id,
|
|
1856
|
+
agentId: agent.agentId,
|
|
1857
|
+
status: result.status,
|
|
1858
|
+
assistantText: assistantText || result.result || ""
|
|
1859
|
+
};
|
|
2876
1860
|
} catch (err) {
|
|
2877
1861
|
if (err instanceof CursorAgentError) {
|
|
2878
1862
|
if (resumed) {
|
|
2879
|
-
|
|
1863
|
+
clearTaskAgentId(workdir, ctx.taskId, ctx.user);
|
|
2880
1864
|
}
|
|
2881
1865
|
throw new Error(
|
|
2882
1866
|
`Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
|
|
@@ -2884,265 +1868,428 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
2884
1868
|
}
|
|
2885
1869
|
throw err;
|
|
2886
1870
|
} finally {
|
|
2887
|
-
logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
|
|
2888
1871
|
signal?.removeEventListener("abort", abortRun);
|
|
2889
|
-
logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
|
|
2890
1872
|
await agent[Symbol.asyncDispose]();
|
|
2891
1873
|
}
|
|
2892
1874
|
}
|
|
2893
1875
|
|
|
2894
|
-
// src/commands/connect/
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
var
|
|
2898
|
-
function
|
|
2899
|
-
return
|
|
2900
|
-
}
|
|
2901
|
-
function
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
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
|
+
}
|
|
1926
|
+
},
|
|
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
|
+
};
|
|
1936
|
+
}
|
|
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";
|
|
1940
|
+
}
|
|
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`;
|
|
1964
|
+
}
|
|
1965
|
+
function loadManifest(rulesDir) {
|
|
1966
|
+
const path10 = join11(rulesDir, MANIFEST_FILE2);
|
|
1967
|
+
if (!existsSync8(toFsPath(path10))) {
|
|
1968
|
+
return { version: 1, rules: {} };
|
|
2905
1969
|
}
|
|
2906
1970
|
try {
|
|
2907
1971
|
const parsed = JSON.parse(
|
|
2908
|
-
|
|
1972
|
+
readFileSync9(toFsPath(path10), "utf8")
|
|
2909
1973
|
);
|
|
2910
|
-
if (parsed?.version === 1 && typeof parsed.
|
|
1974
|
+
if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
|
|
2911
1975
|
return parsed;
|
|
2912
1976
|
}
|
|
2913
1977
|
} catch {
|
|
2914
1978
|
}
|
|
2915
|
-
return
|
|
1979
|
+
return { version: 1, rules: {} };
|
|
2916
1980
|
}
|
|
2917
|
-
function
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
toFsPath(manifestPath(apmDir)),
|
|
1981
|
+
function saveManifest(rulesDir, manifest) {
|
|
1982
|
+
writeFileSync8(
|
|
1983
|
+
toFsPath(join11(rulesDir, MANIFEST_FILE2)),
|
|
2921
1984
|
`${JSON.stringify(manifest, null, 2)}
|
|
2922
1985
|
`,
|
|
2923
1986
|
"utf8"
|
|
2924
1987
|
);
|
|
2925
1988
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
const cached = syncedInSession.get(workdir);
|
|
2929
|
-
if (cached === currentVersion) {
|
|
2930
|
-
return false;
|
|
2931
|
-
}
|
|
2932
|
-
const stored = loadManifest3(workspaceApmDir(workdir));
|
|
2933
|
-
if (stored?.cliVersion === currentVersion) {
|
|
2934
|
-
syncedInSession.set(workdir, currentVersion);
|
|
2935
|
-
return false;
|
|
2936
|
-
}
|
|
2937
|
-
return true;
|
|
1989
|
+
function isBaseRuleFileName(fileName) {
|
|
1990
|
+
return listBaseRuleFileNames().includes(basename2(fileName));
|
|
2938
1991
|
}
|
|
2939
|
-
function
|
|
2940
|
-
|
|
2941
|
-
|
|
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 ?? "");
|
|
2942
1999
|
}
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
}
|
|
2954
|
-
function markBranchDone(sessionId, workdir) {
|
|
2955
|
-
lastBranchKey = sessionWorkdirKey(sessionId, workdir);
|
|
2956
|
-
}
|
|
2957
|
-
function shouldRunPull(sessionId, workdir) {
|
|
2958
|
-
const key = sessionWorkdirKey(sessionId, workdir);
|
|
2959
|
-
const last = lastPullAtByKey.get(key);
|
|
2960
|
-
if (last == null) {
|
|
2961
|
-
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 };
|
|
2962
2011
|
}
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
if (
|
|
2976
|
-
|
|
2977
|
-
|
|
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;
|
|
2978
2029
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
const
|
|
2989
|
-
if (
|
|
2990
|
-
|
|
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 });
|
|
2991
2042
|
}
|
|
2992
|
-
|
|
2993
|
-
|
|
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 };
|
|
2994
2048
|
}
|
|
2995
2049
|
|
|
2996
|
-
// src/commands/connect.ts
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
await api.cli.updateMessageStatus({ id: messageId, status });
|
|
3001
|
-
console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
|
|
3002
|
-
}
|
|
3003
|
-
async function setMessageError(cfg, messageId, error) {
|
|
3004
|
-
const api = createApmApiClient(cfg);
|
|
3005
|
-
await api.cli.setMessageError({ id: messageId, error });
|
|
3006
|
-
console.log(`[apm] \u5DF2\u8BBE\u7F6E\u6D88\u606F\u9519\u8BEF: ${messageId}`);
|
|
3007
|
-
}
|
|
3008
|
-
var SHUTDOWN_DRAIN_MS = 3e3;
|
|
3009
|
-
function isUserCancelled(ctx) {
|
|
3010
|
-
return ctx.perMessageSignal.aborted && !ctx.shutdownSignal.aborted;
|
|
3011
|
-
}
|
|
3012
|
-
async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
3013
|
-
if (isUserCancelled(ctx)) return;
|
|
3014
|
-
if (signal.aborted) return;
|
|
3015
|
-
const messageId = msg.messageId;
|
|
3016
|
-
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);
|
|
3017
2054
|
const apmRoot = workspaceApmDir(workdir);
|
|
3018
|
-
const
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
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;
|
|
3029
2113
|
try {
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
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
|
|
3034
2119
|
);
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
await
|
|
3039
|
-
|
|
3040
|
-
|
|
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
|
|
3041
2128
|
);
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
}
|
|
3049
|
-
let pullRan = false;
|
|
3050
|
-
if (shouldRunPull(msg.sessionId, workdir)) {
|
|
3051
|
-
if (signal.aborted) return;
|
|
3052
|
-
await runStep("pull", () => runPull(msg.sessionId, workdir));
|
|
3053
|
-
markPullDone(msg.sessionId, workdir);
|
|
3054
|
-
pullRan = true;
|
|
3055
|
-
} else {
|
|
3056
|
-
console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
|
|
3057
|
-
}
|
|
3058
|
-
if (pullRan) {
|
|
3059
|
-
if (signal.aborted) return;
|
|
3060
|
-
await runStep(
|
|
3061
|
-
"commit-pull",
|
|
3062
|
-
() => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
|
|
3063
|
-
);
|
|
3064
|
-
} else {
|
|
3065
|
-
console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
|
|
3066
|
-
}
|
|
3067
|
-
const cliVersion = readCliVersion();
|
|
3068
|
-
if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
|
|
3069
|
-
if (signal.aborted) return;
|
|
3070
|
-
console.log(
|
|
3071
|
-
`[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
|
|
3072
|
-
);
|
|
3073
|
-
await runStep("update-skills", async () => {
|
|
3074
|
-
await syncWorkspaceSkills(cfg, workdir);
|
|
3075
|
-
markSkillsSyncedForCliVersion(workdir, cliVersion);
|
|
3076
|
-
});
|
|
3077
|
-
} else {
|
|
3078
|
-
console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
|
|
3079
|
-
}
|
|
3080
|
-
if (signal.aborted) return;
|
|
3081
|
-
if (!pullRan) {
|
|
3082
|
-
await runStep(
|
|
3083
|
-
"sync-project-documents-pull",
|
|
3084
|
-
() => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
|
|
3085
|
-
);
|
|
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);
|
|
3086
2135
|
}
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
{
|
|
3092
|
-
messageId: msg.messageId,
|
|
3093
|
-
sessionId: msg.sessionId,
|
|
3094
|
-
prompt: msg.content,
|
|
3095
|
-
model: msg.model,
|
|
3096
|
-
apiKey: msg.apiKey,
|
|
3097
|
-
workdir,
|
|
3098
|
-
user: msg.user
|
|
3099
|
-
},
|
|
3100
|
-
{ signal }
|
|
3101
|
-
)
|
|
3102
|
-
);
|
|
3103
|
-
await runStep(
|
|
3104
|
-
"sync-documents",
|
|
3105
|
-
() => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
|
|
3106
|
-
);
|
|
3107
|
-
await runStep(
|
|
3108
|
-
"sync-project-documents",
|
|
3109
|
-
() => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
|
|
3110
|
-
);
|
|
3111
|
-
await runStep(
|
|
3112
|
-
"commit-files",
|
|
3113
|
-
() => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
|
|
3114
|
-
);
|
|
3115
|
-
await runStep(
|
|
3116
|
-
"status-success",
|
|
3117
|
-
() => updateMessageStatus(cfg, messageId, "SUCCESS")
|
|
3118
|
-
);
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
try {
|
|
2139
|
+
await api.cli.claimMailboxMessage({ id: mail.id });
|
|
3119
2140
|
} catch (err) {
|
|
3120
|
-
if (isUserCancelled(ctx)) {
|
|
3121
|
-
console.log(`[apm] \u6D88\u606F\u5DF2\u7EC8\u6B62 messageId=${messageId}`);
|
|
3122
|
-
return;
|
|
3123
|
-
}
|
|
3124
2141
|
console.error(
|
|
3125
|
-
|
|
3126
|
-
err instanceof Error ? err.message :
|
|
2142
|
+
`[apm] \u8BA4\u9886\u4FE1\u4EF6\u5931\u8D25 id=${mail.id}:`,
|
|
2143
|
+
err instanceof Error ? err.message : err
|
|
3127
2144
|
);
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
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}`);
|
|
3131
2188
|
try {
|
|
3132
|
-
await
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
);
|
|
3137
|
-
|
|
3138
|
-
} catch (statusErr) {
|
|
2189
|
+
await api.cli.completeMailboxMessage({
|
|
2190
|
+
id: detail.id,
|
|
2191
|
+
status: "FAILED",
|
|
2192
|
+
error: message
|
|
2193
|
+
});
|
|
2194
|
+
} catch (completeErr) {
|
|
3139
2195
|
console.error(
|
|
3140
|
-
|
|
3141
|
-
|
|
2196
|
+
`[apm] \u6807\u8BB0\u4FE1\u4EF6\u5931\u8D25\u72B6\u6001\u65F6\u51FA\u9519 id=${mail.id}:`,
|
|
2197
|
+
completeErr instanceof Error ? completeErr.message : completeErr
|
|
3142
2198
|
);
|
|
3143
2199
|
}
|
|
3144
2200
|
}
|
|
3145
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;
|
|
3146
2293
|
function startHeartbeat(ws, clientMachineId) {
|
|
3147
2294
|
const send = () => {
|
|
3148
2295
|
if (ws.readyState === WebSocket.OPEN) {
|
|
@@ -3192,11 +2339,17 @@ async function runConnect(options) {
|
|
|
3192
2339
|
let stopHeartbeat;
|
|
3193
2340
|
let shuttingDown = false;
|
|
3194
2341
|
const shutdownAbort = new AbortController();
|
|
3195
|
-
|
|
3196
|
-
const
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
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) => {
|
|
3200
2353
|
if (shuttingDown) return;
|
|
3201
2354
|
shuttingDown = true;
|
|
3202
2355
|
logAbortSignalStats(
|
|
@@ -3209,19 +2362,34 @@ async function runConnect(options) {
|
|
|
3209
2362
|
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
3210
2363
|
ws.terminate();
|
|
3211
2364
|
}
|
|
3212
|
-
try {
|
|
3213
|
-
await Promise.race([
|
|
3214
|
-
Promise.all(activeTasks),
|
|
3215
|
-
new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
|
|
3216
|
-
]);
|
|
3217
|
-
} catch {
|
|
3218
|
-
}
|
|
3219
2365
|
resolve5();
|
|
3220
2366
|
process.exit(code);
|
|
3221
2367
|
};
|
|
3222
2368
|
ws.on("open", () => {
|
|
3223
2369
|
console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
|
|
3224
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
|
+
})();
|
|
3225
2393
|
});
|
|
3226
2394
|
ws.on("message", (data) => {
|
|
3227
2395
|
if (shuttingDown) return;
|
|
@@ -3239,72 +2407,19 @@ async function runConnect(options) {
|
|
|
3239
2407
|
console.error(`[apm] \u6536\u5230\u65E0\u6548 WS \u5305: ${validated.reason}`);
|
|
3240
2408
|
return;
|
|
3241
2409
|
}
|
|
3242
|
-
if (validated.data.type === "cancel") {
|
|
3243
|
-
const { messageId } = validated.data;
|
|
3244
|
-
pendingCancels.add(messageId);
|
|
3245
|
-
activeRuns.get(messageId)?.abort();
|
|
3246
|
-
return;
|
|
3247
|
-
}
|
|
3248
2410
|
if (validated.data.type === "deploy") {
|
|
3249
|
-
|
|
3250
|
-
const perDeployController = new AbortController();
|
|
3251
|
-
const signal2 = AbortSignal.any([
|
|
3252
|
-
shutdownAbort.signal,
|
|
3253
|
-
perDeployController.signal
|
|
3254
|
-
]);
|
|
3255
|
-
const task2 = (async () => {
|
|
3256
|
-
await runSlots.acquire();
|
|
3257
|
-
try {
|
|
3258
|
-
await handleInboundDeploy(cfg, msg2, signal2);
|
|
3259
|
-
} finally {
|
|
3260
|
-
runSlots.release();
|
|
3261
|
-
}
|
|
3262
|
-
})();
|
|
3263
|
-
activeTasks.add(task2);
|
|
3264
|
-
void task2.finally(() => {
|
|
3265
|
-
activeTasks.delete(task2);
|
|
3266
|
-
});
|
|
2411
|
+
void handleInboundDeploy(cfg, validated.data, shutdownAbort.signal);
|
|
3267
2412
|
return;
|
|
3268
2413
|
}
|
|
3269
|
-
if (validated.data.type
|
|
3270
|
-
|
|
3271
|
-
}
|
|
3272
|
-
const msg = validated.data;
|
|
3273
|
-
const perMessageController = new AbortController();
|
|
3274
|
-
activeRuns.set(msg.messageId, perMessageController);
|
|
3275
|
-
if (pendingCancels.has(msg.messageId)) {
|
|
3276
|
-
activeRuns.delete(msg.messageId);
|
|
3277
|
-
pendingCancels.delete(msg.messageId);
|
|
3278
|
-
return;
|
|
2414
|
+
if (validated.data.type === "received_mail") {
|
|
2415
|
+
mailProcessor.receive(validated.data);
|
|
3279
2416
|
}
|
|
3280
|
-
const signal = AbortSignal.any([
|
|
3281
|
-
shutdownAbort.signal,
|
|
3282
|
-
perMessageController.signal
|
|
3283
|
-
]);
|
|
3284
|
-
const ctx = {
|
|
3285
|
-
shutdownSignal: shutdownAbort.signal,
|
|
3286
|
-
perMessageSignal: perMessageController.signal
|
|
3287
|
-
};
|
|
3288
|
-
const task = (async () => {
|
|
3289
|
-
await runSlots.acquire();
|
|
3290
|
-
try {
|
|
3291
|
-
await handleInboundMessage(cfg, msg, signal, ctx);
|
|
3292
|
-
} finally {
|
|
3293
|
-
runSlots.release();
|
|
3294
|
-
activeRuns.delete(msg.messageId);
|
|
3295
|
-
pendingCancels.delete(msg.messageId);
|
|
3296
|
-
}
|
|
3297
|
-
})();
|
|
3298
|
-
activeTasks.add(task);
|
|
3299
|
-
void task.finally(() => {
|
|
3300
|
-
activeTasks.delete(task);
|
|
3301
|
-
});
|
|
3302
2417
|
});
|
|
3303
2418
|
ws.on("close", (code, reason) => {
|
|
3304
2419
|
console.log(
|
|
3305
2420
|
`[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
|
|
3306
2421
|
);
|
|
3307
|
-
|
|
2422
|
+
shutdown();
|
|
3308
2423
|
});
|
|
3309
2424
|
ws.on("error", (err) => {
|
|
3310
2425
|
console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
|
|
@@ -3312,55 +2427,31 @@ async function runConnect(options) {
|
|
|
3312
2427
|
});
|
|
3313
2428
|
process.on("SIGINT", () => {
|
|
3314
2429
|
console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
|
|
3315
|
-
|
|
2430
|
+
shutdown();
|
|
3316
2431
|
});
|
|
3317
2432
|
process.on("SIGTERM", () => {
|
|
3318
|
-
|
|
2433
|
+
shutdown();
|
|
3319
2434
|
});
|
|
3320
2435
|
});
|
|
3321
2436
|
}
|
|
3322
2437
|
|
|
3323
|
-
// src/commands/create-pr.ts
|
|
3324
|
-
async function runCreatePr(options) {
|
|
3325
|
-
const sessionId = options.sessionId.trim();
|
|
3326
|
-
if (!sessionId) {
|
|
3327
|
-
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
3328
|
-
process.exit(1);
|
|
3329
|
-
}
|
|
3330
|
-
const title = options.title.trim();
|
|
3331
|
-
if (!title) {
|
|
3332
|
-
console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
|
|
3333
|
-
process.exit(1);
|
|
3334
|
-
}
|
|
3335
|
-
const cfg = await ensureLoggedConfig();
|
|
3336
|
-
const api = createApmApiClient(cfg);
|
|
3337
|
-
const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
|
|
3338
|
-
const pr = await api.cli.createPullRequest({
|
|
3339
|
-
sessionId,
|
|
3340
|
-
workdir,
|
|
3341
|
-
title,
|
|
3342
|
-
content: options.content ?? ""
|
|
3343
|
-
});
|
|
3344
|
-
console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
2438
|
// src/commands/deploy/backend.ts
|
|
3348
2439
|
import path5 from "node:path";
|
|
3349
2440
|
|
|
3350
2441
|
// src/commands/deploy/internal/apm-config.ts
|
|
3351
|
-
import { existsSync as
|
|
2442
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
3352
2443
|
import { resolve as resolve4 } from "node:path";
|
|
3353
2444
|
function loadApmConfig(options) {
|
|
3354
2445
|
const p = resolve4(
|
|
3355
2446
|
process.cwd(),
|
|
3356
2447
|
options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
|
|
3357
2448
|
);
|
|
3358
|
-
if (!
|
|
2449
|
+
if (!existsSync9(p)) {
|
|
3359
2450
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
3360
2451
|
process.exit(1);
|
|
3361
2452
|
}
|
|
3362
2453
|
try {
|
|
3363
|
-
const raw =
|
|
2454
|
+
const raw = readFileSync10(p, "utf8");
|
|
3364
2455
|
return JSON.parse(raw);
|
|
3365
2456
|
} catch (e) {
|
|
3366
2457
|
console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
|
|
@@ -3482,7 +2573,7 @@ import path4 from "node:path";
|
|
|
3482
2573
|
import Docker from "dockerode";
|
|
3483
2574
|
|
|
3484
2575
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
3485
|
-
import { existsSync as
|
|
2576
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
|
|
3486
2577
|
import path from "node:path";
|
|
3487
2578
|
function asOptionalTlsBuffer(value) {
|
|
3488
2579
|
if (typeof value !== "string") {
|
|
@@ -3494,8 +2585,8 @@ function asOptionalTlsBuffer(value) {
|
|
|
3494
2585
|
if (normalized === "") {
|
|
3495
2586
|
return void 0;
|
|
3496
2587
|
}
|
|
3497
|
-
if (
|
|
3498
|
-
return
|
|
2588
|
+
if (existsSync10(normalized)) {
|
|
2589
|
+
return readFileSync11(normalized);
|
|
3499
2590
|
}
|
|
3500
2591
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
3501
2592
|
if (looksLikePath) {
|
|
@@ -3705,7 +2796,7 @@ var DockerodeClient = class {
|
|
|
3705
2796
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
3706
2797
|
|
|
3707
2798
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
3708
|
-
import { existsSync as
|
|
2799
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
|
|
3709
2800
|
import path2 from "node:path";
|
|
3710
2801
|
function stripSurroundingQuotes(value) {
|
|
3711
2802
|
const t = value.trim();
|
|
@@ -3722,10 +2813,10 @@ function loadEnvFromFile(envFilePath) {
|
|
|
3722
2813
|
return {};
|
|
3723
2814
|
}
|
|
3724
2815
|
const targetPath = path2.resolve(envFilePath);
|
|
3725
|
-
if (!
|
|
2816
|
+
if (!existsSync11(targetPath) || !statSync4(targetPath).isFile()) {
|
|
3726
2817
|
return {};
|
|
3727
2818
|
}
|
|
3728
|
-
const raw =
|
|
2819
|
+
const raw = readFileSync12(targetPath, "utf-8");
|
|
3729
2820
|
const result = {};
|
|
3730
2821
|
for (const line of raw.split(/\r?\n/)) {
|
|
3731
2822
|
const normalized = line.trim();
|
|
@@ -3896,12 +2987,12 @@ function dockerPushImage(params, cwd) {
|
|
|
3896
2987
|
}
|
|
3897
2988
|
|
|
3898
2989
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
3899
|
-
import { existsSync as
|
|
2990
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
3900
2991
|
import path3 from "node:path";
|
|
3901
2992
|
function resolveDockerBuildPaths(cwd) {
|
|
3902
2993
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
3903
2994
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
3904
|
-
if (!
|
|
2995
|
+
if (!existsSync12(dockerfilePath)) {
|
|
3905
2996
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
3906
2997
|
}
|
|
3907
2998
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
|
@@ -4030,14 +3121,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
|
|
|
4030
3121
|
import path7 from "node:path";
|
|
4031
3122
|
|
|
4032
3123
|
// src/commands/deploy/internal/minio.ts
|
|
4033
|
-
import { statSync as
|
|
3124
|
+
import { statSync as statSync5 } from "node:fs";
|
|
4034
3125
|
import { readdir, readFile } from "node:fs/promises";
|
|
4035
3126
|
import path6 from "node:path";
|
|
4036
3127
|
import * as Minio from "minio";
|
|
4037
3128
|
var DEFAULT_MAX_FILE_SIZE_MB = 50;
|
|
4038
3129
|
async function isDirectoryPath(dir) {
|
|
4039
3130
|
try {
|
|
4040
|
-
const st =
|
|
3131
|
+
const st = statSync5(dir);
|
|
4041
3132
|
return st.isDirectory();
|
|
4042
3133
|
} catch {
|
|
4043
3134
|
return false;
|
|
@@ -4067,7 +3158,7 @@ async function collectFiles(root) {
|
|
|
4067
3158
|
if (e.isDirectory()) {
|
|
4068
3159
|
await walk(abs, rel);
|
|
4069
3160
|
} else if (e.isFile()) {
|
|
4070
|
-
const st =
|
|
3161
|
+
const st = statSync5(abs);
|
|
4071
3162
|
out.push({
|
|
4072
3163
|
absPath: abs,
|
|
4073
3164
|
relativePath: rel.replace(/\\/g, "/"),
|
|
@@ -4403,14 +3494,12 @@ function buildClearRemoteDirExceptZipCommand(target) {
|
|
|
4403
3494
|
const script = [
|
|
4404
3495
|
`T=${quotedTarget}`,
|
|
4405
3496
|
"S=$(mktemp -d)",
|
|
4406
|
-
'while IFS= read -r -d "" z; do',
|
|
4407
|
-
'r="${z#${T}/}"',
|
|
3497
|
+
'while IFS= read -r -d "" z; do r="${z#${T}/}"',
|
|
4408
3498
|
'mkdir -p "${S}/$(dirname "$r")"',
|
|
4409
3499
|
'mv "$z" "${S}/${r}"',
|
|
4410
3500
|
'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
|
|
4411
3501
|
'rm -rf "${T}"/*',
|
|
4412
|
-
'while IFS= read -r -d "" r; do',
|
|
4413
|
-
'r="${r#./}"',
|
|
3502
|
+
'while IFS= read -r -d "" r; do r="${r#./}"',
|
|
4414
3503
|
'mkdir -p "${T}/$(dirname "$r")"',
|
|
4415
3504
|
'mv "${S}/${r}" "${T}/${r}"',
|
|
4416
3505
|
'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
|
|
@@ -4527,7 +3616,7 @@ function registerDeployCommands(program) {
|
|
|
4527
3616
|
function buildProgram() {
|
|
4528
3617
|
const program = new Command();
|
|
4529
3618
|
program.name("apm").description(
|
|
4530
|
-
`\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
|
|
4531
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`
|
|
4532
3621
|
).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
|
|
4533
3622
|
program.command("login").description(
|
|
@@ -4550,63 +3639,11 @@ function buildProgram() {
|
|
|
4550
3639
|
).action(async () => {
|
|
4551
3640
|
await runUpdateSkills();
|
|
4552
3641
|
});
|
|
4553
|
-
program.command("sync-deploy-config").description(
|
|
4554
|
-
"\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"
|
|
4555
|
-
).action(async () => {
|
|
4556
|
-
await runSyncDeployConfig();
|
|
4557
|
-
});
|
|
4558
|
-
program.command("sync-project-documents").description(
|
|
4559
|
-
"\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"
|
|
4560
|
-
).option("--push", "\u63A8\u9001\u672C\u5730 .apm/project/ \u5230\u5E73\u53F0").option("--pull", "\u4ECE\u5E73\u53F0\u62C9\u53D6\u5230 .apm/project/").action(async (opts) => {
|
|
4561
|
-
await runSyncProjectDocuments(opts);
|
|
4562
|
-
});
|
|
4563
|
-
program.command("pull").description(
|
|
4564
|
-
"\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/"
|
|
4565
|
-
).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
|
|
4566
|
-
await runPull(sessionId);
|
|
4567
|
-
});
|
|
4568
|
-
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(
|
|
4569
|
-
"--file <name>",
|
|
4570
|
-
"\u6587\u6863\u540D\u79F0\uFF08\u5982 PRD \u6216 PRD.md\uFF09\uFF0C\u8BFB\u53D6 .apm/sessions/<sessionId>/docs/ \u4E0B\u5BF9\u5E94\u6587\u4EF6"
|
|
4571
|
-
).action(async (sessionId, opts) => {
|
|
4572
|
-
await runSyncDocument(sessionId, { file: opts.file });
|
|
4573
|
-
});
|
|
4574
|
-
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) => {
|
|
4575
|
-
await runAppendMessage(opts);
|
|
4576
|
-
});
|
|
4577
|
-
program.command("update-message-status").description("\u66F4\u65B0\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u72B6\u6001").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption(
|
|
4578
|
-
"--status <status>",
|
|
4579
|
-
"CREATED | QUEUED | TYPING | SUCCESS | FAILED | CANCELLED"
|
|
4580
|
-
).action(async (opts) => {
|
|
4581
|
-
await runUpdateMessageStatus(opts);
|
|
4582
|
-
});
|
|
4583
3642
|
program.command("connect").description(
|
|
4584
|
-
"\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"
|
|
4585
3644
|
).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
|
|
4586
3645
|
await runConnect(opts);
|
|
4587
3646
|
});
|
|
4588
|
-
program.command("branch").description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
|
|
4589
|
-
"-m, --message <text>",
|
|
4590
|
-
"\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
|
|
4591
|
-
).action(async (sessionId, opts) => {
|
|
4592
|
-
await runBranch(sessionId, { message: opts.message });
|
|
4593
|
-
});
|
|
4594
|
-
program.command("clean-branches").description(
|
|
4595
|
-
"\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"
|
|
4596
|
-
).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (opts) => {
|
|
4597
|
-
await runCleanBranches({ dryRun: opts.dryRun });
|
|
4598
|
-
});
|
|
4599
|
-
program.command("create-pr").description(
|
|
4600
|
-
"\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"
|
|
4601
|
-
).requiredOption("--session <sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--title <title>", "PR \u6807\u9898").option("--content <content>", "PR \u6B63\u6587\uFF08Markdown\uFF09", "").action(
|
|
4602
|
-
async (opts) => {
|
|
4603
|
-
await runCreatePr({
|
|
4604
|
-
sessionId: opts.session,
|
|
4605
|
-
title: opts.title,
|
|
4606
|
-
content: opts.content ?? ""
|
|
4607
|
-
});
|
|
4608
|
-
}
|
|
4609
|
-
);
|
|
4610
3647
|
registerDeployCommands(program);
|
|
4611
3648
|
return program;
|
|
4612
3649
|
}
|