ai-project-manage-cli 4.0.22 → 5.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 +23 -12
- package/dist/index.js +360 -1345
- package/package.json +4 -11
- package/template/apm.config.json +1 -24
- package/template/deploy/README.md +0 -3
- package/template/skills/apm-apply-change/SKILL.md +0 -191
- package/template/skills/apm-deploy/SKILL.md +0 -135
- package/template/skills/apm-dev/SKILL.md +0 -161
- package/template/skills/apm-propose/SKILL.md +0 -123
- package/template/skills/apm-propose/design-instruction.md +0 -94
- package/template/skills/apm-propose/propose-instruction.md +0 -81
- package/template/skills/apm-propose/specs-instruction.md +0 -114
- package/template/skills/apm-propose/tasks-instruction.md +0 -90
- package/template/skills/apm-refine/SKILL.md +0 -99
- package/template/skills/apm-refine/apm-refine-template.md +0 -73
- package/template/skills/apm-review/SKILL.md +0 -111
- package/template/skills/apm-review/output-template.md +0 -60
- package/template/theater-skills/README.md +0 -13
- package/template/theater-skills/apm-theater-backend/SKILL.md +0 -115
- package/template/theater-skills/apm-theater-backend/apm-theater-api-template.md +0 -146
- package/template/theater-skills/apm-theater-backend/apm-theater-backend-template.md +0 -132
- package/template/theater-skills/apm-theater-dev/SKILL.md +0 -174
- package/template/theater-skills/apm-theater-prd/SKILL.md +0 -98
- package/template/workitems/README.md +0 -20
package/dist/index.js
CHANGED
|
@@ -40,12 +40,6 @@ function defaultApmConfig() {
|
|
|
40
40
|
email: ""
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
function httpBaseToWsOrigin(httpBase) {
|
|
44
|
-
const u = httpBase.trim().replace(/\/+$/, "");
|
|
45
|
-
if (u.startsWith("https://")) return "wss://" + u.slice("https://".length);
|
|
46
|
-
if (u.startsWith("http://")) return "ws://" + u.slice("http://".length);
|
|
47
|
-
throw new Error("baseUrl \u5FC5\u987B\u4EE5 http:// \u6216 https:// \u5F00\u5934");
|
|
48
|
-
}
|
|
49
43
|
async function ensureApmConfig() {
|
|
50
44
|
const existing = await readApmConfig();
|
|
51
45
|
if (existing) return existing;
|
|
@@ -65,172 +59,47 @@ async function writeApmConfig(cfg) {
|
|
|
65
59
|
mode: 384
|
|
66
60
|
});
|
|
67
61
|
}
|
|
68
|
-
function buildAgentWsUrl(httpBase, token) {
|
|
69
|
-
const origin = httpBaseToWsOrigin(httpBase);
|
|
70
|
-
const q = new URLSearchParams({ token });
|
|
71
|
-
return `${origin}/ws/agent?${q.toString()}`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// src/commands/comment.ts
|
|
75
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
76
|
-
|
|
77
|
-
// src/api/client.ts
|
|
78
|
-
import { createApiClient } from "listpage-http";
|
|
79
62
|
|
|
80
|
-
// src/
|
|
81
|
-
import {
|
|
82
|
-
|
|
83
|
-
auth: {
|
|
84
|
-
login: defineEndpoint({
|
|
85
|
-
method: "POST",
|
|
86
|
-
path: "/auth/login",
|
|
87
|
-
authRequired: false
|
|
88
|
-
})
|
|
89
|
-
},
|
|
90
|
-
cliRequirements: {
|
|
91
|
-
pull: defineEndpoint({
|
|
92
|
-
method: "GET",
|
|
93
|
-
path: "/cli/requirements/pull"
|
|
94
|
-
}),
|
|
95
|
-
branchBaseline: defineEndpoint({
|
|
96
|
-
method: "GET",
|
|
97
|
-
path: "/cli/requirements/branch-baseline"
|
|
98
|
-
}),
|
|
99
|
-
comment: defineEndpoint({
|
|
100
|
-
method: "POST",
|
|
101
|
-
path: "/cli/requirements/comment"
|
|
102
|
-
}),
|
|
103
|
-
commentStructured: defineEndpoint({
|
|
104
|
-
method: "POST",
|
|
105
|
-
path: "/cli/requirements/comment-structured"
|
|
106
|
-
}),
|
|
107
|
-
refine: defineEndpoint({
|
|
108
|
-
method: "POST",
|
|
109
|
-
path: "/cli/requirements/refine"
|
|
110
|
-
}),
|
|
111
|
-
updateStatus: defineEndpoint({
|
|
112
|
-
method: "POST",
|
|
113
|
-
path: "/cli/requirements/update-status"
|
|
114
|
-
}),
|
|
115
|
-
updateDevStatus: defineEndpoint({
|
|
116
|
-
method: "POST",
|
|
117
|
-
path: "/cli/requirements/update-dev-status"
|
|
118
|
-
})
|
|
119
|
-
},
|
|
120
|
-
requirementAttachment: {
|
|
121
|
-
list: defineEndpoint({
|
|
122
|
-
method: "GET",
|
|
123
|
-
path: "/requirement-attachments/list"
|
|
124
|
-
}),
|
|
125
|
-
download: defineEndpoint({
|
|
126
|
-
method: "GET",
|
|
127
|
-
path: "/requirement-attachments/download",
|
|
128
|
-
mode: "download"
|
|
129
|
-
})
|
|
130
|
-
},
|
|
131
|
-
requirementArtifact: {
|
|
132
|
-
list: defineEndpoint({
|
|
133
|
-
method: "GET",
|
|
134
|
-
path: "/requirement-artifacts/list"
|
|
135
|
-
}),
|
|
136
|
-
get: defineEndpoint({
|
|
137
|
-
method: "GET",
|
|
138
|
-
path: "/requirement-artifacts/get"
|
|
139
|
-
}),
|
|
140
|
-
create: defineEndpoint({
|
|
141
|
-
method: "POST",
|
|
142
|
-
path: "/requirement-artifacts/create"
|
|
143
|
-
}),
|
|
144
|
-
upsert: defineEndpoint({
|
|
145
|
-
method: "POST",
|
|
146
|
-
path: "/requirement-artifacts/upsert"
|
|
147
|
-
}),
|
|
148
|
-
update: defineEndpoint({
|
|
149
|
-
method: "POST",
|
|
150
|
-
path: "/requirement-artifacts/update"
|
|
151
|
-
}),
|
|
152
|
-
delete: defineEndpoint({
|
|
153
|
-
method: "POST",
|
|
154
|
-
path: "/requirement-artifacts/delete"
|
|
155
|
-
})
|
|
156
|
-
},
|
|
157
|
-
theaterSessionArtifact: {
|
|
158
|
-
upsert: defineEndpoint({
|
|
159
|
-
method: "POST",
|
|
160
|
-
path: "/theater-session-artifacts/upsert"
|
|
161
|
-
}),
|
|
162
|
-
create: defineEndpoint({
|
|
163
|
-
method: "POST",
|
|
164
|
-
path: "/theater-session-artifacts/create"
|
|
165
|
-
}),
|
|
166
|
-
update: defineEndpoint({
|
|
167
|
-
method: "POST",
|
|
168
|
-
path: "/theater-session-artifacts/update"
|
|
169
|
-
}),
|
|
170
|
-
list: defineEndpoint({
|
|
171
|
-
method: "GET",
|
|
172
|
-
path: "/theater-session-artifacts/list"
|
|
173
|
-
}),
|
|
174
|
-
get: defineEndpoint({
|
|
175
|
-
method: "GET",
|
|
176
|
-
path: "/theater-session-artifacts/get"
|
|
177
|
-
})
|
|
178
|
-
},
|
|
179
|
-
theater: {
|
|
180
|
-
reportMemberAgentProgress: defineEndpoint({
|
|
181
|
-
method: "POST",
|
|
182
|
-
path: "/theater/sessions/report-member-agent-progress"
|
|
183
|
-
}),
|
|
184
|
-
submitMemberResponse: defineEndpoint({
|
|
185
|
-
method: "POST",
|
|
186
|
-
path: "/theater/sessions/submit-member-response"
|
|
187
|
-
})
|
|
188
|
-
},
|
|
189
|
-
spaceWorkflow: {
|
|
190
|
-
aiStartNode: defineEndpoint({
|
|
191
|
-
method: "POST",
|
|
192
|
-
path: "/space-workflows/ai-start-node"
|
|
193
|
-
}),
|
|
194
|
-
aiConfirmNode: defineEndpoint(
|
|
195
|
-
{
|
|
196
|
-
method: "POST",
|
|
197
|
-
path: "/space-workflows/ai-confirm-node"
|
|
198
|
-
}
|
|
199
|
-
),
|
|
200
|
-
aiFailNode: defineEndpoint({
|
|
201
|
-
method: "POST",
|
|
202
|
-
path: "/space-workflows/ai-fail-node"
|
|
203
|
-
})
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// src/api/client.ts
|
|
208
|
-
function createApmApiClient(cfg) {
|
|
209
|
-
const baseURL = `${cfg.baseUrl.trim().replace(/\/+$/, "")}/api/v1`;
|
|
210
|
-
return createApiClient(requestConfig, {
|
|
211
|
-
baseURL,
|
|
212
|
-
getToken: () => cfg.token || void 0,
|
|
213
|
-
successCodes: [200, 201],
|
|
214
|
-
unauthorizedCodes: [401]
|
|
215
|
-
});
|
|
216
|
-
}
|
|
63
|
+
// src/commands/init.ts
|
|
64
|
+
import { join as join3 } from "path";
|
|
65
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
217
66
|
|
|
218
67
|
// src/command-utils.ts
|
|
219
68
|
import { cpSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
|
|
220
|
-
import { dirname, join as join2, resolve } from "path";
|
|
69
|
+
import { basename, dirname, extname, join as join2, resolve } from "path";
|
|
221
70
|
import { fileURLToPath } from "url";
|
|
222
71
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
223
72
|
var CLI_TEMPLATE_DIR = resolve(__dirname, "../template");
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
73
|
+
function workspaceApmDir(cwd = process.cwd()) {
|
|
74
|
+
return resolve(cwd, ".apm");
|
|
75
|
+
}
|
|
76
|
+
var SESSIONS_SUBDIR = "sessions";
|
|
77
|
+
var SESSION_DOCS_SUBDIR = "docs";
|
|
78
|
+
var SESSION_ATTACHMENTS_SUBDIR = "attachments";
|
|
79
|
+
function sessionDir(sessionId, apmRoot) {
|
|
80
|
+
return join2(apmRoot ?? workspaceApmDir(), SESSIONS_SUBDIR, sessionId);
|
|
81
|
+
}
|
|
82
|
+
function sessionDocsDir(sessionId, apmRoot) {
|
|
83
|
+
return join2(sessionDir(sessionId, apmRoot), SESSION_DOCS_SUBDIR);
|
|
84
|
+
}
|
|
85
|
+
function sessionRulePath(sessionId, apmRoot) {
|
|
86
|
+
return join2(sessionDir(sessionId, apmRoot), "RULE.md");
|
|
227
87
|
}
|
|
228
|
-
function
|
|
229
|
-
return join2(apmRoot, "
|
|
88
|
+
function sessionYamlPath(sessionId, apmRoot) {
|
|
89
|
+
return join2(sessionDir(sessionId, apmRoot), "session.yaml");
|
|
230
90
|
}
|
|
231
|
-
function
|
|
232
|
-
const
|
|
233
|
-
|
|
91
|
+
function documentLocalFileName(platformName) {
|
|
92
|
+
const trimmed = platformName.trim();
|
|
93
|
+
if (!trimmed) return "document.md";
|
|
94
|
+
if (extname(trimmed).toLowerCase() === ".md") return trimmed;
|
|
95
|
+
return `${trimmed}.md`;
|
|
96
|
+
}
|
|
97
|
+
function documentPlatformName(filePath) {
|
|
98
|
+
const base = basename(filePath.trim().replace(/\\/g, "/"));
|
|
99
|
+
if (base.toLowerCase().endsWith(".md")) {
|
|
100
|
+
return base.slice(0, -3);
|
|
101
|
+
}
|
|
102
|
+
return base;
|
|
234
103
|
}
|
|
235
104
|
async function ensureLoggedConfig() {
|
|
236
105
|
const cfg = await ensureApmConfig();
|
|
@@ -246,7 +115,7 @@ async function ensureDirExists(dir) {
|
|
|
246
115
|
mkdirSync2(dir, { recursive: true });
|
|
247
116
|
}
|
|
248
117
|
async function ensureWorkspaceApmDirForInit() {
|
|
249
|
-
const dir =
|
|
118
|
+
const dir = workspaceApmDir();
|
|
250
119
|
if (!existsSync(dir)) {
|
|
251
120
|
mkdirSync2(dir, { recursive: true });
|
|
252
121
|
return;
|
|
@@ -277,994 +146,92 @@ async function copyTemplateFiles(targetDir) {
|
|
|
277
146
|
})
|
|
278
147
|
);
|
|
279
148
|
}
|
|
280
|
-
function xmlEscape(v) {
|
|
281
|
-
return v.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
282
|
-
}
|
|
283
149
|
function resolveCwdPath(file) {
|
|
284
150
|
return resolve(process.cwd(), file);
|
|
285
151
|
}
|
|
286
152
|
|
|
287
|
-
// src/
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
function asRecord(value) {
|
|
299
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
300
|
-
return value;
|
|
301
|
-
}
|
|
302
|
-
throw new Error("YAML \u7ED3\u6784\u65E0\u6548\uFF1A\u9700\u8981\u5BF9\u8C61\u6839\u8282\u70B9");
|
|
303
|
-
}
|
|
304
|
-
function parseAnchor(anchor, index) {
|
|
305
|
-
const rec = asRecord(anchor);
|
|
306
|
-
const start = Number(rec.start ?? rec.startLine);
|
|
307
|
-
const end = Number(rec.end ?? rec.endLine);
|
|
308
|
-
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 1 || end < start) {
|
|
309
|
-
throw new Error(`items[${index}].anchor \u884C\u53F7\u65E0\u6548\uFF08\u9700 1 \u2264 start \u2264 end\uFF09`);
|
|
310
|
-
}
|
|
311
|
-
return { start, end };
|
|
312
|
-
}
|
|
313
|
-
function parseKind(raw, index) {
|
|
314
|
-
const key = String(raw ?? "").trim();
|
|
315
|
-
const kind = KIND_MAP[key] ?? KIND_MAP[key.toLowerCase()];
|
|
316
|
-
if (!kind) {
|
|
317
|
-
if (key.toLowerCase() === "coordination" || key === "COORDINATION") {
|
|
318
|
-
throw new Error(
|
|
319
|
-
`items[${index}].kind \u4E0D\u518D\u652F\u6301 coordination\uFF08\u8054\u8C03\u4F9D\u8D56\uFF09\uFF1B\u8BF7\u52FF\u8F93\u51FA\u63A5\u53E3/\u8054\u8C03\u7C7B\u8BC4\u5BA1\u6761\u76EE`
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
throw new Error(
|
|
323
|
-
`items[${index}].kind \u65E0\u6548\uFF0C\u5E94\u4E3A clarify | difficulty | business`
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
return kind;
|
|
327
|
-
}
|
|
328
|
-
function parseStructuredReviewYaml(raw, cliModel) {
|
|
329
|
-
let doc;
|
|
330
|
-
try {
|
|
331
|
-
doc = parse(raw);
|
|
332
|
-
} catch (e) {
|
|
333
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
334
|
-
throw new Error(`YAML \u89E3\u6790\u5931\u8D25\uFF1A${msg}`);
|
|
335
|
-
}
|
|
336
|
-
const root = asRecord(doc);
|
|
337
|
-
const reviewer = asRecord(root.reviewer ?? {});
|
|
338
|
-
const stance = String(reviewer.stance ?? root.stance ?? "").trim();
|
|
339
|
-
if (!STANCE_VALUES.has(stance)) {
|
|
340
|
-
throw new Error("reviewer.stance \u5FC5\u987B\u4E3A frontend\u3001backend \u6216 fullstack");
|
|
341
|
-
}
|
|
342
|
-
const modelFromYaml = reviewer.model != null ? String(reviewer.model).trim() : "";
|
|
343
|
-
const model = cliModel?.trim() || modelFromYaml || null;
|
|
344
|
-
const itemsRaw = root.items;
|
|
345
|
-
if (!Array.isArray(itemsRaw) || itemsRaw.length === 0) {
|
|
346
|
-
throw new Error("items \u4E0D\u80FD\u4E3A\u7A7A");
|
|
347
|
-
}
|
|
348
|
-
const items = itemsRaw.map((entry, index) => {
|
|
349
|
-
const item = asRecord(entry);
|
|
350
|
-
const anchor = parseAnchor(item.anchor ?? item, index);
|
|
351
|
-
const kind = parseKind(item.kind, index);
|
|
352
|
-
const body = typeof item.body === "string" ? item.body.trim() : String(item.body ?? "").trim();
|
|
353
|
-
if (!body) {
|
|
354
|
-
throw new Error(`items[${index}].body \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
355
|
-
}
|
|
356
|
-
return {
|
|
357
|
-
startLine: anchor.start,
|
|
358
|
-
endLine: anchor.end,
|
|
359
|
-
kind,
|
|
360
|
-
body
|
|
361
|
-
};
|
|
362
|
-
});
|
|
363
|
-
return { stance, model, items };
|
|
364
|
-
}
|
|
365
|
-
function inferCommentFormat(filePath, explicit) {
|
|
366
|
-
if (explicit === "structured" || explicit === "legacy") {
|
|
367
|
-
return explicit;
|
|
368
|
-
}
|
|
369
|
-
const lower = filePath.toLowerCase();
|
|
370
|
-
if (lower.endsWith(".yaml") || lower.endsWith(".yml")) {
|
|
371
|
-
return "structured";
|
|
372
|
-
}
|
|
373
|
-
return "legacy";
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// src/commands/comment.ts
|
|
377
|
-
async function runComment(requirementId, file, options) {
|
|
378
|
-
const cfg = await ensureLoggedConfig();
|
|
379
|
-
const resolvedPath = resolveCwdPath(file);
|
|
380
|
-
const raw = readFileSync2(resolvedPath, "utf8");
|
|
381
|
-
const format = inferCommentFormat(resolvedPath, options?.format);
|
|
382
|
-
const api = createApmApiClient(cfg);
|
|
383
|
-
if (format === "structured") {
|
|
384
|
-
const parsed = parseStructuredReviewYaml(raw, options?.model);
|
|
385
|
-
const data2 = await api.cliRequirements.commentStructured({
|
|
386
|
-
requirementId,
|
|
387
|
-
stance: parsed.stance,
|
|
388
|
-
model: parsed.model,
|
|
389
|
-
items: parsed.items
|
|
390
|
-
});
|
|
391
|
-
console.log(JSON.stringify(data2, null, 2));
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (!raw.trim()) {
|
|
395
|
-
throw new Error("\u8BC4\u8BBA\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A");
|
|
153
|
+
// src/commands/init.ts
|
|
154
|
+
async function runInit(name) {
|
|
155
|
+
await ensureWorkspaceApmDirForInit();
|
|
156
|
+
const apmDir = workspaceApmDir();
|
|
157
|
+
await copyTemplateFiles(apmDir);
|
|
158
|
+
if (name) {
|
|
159
|
+
const apmConfigPath = join3(apmDir, "apm.config.json");
|
|
160
|
+
const config = readFileSync2(apmConfigPath, "utf8");
|
|
161
|
+
const configJson = JSON.parse(config);
|
|
162
|
+
configJson.name = name;
|
|
163
|
+
writeFileSync2(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
|
|
396
164
|
}
|
|
397
|
-
const data = await api.cliRequirements.comment({
|
|
398
|
-
requirementId,
|
|
399
|
-
content: raw,
|
|
400
|
-
model: options?.model
|
|
401
|
-
});
|
|
402
|
-
console.log(JSON.stringify(data, null, 2));
|
|
403
165
|
}
|
|
404
166
|
|
|
405
|
-
// src/commands/
|
|
406
|
-
import {
|
|
407
|
-
import WebSocket from "ws";
|
|
408
|
-
import { Agent } from "@cursor/sdk";
|
|
409
|
-
import { join as join7 } from "path";
|
|
410
|
-
|
|
411
|
-
// src/session-utils.ts
|
|
412
|
-
import { appendFileSync } from "fs";
|
|
413
|
-
import { resolve as resolve2 } from "path";
|
|
414
|
-
var EventSession = class {
|
|
415
|
-
events = [];
|
|
416
|
-
constructor(prompt) {
|
|
417
|
-
this.events.push({
|
|
418
|
-
type: "input",
|
|
419
|
-
content: prompt
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
addEvent(event) {
|
|
423
|
-
const latestEvent = this.events[this.events.length - 1];
|
|
424
|
-
const formatedEvent = this.formatEvent(event);
|
|
425
|
-
if (!formatedEvent) {
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
if (formatedEvent.type === "tool_call") {
|
|
429
|
-
const existingToolCall = this.events.find(
|
|
430
|
-
(e) => e.type === "tool_call" && e.call_id === formatedEvent.call_id
|
|
431
|
-
);
|
|
432
|
-
if (existingToolCall) {
|
|
433
|
-
existingToolCall.args = formatedEvent.args;
|
|
434
|
-
existingToolCall.result = formatedEvent.result;
|
|
435
|
-
existingToolCall.status = formatedEvent.status;
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
this.events.push(formatedEvent);
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (formatedEvent.type === "status") {
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
if (formatedEvent.type === "request") {
|
|
445
|
-
this.events.push(formatedEvent);
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
if (latestEvent?.type === formatedEvent.type) {
|
|
449
|
-
switch (formatedEvent.type) {
|
|
450
|
-
case "assistant":
|
|
451
|
-
latestEvent.content += formatedEvent.content;
|
|
452
|
-
break;
|
|
453
|
-
case "thinking":
|
|
454
|
-
latestEvent.content += formatedEvent.content;
|
|
455
|
-
break;
|
|
456
|
-
case "task":
|
|
457
|
-
latestEvent.status = formatedEvent.status;
|
|
458
|
-
latestEvent.text = formatedEvent.text;
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
this.events.push(formatedEvent);
|
|
464
|
-
}
|
|
465
|
-
formatEvent(event) {
|
|
466
|
-
switch (event.type) {
|
|
467
|
-
case "assistant":
|
|
468
|
-
return {
|
|
469
|
-
type: "assistant",
|
|
470
|
-
content: event.message.content[0].text || event.content || ""
|
|
471
|
-
};
|
|
472
|
-
case "thinking":
|
|
473
|
-
return {
|
|
474
|
-
type: "thinking",
|
|
475
|
-
content: event.text || event.content || ""
|
|
476
|
-
};
|
|
477
|
-
case "tool_call":
|
|
478
|
-
return {
|
|
479
|
-
type: "tool_call",
|
|
480
|
-
args: event.args,
|
|
481
|
-
result: event.result,
|
|
482
|
-
status: event.status,
|
|
483
|
-
call_id: event.call_id,
|
|
484
|
-
name: event.name
|
|
485
|
-
};
|
|
486
|
-
case "task":
|
|
487
|
-
return {
|
|
488
|
-
type: "task",
|
|
489
|
-
status: event.status,
|
|
490
|
-
text: event.text
|
|
491
|
-
};
|
|
492
|
-
case "request":
|
|
493
|
-
return {
|
|
494
|
-
...event,
|
|
495
|
-
type: "request"
|
|
496
|
-
};
|
|
497
|
-
case "status":
|
|
498
|
-
return { type: "status", status: event.status, message: event.message };
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
/** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
|
|
502
|
-
getAssistantText() {
|
|
503
|
-
return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
|
|
504
|
-
}
|
|
505
|
-
writeToFile(cwd, requirementId, agentId) {
|
|
506
|
-
const sessionsDir = resolve2(
|
|
507
|
-
cwd,
|
|
508
|
-
`.apm/workitems/${requirementId}/sessions`
|
|
509
|
-
);
|
|
510
|
-
ensureDirExists(sessionsDir);
|
|
511
|
-
const sessionFile = resolve2(sessionsDir, `${agentId}.md`);
|
|
512
|
-
this.events.forEach((event) => {
|
|
513
|
-
const type = event.type;
|
|
514
|
-
const content = (function(type2) {
|
|
515
|
-
if (type2 === "input") {
|
|
516
|
-
return `## \u7528\u6237\u8F93\u5165
|
|
517
|
-
|
|
518
|
-
${event.content}
|
|
519
|
-
`;
|
|
520
|
-
}
|
|
521
|
-
if (type2 === "assistant") {
|
|
522
|
-
return `## \u6A21\u578B\u8F93\u51FA
|
|
523
|
-
|
|
524
|
-
${event.content}
|
|
525
|
-
`;
|
|
526
|
-
}
|
|
527
|
-
if (type2 === "thinking") {
|
|
528
|
-
return `## \u6A21\u578B\u601D\u8003
|
|
167
|
+
// src/commands/login.ts
|
|
168
|
+
import { ApiError } from "listpage-http";
|
|
529
169
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
if (type2 === "tool_call") {
|
|
534
|
-
return "````toolcall\n" + JSON.stringify(event, null, 2) + "\n````\n";
|
|
535
|
-
}
|
|
536
|
-
return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type2}
|
|
170
|
+
// src/api/client.ts
|
|
171
|
+
import { createApiClient } from "listpage-http";
|
|
537
172
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
173
|
+
// src/api/request-config.ts
|
|
174
|
+
import { defineEndpoint } from "listpage-http";
|
|
175
|
+
var requestConfig = {
|
|
176
|
+
auth: {
|
|
177
|
+
login: defineEndpoint({
|
|
178
|
+
method: "POST",
|
|
179
|
+
path: "/auth/login",
|
|
180
|
+
authRequired: false
|
|
181
|
+
})
|
|
182
|
+
},
|
|
183
|
+
cli: {
|
|
184
|
+
sessionDetail: defineEndpoint({
|
|
185
|
+
method: "GET",
|
|
186
|
+
path: "/cli/sessions/detail"
|
|
187
|
+
}),
|
|
188
|
+
sessionMembers: defineEndpoint({
|
|
189
|
+
method: "GET",
|
|
190
|
+
path: "/cli/sessions/members"
|
|
191
|
+
}),
|
|
192
|
+
listDocuments: defineEndpoint({
|
|
193
|
+
method: "GET",
|
|
194
|
+
path: "/cli/documents"
|
|
195
|
+
}),
|
|
196
|
+
listAttachments: defineEndpoint({
|
|
197
|
+
method: "GET",
|
|
198
|
+
path: "/cli/attachments"
|
|
199
|
+
}),
|
|
200
|
+
upsertDocument: defineEndpoint({
|
|
201
|
+
method: "PUT",
|
|
202
|
+
path: "/cli/documents/upsert"
|
|
203
|
+
}),
|
|
204
|
+
appendMessageContent: defineEndpoint({
|
|
205
|
+
method: "PUT",
|
|
206
|
+
path: "/cli/messages/content"
|
|
207
|
+
}),
|
|
208
|
+
updateMessageStatus: defineEndpoint({
|
|
209
|
+
method: "PUT",
|
|
210
|
+
path: "/cli/messages/status"
|
|
211
|
+
}),
|
|
212
|
+
branchBaseline: defineEndpoint({
|
|
213
|
+
method: "GET",
|
|
214
|
+
path: "/cli/requirements/branch-baseline"
|
|
215
|
+
}),
|
|
216
|
+
listSkills: defineEndpoint({
|
|
217
|
+
method: "GET",
|
|
218
|
+
path: "/cli/skills"
|
|
219
|
+
})
|
|
544
220
|
}
|
|
545
221
|
};
|
|
546
222
|
|
|
547
|
-
// src/
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
async function syncRequirementAttachments(api, requirementId, workitemsDir) {
|
|
556
|
-
const dir = join3(workitemsDir, REQUIREMENT_ATTACHMENTS_SUBDIR);
|
|
557
|
-
await ensureDirExists(dir);
|
|
558
|
-
const { items } = await api.requirementAttachment.list({ requirementId });
|
|
559
|
-
if (items.length === 0) return;
|
|
560
|
-
for (const item of items) {
|
|
561
|
-
const dest = join3(dir, item.fileName);
|
|
562
|
-
if (existsSync2(dest)) continue;
|
|
563
|
-
const { blob } = await api.requirementAttachment.download({
|
|
564
|
-
attachmentId: item.id
|
|
565
|
-
});
|
|
566
|
-
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
567
|
-
writeFileSync2(dest, buffer);
|
|
568
|
-
console.log(
|
|
569
|
-
`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${REQUIREMENT_ATTACHMENTS_SUBDIR}/${item.fileName}`
|
|
570
|
-
);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// src/commands/upload-artifact.ts
|
|
575
|
-
var EXCLUDED_RELATIVE_PATHS = /* @__PURE__ */ new Set([
|
|
576
|
-
"defect.xml",
|
|
577
|
-
"prd.md",
|
|
578
|
-
"requirement-status.yaml",
|
|
579
|
-
"reviews.xml",
|
|
580
|
-
"testcase.xml"
|
|
581
|
-
]);
|
|
582
|
-
function toPosixRelative(root, absoluteFile) {
|
|
583
|
-
return relative(root, absoluteFile).split(sep).join("/");
|
|
584
|
-
}
|
|
585
|
-
function artifactTagFromRelPath(relPosix) {
|
|
586
|
-
const parts = relPosix.split("/");
|
|
587
|
-
const fileName = parts[parts.length - 1] ?? relPosix;
|
|
588
|
-
if (parts.length > 1) {
|
|
589
|
-
return parts[0] ?? fileName;
|
|
590
|
-
}
|
|
591
|
-
const dot = fileName.lastIndexOf(".");
|
|
592
|
-
return dot > 0 ? fileName.slice(0, dot) : fileName;
|
|
593
|
-
}
|
|
594
|
-
function* walkMarkdownFiles(dir) {
|
|
595
|
-
const names = readdirSync2(dir);
|
|
596
|
-
for (const name of names) {
|
|
597
|
-
if (name.startsWith(".")) continue;
|
|
598
|
-
if (name === REQUIREMENT_ATTACHMENTS_SUBDIR) continue;
|
|
599
|
-
const full = join4(dir, name);
|
|
600
|
-
const st = statSync2(full);
|
|
601
|
-
if (st.isDirectory()) {
|
|
602
|
-
yield* walkMarkdownFiles(full);
|
|
603
|
-
continue;
|
|
604
|
-
}
|
|
605
|
-
if (!st.isFile()) continue;
|
|
606
|
-
if (!name.toLowerCase().endsWith(".md")) continue;
|
|
607
|
-
yield full;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
async function runUploadArtifact(requirementId, workspaceDir) {
|
|
611
|
-
const cfg = await ensureLoggedConfig();
|
|
612
|
-
const api = createApmApiClient(cfg);
|
|
613
|
-
const root = requirementWorkitemsDir(requirementId, workspaceDir);
|
|
614
|
-
if (!existsSync3(root)) {
|
|
615
|
-
console.error(
|
|
616
|
-
`[apm] \u76EE\u5F55\u4E0D\u5B58\u5728: ${root}
|
|
617
|
-
\u8BF7\u5148\u6267\u884C: apm pull ${requirementId}`
|
|
618
|
-
);
|
|
619
|
-
process.exit(1);
|
|
620
|
-
}
|
|
621
|
-
for (const abs of walkMarkdownFiles(root)) {
|
|
622
|
-
const relPosix = toPosixRelative(root, abs);
|
|
623
|
-
if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) continue;
|
|
624
|
-
const content = readFileSync3(abs, "utf8");
|
|
625
|
-
const tag = artifactTagFromRelPath(relPosix);
|
|
626
|
-
await api.requirementArtifact.upsert({
|
|
627
|
-
requirementId,
|
|
628
|
-
tag,
|
|
629
|
-
fileName: relPosix,
|
|
630
|
-
content
|
|
631
|
-
});
|
|
632
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u4EA7\u7269: ${relPosix}`);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// src/commands/pull.ts
|
|
637
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
638
|
-
import { join as join5 } from "path";
|
|
639
|
-
import { stringify as yamlStringify } from "yaml";
|
|
640
|
-
|
|
641
|
-
// src/pull-reviews-xml.ts
|
|
642
|
-
function escapeForCdata(text) {
|
|
643
|
-
return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
|
|
644
|
-
}
|
|
645
|
-
function buildReviewsXml(reviews) {
|
|
646
|
-
const lines = ["<reviews>"];
|
|
647
|
-
for (const review of reviews) {
|
|
648
|
-
lines.push(
|
|
649
|
-
` <review id="${xmlEscape(review.id)}" model="${xmlEscape(
|
|
650
|
-
review.model ?? ""
|
|
651
|
-
)}" stance="${xmlEscape(review.stance ?? "")}" memberRole="${xmlEscape(
|
|
652
|
-
review.memberRole
|
|
653
|
-
)}">`
|
|
654
|
-
);
|
|
655
|
-
for (const item of review.items) {
|
|
656
|
-
const kind = item.kind.toLowerCase();
|
|
657
|
-
lines.push(
|
|
658
|
-
` <item id="${xmlEscape(item.id)}" start="${item.startLine}" end="${item.endLine}" kind="${xmlEscape(kind)}" status="open">`
|
|
659
|
-
);
|
|
660
|
-
lines.push(
|
|
661
|
-
` <body><![CDATA[${escapeForCdata(item.body ?? "")}]]></body>`
|
|
662
|
-
);
|
|
663
|
-
const reply = item.reply?.trim() ?? "";
|
|
664
|
-
lines.push(` <reply><![CDATA[${escapeForCdata(reply)}]]></reply>`);
|
|
665
|
-
lines.push(" </item>");
|
|
666
|
-
}
|
|
667
|
-
lines.push(" </review>");
|
|
668
|
-
}
|
|
669
|
-
lines.push("</reviews>", "");
|
|
670
|
-
return lines.join("\n");
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// src/commands/pull.ts
|
|
674
|
-
var PULL_ARTIFACT_FILE_NAMES = ["api.md", "backend.md"];
|
|
675
|
-
function normalizeArtifactPath(fileName) {
|
|
676
|
-
return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "");
|
|
677
|
-
}
|
|
678
|
-
function artifactFileNameMatches(itemFileName, targetFileName) {
|
|
679
|
-
const norm = normalizeArtifactPath(itemFileName);
|
|
680
|
-
const target = normalizeArtifactPath(targetFileName);
|
|
681
|
-
if (norm === target) return true;
|
|
682
|
-
const base = norm.split("/").filter(Boolean).pop() ?? norm;
|
|
683
|
-
return base === target;
|
|
684
|
-
}
|
|
685
|
-
async function fetchArtifactContentByFileName(api, requirementId, fileName) {
|
|
686
|
-
const pageSize = 500;
|
|
687
|
-
let page = 1;
|
|
688
|
-
const items = [];
|
|
689
|
-
while (true) {
|
|
690
|
-
const batch = await api.requirementArtifact.list({
|
|
691
|
-
requirementId,
|
|
692
|
-
page,
|
|
693
|
-
pageSize
|
|
694
|
-
});
|
|
695
|
-
items.push(...batch.items);
|
|
696
|
-
if (batch.total === 0 || items.length >= batch.total) break;
|
|
697
|
-
page += 1;
|
|
698
|
-
}
|
|
699
|
-
const hit = items.find(
|
|
700
|
-
(item) => artifactFileNameMatches(item.fileName, fileName)
|
|
701
|
-
);
|
|
702
|
-
if (!hit) return null;
|
|
703
|
-
const art = await api.requirementArtifact.get({ artifactId: hit.id });
|
|
704
|
-
return art.content;
|
|
705
|
-
}
|
|
706
|
-
function valueToXmlContent(value) {
|
|
707
|
-
if (value === null || value === void 0) return "";
|
|
708
|
-
if (typeof value === "string") return xmlEscape(value);
|
|
709
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
710
|
-
return xmlEscape(String(value));
|
|
711
|
-
}
|
|
712
|
-
return `<![CDATA[${JSON.stringify(value)}]]>`;
|
|
713
|
-
}
|
|
714
|
-
function recordToXmlLines(record, indent) {
|
|
715
|
-
const lines = [];
|
|
716
|
-
for (const [rawKey, val] of Object.entries(record)) {
|
|
717
|
-
const key = /^[a-zA-Z_][\w.-]*$/.test(rawKey) ? rawKey : "field";
|
|
718
|
-
lines.push(`${indent}<${key}>${valueToXmlContent(val)}</${key}>`);
|
|
719
|
-
}
|
|
720
|
-
return lines;
|
|
721
|
-
}
|
|
722
|
-
function unknownArrayToXml(rootName, itemName, items) {
|
|
723
|
-
const lines = [`<${rootName}>`];
|
|
724
|
-
items.forEach((item, index) => {
|
|
725
|
-
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
726
|
-
lines.push(` <${itemName} index="${index}">`);
|
|
727
|
-
lines.push(...recordToXmlLines(item, " "));
|
|
728
|
-
lines.push(` </${itemName}>`);
|
|
729
|
-
} else {
|
|
730
|
-
lines.push(
|
|
731
|
-
` <${itemName} index="${index}">${valueToXmlContent(
|
|
732
|
-
item
|
|
733
|
-
)}</${itemName}>`
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
});
|
|
737
|
-
lines.push(`</${rootName}>`, "");
|
|
738
|
-
return lines.join("\n");
|
|
739
|
-
}
|
|
740
|
-
function escapeForCdata2(text) {
|
|
741
|
-
return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
|
|
742
|
-
}
|
|
743
|
-
function defectsToXml(defects) {
|
|
744
|
-
const sorted = [...defects].sort(
|
|
745
|
-
(a, b) => new Date(a.createdAt ?? 0).getTime() - new Date(b.createdAt ?? 0).getTime()
|
|
746
|
-
);
|
|
747
|
-
const lines = ["<defects>"];
|
|
748
|
-
for (const d of sorted) {
|
|
749
|
-
lines.push(` <defect id="${xmlEscape(d.id)}">`);
|
|
750
|
-
lines.push(` <status>${xmlEscape(d.status)}</status>`);
|
|
751
|
-
lines.push(
|
|
752
|
-
` <current><![CDATA[${escapeForCdata2(
|
|
753
|
-
d.currentState ?? ""
|
|
754
|
-
)}]]></current>`
|
|
755
|
-
);
|
|
756
|
-
lines.push(
|
|
757
|
-
` <expected><![CDATA[${escapeForCdata2(
|
|
758
|
-
d.expectedEffect ?? ""
|
|
759
|
-
)}]]></expected>`
|
|
760
|
-
);
|
|
761
|
-
lines.push(` </defect>`);
|
|
762
|
-
}
|
|
763
|
-
lines.push("</defects>", "");
|
|
764
|
-
return lines.join("\n");
|
|
765
|
-
}
|
|
766
|
-
async function runPull(requirementId, workspaceDir) {
|
|
767
|
-
const cfg = await ensureLoggedConfig();
|
|
768
|
-
const api = createApmApiClient(cfg);
|
|
769
|
-
const data = await api.cliRequirements.pull({ requirementId });
|
|
770
|
-
const WORKITEMS_DIR = requirementWorkitemsDir(requirementId, workspaceDir);
|
|
771
|
-
await ensureDirExists(WORKITEMS_DIR);
|
|
772
|
-
const req2 = data.requirement;
|
|
773
|
-
const statusYaml = yamlStringify(
|
|
774
|
-
{
|
|
775
|
-
id: req2.id,
|
|
776
|
-
status: req2.status,
|
|
777
|
-
title: req2.title
|
|
778
|
-
},
|
|
779
|
-
{ lineWidth: 0 }
|
|
780
|
-
);
|
|
781
|
-
writeFileSync3(
|
|
782
|
-
join5(WORKITEMS_DIR, "requirement-status.yaml"),
|
|
783
|
-
statusYaml.endsWith("\n") ? statusYaml : `${statusYaml}
|
|
784
|
-
`,
|
|
785
|
-
"utf8"
|
|
786
|
-
);
|
|
787
|
-
writeFileSync3(join5(WORKITEMS_DIR, "prd.md"), req2.content || "", "utf8");
|
|
788
|
-
const reviewsXml = buildReviewsXml(data.reviews ?? []);
|
|
789
|
-
writeFileSync3(join5(WORKITEMS_DIR, "reviews.xml"), reviewsXml, "utf8");
|
|
790
|
-
const defectsXml = defectsToXml(data.defects ?? []);
|
|
791
|
-
writeFileSync3(join5(WORKITEMS_DIR, "defect.xml"), defectsXml, "utf8");
|
|
792
|
-
const testCasesXml = unknownArrayToXml(
|
|
793
|
-
"testcases",
|
|
794
|
-
"case",
|
|
795
|
-
data.testCases ?? []
|
|
796
|
-
);
|
|
797
|
-
writeFileSync3(join5(WORKITEMS_DIR, "testcase.xml"), testCasesXml, "utf8");
|
|
798
|
-
for (const fileName of PULL_ARTIFACT_FILE_NAMES) {
|
|
799
|
-
const content = await fetchArtifactContentByFileName(
|
|
800
|
-
api,
|
|
801
|
-
requirementId,
|
|
802
|
-
fileName
|
|
803
|
-
);
|
|
804
|
-
if (content !== null) {
|
|
805
|
-
writeFileSync3(join5(WORKITEMS_DIR, fileName), content, "utf8");
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
await syncRequirementAttachments(api, requirementId, WORKITEMS_DIR);
|
|
809
|
-
return WORKITEMS_DIR;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// src/commands/pull-theater-session.ts
|
|
813
|
-
import { writeFileSync as writeFileSync4 } from "fs";
|
|
814
|
-
import { dirname as dirname2, join as join6 } from "path";
|
|
815
|
-
function normalizeRelativePath(fileName) {
|
|
816
|
-
return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "");
|
|
817
|
-
}
|
|
818
|
-
async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
|
|
819
|
-
const trimmedSessionId = sessionId.trim();
|
|
820
|
-
if (!trimmedSessionId) {
|
|
821
|
-
throw new Error("sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
822
|
-
}
|
|
823
|
-
const cfg = await ensureLoggedConfig();
|
|
824
|
-
const api = createApmApiClient(cfg);
|
|
825
|
-
const sessionDir = theaterSessionDir(trimmedSessionId, apmRoot);
|
|
826
|
-
await ensureDirExists(sessionDir);
|
|
827
|
-
const pageSize = 500;
|
|
828
|
-
let page = 1;
|
|
829
|
-
const items = [];
|
|
830
|
-
while (true) {
|
|
831
|
-
const batch = await api.theaterSessionArtifact.list({
|
|
832
|
-
sessionId: trimmedSessionId,
|
|
833
|
-
page,
|
|
834
|
-
pageSize
|
|
835
|
-
});
|
|
836
|
-
items.push(...batch.items);
|
|
837
|
-
if (batch.total === 0 || items.length >= batch.total) break;
|
|
838
|
-
page += 1;
|
|
839
|
-
}
|
|
840
|
-
for (const item of items) {
|
|
841
|
-
const art = await api.theaterSessionArtifact.get({
|
|
842
|
-
sessionId: trimmedSessionId,
|
|
843
|
-
artifactId: item.id
|
|
844
|
-
});
|
|
845
|
-
const relativePath = normalizeRelativePath(art.fileName);
|
|
846
|
-
const destPath = join6(sessionDir, relativePath);
|
|
847
|
-
await ensureDirExists(dirname2(destPath));
|
|
848
|
-
writeFileSync4(destPath, art.content, "utf8");
|
|
849
|
-
console.log(`[apm] \u5DF2\u8986\u76D6\u4F1A\u8BDD\u6587\u6863: ${relativePath}`);
|
|
850
|
-
}
|
|
851
|
-
return sessionDir;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// src/theater-job-report.ts
|
|
855
|
-
import { randomUUID } from "crypto";
|
|
856
|
-
function extractErrorMessage(err) {
|
|
857
|
-
if (err instanceof Error && err.message.trim()) {
|
|
858
|
-
return err.message.trim();
|
|
859
|
-
}
|
|
860
|
-
if (typeof err === "string" && err.trim()) {
|
|
861
|
-
return err.trim();
|
|
862
|
-
}
|
|
863
|
-
if (typeof err === "object" && err && "message" in err) {
|
|
864
|
-
const msg = err.message;
|
|
865
|
-
if (typeof msg === "string" && msg.trim()) return msg.trim();
|
|
866
|
-
if (Array.isArray(msg)) return msg.map(String).join("\uFF1B");
|
|
867
|
-
}
|
|
868
|
-
return "\u672A\u77E5\u9519\u8BEF";
|
|
869
|
-
}
|
|
870
|
-
function isUnauthorizedMessage(msg) {
|
|
871
|
-
return msg.includes("\u672A\u767B\u5F55") || /401/.test(msg);
|
|
872
|
-
}
|
|
873
|
-
function sendTheaterReportWs(ws, payload) {
|
|
874
|
-
return new Promise((resolve5, reject) => {
|
|
875
|
-
const msg_id = randomUUID();
|
|
876
|
-
const timer = setTimeout(() => {
|
|
877
|
-
ws.off("message", onMessage);
|
|
878
|
-
reject(new Error("WebSocket \u4E0A\u62A5\u8D85\u65F6"));
|
|
879
|
-
}, 3e4);
|
|
880
|
-
const onMessage = (data) => {
|
|
881
|
-
const text = typeof data === "string" ? data : data.toString();
|
|
882
|
-
let frame;
|
|
883
|
-
try {
|
|
884
|
-
frame = JSON.parse(text);
|
|
885
|
-
} catch {
|
|
886
|
-
return;
|
|
887
|
-
}
|
|
888
|
-
if (frame.msg_id !== msg_id) return;
|
|
889
|
-
clearTimeout(timer);
|
|
890
|
-
ws.off("message", onMessage);
|
|
891
|
-
if (frame.type === "ERROR") {
|
|
892
|
-
reject(
|
|
893
|
-
new Error(frame.payload?.message?.trim() || "WebSocket \u4E0A\u62A5\u5931\u8D25")
|
|
894
|
-
);
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
|
-
if (frame.type === "ACK") {
|
|
898
|
-
resolve5();
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
};
|
|
902
|
-
ws.on("message", onMessage);
|
|
903
|
-
ws.send(
|
|
904
|
-
JSON.stringify({
|
|
905
|
-
type: "THEATER_REPORT",
|
|
906
|
-
msg_id,
|
|
907
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
908
|
-
payload
|
|
909
|
-
})
|
|
910
|
-
);
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
async function reportTheaterProgress(api, ws, params) {
|
|
914
|
-
const body = {
|
|
915
|
-
sessionId: params.sessionId,
|
|
916
|
-
memberKey: params.memberKey,
|
|
917
|
-
status: "IN_PROGRESS",
|
|
918
|
-
logId: params.logId
|
|
919
|
-
};
|
|
920
|
-
try {
|
|
921
|
-
await api.theater.reportMemberAgentProgress(body);
|
|
922
|
-
} catch (err) {
|
|
923
|
-
const msg = extractErrorMessage(err);
|
|
924
|
-
if (!isUnauthorizedMessage(msg)) throw err;
|
|
925
|
-
await sendTheaterReportWs(ws, { action: "progress", ...body });
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
async function submitTheaterResult(api, ws, params) {
|
|
929
|
-
const body = {
|
|
930
|
-
sessionId: params.sessionId,
|
|
931
|
-
memberKey: params.memberKey,
|
|
932
|
-
content: params.content,
|
|
933
|
-
agentId: params.agentId ?? "",
|
|
934
|
-
agentStatus: params.agentStatus,
|
|
935
|
-
logId: params.logId
|
|
936
|
-
};
|
|
937
|
-
try {
|
|
938
|
-
await api.theater.submitMemberResponse(body);
|
|
939
|
-
} catch (err) {
|
|
940
|
-
const msg = extractErrorMessage(err);
|
|
941
|
-
if (!isUnauthorizedMessage(msg)) throw err;
|
|
942
|
-
await sendTheaterReportWs(ws, {
|
|
943
|
-
action: "submit",
|
|
944
|
-
sessionId: params.sessionId,
|
|
945
|
-
memberKey: params.memberKey,
|
|
946
|
-
content: params.content,
|
|
947
|
-
agentId: params.agentId,
|
|
948
|
-
agentStatus: params.agentStatus,
|
|
949
|
-
logId: params.logId
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
async function submitTheaterFailure(api, ws, params) {
|
|
954
|
-
const content = `[${params.stage}] ${params.error}`;
|
|
955
|
-
try {
|
|
956
|
-
await submitTheaterResult(api, ws, {
|
|
957
|
-
sessionId: params.sessionId,
|
|
958
|
-
memberKey: params.memberKey,
|
|
959
|
-
content,
|
|
960
|
-
agentId: params.agentId,
|
|
961
|
-
agentStatus: "FAILED",
|
|
962
|
-
logId: params.logId
|
|
963
|
-
});
|
|
964
|
-
return true;
|
|
965
|
-
} catch (submitErr) {
|
|
966
|
-
console.error(
|
|
967
|
-
"[apm] \u65E0\u6CD5\u5C06\u5931\u8D25\u72B6\u6001\u5199\u5165\u5267\u573A\u4F1A\u8BDD:",
|
|
968
|
-
extractErrorMessage(submitErr)
|
|
969
|
-
);
|
|
970
|
-
return false;
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
function logTheaterJobError(stage, err, meta) {
|
|
974
|
-
const msg = extractErrorMessage(err);
|
|
975
|
-
const ctx = [
|
|
976
|
-
meta?.sessionId ? `session=${meta.sessionId}` : "",
|
|
977
|
-
meta?.memberKey ? `member=${meta.memberKey}` : ""
|
|
978
|
-
].filter(Boolean).join(" ");
|
|
979
|
-
console.error(
|
|
980
|
-
`[apm] \u5267\u573A THEATER_JOB \u5931\u8D25 \xB7 \u9636\u6BB5=${stage}${ctx ? ` \xB7 ${ctx}` : ""} \xB7 ${msg}`
|
|
981
|
-
);
|
|
982
|
-
if (isUnauthorizedMessage(msg)) {
|
|
983
|
-
console.error("[apm] \u63D0\u793A: \u8BF7\u6267\u884C apm login \u91CD\u65B0\u767B\u5F55\u540E\u518D\u8BD5");
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
// src/commands/connect.ts
|
|
988
|
-
var DEFAULT_AGENT_MODEL_ID = "default";
|
|
989
|
-
function resolveAgentModelId(model) {
|
|
990
|
-
const id = model?.trim();
|
|
991
|
-
return id || DEFAULT_AGENT_MODEL_ID;
|
|
992
|
-
}
|
|
993
|
-
async function createAgentForJob(payload) {
|
|
994
|
-
const options = {
|
|
995
|
-
apiKey: payload.apiKey,
|
|
996
|
-
model: { id: resolveAgentModelId(payload.model) },
|
|
997
|
-
local: { cwd: payload.cwd }
|
|
998
|
-
};
|
|
999
|
-
if (payload.agentId) {
|
|
1000
|
-
return Agent.resume(payload.agentId, options);
|
|
1001
|
-
}
|
|
1002
|
-
return Agent.create(options);
|
|
1003
|
-
}
|
|
1004
|
-
async function runAgentStream(payload, session, agent) {
|
|
1005
|
-
const run = await agent.send(payload.prompt);
|
|
1006
|
-
let failed = false;
|
|
1007
|
-
let failReason = "";
|
|
1008
|
-
for await (const event of run.stream()) {
|
|
1009
|
-
if (event.type === "status") {
|
|
1010
|
-
console.log(`[Status]`, event.message || event.status);
|
|
1011
|
-
if (event.status === "ERROR") {
|
|
1012
|
-
failed = true;
|
|
1013
|
-
failReason = event.message?.trim() ?? "";
|
|
1014
|
-
console.error("[apm] Agent \u6267\u884C\u5931\u8D25:", failReason || "(\u65E0\u5931\u8D25\u539F\u56E0)");
|
|
1015
|
-
break;
|
|
1016
|
-
}
|
|
1017
|
-
continue;
|
|
1018
|
-
}
|
|
1019
|
-
if (event.type === "assistant") {
|
|
1020
|
-
const output = event.message.content[0].text;
|
|
1021
|
-
console.log(`[Output]`, output);
|
|
1022
|
-
session.addEvent(event);
|
|
1023
|
-
continue;
|
|
1024
|
-
}
|
|
1025
|
-
if (event.type === "thinking") {
|
|
1026
|
-
console.log(`[Thinking]`, event.text);
|
|
1027
|
-
continue;
|
|
1028
|
-
}
|
|
1029
|
-
if (event.type === "tool_call") {
|
|
1030
|
-
if (event.status === "completed" || event.status === "error") {
|
|
1031
|
-
console.log(`[ToolCall(${event.call_id}) Result]`);
|
|
1032
|
-
}
|
|
1033
|
-
session.addEvent(event);
|
|
1034
|
-
continue;
|
|
1035
|
-
}
|
|
1036
|
-
if (event.type === "task") {
|
|
1037
|
-
console.log(`[Task:${event.status}]`);
|
|
1038
|
-
session.addEvent(event);
|
|
1039
|
-
continue;
|
|
1040
|
-
}
|
|
1041
|
-
if (event.type === "request") {
|
|
1042
|
-
console.log(JSON.stringify(event, null, 2));
|
|
1043
|
-
session.addEvent(event);
|
|
1044
|
-
continue;
|
|
1045
|
-
}
|
|
1046
|
-
console.log("\u672A\u77E5\u4E8B\u4EF6:", JSON.stringify(event, null, 2));
|
|
1047
|
-
}
|
|
1048
|
-
return { agentId: run.agentId, failed, failReason };
|
|
1049
|
-
}
|
|
1050
|
-
async function handleTheaterJob(api, ws, payload) {
|
|
1051
|
-
const sessionId = payload.theaterSessionId.trim();
|
|
1052
|
-
const memberKey = payload.memberKey.trim();
|
|
1053
|
-
const logId = payload.logId?.trim() || void 0;
|
|
1054
|
-
if (!sessionId) {
|
|
1055
|
-
throw new Error("THEATER_JOB \u7F3A\u5C11 theaterSessionId");
|
|
1056
|
-
}
|
|
1057
|
-
if (!memberKey) {
|
|
1058
|
-
throw new Error("THEATER_JOB \u7F3A\u5C11 memberKey");
|
|
1059
|
-
}
|
|
1060
|
-
console.log("[apm] \u5267\u573A\u4F1A\u8BDD:", sessionId, "\u6210\u5458:", memberKey);
|
|
1061
|
-
const apmRoot = join7(payload.cwd, ".apm");
|
|
1062
|
-
let agentId = payload.agentId?.trim() || void 0;
|
|
1063
|
-
try {
|
|
1064
|
-
await reportTheaterProgress(api, ws, { sessionId, memberKey, logId });
|
|
1065
|
-
} catch (err) {
|
|
1066
|
-
logTheaterJobError("\u4E0A\u62A5\u6267\u884C\u8FDB\u5EA6", err, { sessionId, memberKey });
|
|
1067
|
-
await submitTheaterFailure(api, ws, {
|
|
1068
|
-
sessionId,
|
|
1069
|
-
memberKey,
|
|
1070
|
-
logId,
|
|
1071
|
-
agentId,
|
|
1072
|
-
stage: "\u4E0A\u62A5\u8FDB\u5EA6",
|
|
1073
|
-
error: extractErrorMessage(err)
|
|
1074
|
-
});
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
try {
|
|
1078
|
-
await runPullTheaterSession(sessionId, apmRoot);
|
|
1079
|
-
} catch (pullErr) {
|
|
1080
|
-
logTheaterJobError("\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863", pullErr, { sessionId, memberKey });
|
|
1081
|
-
await submitTheaterFailure(api, ws, {
|
|
1082
|
-
sessionId,
|
|
1083
|
-
memberKey,
|
|
1084
|
-
logId,
|
|
1085
|
-
agentId,
|
|
1086
|
-
stage: "\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863",
|
|
1087
|
-
error: extractErrorMessage(pullErr)
|
|
1088
|
-
});
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
try {
|
|
1092
|
-
const eventSession = new EventSession(payload.prompt);
|
|
1093
|
-
const agent = await createAgentForJob(payload);
|
|
1094
|
-
const runResult = await runAgentStream(payload, eventSession, agent);
|
|
1095
|
-
agentId = runResult.agentId;
|
|
1096
|
-
const assistantText = eventSession.getAssistantText();
|
|
1097
|
-
const content = runResult.failed ? runResult.failReason || assistantText || "\u672A\u77E5\u9519\u8BEF" : assistantText || "[Agent \u672A\u4EA7\u751F\u6587\u672C\u8F93\u51FA]";
|
|
1098
|
-
await submitTheaterResult(api, ws, {
|
|
1099
|
-
sessionId,
|
|
1100
|
-
memberKey,
|
|
1101
|
-
content,
|
|
1102
|
-
agentId,
|
|
1103
|
-
agentStatus: runResult.failed ? "FAILED" : "SUCCESS",
|
|
1104
|
-
logId
|
|
1105
|
-
});
|
|
1106
|
-
console.log(
|
|
1107
|
-
"[apm] \u5267\u573A\u6210\u5458\u56DE\u590D\u5DF2\u63D0\u4EA4",
|
|
1108
|
-
runResult.failed ? "(\u6267\u884C\u5931\u8D25)" : ""
|
|
1109
|
-
);
|
|
1110
|
-
} catch (err) {
|
|
1111
|
-
logTheaterJobError("\u6267\u884C\u6216\u63D0\u4EA4\u56DE\u590D", err, { sessionId, memberKey });
|
|
1112
|
-
const reported = await submitTheaterFailure(api, ws, {
|
|
1113
|
-
sessionId,
|
|
1114
|
-
memberKey,
|
|
1115
|
-
logId,
|
|
1116
|
-
agentId,
|
|
1117
|
-
stage: "\u6267\u884C\u4EFB\u52A1",
|
|
1118
|
-
error: extractErrorMessage(err)
|
|
1119
|
-
});
|
|
1120
|
-
if (reported) {
|
|
1121
|
-
console.error("[apm] \u5DF2\u5C06\u5931\u8D25\u539F\u56E0\u5199\u5165\u5267\u573A\u4F1A\u8BDD\uFF0C\u53EF\u5728\u7F51\u9875\u67E5\u770B");
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
async function handleWorkflowJob(api, payload) {
|
|
1126
|
-
const apmRoot = join7(payload.cwd, ".apm");
|
|
1127
|
-
console.log("[apm] ROOT:", apmRoot);
|
|
1128
|
-
console.log("[apm] workflow node:", payload.nodeId);
|
|
1129
|
-
try {
|
|
1130
|
-
await runPull(payload.requirementId, apmRoot);
|
|
1131
|
-
} catch (pullErr) {
|
|
1132
|
-
console.error("[apm] apm pull \u5931\u8D25:", pullErr);
|
|
1133
|
-
throw pullErr;
|
|
1134
|
-
}
|
|
1135
|
-
const agent = await createAgentForJob(payload);
|
|
1136
|
-
await api.cliRequirements.updateDevStatus({
|
|
1137
|
-
requirementId: payload.requirementId,
|
|
1138
|
-
status: "WORKING"
|
|
1139
|
-
});
|
|
1140
|
-
await api.spaceWorkflow.aiStartNode({
|
|
1141
|
-
requirementId: payload.requirementId,
|
|
1142
|
-
nodeId: payload.nodeId,
|
|
1143
|
-
agentId: agent.agentId
|
|
1144
|
-
});
|
|
1145
|
-
const session = new EventSession(payload.prompt);
|
|
1146
|
-
const { agentId, failed, failReason } = await runAgentStream(
|
|
1147
|
-
payload,
|
|
1148
|
-
session,
|
|
1149
|
-
agent
|
|
1150
|
-
);
|
|
1151
|
-
await api.cliRequirements.updateDevStatus({
|
|
1152
|
-
requirementId: payload.requirementId,
|
|
1153
|
-
status: "IDLE"
|
|
1154
|
-
});
|
|
1155
|
-
session.writeToFile(payload.cwd, payload.requirementId, agentId);
|
|
1156
|
-
if (failed) {
|
|
1157
|
-
const failResult = await api.spaceWorkflow.aiFailNode({
|
|
1158
|
-
requirementId: payload.requirementId,
|
|
1159
|
-
nodeId: payload.nodeId,
|
|
1160
|
-
error: failReason
|
|
1161
|
-
});
|
|
1162
|
-
console.log("[apm] \u5DE5\u4F5C\u6D41\u8282\u70B9\u72B6\u6001:", failResult.nodeStatus);
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
console.log("[Done]");
|
|
1166
|
-
try {
|
|
1167
|
-
await runUploadArtifact(payload.requirementId, apmRoot);
|
|
1168
|
-
} catch (pullErr) {
|
|
1169
|
-
console.error("[apm] apm upload-artifact \u5931\u8D25:", pullErr);
|
|
1170
|
-
throw pullErr;
|
|
1171
|
-
}
|
|
1172
|
-
const confirmResult = await api.spaceWorkflow.aiConfirmNode({
|
|
1173
|
-
requirementId: payload.requirementId,
|
|
1174
|
-
nodeId: payload.nodeId
|
|
223
|
+
// src/api/client.ts
|
|
224
|
+
function createApmApiClient(cfg) {
|
|
225
|
+
const baseURL = `${cfg.baseUrl.trim().replace(/\/+$/, "")}/api`;
|
|
226
|
+
return createApiClient(requestConfig, {
|
|
227
|
+
baseURL,
|
|
228
|
+
getToken: () => cfg.token || void 0,
|
|
229
|
+
successCodes: [0],
|
|
230
|
+
unauthorizedCodes: [401]
|
|
1175
231
|
});
|
|
1176
|
-
console.log("[apm] \u5DE5\u4F5C\u6D41\u8282\u70B9\u72B6\u6001:", confirmResult.nodeStatus);
|
|
1177
|
-
}
|
|
1178
|
-
function runConnect(opts) {
|
|
1179
|
-
void (async () => {
|
|
1180
|
-
const cfg = await ensureApmConfig();
|
|
1181
|
-
const server = opts.server?.trim();
|
|
1182
|
-
if (server) {
|
|
1183
|
-
cfg.baseUrl = server.replace(/\/+$/, "");
|
|
1184
|
-
await writeApmConfig(cfg);
|
|
1185
|
-
}
|
|
1186
|
-
if (!cfg.token) {
|
|
1187
|
-
console.error(
|
|
1188
|
-
"[apm] \u672A\u68C0\u6D4B\u5230 token\uFF0C\u8BF7\u5148\u6267\u884C: apm login --email ... --password ..."
|
|
1189
|
-
);
|
|
1190
|
-
console.error(`[apm] \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84: ${APM_CONFIG_PATH}`);
|
|
1191
|
-
process.exit(1);
|
|
1192
|
-
}
|
|
1193
|
-
const api = createApmApiClient(cfg);
|
|
1194
|
-
const url = buildAgentWsUrl(cfg.baseUrl, cfg.token);
|
|
1195
|
-
console.error(`[apm] \u8FDE\u63A5 ${url.replace(cfg.token, "<token>")} \u2026`);
|
|
1196
|
-
const ws = new WebSocket(url);
|
|
1197
|
-
ws.on("open", () => {
|
|
1198
|
-
console.error("[apm] \u5DF2\u8FDE\u63A5");
|
|
1199
|
-
const sendHeartbeat = () => {
|
|
1200
|
-
const frame = {
|
|
1201
|
-
type: "HEARTBEAT",
|
|
1202
|
-
msg_id: randomUUID2(),
|
|
1203
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1204
|
-
payload: {}
|
|
1205
|
-
};
|
|
1206
|
-
ws.send(JSON.stringify(frame));
|
|
1207
|
-
};
|
|
1208
|
-
sendHeartbeat();
|
|
1209
|
-
const interval = setInterval(sendHeartbeat, 15e3);
|
|
1210
|
-
ws.on("close", () => clearInterval(interval));
|
|
1211
|
-
});
|
|
1212
|
-
ws.on("message", async (data) => {
|
|
1213
|
-
const text = typeof data === "string" ? data : data.toString();
|
|
1214
|
-
let msg;
|
|
1215
|
-
try {
|
|
1216
|
-
msg = JSON.parse(text);
|
|
1217
|
-
} catch (err) {
|
|
1218
|
-
console.error(
|
|
1219
|
-
"[apm] \u65E0\u6CD5\u89E3\u6790 WebSocket \u6D88\u606F:",
|
|
1220
|
-
text,
|
|
1221
|
-
err.message
|
|
1222
|
-
);
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
try {
|
|
1226
|
-
const jobType = msg.type;
|
|
1227
|
-
if (jobType === "THEATER_JOB") {
|
|
1228
|
-
await handleTheaterJob(api, ws, msg.payload);
|
|
1229
|
-
return;
|
|
1230
|
-
}
|
|
1231
|
-
if (jobType === "JOB") {
|
|
1232
|
-
await handleWorkflowJob(api, msg.payload);
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
1235
|
-
} catch (err) {
|
|
1236
|
-
const jobType = msg.type;
|
|
1237
|
-
const label = jobType === "THEATER_JOB" ? "\u5267\u573A THEATER_JOB" : jobType === "JOB" ? "\u5DE5\u4F5C\u6D41 JOB" : String(jobType);
|
|
1238
|
-
console.error(`[apm] ${label} \u5904\u7406\u5931\u8D25:`, extractErrorMessage(err));
|
|
1239
|
-
}
|
|
1240
|
-
});
|
|
1241
|
-
ws.on("error", (err) => {
|
|
1242
|
-
console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
|
|
1243
|
-
});
|
|
1244
|
-
ws.on("close", (code, reason) => {
|
|
1245
|
-
console.error(`[apm] \u8FDE\u63A5\u5173\u95ED code=${code} reason=${reason.toString()}`);
|
|
1246
|
-
process.exit(code === 1e3 ? 0 : 1);
|
|
1247
|
-
});
|
|
1248
|
-
})();
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
// src/commands/init.ts
|
|
1252
|
-
import { join as join8 } from "path";
|
|
1253
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
1254
|
-
async function runInit(name) {
|
|
1255
|
-
await ensureWorkspaceApmDirForInit();
|
|
1256
|
-
await copyTemplateFiles(WORKSPACE_APM_DIR);
|
|
1257
|
-
if (name) {
|
|
1258
|
-
const apmConfigPath = join8(WORKSPACE_APM_DIR, "apm.config.json");
|
|
1259
|
-
const config = readFileSync4(apmConfigPath, "utf8");
|
|
1260
|
-
const configJson = JSON.parse(config);
|
|
1261
|
-
configJson.name = name;
|
|
1262
|
-
writeFileSync5(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
|
|
1263
|
-
}
|
|
1264
232
|
}
|
|
1265
233
|
|
|
1266
234
|
// src/commands/login.ts
|
|
1267
|
-
import { ApiError } from "listpage-http";
|
|
1268
235
|
async function runLogin(opts) {
|
|
1269
236
|
const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1270
237
|
const api = createApmApiClient({
|
|
@@ -1310,15 +277,15 @@ async function runLogin(opts) {
|
|
|
1310
277
|
|
|
1311
278
|
// src/commands/branch.ts
|
|
1312
279
|
import { execFile } from "child_process";
|
|
1313
|
-
import { resolve as
|
|
280
|
+
import { resolve as resolve2 } from "path";
|
|
1314
281
|
import { promisify } from "util";
|
|
1315
282
|
var execFileAsync = promisify(execFile);
|
|
1316
|
-
async function fetchBaselineBranchFromApi(
|
|
283
|
+
async function fetchBaselineBranchFromApi(sessionId, cwd) {
|
|
1317
284
|
const cfg = await ensureLoggedConfig();
|
|
1318
285
|
const api = createApmApiClient(cfg);
|
|
1319
|
-
const workdirPath =
|
|
1320
|
-
const res = await api.
|
|
1321
|
-
|
|
286
|
+
const workdirPath = resolve2(cwd);
|
|
287
|
+
const res = await api.cli.branchBaseline({
|
|
288
|
+
sessionId,
|
|
1322
289
|
workdirPath
|
|
1323
290
|
});
|
|
1324
291
|
if (!res.repositoryId) {
|
|
@@ -1395,17 +362,26 @@ async function localBranchExists(cwd, branch) {
|
|
|
1395
362
|
return false;
|
|
1396
363
|
}
|
|
1397
364
|
}
|
|
1398
|
-
async function
|
|
365
|
+
async function commitWorkingTreeIfDirty(cwd, message) {
|
|
366
|
+
await ensureGitRepo(cwd);
|
|
367
|
+
if (!await isWorkingTreeDirty(cwd)) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
|
|
371
|
+
await execGit(cwd, ["add", "-A"]);
|
|
372
|
+
await execGit(cwd, ["commit", "-m", commitMessage]);
|
|
373
|
+
console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
async function ensureFeatureBranch(branch, options, fetchBaseline) {
|
|
1399
377
|
const cwd = options.cwd ?? process.cwd();
|
|
1400
|
-
const branch = branchNameForRequirement(requirementId);
|
|
1401
378
|
const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
|
|
1402
379
|
await ensureGitRepo(cwd);
|
|
1403
380
|
const current = await getCurrentBranch(cwd);
|
|
1404
381
|
const dirty = await isWorkingTreeDirty(cwd);
|
|
1405
382
|
if (dirty) {
|
|
1406
383
|
if (current === branch) {
|
|
1407
|
-
await
|
|
1408
|
-
await execGit(cwd, ["commit", "-m", commitMessage]);
|
|
384
|
+
await commitWorkingTreeIfDirty(cwd, commitMessage);
|
|
1409
385
|
} else {
|
|
1410
386
|
await execGit(cwd, [
|
|
1411
387
|
"stash",
|
|
@@ -1433,10 +409,7 @@ async function runBranch(requirementId, options = {}) {
|
|
|
1433
409
|
if (hasLocal) {
|
|
1434
410
|
await execGit(cwd, ["checkout", branch]);
|
|
1435
411
|
} else {
|
|
1436
|
-
const baselineBranch = await
|
|
1437
|
-
requirementId,
|
|
1438
|
-
cwd
|
|
1439
|
-
);
|
|
412
|
+
const baselineBranch = await fetchBaseline();
|
|
1440
413
|
await execGit(cwd, ["fetch", "origin", baselineBranch]);
|
|
1441
414
|
await execGit(cwd, [
|
|
1442
415
|
"checkout",
|
|
@@ -1451,32 +424,129 @@ async function runBranch(requirementId, options = {}) {
|
|
|
1451
424
|
console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
|
|
1452
425
|
return branch;
|
|
1453
426
|
}
|
|
427
|
+
async function runBranch(sessionId, options = {}) {
|
|
428
|
+
const trimmedSessionId = sessionId.trim();
|
|
429
|
+
if (!trimmedSessionId) {
|
|
430
|
+
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
const cfg = await ensureLoggedConfig();
|
|
434
|
+
const api = createApmApiClient(cfg);
|
|
435
|
+
const cwd = options.cwd ?? process.cwd();
|
|
436
|
+
const baseline = await api.cli.branchBaseline({
|
|
437
|
+
sessionId: trimmedSessionId,
|
|
438
|
+
workdirPath: resolve2(cwd)
|
|
439
|
+
});
|
|
440
|
+
const branch = branchNameForRequirement(baseline.requirementId);
|
|
441
|
+
return ensureFeatureBranch(
|
|
442
|
+
branch,
|
|
443
|
+
options,
|
|
444
|
+
() => fetchBaselineBranchFromApi(trimmedSessionId, cwd)
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/commands/pull.ts
|
|
449
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
450
|
+
import { join as join5 } from "path";
|
|
451
|
+
import { stringify as yamlStringify } from "yaml";
|
|
452
|
+
|
|
453
|
+
// src/commands/sync-session-attachments.ts
|
|
454
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
455
|
+
import { join as join4 } from "path";
|
|
456
|
+
async function downloadAttachment(cfg, sessionId, attachmentId) {
|
|
457
|
+
const base = cfg.baseUrl.trim().replace(/\/+$/, "");
|
|
458
|
+
const q = new URLSearchParams({ sessionId, attachmentId });
|
|
459
|
+
const url = `${base}/api/cli/attachments/file?${q.toString()}`;
|
|
460
|
+
const res = await fetch(url, {
|
|
461
|
+
headers: { Authorization: `Bearer ${cfg.token}` }
|
|
462
|
+
});
|
|
463
|
+
if (!res.ok) {
|
|
464
|
+
throw new Error(
|
|
465
|
+
`[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
return Buffer.from(await res.arrayBuffer());
|
|
469
|
+
}
|
|
470
|
+
async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
471
|
+
if (attachments.length === 0) return;
|
|
472
|
+
const dir = join4(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
|
|
473
|
+
await ensureDirExists(dir);
|
|
474
|
+
for (const item of attachments) {
|
|
475
|
+
const dest = join4(dir, item.name);
|
|
476
|
+
const buffer = await downloadAttachment(cfg, sessionId, item.id);
|
|
477
|
+
writeFileSync3(dest, buffer);
|
|
478
|
+
console.log(
|
|
479
|
+
`[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
1454
483
|
|
|
1455
|
-
// src/commands/
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
484
|
+
// src/commands/pull.ts
|
|
485
|
+
async function runPull(sessionId, apmRoot) {
|
|
486
|
+
const trimmedId = sessionId.trim();
|
|
487
|
+
if (!trimmedId) {
|
|
488
|
+
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
1459
491
|
const cfg = await ensureLoggedConfig();
|
|
1460
|
-
const filePath = join9(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
|
|
1461
|
-
const content = readFileSync5(filePath, "utf8");
|
|
1462
492
|
const api = createApmApiClient(cfg);
|
|
1463
|
-
const
|
|
1464
|
-
|
|
493
|
+
const [detail, members, documents, attachments] = await Promise.all([
|
|
494
|
+
api.cli.sessionDetail({ sessionId: trimmedId }),
|
|
495
|
+
api.cli.sessionMembers({ sessionId: trimmedId }),
|
|
496
|
+
api.cli.listDocuments({ sessionId: trimmedId }),
|
|
497
|
+
api.cli.listAttachments({ sessionId: trimmedId })
|
|
498
|
+
]);
|
|
499
|
+
const dir = sessionDir(trimmedId, apmRoot);
|
|
500
|
+
const docsDir = sessionDocsDir(trimmedId, apmRoot);
|
|
501
|
+
await ensureDirExists(docsDir);
|
|
502
|
+
writeFileSync4(
|
|
503
|
+
sessionRulePath(trimmedId, apmRoot),
|
|
504
|
+
detail.description ?? "",
|
|
505
|
+
"utf8"
|
|
506
|
+
);
|
|
507
|
+
for (const doc of documents) {
|
|
508
|
+
const fileName = documentLocalFileName(doc.name);
|
|
509
|
+
writeFileSync4(join5(docsDir, fileName), doc.content ?? "", "utf8");
|
|
510
|
+
}
|
|
511
|
+
const sessionYaml = yamlStringify(
|
|
512
|
+
{
|
|
513
|
+
name: detail.title,
|
|
514
|
+
description: "./RULE.md",
|
|
515
|
+
members: members.map((m) => ({
|
|
516
|
+
name: m.displayName,
|
|
517
|
+
position: m.position,
|
|
518
|
+
system_persona: m.systemPersona,
|
|
519
|
+
skills: []
|
|
520
|
+
})),
|
|
521
|
+
skills: [],
|
|
522
|
+
attachments: attachments.map((a) => ({ name: a.name }))
|
|
523
|
+
},
|
|
524
|
+
{ lineWidth: 0 }
|
|
525
|
+
);
|
|
526
|
+
writeFileSync4(
|
|
527
|
+
sessionYamlPath(trimmedId, apmRoot),
|
|
528
|
+
sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
|
|
529
|
+
`,
|
|
530
|
+
"utf8"
|
|
531
|
+
);
|
|
532
|
+
await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
|
|
533
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
|
|
534
|
+
return dir;
|
|
1465
535
|
}
|
|
1466
536
|
|
|
1467
537
|
// src/commands/update.ts
|
|
1468
538
|
import { spawnSync } from "child_process";
|
|
1469
539
|
|
|
1470
540
|
// src/version.ts
|
|
1471
|
-
import { readFileSync as
|
|
1472
|
-
import { dirname as
|
|
541
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
542
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
1473
543
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1474
544
|
var CLI_PACKAGE_NAME = "ai-project-manage-cli";
|
|
1475
545
|
function readCliVersion() {
|
|
1476
546
|
try {
|
|
1477
|
-
const dir =
|
|
1478
|
-
const pkgPath =
|
|
1479
|
-
const pkg = JSON.parse(
|
|
547
|
+
const dir = dirname2(fileURLToPath2(import.meta.url));
|
|
548
|
+
const pkgPath = join6(dir, "..", "package.json");
|
|
549
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
1480
550
|
return pkg.version ?? "0.0.0";
|
|
1481
551
|
} catch {
|
|
1482
552
|
return "0.0.0";
|
|
@@ -1543,147 +613,139 @@ async function runUpdate() {
|
|
|
1543
613
|
}
|
|
1544
614
|
|
|
1545
615
|
// src/commands/update-skills.ts
|
|
1546
|
-
import {
|
|
1547
|
-
import { join as
|
|
1548
|
-
|
|
616
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
617
|
+
import { join as join7 } from "path";
|
|
618
|
+
function sanitizeDirName(name) {
|
|
619
|
+
const trimmed = name.trim();
|
|
620
|
+
if (!trimmed) return "skill";
|
|
621
|
+
return trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
622
|
+
}
|
|
1549
623
|
async function runUpdateSkills() {
|
|
1550
|
-
const apmDir =
|
|
1551
|
-
if (!
|
|
624
|
+
const apmDir = workspaceApmDir();
|
|
625
|
+
if (!existsSync2(apmDir)) {
|
|
1552
626
|
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1553
627
|
process.exit(1);
|
|
1554
628
|
}
|
|
1555
|
-
const apmStat =
|
|
629
|
+
const apmStat = statSync2(apmDir);
|
|
1556
630
|
if (!apmStat.isDirectory()) {
|
|
1557
631
|
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
1558
632
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
}
|
|
1565
|
-
if (!templateStat.isDirectory()) {
|
|
1566
|
-
throw new Error(`[apm] \u5185\u7F6E\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_SKILLS_DIR}`);
|
|
1567
|
-
}
|
|
1568
|
-
const skillsDir = join11(apmDir, "skills");
|
|
1569
|
-
if (existsSync4(skillsDir)) {
|
|
633
|
+
const cfg = await ensureLoggedConfig();
|
|
634
|
+
const api = createApmApiClient(cfg);
|
|
635
|
+
const { list } = await api.cli.listSkills({});
|
|
636
|
+
const skillsDir = join7(apmDir, "skills");
|
|
637
|
+
if (existsSync2(skillsDir)) {
|
|
1570
638
|
rmSync(skillsDir, { recursive: true, force: true });
|
|
1571
639
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
import { join as join12 } from "path";
|
|
1580
|
-
var TEMPLATE_THEATER_SKILLS_DIR = join12(CLI_TEMPLATE_DIR, "theater-skills");
|
|
1581
|
-
async function runUpdateTheaterSkills() {
|
|
1582
|
-
const apmDir = WORKSPACE_APM_DIR;
|
|
1583
|
-
if (!existsSync5(apmDir)) {
|
|
1584
|
-
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1585
|
-
process.exit(1);
|
|
1586
|
-
}
|
|
1587
|
-
const apmStat = statSync4(apmDir);
|
|
1588
|
-
if (!apmStat.isDirectory()) {
|
|
1589
|
-
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
1590
|
-
}
|
|
1591
|
-
let templateStat;
|
|
1592
|
-
try {
|
|
1593
|
-
templateStat = statSync4(TEMPLATE_THEATER_SKILLS_DIR);
|
|
1594
|
-
} catch {
|
|
1595
|
-
throw new Error(
|
|
1596
|
-
`[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u5B58\u5728: ${TEMPLATE_THEATER_SKILLS_DIR}`
|
|
1597
|
-
);
|
|
640
|
+
mkdirSync3(skillsDir, { recursive: true });
|
|
641
|
+
for (const skill of list) {
|
|
642
|
+
const dirName = sanitizeDirName(skill.name);
|
|
643
|
+
const skillDir = join7(skillsDir, dirName);
|
|
644
|
+
mkdirSync3(skillDir, { recursive: true });
|
|
645
|
+
writeFileSync5(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
|
|
646
|
+
console.log(`[apm] \u5DF2\u5199\u5165\u6280\u80FD: skills/${dirName}/SKILL.md`);
|
|
1598
647
|
}
|
|
1599
|
-
|
|
1600
|
-
throw new Error(
|
|
1601
|
-
`[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_THEATER_SKILLS_DIR}`
|
|
1602
|
-
);
|
|
1603
|
-
}
|
|
1604
|
-
const theaterSkillsDir = join12(apmDir, "theater-skills");
|
|
1605
|
-
if (existsSync5(theaterSkillsDir)) {
|
|
1606
|
-
rmSync2(theaterSkillsDir, { recursive: true, force: true });
|
|
1607
|
-
}
|
|
1608
|
-
await ensureDirExists(apmDir);
|
|
1609
|
-
cpSync3(TEMPLATE_THEATER_SKILLS_DIR, theaterSkillsDir, { recursive: true });
|
|
1610
|
-
console.log("[apm] \u5DF2\u66F4\u65B0 .apm/theater-skills");
|
|
648
|
+
console.log(`[apm] \u5DF2\u66F4\u65B0 ${list.length} \u4E2A\u6280\u80FD\u5230 .apm/skills`);
|
|
1611
649
|
}
|
|
1612
650
|
|
|
1613
|
-
// src/commands/sync-
|
|
1614
|
-
import { existsSync as
|
|
1615
|
-
|
|
1616
|
-
async function runSyncSessionDocument(sessionId, options) {
|
|
651
|
+
// src/commands/sync-document.ts
|
|
652
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
653
|
+
async function runSyncDocument(sessionId, options) {
|
|
1617
654
|
const trimmedSessionId = sessionId.trim();
|
|
1618
655
|
if (!trimmedSessionId) {
|
|
1619
656
|
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1620
657
|
process.exit(1);
|
|
1621
658
|
}
|
|
1622
|
-
const
|
|
1623
|
-
|
|
1624
|
-
"
|
|
1625
|
-
);
|
|
1626
|
-
const apmRoot = options?.apmRoot ?? WORKSPACE_APM_DIR;
|
|
1627
|
-
const absPath = theaterSessionDocumentPath(
|
|
1628
|
-
trimmedSessionId,
|
|
1629
|
-
relativePath,
|
|
1630
|
-
apmRoot
|
|
1631
|
-
);
|
|
1632
|
-
if (!existsSync6(absPath)) {
|
|
1633
|
-
console.error(`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
|
|
1634
|
-
\u8BF7\u5148\u521B\u5EFA\u8BE5\u6587\u4EF6\u540E\u518D\u540C\u6B65`);
|
|
659
|
+
const fileArg = options.file?.trim();
|
|
660
|
+
if (!fileArg) {
|
|
661
|
+
console.error("[apm] \u8BF7\u6307\u5B9A --file <\u672C\u5730\u8DEF\u5F84>");
|
|
1635
662
|
process.exit(1);
|
|
1636
663
|
}
|
|
1637
|
-
const
|
|
664
|
+
const absPath = resolveCwdPath(fileArg);
|
|
665
|
+
if (!existsSync3(absPath)) {
|
|
666
|
+
console.error(`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}`);
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
const content = readFileSync4(absPath, "utf8");
|
|
670
|
+
const name = documentPlatformName(absPath);
|
|
1638
671
|
const cfg = await ensureLoggedConfig();
|
|
1639
672
|
const api = createApmApiClient(cfg);
|
|
1640
|
-
const
|
|
673
|
+
const doc = await api.cli.upsertDocument({
|
|
1641
674
|
sessionId: trimmedSessionId,
|
|
1642
|
-
|
|
675
|
+
name,
|
|
1643
676
|
content
|
|
1644
677
|
});
|
|
1645
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\
|
|
678
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${name} (id=${doc.id})`);
|
|
1646
679
|
}
|
|
1647
680
|
|
|
1648
|
-
// src/commands/
|
|
1649
|
-
|
|
681
|
+
// src/commands/append-message.ts
|
|
682
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
683
|
+
async function runAppendMessage(options) {
|
|
684
|
+
const messageId = options.id?.trim();
|
|
685
|
+
if (!messageId) {
|
|
686
|
+
console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
const fileArg = options.file?.trim();
|
|
690
|
+
if (!fileArg) {
|
|
691
|
+
console.error("[apm] \u8BF7\u6307\u5B9A --file <\u672C\u5730\u8DEF\u5F84>");
|
|
692
|
+
process.exit(1);
|
|
693
|
+
}
|
|
694
|
+
const absPath = resolveCwdPath(fileArg);
|
|
695
|
+
if (!existsSync4(absPath)) {
|
|
696
|
+
console.error(`[apm] \u6587\u4EF6\u4E0D\u5B58\u5728: ${absPath}`);
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
const content = readFileSync5(absPath, "utf8");
|
|
1650
700
|
const cfg = await ensureLoggedConfig();
|
|
1651
701
|
const api = createApmApiClient(cfg);
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
status
|
|
1655
|
-
});
|
|
1656
|
-
console.log(JSON.stringify(data, null, 2));
|
|
702
|
+
await api.cli.appendMessageContent({ id: messageId, content });
|
|
703
|
+
console.log(`[apm] \u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9: ${messageId}`);
|
|
1657
704
|
}
|
|
1658
705
|
|
|
1659
|
-
// src/commands/update-status.ts
|
|
1660
|
-
|
|
706
|
+
// src/commands/update-message-status.ts
|
|
707
|
+
var VALID_STATUSES = [
|
|
708
|
+
"CREATED",
|
|
709
|
+
"TYPING",
|
|
710
|
+
"SUCCESS",
|
|
711
|
+
"FAILED"
|
|
712
|
+
];
|
|
713
|
+
async function runUpdateMessageStatus(options) {
|
|
714
|
+
const messageId = options.id?.trim();
|
|
715
|
+
const status = options.status?.trim().toUpperCase();
|
|
716
|
+
if (!messageId) {
|
|
717
|
+
console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
if (!VALID_STATUSES.includes(status)) {
|
|
721
|
+
console.error(
|
|
722
|
+
`[apm] \u65E0\u6548\u72B6\u6001: ${options.status}\uFF0C\u53EF\u9009: ${VALID_STATUSES.join(", ")}`
|
|
723
|
+
);
|
|
724
|
+
process.exit(1);
|
|
725
|
+
}
|
|
1661
726
|
const cfg = await ensureLoggedConfig();
|
|
1662
727
|
const api = createApmApiClient(cfg);
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
status
|
|
1666
|
-
});
|
|
1667
|
-
console.log(JSON.stringify(data, null, 2));
|
|
728
|
+
await api.cli.updateMessageStatus({ id: messageId, status });
|
|
729
|
+
console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
|
|
1668
730
|
}
|
|
1669
731
|
|
|
1670
732
|
// src/commands/deploy/backend.ts
|
|
1671
733
|
import path5 from "node:path";
|
|
1672
734
|
|
|
1673
735
|
// src/commands/deploy/internal/apm-config.ts
|
|
1674
|
-
import { existsSync as
|
|
1675
|
-
import { resolve as
|
|
736
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
737
|
+
import { resolve as resolve3 } from "node:path";
|
|
1676
738
|
function loadApmConfig(options) {
|
|
1677
|
-
const p =
|
|
739
|
+
const p = resolve3(
|
|
1678
740
|
process.cwd(),
|
|
1679
|
-
options?.configPath ??
|
|
741
|
+
options?.configPath ?? resolve3(workspaceApmDir(), "apm.config.json")
|
|
1680
742
|
);
|
|
1681
|
-
if (!
|
|
743
|
+
if (!existsSync5(p)) {
|
|
1682
744
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
1683
745
|
process.exit(1);
|
|
1684
746
|
}
|
|
1685
747
|
try {
|
|
1686
|
-
const raw =
|
|
748
|
+
const raw = readFileSync6(p, "utf8");
|
|
1687
749
|
return JSON.parse(raw);
|
|
1688
750
|
} catch (e) {
|
|
1689
751
|
console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
|
|
@@ -1710,9 +772,7 @@ function reqTopLevelName(cfg) {
|
|
|
1710
772
|
function reqBackendPositiveInt(v, field) {
|
|
1711
773
|
const n = Number(v);
|
|
1712
774
|
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
|
|
1713
|
-
console.error(
|
|
1714
|
-
`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`
|
|
1715
|
-
);
|
|
775
|
+
console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
|
|
1716
776
|
process.exit(1);
|
|
1717
777
|
}
|
|
1718
778
|
return n;
|
|
@@ -1782,7 +842,7 @@ import path4 from "node:path";
|
|
|
1782
842
|
import Docker from "dockerode";
|
|
1783
843
|
|
|
1784
844
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
1785
|
-
import { existsSync as
|
|
845
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
1786
846
|
import path from "node:path";
|
|
1787
847
|
function asOptionalTlsBuffer(value) {
|
|
1788
848
|
if (typeof value !== "string") {
|
|
@@ -1794,8 +854,8 @@ function asOptionalTlsBuffer(value) {
|
|
|
1794
854
|
if (normalized === "") {
|
|
1795
855
|
return void 0;
|
|
1796
856
|
}
|
|
1797
|
-
if (
|
|
1798
|
-
return
|
|
857
|
+
if (existsSync6(normalized)) {
|
|
858
|
+
return readFileSync7(normalized);
|
|
1799
859
|
}
|
|
1800
860
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
1801
861
|
if (looksLikePath) {
|
|
@@ -1920,17 +980,17 @@ var DockerodeClient = class {
|
|
|
1920
980
|
await this.client.getImage(image).remove({ force: true });
|
|
1921
981
|
}
|
|
1922
982
|
async pullImage(image, auth) {
|
|
1923
|
-
const stream = await new Promise((
|
|
983
|
+
const stream = await new Promise((resolve4, reject) => {
|
|
1924
984
|
const pullOptions = auth ? { authconfig: auth } : void 0;
|
|
1925
985
|
this.client.pull(image, pullOptions, (err, output) => {
|
|
1926
986
|
if (err || !output) {
|
|
1927
987
|
reject(err ?? new Error("docker pull \u8FD4\u56DE\u7A7A\u8F93\u51FA"));
|
|
1928
988
|
return;
|
|
1929
989
|
}
|
|
1930
|
-
|
|
990
|
+
resolve4(output);
|
|
1931
991
|
});
|
|
1932
992
|
});
|
|
1933
|
-
await new Promise((
|
|
993
|
+
await new Promise((resolve4, reject) => {
|
|
1934
994
|
this.client.modem.followProgress(
|
|
1935
995
|
stream,
|
|
1936
996
|
(err) => {
|
|
@@ -1938,7 +998,7 @@ var DockerodeClient = class {
|
|
|
1938
998
|
reject(err);
|
|
1939
999
|
return;
|
|
1940
1000
|
}
|
|
1941
|
-
|
|
1001
|
+
resolve4();
|
|
1942
1002
|
},
|
|
1943
1003
|
() => void 0
|
|
1944
1004
|
);
|
|
@@ -2005,7 +1065,7 @@ var DockerodeClient = class {
|
|
|
2005
1065
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
2006
1066
|
|
|
2007
1067
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
2008
|
-
import { existsSync as
|
|
1068
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
|
|
2009
1069
|
import path2 from "node:path";
|
|
2010
1070
|
function stripSurroundingQuotes(value) {
|
|
2011
1071
|
const t = value.trim();
|
|
@@ -2022,10 +1082,10 @@ function loadEnvFromFile(envFilePath) {
|
|
|
2022
1082
|
return {};
|
|
2023
1083
|
}
|
|
2024
1084
|
const targetPath = path2.resolve(envFilePath);
|
|
2025
|
-
if (!
|
|
1085
|
+
if (!existsSync7(targetPath) || !statSync3(targetPath).isFile()) {
|
|
2026
1086
|
return {};
|
|
2027
1087
|
}
|
|
2028
|
-
const raw =
|
|
1088
|
+
const raw = readFileSync8(targetPath, "utf-8");
|
|
2029
1089
|
const result = {};
|
|
2030
1090
|
for (const line of raw.split(/\r?\n/)) {
|
|
2031
1091
|
const normalized = line.trim();
|
|
@@ -2196,12 +1256,12 @@ function dockerPushImage(params, cwd) {
|
|
|
2196
1256
|
}
|
|
2197
1257
|
|
|
2198
1258
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
2199
|
-
import { existsSync as
|
|
1259
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
2200
1260
|
import path3 from "node:path";
|
|
2201
1261
|
function resolveDockerBuildPaths(cwd) {
|
|
2202
1262
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
2203
1263
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
2204
|
-
if (!
|
|
1264
|
+
if (!existsSync8(dockerfilePath)) {
|
|
2205
1265
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
2206
1266
|
}
|
|
2207
1267
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
|
@@ -2330,16 +1390,16 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
|
|
|
2330
1390
|
import path7 from "node:path";
|
|
2331
1391
|
|
|
2332
1392
|
// src/commands/deploy/internal/load-apm-dotenv.ts
|
|
2333
|
-
import { existsSync as
|
|
2334
|
-
import { join as
|
|
1393
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
|
|
1394
|
+
import { join as join8 } from "node:path";
|
|
2335
1395
|
function loadApmDotEnvIfPresent() {
|
|
2336
|
-
const p =
|
|
2337
|
-
if (!
|
|
1396
|
+
const p = join8(workspaceApmDir(), ".env");
|
|
1397
|
+
if (!existsSync9(p)) {
|
|
2338
1398
|
return;
|
|
2339
1399
|
}
|
|
2340
1400
|
let text;
|
|
2341
1401
|
try {
|
|
2342
|
-
text =
|
|
1402
|
+
text = readFileSync9(p, "utf8");
|
|
2343
1403
|
} catch {
|
|
2344
1404
|
return;
|
|
2345
1405
|
}
|
|
@@ -2364,14 +1424,14 @@ function loadApmDotEnvIfPresent() {
|
|
|
2364
1424
|
}
|
|
2365
1425
|
|
|
2366
1426
|
// src/commands/deploy/internal/minio.ts
|
|
2367
|
-
import { statSync as
|
|
1427
|
+
import { statSync as statSync4 } from "node:fs";
|
|
2368
1428
|
import { readdir, readFile } from "node:fs/promises";
|
|
2369
1429
|
import path6 from "node:path";
|
|
2370
1430
|
import * as Minio from "minio";
|
|
2371
1431
|
var DEFAULT_MAX_FILE_SIZE_MB = 50;
|
|
2372
1432
|
async function isDirectoryPath(dir) {
|
|
2373
1433
|
try {
|
|
2374
|
-
const st =
|
|
1434
|
+
const st = statSync4(dir);
|
|
2375
1435
|
return st.isDirectory();
|
|
2376
1436
|
} catch {
|
|
2377
1437
|
return false;
|
|
@@ -2401,7 +1461,7 @@ async function collectFiles(root) {
|
|
|
2401
1461
|
if (e.isDirectory()) {
|
|
2402
1462
|
await walk(abs, rel);
|
|
2403
1463
|
} else if (e.isFile()) {
|
|
2404
|
-
const st =
|
|
1464
|
+
const st = statSync4(abs);
|
|
2405
1465
|
out.push({
|
|
2406
1466
|
absPath: abs,
|
|
2407
1467
|
relativePath: rel.replace(/\\/g, "/"),
|
|
@@ -2463,14 +1523,14 @@ var MinioClient = class {
|
|
|
2463
1523
|
async deleteObjectsByPrefix(bucket, prefix) {
|
|
2464
1524
|
const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
|
|
2465
1525
|
const keys = [];
|
|
2466
|
-
await new Promise((
|
|
1526
|
+
await new Promise((resolve4, reject) => {
|
|
2467
1527
|
objectsStream.on("data", (obj) => {
|
|
2468
1528
|
if (obj.name) {
|
|
2469
1529
|
keys.push(obj.name);
|
|
2470
1530
|
}
|
|
2471
1531
|
});
|
|
2472
1532
|
objectsStream.on("error", reject);
|
|
2473
|
-
objectsStream.on("end",
|
|
1533
|
+
objectsStream.on("end", resolve4);
|
|
2474
1534
|
});
|
|
2475
1535
|
const chunkSize = 500;
|
|
2476
1536
|
for (let i = 0; i < keys.length; i += chunkSize) {
|
|
@@ -2697,7 +1757,7 @@ function registerDeployCommands(program) {
|
|
|
2697
1757
|
function buildProgram() {
|
|
2698
1758
|
const program = new Command();
|
|
2699
1759
|
program.name("apm").description(
|
|
2700
|
-
|
|
1760
|
+
`OPC \u5E73\u53F0\u547D\u4EE4\u884C\uFF08\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
|
|
2701
1761
|
\u672A\u4F20 --server \u65F6\u4F18\u5148\u4F7F\u7528\u73AF\u5883\u53D8\u91CF AI_PM_SERVER\uFF0C\u5426\u5219\u9ED8\u8BA4 ${DEFAULT_BASE_URL}\u3002`
|
|
2702
1762
|
).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
|
|
2703
1763
|
program.command("login").description(
|
|
@@ -2707,9 +1767,6 @@ function buildProgram() {
|
|
|
2707
1767
|
await runLogin(opts);
|
|
2708
1768
|
}
|
|
2709
1769
|
);
|
|
2710
|
-
program.command("connect").description("\u8FDE\u63A5 WebSocket /ws/agent\uFF0C\u5E76\u6309\u56FA\u5B9A\u95F4\u9694\u53D1\u9001 HEARTBEAT").option("--server <url>", "API \u6839\u5730\u5740\uFF08\u5199\u5165\u672C\u5730\u914D\u7F6E\uFF09").action((opts) => {
|
|
2711
|
-
runConnect(opts);
|
|
2712
|
-
});
|
|
2713
1770
|
program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521D\u59CB\u5316 .apm \u6A21\u677F\uFF08\u82E5 .apm \u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\u5219\u62A5\u9519\uFF09").option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
|
|
2714
1771
|
await runInit(opts.name);
|
|
2715
1772
|
});
|
|
@@ -2718,72 +1775,30 @@ function buildProgram() {
|
|
|
2718
1775
|
).action(async () => {
|
|
2719
1776
|
await runUpdate();
|
|
2720
1777
|
});
|
|
2721
|
-
program.command("update-skills").description(
|
|
2722
|
-
"\u5220\u9664\u5DE5\u4F5C\u533A .apm/skills \u540E\uFF0C\u4ECE\u5F53\u524D CLI \u5185\u7F6E\u6A21\u677F\u91CD\u65B0\u590D\u5236\u6280\u80FD\u76EE\u5F55"
|
|
2723
|
-
).action(async () => {
|
|
1778
|
+
program.command("update-skills").description("\u4ECE\u5E73\u53F0\u62C9\u53D6\u5F53\u524D\u7528\u6237\u53EF\u8BBF\u95EE\u6280\u80FD\u5230 .apm/skills/").action(async () => {
|
|
2724
1779
|
await runUpdateSkills();
|
|
2725
1780
|
});
|
|
2726
|
-
program.command("
|
|
2727
|
-
"\
|
|
2728
|
-
).action(async () => {
|
|
2729
|
-
await
|
|
2730
|
-
});
|
|
2731
|
-
program.command("pull-session").description(
|
|
2732
|
-
"\u5C06\u5E73\u53F0\u5267\u573A\u4F1A\u8BDD\u4EA7\u7269\u6587\u6863\u4E0B\u8F7D\u5230 .apm/theater-sessions/<sessionId>/\uFF08\u5982 docs/prd.md\uFF09\uFF1B\u672C\u5730\u5DF2\u6709\u540C\u540D\u6587\u4EF6\u65F6\u8986\u76D6"
|
|
2733
|
-
).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").action(async (sessionId) => {
|
|
2734
|
-
const dir = await runPullTheaterSession(sessionId);
|
|
2735
|
-
console.log(`[apm] \u4F1A\u8BDD\u6587\u6863\u76EE\u5F55: ${dir}`);
|
|
1781
|
+
program.command("pull").description(
|
|
1782
|
+
"\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF08session.yaml\u3001RULE.md\u3001docs\u3001attachments\uFF09"
|
|
1783
|
+
).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
|
|
1784
|
+
await runPull(sessionId);
|
|
2736
1785
|
});
|
|
2737
|
-
program.command("sync-
|
|
2738
|
-
|
|
2739
|
-
).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").option(
|
|
2740
|
-
"--file <path>",
|
|
2741
|
-
"\u76F8\u5BF9\u4F1A\u8BDD\u76EE\u5F55\u7684\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 docs/prd.md\uFF1B\u540E\u7AEF\u5E38\u7528 docs/backend.md\u3001docs/api.md\uFF09"
|
|
2742
|
-
).action(async (sessionId, opts) => {
|
|
2743
|
-
await runSyncSessionDocument(sessionId, { file: opts.file });
|
|
1786
|
+
program.command("sync-document").description("\u5C06\u672C\u5730 Markdown \u8986\u76D6\u5F0F upsert \u5230\u5E73\u53F0\u9700\u6C42\u6587\u6863").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--file <path>", "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84").action(async (sessionId, opts) => {
|
|
1787
|
+
await runSyncDocument(sessionId, { file: opts.file });
|
|
2744
1788
|
});
|
|
2745
|
-
program.command("
|
|
2746
|
-
|
|
2747
|
-
).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
|
|
2748
|
-
await runPull(requirementId);
|
|
1789
|
+
program.command("append-message").description("\u5411\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u5185\u5BB9\uFF08PUT /api/cli/messages/content\uFF09").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption("--file <path>", "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84").action(async (opts) => {
|
|
1790
|
+
await runAppendMessage(opts);
|
|
2749
1791
|
});
|
|
2750
|
-
program.command("
|
|
2751
|
-
|
|
2752
|
-
).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
|
|
2753
|
-
await runUploadArtifact(requirementId);
|
|
1792
|
+
program.command("update-message-status").description("\u66F4\u65B0\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u72B6\u6001").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption("--status <status>", "CREATED | TYPING | SUCCESS | FAILED").action(async (opts) => {
|
|
1793
|
+
await runUpdateMessageStatus(opts);
|
|
2754
1794
|
});
|
|
2755
1795
|
program.command("branch").description(
|
|
2756
|
-
"\u5207\u6362\u6216\u521B\u5EFA\u9700\u6C42\u5206\u652F feat/req-<
|
|
2757
|
-
).argument("<
|
|
1796
|
+
"\u5207\u6362\u6216\u521B\u5EFA\u9700\u6C42\u5206\u652F feat/req-<requirementId>\uFF08\u7531 sessionId \u89E3\u6790\u9700\u6C42\uFF09"
|
|
1797
|
+
).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
|
|
2758
1798
|
"-m, --message <text>",
|
|
2759
|
-
"\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E
|
|
2760
|
-
).action(async (
|
|
2761
|
-
await runBranch(
|
|
2762
|
-
});
|
|
2763
|
-
program.command("comment").description(
|
|
2764
|
-
"\u63D0\u4EA4\u9700\u6C42\u8BC4\u5BA1\uFF1Alegacy \u4E3A\u6574\u7BC7 Markdown\uFF08POST \u2026/comment\uFF09\uFF1Bstructured \u4E3A\u884C\u7EA7 YAML\uFF08POST \u2026/comment-structured\uFF09"
|
|
2765
|
-
).argument("<requirementId>", "\u9700\u6C42 ID").requiredOption(
|
|
2766
|
-
"--file <path>",
|
|
2767
|
-
"\u8BC4\u8BBA\u6587\u4EF6\u8DEF\u5F84\uFF08.yaml/.yml \u9ED8\u8BA4 structured\uFF09"
|
|
2768
|
-
).option(
|
|
2769
|
-
"--format <format>",
|
|
2770
|
-
"structured | legacy\uFF08\u9ED8\u8BA4 legacy\uFF1B.yaml/.yml \u672A\u6307\u5B9A\u65F6\u89C6\u4E3A structured\uFF09"
|
|
2771
|
-
).option("--model <model>", "\u8BC4\u8BBA\u6A21\u578B\uFF08\u8986\u76D6 YAML reviewer.model\uFF09").action(
|
|
2772
|
-
async (requirementId, options) => {
|
|
2773
|
-
await runComment(requirementId, options.file, {
|
|
2774
|
-
model: options.model,
|
|
2775
|
-
format: options.format
|
|
2776
|
-
});
|
|
2777
|
-
}
|
|
2778
|
-
);
|
|
2779
|
-
program.command("refine").description("POST /api/cli/requirements/refine\uFF08\u6B63\u6587\u6765\u81EA\u6587\u4EF6\uFF09").argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
|
|
2780
|
-
await runRefine(requirementId);
|
|
2781
|
-
});
|
|
2782
|
-
program.command("update-status").description("POST /api/cli/requirements/update-status").argument("<requirementId>", "\u9700\u6C42 ID").requiredOption("--status <RequirementStatus>", "\u9700\u6C42\u72B6\u6001\u679A\u4E3E\u503C").action(async (requirementId, options) => {
|
|
2783
|
-
await runUpdateStatus(requirementId, options.status);
|
|
2784
|
-
});
|
|
2785
|
-
program.command("update-dev-status").description("POST /api/cli/requirements/update-dev-status").argument("<requirementId>", "\u9700\u6C42 ID").requiredOption("--status <status>", "\u6210\u5458\u5F00\u53D1\u72B6\u6001\uFF08\u81EA\u7531\u6587\u672C\uFF09").action(async (requirementId, options) => {
|
|
2786
|
-
await runUpdateDevStatus(requirementId, options.status);
|
|
1799
|
+
"\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
|
|
1800
|
+
).action(async (sessionId, opts) => {
|
|
1801
|
+
await runBranch(sessionId, { message: opts.message });
|
|
2787
1802
|
});
|
|
2788
1803
|
registerDeployCommands(program);
|
|
2789
1804
|
return program;
|