harness-wingman 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/apps/cli/dist/main.js +1908 -296
- package/apps/panel/dist/assets/index-CP99dQS1.css +1 -0
- package/apps/panel/dist/assets/index-N-unp8i7.js +202 -0
- package/apps/panel/dist/index.html +2 -2
- package/package.json +2 -2
- package/apps/panel/dist/assets/index-B6cxhnAv.css +0 -1
- package/apps/panel/dist/assets/index-BAr3rYk2.js +0 -116
package/apps/cli/dist/main.js
CHANGED
|
@@ -253,23 +253,82 @@ var init_dist = __esm({
|
|
|
253
253
|
}
|
|
254
254
|
});
|
|
255
255
|
|
|
256
|
+
// packages/adapter-cc/dist/walk.js
|
|
257
|
+
import { readdirSync } from "node:fs";
|
|
258
|
+
import { join as join5, parse, resolve } from "node:path";
|
|
259
|
+
function newWalkStats(entryBudget = MAX_WALK_ENTRIES) {
|
|
260
|
+
return { unknownErrors: 0, truncated: false, entriesLeft: entryBudget };
|
|
261
|
+
}
|
|
262
|
+
function safeReaddir(dir, stats) {
|
|
263
|
+
try {
|
|
264
|
+
return readdirSync(dir, { withFileTypes: true });
|
|
265
|
+
} catch (e) {
|
|
266
|
+
const code = e.code;
|
|
267
|
+
if (stats && (code === void 0 || !TOLERATED_CODES.has(code)))
|
|
268
|
+
stats.unknownErrors += 1;
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function walkFiles(dir, fileName, stats = newWalkStats()) {
|
|
273
|
+
const found = [];
|
|
274
|
+
const visit = (d, depth) => {
|
|
275
|
+
const entries = safeReaddir(d, stats).sort((a, b) => a.name.localeCompare(b.name));
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
if (stats.entriesLeft <= 0) {
|
|
278
|
+
stats.truncated = true;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
stats.entriesLeft -= 1;
|
|
282
|
+
const p = join5(d, entry.name);
|
|
283
|
+
if (entry.isDirectory()) {
|
|
284
|
+
if (entry.name.startsWith(".") || SKIP_DIR_NAMES.has(entry.name))
|
|
285
|
+
continue;
|
|
286
|
+
if (depth < MAX_WALK_DEPTH)
|
|
287
|
+
visit(p, depth + 1);
|
|
288
|
+
if (stats.truncated)
|
|
289
|
+
return;
|
|
290
|
+
} else if (entry.name === fileName) {
|
|
291
|
+
found.push(p);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
visit(dir, 1);
|
|
296
|
+
return found;
|
|
297
|
+
}
|
|
298
|
+
function listMdFiles(dir, stats) {
|
|
299
|
+
return safeReaddir(dir, stats).map((e) => e.name).filter((n) => n.endsWith(".md")).sort().map((n) => join5(dir, n));
|
|
300
|
+
}
|
|
301
|
+
function isBareProjectDir(projectDir, homeDir) {
|
|
302
|
+
const r = resolve(projectDir);
|
|
303
|
+
return r === resolve(homeDir) || r === parse(r).root;
|
|
304
|
+
}
|
|
305
|
+
var MAX_WALK_DEPTH, MAX_WALK_ENTRIES, SKIP_DIR_NAMES, TOLERATED_CODES;
|
|
306
|
+
var init_walk = __esm({
|
|
307
|
+
"packages/adapter-cc/dist/walk.js"() {
|
|
308
|
+
"use strict";
|
|
309
|
+
MAX_WALK_DEPTH = 6;
|
|
310
|
+
MAX_WALK_ENTRIES = 5e4;
|
|
311
|
+
SKIP_DIR_NAMES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", "Library"]);
|
|
312
|
+
TOLERATED_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "ENOENT"]);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
256
316
|
// packages/adapter-cc/dist/detect.js
|
|
257
|
-
import { existsSync as existsSync2,
|
|
317
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
258
318
|
import { homedir as homedir3 } from "node:os";
|
|
259
|
-
import { join as
|
|
319
|
+
import { join as join6 } from "node:path";
|
|
260
320
|
function probeVersion(homeDir) {
|
|
261
|
-
const root =
|
|
262
|
-
if (!existsSync2(root))
|
|
263
|
-
return void 0;
|
|
321
|
+
const root = join6(homeDir, ".claude", "projects");
|
|
264
322
|
let latest;
|
|
265
|
-
for (const entry of
|
|
323
|
+
for (const entry of safeReaddir(root)) {
|
|
266
324
|
if (!entry.isDirectory())
|
|
267
325
|
continue;
|
|
268
|
-
const dir =
|
|
269
|
-
for (const
|
|
326
|
+
const dir = join6(root, entry.name);
|
|
327
|
+
for (const sub of safeReaddir(dir)) {
|
|
328
|
+
const name = sub.name;
|
|
270
329
|
if (!name.endsWith(".jsonl"))
|
|
271
330
|
continue;
|
|
272
|
-
const file =
|
|
331
|
+
const file = join6(dir, name);
|
|
273
332
|
const mtimeMs = statSync(file).mtimeMs;
|
|
274
333
|
if (!latest || mtimeMs > latest.mtimeMs)
|
|
275
334
|
latest = { file, mtimeMs };
|
|
@@ -293,7 +352,7 @@ function probeVersion(homeDir) {
|
|
|
293
352
|
}
|
|
294
353
|
async function detectCc(options) {
|
|
295
354
|
const homeDir = options?.homeDir ?? homedir3();
|
|
296
|
-
const installed = existsSync2(
|
|
355
|
+
const installed = existsSync2(join6(homeDir, ".claude")) || existsSync2(join6(homeDir, ".claude.json"));
|
|
297
356
|
if (!installed)
|
|
298
357
|
return { installed: false };
|
|
299
358
|
const version = probeVersion(homeDir);
|
|
@@ -302,14 +361,15 @@ async function detectCc(options) {
|
|
|
302
361
|
var init_detect = __esm({
|
|
303
362
|
"packages/adapter-cc/dist/detect.js"() {
|
|
304
363
|
"use strict";
|
|
364
|
+
init_walk();
|
|
305
365
|
}
|
|
306
366
|
});
|
|
307
367
|
|
|
308
368
|
// packages/adapter-cc/dist/scan.js
|
|
309
369
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
310
|
-
import { existsSync as existsSync3,
|
|
370
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
311
371
|
import { homedir as homedir4 } from "node:os";
|
|
312
|
-
import { basename, dirname as dirname3, join as
|
|
372
|
+
import { basename, dirname as dirname3, join as join7, resolve as resolve2, sep } from "node:path";
|
|
313
373
|
function readJsonFile(file) {
|
|
314
374
|
const text = readFileSync4(file, "utf8");
|
|
315
375
|
try {
|
|
@@ -325,34 +385,11 @@ function recordField(v, key) {
|
|
|
325
385
|
const rec = asRecord(v);
|
|
326
386
|
return rec ? asRecord(rec[key]) : void 0;
|
|
327
387
|
}
|
|
328
|
-
function walkFiles(dir, fileName, skipDotDirs, found = []) {
|
|
329
|
-
if (!existsSync3(dir))
|
|
330
|
-
return found;
|
|
331
|
-
const entries = readdirSync2(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
332
|
-
for (const entry of entries) {
|
|
333
|
-
const p = join5(dir, entry.name);
|
|
334
|
-
if (entry.isDirectory()) {
|
|
335
|
-
if (IGNORED_WALK_DIRS.has(entry.name))
|
|
336
|
-
continue;
|
|
337
|
-
if (skipDotDirs && entry.name.startsWith("."))
|
|
338
|
-
continue;
|
|
339
|
-
walkFiles(p, fileName, skipDotDirs, found);
|
|
340
|
-
} else if (entry.name === fileName) {
|
|
341
|
-
found.push(p);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return found;
|
|
345
|
-
}
|
|
346
|
-
function listMdFiles(dir) {
|
|
347
|
-
if (!existsSync3(dir))
|
|
348
|
-
return [];
|
|
349
|
-
return readdirSync2(dir).filter((n) => n.endsWith(".md")).sort().map((n) => join5(dir, n));
|
|
350
|
-
}
|
|
351
388
|
function readSettingsChain(homeDir, projectDir) {
|
|
352
389
|
const chain = [
|
|
353
|
-
{ file:
|
|
354
|
-
{ file:
|
|
355
|
-
{ file:
|
|
390
|
+
{ file: join7(homeDir, ".claude", "settings.json"), source: "user" },
|
|
391
|
+
{ file: join7(projectDir, ".claude", "settings.json"), source: "project" },
|
|
392
|
+
{ file: join7(projectDir, ".claude", "settings.local.json"), source: "local" }
|
|
356
393
|
];
|
|
357
394
|
const out = [];
|
|
358
395
|
for (const { file, source } of chain) {
|
|
@@ -409,14 +446,20 @@ function ruleItem(file, source) {
|
|
|
409
446
|
}
|
|
410
447
|
function buildRules(homeDir, projectDir) {
|
|
411
448
|
const items = [];
|
|
412
|
-
const userClaudeMd =
|
|
449
|
+
const userClaudeMd = join7(homeDir, ".claude", "CLAUDE.md");
|
|
413
450
|
if (existsSync3(userClaudeMd))
|
|
414
451
|
items.push(ruleItem(userClaudeMd, "user"));
|
|
415
|
-
for (const f of listMdFiles(
|
|
452
|
+
for (const f of listMdFiles(join7(homeDir, ".claude", "rules")))
|
|
416
453
|
items.push(ruleItem(f, "user"));
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
454
|
+
if (isBareProjectDir(projectDir, homeDir)) {
|
|
455
|
+
const direct = join7(projectDir, "CLAUDE.md");
|
|
456
|
+
if (existsSync3(direct))
|
|
457
|
+
items.push(ruleItem(direct, "project"));
|
|
458
|
+
} else {
|
|
459
|
+
for (const f of walkFiles(projectDir, "CLAUDE.md"))
|
|
460
|
+
items.push(ruleItem(f, "project"));
|
|
461
|
+
}
|
|
462
|
+
for (const f of listMdFiles(join7(projectDir, ".claude", "rules"))) {
|
|
420
463
|
items.push(ruleItem(f, "project"));
|
|
421
464
|
}
|
|
422
465
|
return items;
|
|
@@ -439,17 +482,19 @@ function parseSkillFrontmatter(content) {
|
|
|
439
482
|
return out;
|
|
440
483
|
}
|
|
441
484
|
function buildSkills(homeDir, projectDir, overrides) {
|
|
442
|
-
const userRoot =
|
|
443
|
-
const syncedRoot =
|
|
485
|
+
const userRoot = join7(homeDir, ".claude", "skills");
|
|
486
|
+
const syncedRoot = join7(userRoot, "synced");
|
|
444
487
|
const roots = [
|
|
445
488
|
{ root: userRoot, source: "user", exclude: syncedRoot },
|
|
446
|
-
|
|
447
|
-
|
|
489
|
+
// projectDir 为家目录时,项目 skills 根与 user 根是同一目录,再走一遍会把每个
|
|
490
|
+
// skill 重复列为 project 层;bare root 无项目上下文 → 不设项目 skills 根
|
|
491
|
+
...isBareProjectDir(projectDir, homeDir) ? [] : [{ root: join7(projectDir, ".claude", "skills"), source: "project" }],
|
|
492
|
+
{ root: join7(homeDir, ".claude", "plugins"), source: "plugin" },
|
|
448
493
|
{ root: syncedRoot, source: "synced" }
|
|
449
494
|
];
|
|
450
495
|
const items = [];
|
|
451
496
|
for (const { root, source, exclude } of roots) {
|
|
452
|
-
for (const skillFile of walkFiles(root, "SKILL.md"
|
|
497
|
+
for (const skillFile of walkFiles(root, "SKILL.md")) {
|
|
453
498
|
if (exclude && (skillFile === exclude || skillFile.startsWith(exclude + sep)))
|
|
454
499
|
continue;
|
|
455
500
|
const fm = parseSkillFrontmatter(readFileSync4(skillFile, "utf8"));
|
|
@@ -473,11 +518,11 @@ function buildSkills(homeDir, projectDir, overrides) {
|
|
|
473
518
|
}
|
|
474
519
|
function buildTools(homeDir, projectDir) {
|
|
475
520
|
const items = [];
|
|
476
|
-
const claudeJsonPath =
|
|
521
|
+
const claudeJsonPath = join7(homeDir, ".claude.json");
|
|
477
522
|
let claudeJson;
|
|
478
523
|
if (existsSync3(claudeJsonPath))
|
|
479
524
|
claudeJson = asRecord(readJsonFile(claudeJsonPath));
|
|
480
|
-
const projectEntry = recordField(claudeJson?.projects,
|
|
525
|
+
const projectEntry = recordField(claudeJson?.projects, resolve2(projectDir));
|
|
481
526
|
const disabledRaw = projectEntry?.disabledMcpServers;
|
|
482
527
|
const disabled = new Set(Array.isArray(disabledRaw) ? disabledRaw.filter((v) => typeof v === "string") : []);
|
|
483
528
|
const mcpItem = (name, source, nativePath) => ({
|
|
@@ -499,7 +544,7 @@ function buildTools(homeDir, projectDir) {
|
|
|
499
544
|
for (const name of Object.keys(localServers).sort()) {
|
|
500
545
|
items.push(mcpItem(name, "local", claudeJsonPath));
|
|
501
546
|
}
|
|
502
|
-
const mcpJsonPath =
|
|
547
|
+
const mcpJsonPath = join7(projectDir, ".mcp.json");
|
|
503
548
|
if (existsSync3(mcpJsonPath)) {
|
|
504
549
|
const projectServers = recordField(readJsonFile(mcpJsonPath), "mcpServers") ?? {};
|
|
505
550
|
for (const name of Object.keys(projectServers).sort()) {
|
|
@@ -540,7 +585,7 @@ function buildGates(chain) {
|
|
|
540
585
|
return items;
|
|
541
586
|
}
|
|
542
587
|
function projectSlug(projectDir) {
|
|
543
|
-
return
|
|
588
|
+
return resolve2(projectDir).replace(/[^a-zA-Z0-9]/g, "-");
|
|
544
589
|
}
|
|
545
590
|
function lastContextTotal(file) {
|
|
546
591
|
let total;
|
|
@@ -567,12 +612,10 @@ function lastContextTotal(file) {
|
|
|
567
612
|
return total;
|
|
568
613
|
}
|
|
569
614
|
function buildHistory(homeDir, projectDir) {
|
|
570
|
-
const dir =
|
|
571
|
-
if (!existsSync3(dir))
|
|
572
|
-
return [];
|
|
615
|
+
const dir = join7(homeDir, ".claude", "projects", projectSlug(projectDir));
|
|
573
616
|
const items = [];
|
|
574
|
-
for (const name of
|
|
575
|
-
const file =
|
|
617
|
+
for (const name of safeReaddir(dir).map((e) => e.name).filter((n) => n.endsWith(".jsonl")).sort()) {
|
|
618
|
+
const file = join7(dir, name);
|
|
576
619
|
const total = lastContextTotal(file);
|
|
577
620
|
if (total === void 0)
|
|
578
621
|
continue;
|
|
@@ -662,20 +705,19 @@ async function scanCc(options) {
|
|
|
662
705
|
}
|
|
663
706
|
};
|
|
664
707
|
}
|
|
665
|
-
var IGNORED_WALK_DIRS;
|
|
666
708
|
var init_scan = __esm({
|
|
667
709
|
"packages/adapter-cc/dist/scan.js"() {
|
|
668
710
|
"use strict";
|
|
669
711
|
init_dist();
|
|
670
712
|
init_detect();
|
|
671
|
-
|
|
713
|
+
init_walk();
|
|
672
714
|
}
|
|
673
715
|
});
|
|
674
716
|
|
|
675
717
|
// packages/adapter-cc/dist/actions.js
|
|
676
718
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
677
719
|
import { homedir as homedir5 } from "node:os";
|
|
678
|
-
import { basename as basename2, dirname as dirname4, join as
|
|
720
|
+
import { basename as basename2, dirname as dirname4, join as join8, resolve as resolve3 } from "node:path";
|
|
679
721
|
function asRecord2(v) {
|
|
680
722
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
|
|
681
723
|
}
|
|
@@ -699,17 +741,17 @@ function deepEqual(a, b) {
|
|
|
699
741
|
}
|
|
700
742
|
function backupDirPath(homeDir) {
|
|
701
743
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
702
|
-
const root =
|
|
703
|
-
let dir =
|
|
744
|
+
const root = join8(homeDir, ".wingman", "backups");
|
|
745
|
+
let dir = join8(root, ts);
|
|
704
746
|
for (let n = 2; existsSync4(dir); n++)
|
|
705
|
-
dir =
|
|
747
|
+
dir = join8(root, `${ts}-${n}`);
|
|
706
748
|
return dir;
|
|
707
749
|
}
|
|
708
750
|
function readSettingsSnapshots(homeDir, projectDir) {
|
|
709
751
|
const files = [
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
752
|
+
join8(homeDir, ".claude", "settings.json"),
|
|
753
|
+
join8(projectDir, ".claude", "settings.json"),
|
|
754
|
+
join8(projectDir, ".claude", "settings.local.json")
|
|
713
755
|
];
|
|
714
756
|
const out = [];
|
|
715
757
|
for (const file of files) {
|
|
@@ -738,7 +780,7 @@ function overridesRecord(s) {
|
|
|
738
780
|
return rec;
|
|
739
781
|
}
|
|
740
782
|
function skillTargetFile(item, homeDir, projectDir) {
|
|
741
|
-
return item.source === "project" ?
|
|
783
|
+
return item.source === "project" ? join8(projectDir, ".claude", "settings.json") : join8(homeDir, ".claude", "settings.json");
|
|
742
784
|
}
|
|
743
785
|
function planSkillToggle(item, enable, homeDir, projectDir) {
|
|
744
786
|
const name = item.nativeName;
|
|
@@ -791,7 +833,7 @@ function planSkillToggle(item, enable, homeDir, projectDir) {
|
|
|
791
833
|
});
|
|
792
834
|
}
|
|
793
835
|
function planMcpToggle(name, enable, homeDir, projectDir) {
|
|
794
|
-
const file =
|
|
836
|
+
const file = join8(homeDir, ".claude.json");
|
|
795
837
|
if (!existsSync4(file)) {
|
|
796
838
|
throw new Error(`\u62D2\u7EDD:\u627E\u4E0D\u5230 ${file}(MCP \u8FDE\u63A5\u5F00\u5173\u6309\u9879\u76EE\u5B58\u653E\u4E8E\u8BE5\u6587\u4EF6)`);
|
|
797
839
|
}
|
|
@@ -806,7 +848,7 @@ function planMcpToggle(name, enable, homeDir, projectDir) {
|
|
|
806
848
|
if (!root)
|
|
807
849
|
throw new Error(`\u62D2\u5199:${file} \u9876\u5C42\u4E0D\u662F JSON \u5BF9\u8C61,\u4E0D\u731C\u683C\u5F0F`);
|
|
808
850
|
const projects = asRecord2(root.projects);
|
|
809
|
-
const projectKey =
|
|
851
|
+
const projectKey = resolve3(projectDir);
|
|
810
852
|
const entry = projects ? asRecord2(projects[projectKey]) : void 0;
|
|
811
853
|
if (!projects || !entry) {
|
|
812
854
|
throw new Error(`\u62D2\u7EDD:${file} \u7684 projects \u91CC\u6CA1\u6709\u672C\u9879\u76EE(${projectKey})\u7684\u8BB0\u5F55;MCP \u8FDE\u63A5\u5F00\u5173\u6309\u9879\u76EE\u5B58\u653E,\u8BF7\u5148\u5728\u8BE5\u9879\u76EE\u91CC\u8FD0\u884C\u8FC7 Claude Code`);
|
|
@@ -872,36 +914,40 @@ function collectRefSources(homeDir, projectDir, exclude) {
|
|
|
872
914
|
if (!seen.has(file) && existsSync4(file))
|
|
873
915
|
push(file, readFileSync5(file, "utf8"));
|
|
874
916
|
};
|
|
875
|
-
|
|
876
|
-
|
|
917
|
+
const bareRoot = isBareProjectDir(projectDir, homeDir);
|
|
918
|
+
pushFile(join8(homeDir, ".claude", "CLAUDE.md"));
|
|
919
|
+
for (const f of listMdFiles(join8(homeDir, ".claude", "rules")))
|
|
877
920
|
pushFile(f);
|
|
878
|
-
|
|
879
|
-
pushFile(
|
|
880
|
-
|
|
921
|
+
if (bareRoot)
|
|
922
|
+
pushFile(join8(projectDir, "CLAUDE.md"));
|
|
923
|
+
else
|
|
924
|
+
for (const f of walkFiles(projectDir, "CLAUDE.md"))
|
|
925
|
+
pushFile(f);
|
|
926
|
+
for (const f of listMdFiles(join8(projectDir, ".claude", "rules")))
|
|
881
927
|
pushFile(f);
|
|
882
928
|
const skillRoots = [
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
929
|
+
join8(homeDir, ".claude", "skills"),
|
|
930
|
+
...bareRoot ? [] : [join8(projectDir, ".claude", "skills")],
|
|
931
|
+
join8(homeDir, ".claude", "plugins")
|
|
886
932
|
];
|
|
887
933
|
for (const root of skillRoots) {
|
|
888
|
-
for (const f of walkFiles(root, "SKILL.md"
|
|
934
|
+
for (const f of walkFiles(root, "SKILL.md")) {
|
|
889
935
|
if (f !== exclude.skillFile)
|
|
890
936
|
pushFile(f);
|
|
891
937
|
}
|
|
892
938
|
}
|
|
893
939
|
for (const dir of [
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
940
|
+
join8(homeDir, ".claude", "agents"),
|
|
941
|
+
join8(projectDir, ".claude", "agents"),
|
|
942
|
+
join8(homeDir, ".claude", "commands"),
|
|
943
|
+
join8(projectDir, ".claude", "commands")
|
|
898
944
|
]) {
|
|
899
945
|
for (const f of listMdFiles(dir))
|
|
900
946
|
pushFile(f);
|
|
901
947
|
}
|
|
902
948
|
for (const s of readSettingsSnapshots(homeDir, projectDir))
|
|
903
949
|
push(s.file, s.raw);
|
|
904
|
-
for (const file of [
|
|
950
|
+
for (const file of [join8(projectDir, ".mcp.json"), join8(homeDir, ".claude.json")]) {
|
|
905
951
|
if (seen.has(file) || !existsSync4(file))
|
|
906
952
|
continue;
|
|
907
953
|
const raw = readFileSync5(file, "utf8");
|
|
@@ -1029,9 +1075,9 @@ async function applyActionCc(plan, _options) {
|
|
|
1029
1075
|
target: w.file
|
|
1030
1076
|
}));
|
|
1031
1077
|
toBackup.forEach((w, i) => {
|
|
1032
|
-
writeFileSync3(
|
|
1078
|
+
writeFileSync3(join8(plan.backupDir, `${i}-${basename2(w.file)}`), w.before ?? "");
|
|
1033
1079
|
});
|
|
1034
|
-
writeFileSync3(
|
|
1080
|
+
writeFileSync3(join8(plan.backupDir, "manifest.json"), serialize({
|
|
1035
1081
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1036
1082
|
harness: "cc",
|
|
1037
1083
|
action: plan.action,
|
|
@@ -1088,6 +1134,7 @@ var init_actions = __esm({
|
|
|
1088
1134
|
"use strict";
|
|
1089
1135
|
init_dist();
|
|
1090
1136
|
init_scan();
|
|
1137
|
+
init_walk();
|
|
1091
1138
|
MCP_PREFIX = "mcpServers.";
|
|
1092
1139
|
KEY_DEF = /"([^"\\]+)"\s*:\s*\{/;
|
|
1093
1140
|
}
|
|
@@ -1096,7 +1143,7 @@ var init_actions = __esm({
|
|
|
1096
1143
|
// packages/adapter-cc/dist/hooks.js
|
|
1097
1144
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1098
1145
|
import { homedir as homedir6 } from "node:os";
|
|
1099
|
-
import { dirname as dirname5, join as
|
|
1146
|
+
import { dirname as dirname5, join as join9 } from "node:path";
|
|
1100
1147
|
function wingmanUrl(port) {
|
|
1101
1148
|
return `http://127.0.0.1:${port}${SIGNATURE}`;
|
|
1102
1149
|
}
|
|
@@ -1108,10 +1155,10 @@ function isWingmanHandler(h) {
|
|
|
1108
1155
|
return rec?.type === "http" && typeof rec.url === "string" && rec.url.includes(SIGNATURE);
|
|
1109
1156
|
}
|
|
1110
1157
|
function wingmanRoot(homeDir) {
|
|
1111
|
-
return
|
|
1158
|
+
return join9(homeDir, ".wingman");
|
|
1112
1159
|
}
|
|
1113
1160
|
function statePath(homeDir) {
|
|
1114
|
-
return
|
|
1161
|
+
return join9(wingmanRoot(homeDir), "state", "cc-hooks.json");
|
|
1115
1162
|
}
|
|
1116
1163
|
function readState(homeDir) {
|
|
1117
1164
|
const file = statePath(homeDir);
|
|
@@ -1138,18 +1185,18 @@ function readState(homeDir) {
|
|
|
1138
1185
|
}
|
|
1139
1186
|
return rec;
|
|
1140
1187
|
}
|
|
1141
|
-
function writeState(homeDir,
|
|
1188
|
+
function writeState(homeDir, state2) {
|
|
1142
1189
|
const file = statePath(homeDir);
|
|
1143
1190
|
mkdirSync3(dirname5(file), { recursive: true });
|
|
1144
|
-
writeFileSync4(file, `${JSON.stringify(
|
|
1191
|
+
writeFileSync4(file, `${JSON.stringify(state2, null, 2)}
|
|
1145
1192
|
`);
|
|
1146
1193
|
}
|
|
1147
1194
|
function makeBackupDir(homeDir) {
|
|
1148
1195
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
1149
|
-
const root =
|
|
1150
|
-
let dir =
|
|
1196
|
+
const root = join9(wingmanRoot(homeDir), "backups");
|
|
1197
|
+
let dir = join9(root, ts);
|
|
1151
1198
|
for (let n = 2; existsSync5(dir); n++)
|
|
1152
|
-
dir =
|
|
1199
|
+
dir = join9(root, `${ts}-${n}`);
|
|
1153
1200
|
mkdirSync3(dir, { recursive: true });
|
|
1154
1201
|
return dir;
|
|
1155
1202
|
}
|
|
@@ -1259,7 +1306,7 @@ function deepEqual2(a, b) {
|
|
|
1259
1306
|
return false;
|
|
1260
1307
|
}
|
|
1261
1308
|
function targetPath(homeDir) {
|
|
1262
|
-
return
|
|
1309
|
+
return join9(homeDir, ".claude", "settings.json");
|
|
1263
1310
|
}
|
|
1264
1311
|
async function installHooksCc(port, options) {
|
|
1265
1312
|
const homeDir = options?.homeDir ?? homedir6();
|
|
@@ -1281,7 +1328,7 @@ async function installHooksCc(port, options) {
|
|
|
1281
1328
|
}
|
|
1282
1329
|
const backupDir = makeBackupDir(homeDir);
|
|
1283
1330
|
if (bytes !== null)
|
|
1284
|
-
writeFileSync4(
|
|
1331
|
+
writeFileSync4(join9(backupDir, "settings.json"), bytes);
|
|
1285
1332
|
const prev = readState(homeDir);
|
|
1286
1333
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1287
1334
|
writeState(homeDir, prev ? { ...prev, port, installedAt: now } : { target, original: bytes, backupDir, port, installedAt: now });
|
|
@@ -1300,7 +1347,7 @@ async function installHooksCc(port, options) {
|
|
|
1300
1347
|
async function uninstallHooksCc(options) {
|
|
1301
1348
|
const homeDir = options?.homeDir ?? homedir6();
|
|
1302
1349
|
const target = targetPath(homeDir);
|
|
1303
|
-
const
|
|
1350
|
+
const state2 = readState(homeDir);
|
|
1304
1351
|
if (!existsSync5(target)) {
|
|
1305
1352
|
rmSync2(statePath(homeDir), { force: true });
|
|
1306
1353
|
return;
|
|
@@ -1313,15 +1360,15 @@ async function uninstallHooksCc(options) {
|
|
|
1313
1360
|
}
|
|
1314
1361
|
const backupDir = makeBackupDir(homeDir);
|
|
1315
1362
|
if (bytes !== null)
|
|
1316
|
-
writeFileSync4(
|
|
1317
|
-
if (
|
|
1318
|
-
const originalParsed = asRecord3(JSON.parse(
|
|
1363
|
+
writeFileSync4(join9(backupDir, "settings.json"), bytes);
|
|
1364
|
+
if (state2 && state2.original !== null) {
|
|
1365
|
+
const originalParsed = asRecord3(JSON.parse(state2.original));
|
|
1319
1366
|
if (originalParsed && deepEqual2(normalizeForCompare(settings), normalizeForCompare(originalParsed))) {
|
|
1320
|
-
writeFileSync4(target,
|
|
1367
|
+
writeFileSync4(target, state2.original);
|
|
1321
1368
|
rmSync2(statePath(homeDir), { force: true });
|
|
1322
1369
|
return;
|
|
1323
1370
|
}
|
|
1324
|
-
} else if (
|
|
1371
|
+
} else if (state2 && state2.original === null) {
|
|
1325
1372
|
if (deepEqual2(normalizeForCompare(settings), {})) {
|
|
1326
1373
|
rmSync2(target, { force: true });
|
|
1327
1374
|
rmSync2(statePath(homeDir), { force: true });
|
|
@@ -1584,33 +1631,33 @@ function parseString(ctx) {
|
|
|
1584
1631
|
}
|
|
1585
1632
|
let parsed = "";
|
|
1586
1633
|
let sliceStart = ctx.p;
|
|
1587
|
-
let
|
|
1634
|
+
let state2 = 0;
|
|
1588
1635
|
for (; ctx.p < ctx.s.length; ctx.p++) {
|
|
1589
1636
|
c = ctx.s.charCodeAt(ctx.p);
|
|
1590
1637
|
if (isMultiline && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) {
|
|
1591
|
-
|
|
1638
|
+
state2 = state2 && 3;
|
|
1592
1639
|
} else if (c < 32 && c !== 9 || c === 127) {
|
|
1593
1640
|
throw new TomlError("control characters are not allowed in strings", {
|
|
1594
1641
|
toml: ctx.s,
|
|
1595
1642
|
ptr: ctx.p
|
|
1596
1643
|
});
|
|
1597
|
-
} else if ((!
|
|
1644
|
+
} else if ((!state2 || state2 === 3) && c === first && (!isMultiline || ctx.s.charCodeAt(ctx.p + 1) === first && ctx.s.charCodeAt(ctx.p + 2) === first)) {
|
|
1598
1645
|
if (isMultiline) {
|
|
1599
1646
|
if (ctx.s.charCodeAt(ctx.p + 3) === first)
|
|
1600
1647
|
ctx.p++;
|
|
1601
1648
|
if (ctx.s.charCodeAt(ctx.p + 3) === first)
|
|
1602
1649
|
ctx.p++;
|
|
1603
1650
|
}
|
|
1604
|
-
if (!
|
|
1651
|
+
if (!state2)
|
|
1605
1652
|
parsed += ctx.s.slice(sliceStart, ctx.p);
|
|
1606
1653
|
ctx.p += isMultiline ? 3 : 1;
|
|
1607
1654
|
return parsed;
|
|
1608
|
-
} else if (!
|
|
1655
|
+
} else if (!state2) {
|
|
1609
1656
|
if (!isLiteral && c === 92) {
|
|
1610
1657
|
parsed += ctx.s.slice(sliceStart, sliceStart = ctx.p);
|
|
1611
|
-
|
|
1658
|
+
state2 = 1;
|
|
1612
1659
|
}
|
|
1613
|
-
} else if (
|
|
1660
|
+
} else if (state2 === 1) {
|
|
1614
1661
|
if (c === 120 || c === 117 || c === 85) {
|
|
1615
1662
|
let value = 0;
|
|
1616
1663
|
let len = c === 120 ? 2 : c === 117 ? 4 : 8;
|
|
@@ -1635,9 +1682,9 @@ function parseString(ctx) {
|
|
|
1635
1682
|
}
|
|
1636
1683
|
parsed += String.fromCodePoint(value);
|
|
1637
1684
|
sliceStart = ctx.p + 1;
|
|
1638
|
-
|
|
1685
|
+
state2 = 0;
|
|
1639
1686
|
} else if (c === 32 || c === 9) {
|
|
1640
|
-
|
|
1687
|
+
state2 = 2;
|
|
1641
1688
|
} else {
|
|
1642
1689
|
if (c === 98)
|
|
1643
1690
|
parsed += "\b";
|
|
@@ -1658,16 +1705,16 @@ function parseString(ctx) {
|
|
|
1658
1705
|
else
|
|
1659
1706
|
throw new TomlError("unrecognized escape sequence", { toml: ctx.s, ptr: ctx.p });
|
|
1660
1707
|
sliceStart = ctx.p + 1;
|
|
1661
|
-
|
|
1708
|
+
state2 = 0;
|
|
1662
1709
|
}
|
|
1663
1710
|
} else if (c !== 32 && c !== 9) {
|
|
1664
|
-
if (
|
|
1711
|
+
if (state2 === 2) {
|
|
1665
1712
|
throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
|
|
1666
1713
|
toml: ctx.s,
|
|
1667
1714
|
ptr: sliceStart
|
|
1668
1715
|
});
|
|
1669
1716
|
}
|
|
1670
|
-
|
|
1717
|
+
state2 = !isLiteral && c === 92 ? 1 : 0;
|
|
1671
1718
|
sliceStart = ctx.p;
|
|
1672
1719
|
}
|
|
1673
1720
|
}
|
|
@@ -1930,15 +1977,15 @@ function peekTable(key, table, meta, type) {
|
|
|
1930
1977
|
let m = meta;
|
|
1931
1978
|
let k;
|
|
1932
1979
|
let hasOwn = false;
|
|
1933
|
-
let
|
|
1980
|
+
let state2;
|
|
1934
1981
|
for (let i = 0; i < key.length; i++) {
|
|
1935
1982
|
if (i) {
|
|
1936
1983
|
t = hasOwn ? t[k] : t[k] = {};
|
|
1937
|
-
m = (
|
|
1938
|
-
if (type === 0 && (
|
|
1984
|
+
m = (state2 = m[k]).c;
|
|
1985
|
+
if (type === 0 && (state2.t === 1 || state2.t === 2)) {
|
|
1939
1986
|
return null;
|
|
1940
1987
|
}
|
|
1941
|
-
if (
|
|
1988
|
+
if (state2.t === 2) {
|
|
1942
1989
|
let l = t.length - 1;
|
|
1943
1990
|
t = t[l];
|
|
1944
1991
|
m = m[l].c;
|
|
@@ -1961,30 +2008,30 @@ function peekTable(key, table, meta, type) {
|
|
|
1961
2008
|
};
|
|
1962
2009
|
}
|
|
1963
2010
|
}
|
|
1964
|
-
|
|
1965
|
-
if (
|
|
2011
|
+
state2 = m[k];
|
|
2012
|
+
if (state2.t !== type && !(type === 1 && state2.t === 3)) {
|
|
1966
2013
|
return null;
|
|
1967
2014
|
}
|
|
1968
2015
|
if (type === 2) {
|
|
1969
|
-
if (!
|
|
1970
|
-
|
|
2016
|
+
if (!state2.d) {
|
|
2017
|
+
state2.d = true;
|
|
1971
2018
|
t[k] = [];
|
|
1972
2019
|
}
|
|
1973
2020
|
t[k].push(t = {});
|
|
1974
|
-
|
|
2021
|
+
state2.c[state2.i++] = state2 = { t: 1, d: false, i: 0, c: {} };
|
|
1975
2022
|
}
|
|
1976
|
-
if (
|
|
2023
|
+
if (state2.d) {
|
|
1977
2024
|
return null;
|
|
1978
2025
|
}
|
|
1979
|
-
|
|
2026
|
+
state2.d = true;
|
|
1980
2027
|
if (type === 1) {
|
|
1981
2028
|
t = hasOwn ? t[k] : t[k] = {};
|
|
1982
2029
|
} else if (type === 0 && hasOwn) {
|
|
1983
2030
|
return null;
|
|
1984
2031
|
}
|
|
1985
|
-
return [k, t,
|
|
2032
|
+
return [k, t, state2.c];
|
|
1986
2033
|
}
|
|
1987
|
-
function
|
|
2034
|
+
function parse2(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
|
1988
2035
|
let ctx = { s: toml, p: 0, d: maxDepth };
|
|
1989
2036
|
let res = {};
|
|
1990
2037
|
let meta = {};
|
|
@@ -2077,14 +2124,14 @@ var init_dist3 = __esm({
|
|
|
2077
2124
|
|
|
2078
2125
|
// packages/adapter-codex/dist/shared.js
|
|
2079
2126
|
import { homedir as homedir7 } from "node:os";
|
|
2080
|
-
import { join as
|
|
2127
|
+
import { join as join10 } from "node:path";
|
|
2081
2128
|
function resolvePaths(options) {
|
|
2082
2129
|
const homeDir = options?.homeDir ?? homedir7();
|
|
2083
2130
|
const projectDir = options?.projectDir ?? process.cwd();
|
|
2084
|
-
return { homeDir, projectDir, codexHome:
|
|
2131
|
+
return { homeDir, projectDir, codexHome: join10(homeDir, ".codex") };
|
|
2085
2132
|
}
|
|
2086
2133
|
function wingmanStateRoot(paths) {
|
|
2087
|
-
return
|
|
2134
|
+
return join10(paths.homeDir, ".wingman");
|
|
2088
2135
|
}
|
|
2089
2136
|
function isTable(value) {
|
|
2090
2137
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
@@ -2393,8 +2440,8 @@ var init_toml_edit = __esm({
|
|
|
2393
2440
|
});
|
|
2394
2441
|
|
|
2395
2442
|
// packages/adapter-codex/dist/actions.js
|
|
2396
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync as
|
|
2397
|
-
import { basename as basename3, dirname as dirname6, join as
|
|
2443
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2444
|
+
import { basename as basename3, dirname as dirname6, join as join11 } from "node:path";
|
|
2398
2445
|
async function planAction(action, options) {
|
|
2399
2446
|
const paths = resolvePaths(options);
|
|
2400
2447
|
const resolved = resolveToggle(action, paths);
|
|
@@ -2423,7 +2470,7 @@ async function applyAction(plan, options) {
|
|
|
2423
2470
|
error: "plan.writes \u4E0D\u662F\u672C adapter \u4EA7\u51FA\u7684\u5F62\u72B6(\u5E94\u6070\u4E3A\u4E00\u5904 modify),\u62D2\u7EDD\u6267\u884C"
|
|
2424
2471
|
};
|
|
2425
2472
|
}
|
|
2426
|
-
const backupsRoot =
|
|
2473
|
+
const backupsRoot = join11(wingmanStateRoot(paths), "backups");
|
|
2427
2474
|
if (dirname6(plan.backupDir) !== backupsRoot) {
|
|
2428
2475
|
return { ok: false, error: `backupDir \u4E0D\u5728 wingman \u5907\u4EFD\u6839(${backupsRoot})\u4E0B,\u62D2\u7EDD\u6267\u884C` };
|
|
2429
2476
|
}
|
|
@@ -2439,7 +2486,7 @@ async function applyAction(plan, options) {
|
|
|
2439
2486
|
return { ok: false, error: "plan \u4E0E\u672C adapter \u7684\u63A8\u6F14\u4E0D\u4E00\u81F4,\u62D2\u7EDD\u6267\u884C;\u8BF7\u91CD\u65B0 plan" };
|
|
2440
2487
|
}
|
|
2441
2488
|
mkdirSync4(plan.backupDir, { recursive: true });
|
|
2442
|
-
writeFileSync5(
|
|
2489
|
+
writeFileSync5(join11(plan.backupDir, basename3(write.file)), currentRaw);
|
|
2443
2490
|
writeFileSync5(write.file, write.after);
|
|
2444
2491
|
return { ok: true, backupDir: plan.backupDir };
|
|
2445
2492
|
} catch (error) {
|
|
@@ -2448,10 +2495,10 @@ async function applyAction(plan, options) {
|
|
|
2448
2495
|
}
|
|
2449
2496
|
function nextBackupDir(paths) {
|
|
2450
2497
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
2451
|
-
const base =
|
|
2452
|
-
let dir =
|
|
2498
|
+
const base = join11(wingmanStateRoot(paths), "backups");
|
|
2499
|
+
let dir = join11(base, stamp);
|
|
2453
2500
|
for (let n = 1; existsSync6(dir); n += 1)
|
|
2454
|
-
dir =
|
|
2501
|
+
dir = join11(base, `${stamp}-${n}`);
|
|
2455
2502
|
return dir;
|
|
2456
2503
|
}
|
|
2457
2504
|
function resolveToggle(action, paths) {
|
|
@@ -2465,13 +2512,13 @@ function resolveToggle(action, paths) {
|
|
|
2465
2512
|
const slot = m[1];
|
|
2466
2513
|
const source = m[2];
|
|
2467
2514
|
const nativeName = m[3];
|
|
2468
|
-
const file = source === "user" ?
|
|
2515
|
+
const file = source === "user" ? join11(paths.codexHome, "config.toml") : join11(paths.projectDir, ".codex", "config.toml");
|
|
2469
2516
|
if (!existsSync6(file))
|
|
2470
2517
|
throw new Error(`itemId \u627E\u4E0D\u5230:\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728(${file})`);
|
|
2471
2518
|
const before = readFileSync7(file, "utf8");
|
|
2472
2519
|
let table;
|
|
2473
2520
|
try {
|
|
2474
|
-
table =
|
|
2521
|
+
table = parse2(before);
|
|
2475
2522
|
} catch (error) {
|
|
2476
2523
|
throw new Error(`Codex config.toml \u89E3\u6790\u5931\u8D25,\u62D2\u7EDD\u52A8\u4F5C(${file}):${error.message}`);
|
|
2477
2524
|
}
|
|
@@ -2679,7 +2726,7 @@ function readToolList(entry, key, serverId, file) {
|
|
|
2679
2726
|
function verifyEdit(table, edit, file) {
|
|
2680
2727
|
let reparsed;
|
|
2681
2728
|
try {
|
|
2682
|
-
reparsed =
|
|
2729
|
+
reparsed = parse2(edit.after);
|
|
2683
2730
|
} catch (error) {
|
|
2684
2731
|
throw new Error(`\u7F16\u8F91\u7ED3\u679C\u4E0D\u662F\u5408\u6CD5 TOML,\u5DF2\u653E\u5F03\u5199\u5165(${file}):${error.message}`);
|
|
2685
2732
|
}
|
|
@@ -2691,13 +2738,13 @@ function verifyEdit(table, edit, file) {
|
|
|
2691
2738
|
}
|
|
2692
2739
|
function collectCorpus(paths) {
|
|
2693
2740
|
const candidates = [
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
...listFilesRecursive(
|
|
2741
|
+
join11(paths.codexHome, "AGENTS.md"),
|
|
2742
|
+
join11(paths.projectDir, "AGENTS.md"),
|
|
2743
|
+
join11(paths.codexHome, "config.toml"),
|
|
2744
|
+
join11(paths.projectDir, ".codex", "config.toml"),
|
|
2745
|
+
join11(paths.codexHome, "hooks.json"),
|
|
2746
|
+
join11(paths.projectDir, ".codex", "hooks.json"),
|
|
2747
|
+
...listFilesRecursive(join11(paths.codexHome, "prompts"))
|
|
2701
2748
|
];
|
|
2702
2749
|
const sources = [];
|
|
2703
2750
|
for (const file of candidates) {
|
|
@@ -2714,13 +2761,13 @@ function collectCorpus(paths) {
|
|
|
2714
2761
|
function listFilesRecursive(dir) {
|
|
2715
2762
|
let entries;
|
|
2716
2763
|
try {
|
|
2717
|
-
entries =
|
|
2764
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
2718
2765
|
} catch {
|
|
2719
2766
|
return [];
|
|
2720
2767
|
}
|
|
2721
2768
|
const files = [];
|
|
2722
2769
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2723
|
-
const path =
|
|
2770
|
+
const path = join11(dir, entry.name);
|
|
2724
2771
|
if (entry.isDirectory())
|
|
2725
2772
|
files.push(...listFilesRecursive(path));
|
|
2726
2773
|
else if (entry.isFile())
|
|
@@ -2787,15 +2834,15 @@ var init_actions2 = __esm({
|
|
|
2787
2834
|
|
|
2788
2835
|
// packages/adapter-codex/dist/hooks.js
|
|
2789
2836
|
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync8, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2790
|
-
import { basename as basename4, dirname as dirname7, join as
|
|
2837
|
+
import { basename as basename4, dirname as dirname7, join as join12 } from "node:path";
|
|
2791
2838
|
async function installHooks(port, options) {
|
|
2792
2839
|
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
2793
2840
|
throw new Error(`\u975E\u6CD5\u7AEF\u53E3:${port}`);
|
|
2794
2841
|
}
|
|
2795
2842
|
const paths = resolvePaths(options);
|
|
2796
2843
|
const root = wingmanStateRoot(paths);
|
|
2797
|
-
const scriptPath =
|
|
2798
|
-
const configPath =
|
|
2844
|
+
const scriptPath = join12(root, FORWARD_SCRIPT_NAME);
|
|
2845
|
+
const configPath = join12(paths.codexHome, "config.toml");
|
|
2799
2846
|
const raw = existsSync7(configPath) ? readFileSync8(configPath) : void 0;
|
|
2800
2847
|
const text = raw?.toString("utf8") ?? "";
|
|
2801
2848
|
const { remainder, block } = splitManagedBlock(text, configPath);
|
|
@@ -2827,17 +2874,17 @@ async function installHooks(port, options) {
|
|
|
2827
2874
|
async function uninstallHooks(options) {
|
|
2828
2875
|
const paths = resolvePaths(options);
|
|
2829
2876
|
const root = wingmanStateRoot(paths);
|
|
2830
|
-
const scriptPath =
|
|
2877
|
+
const scriptPath = join12(root, FORWARD_SCRIPT_NAME);
|
|
2831
2878
|
const statePath2 = hooksStatePath(root);
|
|
2832
|
-
const configPath =
|
|
2833
|
-
const
|
|
2879
|
+
const configPath = join12(paths.codexHome, "config.toml");
|
|
2880
|
+
const state2 = readState2(statePath2);
|
|
2834
2881
|
const raw = existsSync7(configPath) ? readFileSync8(configPath) : void 0;
|
|
2835
2882
|
if (raw !== void 0) {
|
|
2836
2883
|
const text = raw.toString("utf8");
|
|
2837
2884
|
const { remainder, block } = splitManagedBlock(text, configPath);
|
|
2838
2885
|
if (block !== void 0) {
|
|
2839
2886
|
backupFile(root, configPath, raw);
|
|
2840
|
-
const restored = restoreTarget(remainder,
|
|
2887
|
+
const restored = restoreTarget(remainder, state2);
|
|
2841
2888
|
if (restored === null)
|
|
2842
2889
|
rmSync3(configPath);
|
|
2843
2890
|
else
|
|
@@ -2848,13 +2895,13 @@ async function uninstallHooks(options) {
|
|
|
2848
2895
|
rmSync3(statePath2, { force: true });
|
|
2849
2896
|
}
|
|
2850
2897
|
function hasManagedHooks(paths) {
|
|
2851
|
-
const configPath =
|
|
2898
|
+
const configPath = join12(paths.codexHome, "config.toml");
|
|
2852
2899
|
if (!existsSync7(configPath))
|
|
2853
2900
|
return false;
|
|
2854
2901
|
return readFileSync8(configPath, "utf8").includes(MANAGED_BLOCK_START);
|
|
2855
2902
|
}
|
|
2856
|
-
function restoreTarget(remainder,
|
|
2857
|
-
const original =
|
|
2903
|
+
function restoreTarget(remainder, state2) {
|
|
2904
|
+
const original = state2?.original;
|
|
2858
2905
|
if (original === void 0)
|
|
2859
2906
|
return remainder;
|
|
2860
2907
|
if (!original.existed) {
|
|
@@ -2862,7 +2909,7 @@ function restoreTarget(remainder, state) {
|
|
|
2862
2909
|
}
|
|
2863
2910
|
const originalBytes = Buffer.from(original.base64 ?? "", "base64");
|
|
2864
2911
|
try {
|
|
2865
|
-
if (deepEqual3(
|
|
2912
|
+
if (deepEqual3(parse2(remainder), parse2(originalBytes.toString("utf8"))))
|
|
2866
2913
|
return originalBytes;
|
|
2867
2914
|
} catch {
|
|
2868
2915
|
}
|
|
@@ -2897,7 +2944,7 @@ function splitManagedBlock(text, configPath) {
|
|
|
2897
2944
|
function assertWritable(remainder, configPath) {
|
|
2898
2945
|
let table;
|
|
2899
2946
|
try {
|
|
2900
|
-
table =
|
|
2947
|
+
table = parse2(remainder);
|
|
2901
2948
|
} catch (error) {
|
|
2902
2949
|
throw new Error(`Codex config.toml \u89E3\u6790\u5931\u8D25,\u62D2\u7EDD\u5199\u5165(${configPath}):${error.message}`);
|
|
2903
2950
|
}
|
|
@@ -2914,15 +2961,15 @@ function assertWritable(remainder, configPath) {
|
|
|
2914
2961
|
}
|
|
2915
2962
|
function backupFile(root, filePath, contents) {
|
|
2916
2963
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
2917
|
-
let dir =
|
|
2964
|
+
let dir = join12(root, "backups", stamp);
|
|
2918
2965
|
for (let n = 1; existsSync7(dir); n += 1)
|
|
2919
|
-
dir =
|
|
2966
|
+
dir = join12(root, "backups", `${stamp}-${n}`);
|
|
2920
2967
|
mkdirSync5(dir, { recursive: true });
|
|
2921
|
-
writeFileSync6(
|
|
2968
|
+
writeFileSync6(join12(dir, basename4(filePath)), contents);
|
|
2922
2969
|
return dir;
|
|
2923
2970
|
}
|
|
2924
2971
|
function hooksStatePath(root) {
|
|
2925
|
-
return
|
|
2972
|
+
return join12(root, "state", "codex-hooks.json");
|
|
2926
2973
|
}
|
|
2927
2974
|
function readState2(statePath2) {
|
|
2928
2975
|
if (!existsSync7(statePath2))
|
|
@@ -2933,9 +2980,9 @@ function readState2(statePath2) {
|
|
|
2933
2980
|
return void 0;
|
|
2934
2981
|
}
|
|
2935
2982
|
}
|
|
2936
|
-
function writeState2(statePath2,
|
|
2983
|
+
function writeState2(statePath2, state2) {
|
|
2937
2984
|
mkdirSync5(dirname7(statePath2), { recursive: true });
|
|
2938
|
-
writeFileSync6(statePath2, `${JSON.stringify(
|
|
2985
|
+
writeFileSync6(statePath2, `${JSON.stringify(state2, null, 2)}
|
|
2939
2986
|
`);
|
|
2940
2987
|
}
|
|
2941
2988
|
function deepEqual3(a, b) {
|
|
@@ -2987,8 +3034,8 @@ process.stdin.on('end', async () => {
|
|
|
2987
3034
|
});
|
|
2988
3035
|
|
|
2989
3036
|
// packages/adapter-codex/dist/scan.js
|
|
2990
|
-
import { existsSync as existsSync8, readdirSync as
|
|
2991
|
-
import { dirname as dirname8, isAbsolute, join as
|
|
3037
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
|
|
3038
|
+
import { dirname as dirname8, isAbsolute, join as join13, resolve as resolve4 } from "node:path";
|
|
2992
3039
|
async function detect(options) {
|
|
2993
3040
|
const { codexHome } = resolvePaths(options);
|
|
2994
3041
|
return { installed: existsSync8(codexHome) };
|
|
@@ -3028,8 +3075,8 @@ async function scan(options) {
|
|
|
3028
3075
|
}
|
|
3029
3076
|
function loadConfigLayers(paths) {
|
|
3030
3077
|
const candidates = [
|
|
3031
|
-
["user",
|
|
3032
|
-
["project",
|
|
3078
|
+
["user", join13(paths.codexHome, "config.toml")],
|
|
3079
|
+
["project", join13(paths.projectDir, ".codex", "config.toml")]
|
|
3033
3080
|
];
|
|
3034
3081
|
const layers = [];
|
|
3035
3082
|
for (const [source, path] of candidates) {
|
|
@@ -3037,7 +3084,7 @@ function loadConfigLayers(paths) {
|
|
|
3037
3084
|
continue;
|
|
3038
3085
|
const text = readFileSync9(path, "utf8");
|
|
3039
3086
|
try {
|
|
3040
|
-
layers.push({ source, path, table:
|
|
3087
|
+
layers.push({ source, path, table: parse2(text) });
|
|
3041
3088
|
} catch (error) {
|
|
3042
3089
|
throw new Error(`Codex config.toml \u89E3\u6790\u5931\u8D25(${path}):${error.message}`);
|
|
3043
3090
|
}
|
|
@@ -3046,8 +3093,8 @@ function loadConfigLayers(paths) {
|
|
|
3046
3093
|
}
|
|
3047
3094
|
function collectRules(out, paths, layers) {
|
|
3048
3095
|
const agentsChain = [
|
|
3049
|
-
["user",
|
|
3050
|
-
["project",
|
|
3096
|
+
["user", join13(paths.codexHome, "AGENTS.md")],
|
|
3097
|
+
["project", join13(paths.projectDir, "AGENTS.md")]
|
|
3051
3098
|
];
|
|
3052
3099
|
for (const [source, file] of agentsChain) {
|
|
3053
3100
|
const size = fileSize(file);
|
|
@@ -3082,7 +3129,7 @@ function collectRules(out, paths, layers) {
|
|
|
3082
3129
|
}
|
|
3083
3130
|
const instructionsFile = layer.table.model_instructions_file;
|
|
3084
3131
|
if (typeof instructionsFile === "string") {
|
|
3085
|
-
const filePath = isAbsolute(instructionsFile) ? instructionsFile :
|
|
3132
|
+
const filePath = isAbsolute(instructionsFile) ? instructionsFile : resolve4(dirname8(layer.path), instructionsFile);
|
|
3086
3133
|
const item = {
|
|
3087
3134
|
id: `codex:rules:${layer.source}:model_instructions_file`,
|
|
3088
3135
|
slot: "rules",
|
|
@@ -3191,7 +3238,7 @@ function collectGates(out, layer) {
|
|
|
3191
3238
|
}
|
|
3192
3239
|
}
|
|
3193
3240
|
function collectHistory(out, paths, layers) {
|
|
3194
|
-
const historyFile =
|
|
3241
|
+
const historyFile = join13(paths.codexHome, "history.jsonl");
|
|
3195
3242
|
const historyBytes = fileSize(historyFile);
|
|
3196
3243
|
if (historyBytes !== void 0) {
|
|
3197
3244
|
out.push({
|
|
@@ -3206,7 +3253,7 @@ function collectHistory(out, paths, layers) {
|
|
|
3206
3253
|
});
|
|
3207
3254
|
}
|
|
3208
3255
|
const configured = layers.find((layer) => layer.source === "user")?.table.log_dir;
|
|
3209
|
-
const logDir = typeof configured === "string" ? expandPath(configured, paths) :
|
|
3256
|
+
const logDir = typeof configured === "string" ? expandPath(configured, paths) : join13(paths.codexHome, "log");
|
|
3210
3257
|
if (isDirectory(logDir)) {
|
|
3211
3258
|
out.push({
|
|
3212
3259
|
id: "codex:history:user:log_dir",
|
|
@@ -3222,8 +3269,8 @@ function collectHistory(out, paths, layers) {
|
|
|
3222
3269
|
}
|
|
3223
3270
|
function expandPath(value, paths) {
|
|
3224
3271
|
if (value === "~" || value.startsWith("~/"))
|
|
3225
|
-
return
|
|
3226
|
-
return isAbsolute(value) ? value :
|
|
3272
|
+
return join13(paths.homeDir, value.slice(2));
|
|
3273
|
+
return isAbsolute(value) ? value : resolve4(paths.codexHome, value);
|
|
3227
3274
|
}
|
|
3228
3275
|
function assertUniqueIds(slots) {
|
|
3229
3276
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3253,8 +3300,8 @@ function isDirectory(path) {
|
|
|
3253
3300
|
}
|
|
3254
3301
|
function directoryBytes(dir) {
|
|
3255
3302
|
let total = 0;
|
|
3256
|
-
for (const entry of
|
|
3257
|
-
const entryPath =
|
|
3303
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
3304
|
+
const entryPath = join13(dir, entry.name);
|
|
3258
3305
|
if (entry.isDirectory())
|
|
3259
3306
|
total += directoryBytes(entryPath);
|
|
3260
3307
|
else if (entry.isFile())
|
|
@@ -9087,11 +9134,11 @@ var require_lexer = __commonJS({
|
|
|
9087
9134
|
hasChars(n) {
|
|
9088
9135
|
return this.pos + n <= this.buffer.length;
|
|
9089
9136
|
}
|
|
9090
|
-
setNext(
|
|
9137
|
+
setNext(state2) {
|
|
9091
9138
|
this.buffer = this.buffer.substring(this.pos);
|
|
9092
9139
|
this.pos = 0;
|
|
9093
9140
|
this.lineEndPos = null;
|
|
9094
|
-
this.next =
|
|
9141
|
+
this.next = state2;
|
|
9095
9142
|
return null;
|
|
9096
9143
|
}
|
|
9097
9144
|
peek(n) {
|
|
@@ -10527,7 +10574,7 @@ var require_public_api = __commonJS({
|
|
|
10527
10574
|
}
|
|
10528
10575
|
return doc;
|
|
10529
10576
|
}
|
|
10530
|
-
function
|
|
10577
|
+
function parse4(src, reviver, options) {
|
|
10531
10578
|
let _reviver = void 0;
|
|
10532
10579
|
if (typeof reviver === "function") {
|
|
10533
10580
|
_reviver = reviver;
|
|
@@ -10568,7 +10615,7 @@ var require_public_api = __commonJS({
|
|
|
10568
10615
|
return value.toString(options);
|
|
10569
10616
|
return new Document.Document(value, _replacer, options).toString(options);
|
|
10570
10617
|
}
|
|
10571
|
-
exports.parse =
|
|
10618
|
+
exports.parse = parse4;
|
|
10572
10619
|
exports.parseAllDocuments = parseAllDocuments;
|
|
10573
10620
|
exports.parseDocument = parseDocument3;
|
|
10574
10621
|
exports.stringify = stringify2;
|
|
@@ -10837,20 +10884,28 @@ var init_dump_config = __esm({
|
|
|
10837
10884
|
import { Buffer as Buffer3 } from "node:buffer";
|
|
10838
10885
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
10839
10886
|
import { homedir as homedir8 } from "node:os";
|
|
10840
|
-
import { dirname as dirname9, join as
|
|
10887
|
+
import { dirname as dirname9, join as join14 } from "node:path";
|
|
10888
|
+
function resolveDshHome(options) {
|
|
10889
|
+
if (options?.homeDir !== void 0)
|
|
10890
|
+
return join14(options.homeDir, ".dsh");
|
|
10891
|
+
const envHome = process.env.DSH_HOME;
|
|
10892
|
+
if (envHome !== void 0 && envHome !== "")
|
|
10893
|
+
return envHome;
|
|
10894
|
+
return join14(homedir8(), ".dsh");
|
|
10895
|
+
}
|
|
10841
10896
|
function resolveDshHookPaths(options) {
|
|
10842
10897
|
const home = options?.homeDir ?? homedir8();
|
|
10843
|
-
const dshHome =
|
|
10844
|
-
const wingmanRoot2 =
|
|
10845
|
-
const hooksDir =
|
|
10898
|
+
const dshHome = resolveDshHome(options);
|
|
10899
|
+
const wingmanRoot2 = join14(home, ".wingman");
|
|
10900
|
+
const hooksDir = join14(wingmanRoot2, "dsh");
|
|
10846
10901
|
return {
|
|
10847
10902
|
dshHome,
|
|
10848
|
-
patchFile:
|
|
10903
|
+
patchFile: join14(dshHome, "cordis.patch.yml"),
|
|
10849
10904
|
wingmanRoot: wingmanRoot2,
|
|
10850
|
-
hooksConfigFile:
|
|
10851
|
-
forwarderFile:
|
|
10852
|
-
stateFile:
|
|
10853
|
-
backupsRoot:
|
|
10905
|
+
hooksConfigFile: join14(hooksDir, "hooks-claude-code.json"),
|
|
10906
|
+
forwarderFile: join14(hooksDir, "forward.mjs"),
|
|
10907
|
+
stateFile: join14(wingmanRoot2, "state", "dsh-hooks.json"),
|
|
10908
|
+
backupsRoot: join14(wingmanRoot2, "backups")
|
|
10854
10909
|
};
|
|
10855
10910
|
}
|
|
10856
10911
|
function yamlSingleQuote(value) {
|
|
@@ -10973,20 +11028,20 @@ async function readState3(paths) {
|
|
|
10973
11028
|
const buffer = await readIfExists(paths.stateFile);
|
|
10974
11029
|
if (buffer === void 0)
|
|
10975
11030
|
return void 0;
|
|
10976
|
-
const
|
|
10977
|
-
if (
|
|
10978
|
-
throw new Error(`\u672A\u77E5\u7684\u63A5\u7EBF\u72B6\u6001\u7248\u672C:${String(
|
|
10979
|
-
return
|
|
11031
|
+
const state2 = JSON.parse(buffer.toString("utf8"));
|
|
11032
|
+
if (state2.version !== 1)
|
|
11033
|
+
throw new Error(`\u672A\u77E5\u7684\u63A5\u7EBF\u72B6\u6001\u7248\u672C:${String(state2.version)},\u4E0D\u731C`);
|
|
11034
|
+
return state2;
|
|
10980
11035
|
}
|
|
10981
|
-
async function writeState3(paths,
|
|
11036
|
+
async function writeState3(paths, state2) {
|
|
10982
11037
|
await mkdir(dirname9(paths.stateFile), { recursive: true });
|
|
10983
|
-
await writeFile(paths.stateFile, `${JSON.stringify(
|
|
11038
|
+
await writeFile(paths.stateFile, `${JSON.stringify(state2, void 0, 2)}
|
|
10984
11039
|
`, "utf8");
|
|
10985
11040
|
}
|
|
10986
11041
|
async function backupPatchFile(paths, bytes) {
|
|
10987
|
-
const dir =
|
|
11042
|
+
const dir = join14(paths.backupsRoot, (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
|
|
10988
11043
|
await mkdir(dir, { recursive: true });
|
|
10989
|
-
await writeFile(
|
|
11044
|
+
await writeFile(join14(dir, "cordis.patch.yml"), bytes);
|
|
10990
11045
|
return dir;
|
|
10991
11046
|
}
|
|
10992
11047
|
async function writeOwnedFiles(paths, url) {
|
|
@@ -11046,13 +11101,13 @@ async function installDshHooks(port, options) {
|
|
|
11046
11101
|
}
|
|
11047
11102
|
async function uninstallDshHooks(options) {
|
|
11048
11103
|
const paths = resolveDshHookPaths(options);
|
|
11049
|
-
const
|
|
11104
|
+
const state2 = await readState3(paths);
|
|
11050
11105
|
const currentBuffer = await readIfExists(paths.patchFile);
|
|
11051
11106
|
if (currentBuffer !== void 0) {
|
|
11052
11107
|
const currentText = decodeUtf8Strict(currentBuffer, paths.patchFile);
|
|
11053
11108
|
if (currentText.includes(HOOKS_MARKER_BEGIN_PREFIX)) {
|
|
11054
11109
|
const surgical = stripLiveHooksBlocks(currentText, paths.patchFile);
|
|
11055
|
-
const restored = resolveRestoredText(surgical,
|
|
11110
|
+
const restored = resolveRestoredText(surgical, state2, paths);
|
|
11056
11111
|
if (restored !== currentText) {
|
|
11057
11112
|
await backupPatchFile(paths, currentBuffer);
|
|
11058
11113
|
if (restored === void 0)
|
|
@@ -11066,9 +11121,9 @@ async function uninstallDshHooks(options) {
|
|
|
11066
11121
|
await rm(paths.forwarderFile, { force: true });
|
|
11067
11122
|
await rm(paths.stateFile, { force: true });
|
|
11068
11123
|
}
|
|
11069
|
-
function resolveRestoredText(surgical,
|
|
11070
|
-
if (
|
|
11071
|
-
const originalText = Buffer3.from(
|
|
11124
|
+
function resolveRestoredText(surgical, state2, paths) {
|
|
11125
|
+
if (state2?.original.existed === true) {
|
|
11126
|
+
const originalText = Buffer3.from(state2.original.bytesBase64, "base64").toString("utf8");
|
|
11072
11127
|
const surgicalEntries = tryParsePatchEntries(surgical, paths.patchFile);
|
|
11073
11128
|
const originalEntries = tryParsePatchEntries(originalText, paths.patchFile);
|
|
11074
11129
|
if (surgicalEntries !== void 0 && originalEntries !== void 0 && JSON.stringify(surgicalEntries) === JSON.stringify(originalEntries)) {
|
|
@@ -11138,7 +11193,7 @@ if (url !== undefined) {
|
|
|
11138
11193
|
import { Buffer as Buffer4 } from "node:buffer";
|
|
11139
11194
|
import { randomBytes } from "node:crypto";
|
|
11140
11195
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
11141
|
-
import { dirname as dirname10, isAbsolute as isAbsolute2, join as
|
|
11196
|
+
import { dirname as dirname10, isAbsolute as isAbsolute2, join as join15, relative } from "node:path";
|
|
11142
11197
|
function buildToggleBlock(instanceId, flags) {
|
|
11143
11198
|
const lines = [`${TOGGLE_BEGIN_PREFIX}${instanceId}${TOGGLE_BEGIN_SUFFIX}`];
|
|
11144
11199
|
if (flags.fileCreated)
|
|
@@ -11429,7 +11484,7 @@ async function planDshToggle(action, dump, options) {
|
|
|
11429
11484
|
harness: "dsh",
|
|
11430
11485
|
action: toggle,
|
|
11431
11486
|
writes: [change],
|
|
11432
|
-
backupDir:
|
|
11487
|
+
backupDir: join15(paths.backupsRoot, backupDirName()),
|
|
11433
11488
|
references: scanDshReferences(paths.patchFile, text, dump, row),
|
|
11434
11489
|
// 生效时机:patch 层在 boot 时组装(composeProfile),保证下次启动生效;钉定版本
|
|
11435
11490
|
// 还会 watchUserPatches 热重载家目录层,但该监听失败被静默吞掉(suppressShutdownError)
|
|
@@ -11489,7 +11544,7 @@ async function applyDshToggle(plan, options) {
|
|
|
11489
11544
|
}
|
|
11490
11545
|
await mkdir2(plan.backupDir, { recursive: true });
|
|
11491
11546
|
if (buffer !== void 0) {
|
|
11492
|
-
await writeFile2(
|
|
11547
|
+
await writeFile2(join15(plan.backupDir, "cordis.patch.yml"), buffer);
|
|
11493
11548
|
}
|
|
11494
11549
|
if (change.kind === "delete") {
|
|
11495
11550
|
await rm2(paths.patchFile);
|
|
@@ -11528,70 +11583,95 @@ __export(dist_exports3, {
|
|
|
11528
11583
|
createAdapter: () => createAdapter3,
|
|
11529
11584
|
detectDshLiveWiring: () => detectDshLiveWiring,
|
|
11530
11585
|
parseDumpConfig: () => parseDumpConfig,
|
|
11586
|
+
resolveDshHome: () => resolveDshHome,
|
|
11531
11587
|
resolveDshHookPaths: () => resolveDshHookPaths
|
|
11532
11588
|
});
|
|
11533
|
-
import {
|
|
11534
|
-
import { readFileSync as readFileSync10 } from "node:fs";
|
|
11589
|
+
import { execFile } from "node:child_process";
|
|
11590
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
11535
11591
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
11536
11592
|
import { createRequire } from "node:module";
|
|
11537
|
-
import {
|
|
11538
|
-
|
|
11593
|
+
import { join as join16 } from "node:path";
|
|
11594
|
+
import { promisify } from "node:util";
|
|
11595
|
+
function resolvePinnedDshVersion() {
|
|
11539
11596
|
try {
|
|
11540
11597
|
const require2 = createRequire(import.meta.url);
|
|
11541
|
-
const
|
|
11542
|
-
|
|
11543
|
-
const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.dsh;
|
|
11544
|
-
if (typeof pkg.version !== "string" || typeof binRel !== "string")
|
|
11545
|
-
return void 0;
|
|
11546
|
-
return { version: pkg.version, binPath: join14(dirname11(pkgJsonPath), binRel) };
|
|
11598
|
+
const pkg = JSON.parse(readFileSync10(require2.resolve("@deepseek-ai/dsh/package.json"), "utf8"));
|
|
11599
|
+
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
11547
11600
|
} catch {
|
|
11548
11601
|
return void 0;
|
|
11549
11602
|
}
|
|
11550
11603
|
}
|
|
11551
|
-
async function
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
return await new Promise((resolve6, reject) => {
|
|
11557
|
-
const child = spawn(process.execPath, [pinned.binPath, "--profile", profile, "--dump-config"], {
|
|
11558
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
11559
|
-
});
|
|
11560
|
-
let stdout = "";
|
|
11561
|
-
let stderr = "";
|
|
11562
|
-
child.stdout.setEncoding("utf8");
|
|
11563
|
-
child.stderr.setEncoding("utf8");
|
|
11564
|
-
child.stdout.on("data", (chunk) => {
|
|
11565
|
-
stdout += chunk;
|
|
11566
|
-
});
|
|
11567
|
-
child.stderr.on("data", (chunk) => {
|
|
11568
|
-
stderr += chunk;
|
|
11569
|
-
});
|
|
11570
|
-
child.on("error", reject);
|
|
11571
|
-
child.on("close", (code) => {
|
|
11572
|
-
if (code === 0)
|
|
11573
|
-
resolve6(stdout);
|
|
11574
|
-
else
|
|
11575
|
-
reject(new Error(`dsh --dump-config \u9000\u51FA\u7801 ${String(code)}:${stderr.slice(0, 2e3)}`));
|
|
11604
|
+
async function execDumpConfigViaPath(profile) {
|
|
11605
|
+
try {
|
|
11606
|
+
const { stdout } = await execFileAsync("dsh", ["--profile", profile, "--dump-config"], {
|
|
11607
|
+
timeout: 1e4,
|
|
11608
|
+
maxBuffer: 16 * 1024 * 1024
|
|
11576
11609
|
});
|
|
11577
|
-
|
|
11610
|
+
return stdout;
|
|
11611
|
+
} catch (error) {
|
|
11612
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11613
|
+
throw new Error(`dsh --dump-config \u6267\u884C\u5931\u8D25(PATH \u4E0A\u65E0 dsh\u3001\u8D85\u65F6\u6216\u975E\u96F6\u9000\u51FA):${message}`);
|
|
11614
|
+
}
|
|
11615
|
+
}
|
|
11616
|
+
function degradedConfig(wired, version) {
|
|
11617
|
+
const config = {
|
|
11618
|
+
harness: "dsh",
|
|
11619
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11620
|
+
slots: {
|
|
11621
|
+
base: [
|
|
11622
|
+
{
|
|
11623
|
+
id: "dsh:base:bundle:dsh-cli-unavailable",
|
|
11624
|
+
slot: "base",
|
|
11625
|
+
nativeName: "dsh \u5DF2\u68C0\u6D4B\u5230;\u8BFB\u53D6\u914D\u7F6E\u9700 dsh CLI \u53EF\u7528(PATH \u4E0A\u6267\u884C dsh --dump-config \u5931\u8D25)",
|
|
11626
|
+
nativePath: "",
|
|
11627
|
+
toggleable: false,
|
|
11628
|
+
enabled: true,
|
|
11629
|
+
source: "bundle"
|
|
11630
|
+
}
|
|
11631
|
+
],
|
|
11632
|
+
rules: [],
|
|
11633
|
+
skills: [],
|
|
11634
|
+
tools: [],
|
|
11635
|
+
gates: [],
|
|
11636
|
+
history: []
|
|
11637
|
+
},
|
|
11638
|
+
capabilities: { live: wired ? "full" : "none", actions: [] }
|
|
11639
|
+
};
|
|
11640
|
+
if (version !== void 0)
|
|
11641
|
+
config.harnessVersion = version;
|
|
11642
|
+
return config;
|
|
11578
11643
|
}
|
|
11579
11644
|
function createAdapter3(options = {}) {
|
|
11580
11645
|
const profile = options.profile ?? "web";
|
|
11581
11646
|
const dumpConfigPath = options.dumpConfigPath;
|
|
11647
|
+
const runDump = options.dumpRunner ?? execDumpConfigViaPath;
|
|
11648
|
+
const loadDumpText = async () => dumpConfigPath !== void 0 ? await readFile2(dumpConfigPath, "utf8") : await runDump(profile);
|
|
11582
11649
|
return {
|
|
11583
11650
|
harness: "dsh",
|
|
11584
|
-
//
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11651
|
+
// 检测与 CC/Codex 同构:探用户 dsh 家目录(homeDir/.dsh → $DSH_HOME → ~/.dsh)是否存在。
|
|
11652
|
+
// 版本尽力:钉定 devDependency 可解析时用其版本(仓库开发形态);发布产物无此依赖
|
|
11653
|
+
// → 省略,不跑命令探版本。
|
|
11654
|
+
async detect(options2) {
|
|
11655
|
+
if (!existsSync9(resolveDshHome(options2)))
|
|
11656
|
+
return { installed: false };
|
|
11657
|
+
const version = resolvePinnedDshVersion();
|
|
11658
|
+
return version !== void 0 ? { installed: true, version } : { installed: true };
|
|
11588
11659
|
},
|
|
11589
11660
|
// anatomy 唯一来源 = dump-config 输出;homeDir 只用于接线检测(hermetic 注入)
|
|
11590
11661
|
async scan(options2) {
|
|
11591
|
-
const text = dumpConfigPath !== void 0 ? await readFile2(dumpConfigPath, "utf8") : await execDumpConfig(profile);
|
|
11592
|
-
const slots = buildSlots(parseDumpConfig(text));
|
|
11593
|
-
const pinned = resolvePinnedDsh();
|
|
11594
11662
|
const wired = await detectDshLiveWiring(options2);
|
|
11663
|
+
const version = resolvePinnedDshVersion();
|
|
11664
|
+
let text;
|
|
11665
|
+
if (dumpConfigPath !== void 0) {
|
|
11666
|
+
text = await readFile2(dumpConfigPath, "utf8");
|
|
11667
|
+
} else {
|
|
11668
|
+
try {
|
|
11669
|
+
text = await runDump(profile);
|
|
11670
|
+
} catch {
|
|
11671
|
+
return degradedConfig(wired, version);
|
|
11672
|
+
}
|
|
11673
|
+
}
|
|
11674
|
+
const slots = buildSlots(parseDumpConfig(text));
|
|
11595
11675
|
const config = {
|
|
11596
11676
|
harness: "dsh",
|
|
11597
11677
|
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11599,13 +11679,13 @@ function createAdapter3(options = {}) {
|
|
|
11599
11679
|
// M3:toggle 已落地(patch row 开关);open_editor 不适用(dump 条目无 nativePath)
|
|
11600
11680
|
capabilities: { live: wired ? "full" : "none", actions: ["toggle"] }
|
|
11601
11681
|
};
|
|
11602
|
-
if (
|
|
11603
|
-
config.harnessVersion =
|
|
11682
|
+
if (version !== void 0)
|
|
11683
|
+
config.harnessVersion = version;
|
|
11604
11684
|
return config;
|
|
11605
11685
|
},
|
|
11606
11686
|
async planAction(action, options2) {
|
|
11607
|
-
const text =
|
|
11608
|
-
const file = dumpConfigPath ??
|
|
11687
|
+
const text = await loadDumpText();
|
|
11688
|
+
const file = dumpConfigPath ?? join16(resolveDshHookPaths(options2).dshHome, `dump-config(${profile})`);
|
|
11609
11689
|
return await planDshToggle(action, { text, file }, options2);
|
|
11610
11690
|
},
|
|
11611
11691
|
async applyAction(plan, options2) {
|
|
@@ -11619,6 +11699,7 @@ function createAdapter3(options = {}) {
|
|
|
11619
11699
|
}
|
|
11620
11700
|
};
|
|
11621
11701
|
}
|
|
11702
|
+
var execFileAsync;
|
|
11622
11703
|
var init_dist5 = __esm({
|
|
11623
11704
|
"packages/adapter-dsh/dist/index.js"() {
|
|
11624
11705
|
"use strict";
|
|
@@ -11627,13 +11708,23 @@ var init_dist5 = __esm({
|
|
|
11627
11708
|
init_toggle();
|
|
11628
11709
|
init_dump_config();
|
|
11629
11710
|
init_hooks3();
|
|
11711
|
+
execFileAsync = promisify(execFile);
|
|
11630
11712
|
}
|
|
11631
11713
|
});
|
|
11632
11714
|
|
|
11633
11715
|
// apps/cli/dist/main.js
|
|
11634
11716
|
init_dist();
|
|
11635
|
-
import {
|
|
11636
|
-
import {
|
|
11717
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
11718
|
+
import { dirname as dirname12, resolve as resolve6 } from "node:path";
|
|
11719
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
11720
|
+
|
|
11721
|
+
// packages/fixtures/dist/index.js
|
|
11722
|
+
import { dirname, join } from "node:path";
|
|
11723
|
+
import { fileURLToPath } from "node:url";
|
|
11724
|
+
var fixturesRoot = join(dirname(dirname(fileURLToPath(import.meta.url))), "data");
|
|
11725
|
+
function fixturePath(...segments) {
|
|
11726
|
+
return join(fixturesRoot, ...segments);
|
|
11727
|
+
}
|
|
11637
11728
|
|
|
11638
11729
|
// packages/live/dist/receiver.js
|
|
11639
11730
|
init_dist();
|
|
@@ -11646,6 +11737,7 @@ function createReceiver(options = {}) {
|
|
|
11646
11737
|
buffer.push(event);
|
|
11647
11738
|
if (buffer.length > bufferSize)
|
|
11648
11739
|
buffer.shift();
|
|
11740
|
+
options.onEvent?.(event);
|
|
11649
11741
|
const data = frame(event);
|
|
11650
11742
|
for (const subscriber of subscribers)
|
|
11651
11743
|
subscriber.res.write(data);
|
|
@@ -11675,7 +11767,7 @@ function createReceiver(options = {}) {
|
|
|
11675
11767
|
}
|
|
11676
11768
|
accept({ harness, received_at: (/* @__PURE__ */ new Date()).toISOString(), payload: parsed });
|
|
11677
11769
|
sendText(res, 200, "ok");
|
|
11678
|
-
}, () => sendText(res, 400, "\u8BFB\u53D6\u8BF7\u6C42\u4F53\u5931\u8D25"));
|
|
11770
|
+
}, (error) => sendText(res, 400, error instanceof Error ? error.message : "\u8BFB\u53D6\u8BF7\u6C42\u4F53\u5931\u8D25"));
|
|
11679
11771
|
}
|
|
11680
11772
|
function handleEvents(req, res) {
|
|
11681
11773
|
if (req.method !== "GET") {
|
|
@@ -11732,11 +11824,21 @@ function isHookPayload(value) {
|
|
|
11732
11824
|
const name = value.hook_event_name;
|
|
11733
11825
|
return typeof name === "string" && name.length > 0;
|
|
11734
11826
|
}
|
|
11827
|
+
var HOOK_BODY_LIMIT = 1024 * 1024;
|
|
11735
11828
|
function readBody(req) {
|
|
11736
|
-
return new Promise((
|
|
11829
|
+
return new Promise((resolve7, reject) => {
|
|
11737
11830
|
const chunks = [];
|
|
11738
|
-
|
|
11739
|
-
req.on("
|
|
11831
|
+
let size = 0;
|
|
11832
|
+
req.on("data", (chunk) => {
|
|
11833
|
+
size += chunk.length;
|
|
11834
|
+
if (size > HOOK_BODY_LIMIT) {
|
|
11835
|
+
reject(new Error("body \u8D85\u8FC7 1MB \u4E0A\u9650"));
|
|
11836
|
+
req.destroy();
|
|
11837
|
+
return;
|
|
11838
|
+
}
|
|
11839
|
+
chunks.push(chunk);
|
|
11840
|
+
});
|
|
11841
|
+
req.on("end", () => resolve7(Buffer.concat(chunks).toString("utf8")));
|
|
11740
11842
|
req.on("error", reject);
|
|
11741
11843
|
});
|
|
11742
11844
|
}
|
|
@@ -11749,7 +11851,7 @@ function sendText(res, status, body) {
|
|
|
11749
11851
|
import { verify } from "node:crypto";
|
|
11750
11852
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
11751
11853
|
import { homedir } from "node:os";
|
|
11752
|
-
import { join } from "node:path";
|
|
11854
|
+
import { join as join2 } from "node:path";
|
|
11753
11855
|
var OFFLINE_GRACE_DAYS = 14;
|
|
11754
11856
|
var PROD_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAyU4yO0SJcflt/CV77wqVxIwCkM52CYGC6qrPGP8dMME=\n-----END PUBLIC KEY-----\n";
|
|
11755
11857
|
function checkLicense(options = {}) {
|
|
@@ -11763,7 +11865,7 @@ function licenseStatus(options = {}) {
|
|
|
11763
11865
|
const home = options.homeDir ?? homedir();
|
|
11764
11866
|
let raw;
|
|
11765
11867
|
try {
|
|
11766
|
-
raw = readFileSync(
|
|
11868
|
+
raw = readFileSync(join2(home, ".wingman", "license.json"), "utf8");
|
|
11767
11869
|
} catch {
|
|
11768
11870
|
return { state: "none", detail: "\u672A\u5B89\u88C5 license(anatomy \u4E0E\u5B9E\u51B5\u514D\u8D39\u53EF\u7528;\u5199\u52A8\u4F5C\u9700\u8BA2\u9605)" };
|
|
11769
11871
|
}
|
|
@@ -11832,9 +11934,9 @@ function installLicense(raw, options = {}) {
|
|
|
11832
11934
|
return { ok: false, error: status.detail };
|
|
11833
11935
|
}
|
|
11834
11936
|
}
|
|
11835
|
-
const dir =
|
|
11836
|
-
const licensePath =
|
|
11837
|
-
const tmpPath =
|
|
11937
|
+
const dir = join2(options.homeDir ?? homedir(), ".wingman");
|
|
11938
|
+
const licensePath = join2(dir, "license.json");
|
|
11939
|
+
const tmpPath = join2(dir, `.license.json.tmp-${process.pid}`);
|
|
11838
11940
|
try {
|
|
11839
11941
|
mkdirSync(dir, { recursive: true });
|
|
11840
11942
|
writeFileSync(tmpPath, raw);
|
|
@@ -11851,12 +11953,12 @@ function installLicense(raw, options = {}) {
|
|
|
11851
11953
|
// apps/cli/dist/license-refresh.js
|
|
11852
11954
|
import { readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
11853
11955
|
import { homedir as homedir2 } from "node:os";
|
|
11854
|
-
import { dirname, join as
|
|
11956
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
11855
11957
|
var DEFAULT_LICENSE_SERVER = "https://api.getwingman.dev";
|
|
11856
11958
|
async function refreshLicense(options = {}) {
|
|
11857
11959
|
const home = options.homeDir ?? homedir2();
|
|
11858
11960
|
const baseUrl = options.baseUrl ?? process.env.WINGMAN_LICENSE_SERVER ?? DEFAULT_LICENSE_SERVER;
|
|
11859
|
-
const licensePath =
|
|
11961
|
+
const licensePath = join3(home, ".wingman", "license.json");
|
|
11860
11962
|
let raw;
|
|
11861
11963
|
try {
|
|
11862
11964
|
raw = readFileSync2(licensePath, "utf8");
|
|
@@ -11898,7 +12000,7 @@ async function refreshLicense(options = {}) {
|
|
|
11898
12000
|
}
|
|
11899
12001
|
const next = `${JSON.stringify({ token: entitlement.token, signature: entitlement.signature })}
|
|
11900
12002
|
`;
|
|
11901
|
-
const tmpPath =
|
|
12003
|
+
const tmpPath = join3(dirname2(licensePath), `.license.json.tmp-${process.pid}`);
|
|
11902
12004
|
try {
|
|
11903
12005
|
writeFileSync2(tmpPath, next);
|
|
11904
12006
|
renameSync2(tmpPath, licensePath);
|
|
@@ -11917,18 +12019,1184 @@ function expSuffix(token) {
|
|
|
11917
12019
|
}
|
|
11918
12020
|
}
|
|
11919
12021
|
|
|
11920
|
-
// apps/cli/dist/
|
|
11921
|
-
|
|
12022
|
+
// apps/cli/dist/narrative-service.js
|
|
12023
|
+
init_dist();
|
|
12024
|
+
import { readdir } from "node:fs/promises";
|
|
12025
|
+
import { join as join4 } from "node:path";
|
|
11922
12026
|
|
|
11923
|
-
// packages/
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
12027
|
+
// packages/narrative/dist/attribute.js
|
|
12028
|
+
function extractSteps(lines) {
|
|
12029
|
+
const steps = [];
|
|
12030
|
+
const byToolUseId = /* @__PURE__ */ new Map();
|
|
12031
|
+
for (const line of lines) {
|
|
12032
|
+
if (line.kind === "assistant") {
|
|
12033
|
+
for (const block of line.blocks) {
|
|
12034
|
+
if (block.kind !== "tool_use")
|
|
12035
|
+
continue;
|
|
12036
|
+
const step = {
|
|
12037
|
+
toolUseId: block.id,
|
|
12038
|
+
toolName: block.name,
|
|
12039
|
+
toolInput: block.input,
|
|
12040
|
+
isError: false
|
|
12041
|
+
};
|
|
12042
|
+
if (line.timestamp !== void 0)
|
|
12043
|
+
step.ts = line.timestamp;
|
|
12044
|
+
if (line.agentId !== void 0)
|
|
12045
|
+
step.agentId = line.agentId;
|
|
12046
|
+
steps.push(step);
|
|
12047
|
+
byToolUseId.set(block.id, step);
|
|
12048
|
+
}
|
|
12049
|
+
} else if (line.kind === "user") {
|
|
12050
|
+
for (const result of line.toolResults) {
|
|
12051
|
+
const step = byToolUseId.get(result.toolUseId);
|
|
12052
|
+
if (step === void 0)
|
|
12053
|
+
continue;
|
|
12054
|
+
if (line.timestamp !== void 0)
|
|
12055
|
+
step.endTs = line.timestamp;
|
|
12056
|
+
if (result.isError)
|
|
12057
|
+
step.isError = true;
|
|
12058
|
+
}
|
|
12059
|
+
}
|
|
12060
|
+
}
|
|
12061
|
+
return steps;
|
|
12062
|
+
}
|
|
12063
|
+
function deriveSubagentIntervals(lines) {
|
|
12064
|
+
const agentSteps = extractSteps(lines).filter((step) => step.toolName === "Agent");
|
|
12065
|
+
return agentSteps.map((step) => {
|
|
12066
|
+
const interval = { agentId: step.toolUseId };
|
|
12067
|
+
if (step.ts !== void 0)
|
|
12068
|
+
interval.startTs = step.ts;
|
|
12069
|
+
if (step.endTs !== void 0)
|
|
12070
|
+
interval.endTs = step.endTs;
|
|
12071
|
+
return interval;
|
|
12072
|
+
});
|
|
12073
|
+
}
|
|
12074
|
+
function attributeSteps(steps, timeline, intervals = []) {
|
|
12075
|
+
return steps.map((step) => ({ step, attribution: attributeOne(step, timeline, intervals) }));
|
|
12076
|
+
}
|
|
12077
|
+
function attributeOne(step, timeline, intervals) {
|
|
12078
|
+
const ts = step.ts;
|
|
12079
|
+
if (ts === void 0)
|
|
12080
|
+
return { kind: "other", reason: "no-timestamp" };
|
|
12081
|
+
const active = timeline.filter((entry) => isInProgressAt(entry, ts));
|
|
12082
|
+
if (active.length === 0)
|
|
12083
|
+
return { kind: "other", reason: "no-active-todo" };
|
|
12084
|
+
const only = active[0];
|
|
12085
|
+
if (active.length === 1 && only !== void 0)
|
|
12086
|
+
return { kind: "todo", todoId: only.id };
|
|
12087
|
+
if (step.agentId !== void 0) {
|
|
12088
|
+
const todoIds = /* @__PURE__ */ new Set();
|
|
12089
|
+
for (const interval of intervals) {
|
|
12090
|
+
if (interval.agentId !== step.agentId)
|
|
12091
|
+
continue;
|
|
12092
|
+
const todoId = resolveIntervalTodo(interval, timeline);
|
|
12093
|
+
if (todoId !== void 0)
|
|
12094
|
+
todoIds.add(todoId);
|
|
12095
|
+
}
|
|
12096
|
+
if (todoIds.size === 1) {
|
|
12097
|
+
const [todoId] = todoIds;
|
|
12098
|
+
if (todoId !== void 0 && active.some((entry) => entry.id === todoId)) {
|
|
12099
|
+
return { kind: "todo", todoId };
|
|
12100
|
+
}
|
|
12101
|
+
}
|
|
12102
|
+
}
|
|
12103
|
+
return { kind: "other", reason: "ambiguous" };
|
|
12104
|
+
}
|
|
12105
|
+
function resolveIntervalTodo(interval, timeline) {
|
|
12106
|
+
const start = interval.startTs;
|
|
12107
|
+
if (start === void 0)
|
|
12108
|
+
return void 0;
|
|
12109
|
+
const active = timeline.filter((entry) => isInProgressAt(entry, start));
|
|
12110
|
+
const only = active[0];
|
|
12111
|
+
return active.length === 1 && only !== void 0 ? only.id : void 0;
|
|
12112
|
+
}
|
|
12113
|
+
function isInProgressAt(entry, ts) {
|
|
12114
|
+
return entry.spans.some((span) => span.status === "in_progress" && span.from !== void 0 && span.from <= ts && (span.to === void 0 || ts < span.to));
|
|
12115
|
+
}
|
|
12116
|
+
|
|
12117
|
+
// packages/narrative/dist/parse.js
|
|
12118
|
+
function parseTranscriptLine(line) {
|
|
12119
|
+
const trimmed = line.trim();
|
|
12120
|
+
if (trimmed === "")
|
|
12121
|
+
return { kind: "unknown", reason: "\u7A7A\u884C", rawText: line };
|
|
12122
|
+
let value;
|
|
12123
|
+
try {
|
|
12124
|
+
value = JSON.parse(trimmed);
|
|
12125
|
+
} catch {
|
|
12126
|
+
return { kind: "unknown", reason: "JSON \u89E3\u6790\u5931\u8D25", rawText: line };
|
|
12127
|
+
}
|
|
12128
|
+
if (!isRecord2(value))
|
|
12129
|
+
return { kind: "unknown", reason: "\u884C\u4E0D\u662F JSON \u5BF9\u8C61", rawText: line };
|
|
12130
|
+
const type = value.type;
|
|
12131
|
+
if (typeof type !== "string") {
|
|
12132
|
+
return { kind: "unknown", reason: "\u7F3A type \u5B57\u6BB5\u6216\u975E\u5B57\u7B26\u4E32", rawText: line };
|
|
12133
|
+
}
|
|
12134
|
+
const common = extractCommon(value);
|
|
12135
|
+
if (type === "user")
|
|
12136
|
+
return parseUser(value, common);
|
|
12137
|
+
if (type === "assistant")
|
|
12138
|
+
return parseAssistant(value, common);
|
|
12139
|
+
if (type === "attachment") {
|
|
12140
|
+
const attachment = {
|
|
12141
|
+
kind: "attachment",
|
|
12142
|
+
...common,
|
|
12143
|
+
attachment: isRecord2(value.attachment) ? value.attachment : {}
|
|
12144
|
+
};
|
|
12145
|
+
return attachment;
|
|
12146
|
+
}
|
|
12147
|
+
return { kind: "other", type, ...common };
|
|
12148
|
+
}
|
|
12149
|
+
function extractCommon(raw) {
|
|
12150
|
+
const common = { raw };
|
|
12151
|
+
if (typeof raw.uuid === "string")
|
|
12152
|
+
common.uuid = raw.uuid;
|
|
12153
|
+
if (typeof raw.parentUuid === "string")
|
|
12154
|
+
common.parentUuid = raw.parentUuid;
|
|
12155
|
+
if (typeof raw.timestamp === "string")
|
|
12156
|
+
common.timestamp = raw.timestamp;
|
|
12157
|
+
if (typeof raw.sessionId === "string")
|
|
12158
|
+
common.sessionId = raw.sessionId;
|
|
12159
|
+
if (typeof raw.agentId === "string")
|
|
12160
|
+
common.agentId = raw.agentId;
|
|
12161
|
+
if (typeof raw.isSidechain === "boolean")
|
|
12162
|
+
common.isSidechain = raw.isSidechain;
|
|
12163
|
+
return common;
|
|
12164
|
+
}
|
|
12165
|
+
function parseUser(raw, common) {
|
|
12166
|
+
const line = { kind: "user", ...common, toolResults: [] };
|
|
12167
|
+
const message = isRecord2(raw.message) ? raw.message : void 0;
|
|
12168
|
+
const content = message?.content;
|
|
12169
|
+
if (typeof content === "string") {
|
|
12170
|
+
line.text = content;
|
|
12171
|
+
} else if (Array.isArray(content)) {
|
|
12172
|
+
const texts = [];
|
|
12173
|
+
for (const block of content) {
|
|
12174
|
+
if (!isRecord2(block))
|
|
12175
|
+
continue;
|
|
12176
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
12177
|
+
const result = {
|
|
12178
|
+
toolUseId: block.tool_use_id,
|
|
12179
|
+
// 实测:成功结果常无 is_error 字段;仅显式 true 视为失败
|
|
12180
|
+
isError: block.is_error === true,
|
|
12181
|
+
content: block.content
|
|
12182
|
+
};
|
|
12183
|
+
line.toolResults.push(result);
|
|
12184
|
+
} else if (block.type === "text" && typeof block.text === "string") {
|
|
12185
|
+
texts.push(block.text);
|
|
12186
|
+
}
|
|
12187
|
+
}
|
|
12188
|
+
if (texts.length > 0)
|
|
12189
|
+
line.text = texts.join("\n");
|
|
12190
|
+
}
|
|
12191
|
+
if ("toolUseResult" in raw)
|
|
12192
|
+
line.toolUseResult = raw.toolUseResult;
|
|
12193
|
+
return line;
|
|
12194
|
+
}
|
|
12195
|
+
function parseAssistant(raw, common) {
|
|
12196
|
+
const line = { kind: "assistant", ...common, blocks: [] };
|
|
12197
|
+
const message = isRecord2(raw.message) ? raw.message : void 0;
|
|
12198
|
+
if (message === void 0)
|
|
12199
|
+
return line;
|
|
12200
|
+
if (typeof message.model === "string")
|
|
12201
|
+
line.model = message.model;
|
|
12202
|
+
if (typeof message.id === "string")
|
|
12203
|
+
line.messageId = message.id;
|
|
12204
|
+
if (isRecord2(message.usage))
|
|
12205
|
+
line.usage = message.usage;
|
|
12206
|
+
const content = message.content;
|
|
12207
|
+
if (!Array.isArray(content))
|
|
12208
|
+
return line;
|
|
12209
|
+
for (const block of content) {
|
|
12210
|
+
if (!isRecord2(block))
|
|
12211
|
+
continue;
|
|
12212
|
+
line.blocks.push(parseAssistantBlock(block));
|
|
12213
|
+
}
|
|
12214
|
+
return line;
|
|
12215
|
+
}
|
|
12216
|
+
function parseAssistantBlock(block) {
|
|
12217
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
12218
|
+
return { kind: "text", text: block.text };
|
|
12219
|
+
}
|
|
12220
|
+
if (block.type === "thinking" && typeof block.thinking === "string") {
|
|
12221
|
+
return { kind: "thinking", thinking: block.thinking };
|
|
12222
|
+
}
|
|
12223
|
+
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
12224
|
+
return {
|
|
12225
|
+
kind: "tool_use",
|
|
12226
|
+
id: block.id,
|
|
12227
|
+
name: block.name,
|
|
12228
|
+
input: isRecord2(block.input) ? block.input : {}
|
|
12229
|
+
};
|
|
12230
|
+
}
|
|
12231
|
+
return {
|
|
12232
|
+
kind: "other",
|
|
12233
|
+
type: typeof block.type === "string" ? block.type : "unknown",
|
|
12234
|
+
raw: block
|
|
12235
|
+
};
|
|
12236
|
+
}
|
|
12237
|
+
function isRecord2(value) {
|
|
12238
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12239
|
+
}
|
|
12240
|
+
|
|
12241
|
+
// packages/narrative/dist/phase.js
|
|
12242
|
+
var PHASE_LABELS = {
|
|
12243
|
+
inspect: "\u68C0\u67E5",
|
|
12244
|
+
modify: "\u4FEE\u6539",
|
|
12245
|
+
verify: "\u9A8C\u8BC1",
|
|
12246
|
+
other: "\u5176\u4ED6"
|
|
12247
|
+
};
|
|
12248
|
+
var PHASE_TOOL_RULES = {
|
|
12249
|
+
Read: "inspect",
|
|
12250
|
+
Grep: "inspect",
|
|
12251
|
+
Glob: "inspect",
|
|
12252
|
+
WebFetch: "inspect",
|
|
12253
|
+
Edit: "modify",
|
|
12254
|
+
Write: "modify",
|
|
12255
|
+
NotebookEdit: "modify"
|
|
12256
|
+
};
|
|
12257
|
+
var VERIFY_COMMAND_WORDS = [
|
|
12258
|
+
"test",
|
|
12259
|
+
"vitest",
|
|
12260
|
+
"jest",
|
|
12261
|
+
"pytest",
|
|
12262
|
+
"playwright",
|
|
12263
|
+
"build",
|
|
12264
|
+
"lint",
|
|
12265
|
+
"biome",
|
|
12266
|
+
"eslint",
|
|
12267
|
+
"tsc",
|
|
12268
|
+
"typecheck",
|
|
12269
|
+
"check"
|
|
12270
|
+
];
|
|
12271
|
+
var VERIFY_COMMAND_RE = new RegExp(`\\b(?:${VERIFY_COMMAND_WORDS.join("|")})\\b`);
|
|
12272
|
+
function classifyPhase(toolName, toolInput) {
|
|
12273
|
+
const ruled = PHASE_TOOL_RULES[toolName];
|
|
12274
|
+
if (ruled !== void 0)
|
|
12275
|
+
return ruled;
|
|
12276
|
+
if (toolName === "Bash") {
|
|
12277
|
+
const command = toolInput?.command;
|
|
12278
|
+
if (typeof command === "string" && VERIFY_COMMAND_RE.test(command))
|
|
12279
|
+
return "verify";
|
|
12280
|
+
}
|
|
12281
|
+
return "other";
|
|
12282
|
+
}
|
|
12283
|
+
function consolidatePhases(steps) {
|
|
12284
|
+
const nodes = [];
|
|
12285
|
+
for (const step of steps) {
|
|
12286
|
+
const phase = classifyPhase(step.toolName, step.toolInput);
|
|
12287
|
+
const last = nodes[nodes.length - 1];
|
|
12288
|
+
if (last !== void 0 && last.phase === phase) {
|
|
12289
|
+
last.steps.push(step);
|
|
12290
|
+
if (step.isError)
|
|
12291
|
+
last.failed = true;
|
|
12292
|
+
} else {
|
|
12293
|
+
const node = { phase, steps: [step], failed: step.isError };
|
|
12294
|
+
nodes.push(node);
|
|
12295
|
+
}
|
|
12296
|
+
}
|
|
12297
|
+
for (const node of nodes) {
|
|
12298
|
+
const first = node.steps[0];
|
|
12299
|
+
const lastStep = node.steps[node.steps.length - 1];
|
|
12300
|
+
if (first?.ts !== void 0)
|
|
12301
|
+
node.startTs = first.ts;
|
|
12302
|
+
const end = lastStep?.endTs ?? lastStep?.ts;
|
|
12303
|
+
if (end !== void 0)
|
|
12304
|
+
node.endTs = end;
|
|
12305
|
+
if (node.startTs !== void 0 && node.endTs !== void 0) {
|
|
12306
|
+
const start = Date.parse(node.startTs);
|
|
12307
|
+
const endMs = Date.parse(node.endTs);
|
|
12308
|
+
if (Number.isFinite(start) && Number.isFinite(endMs) && endMs >= start) {
|
|
12309
|
+
node.durationMs = endMs - start;
|
|
12310
|
+
}
|
|
12311
|
+
}
|
|
12312
|
+
}
|
|
12313
|
+
return nodes;
|
|
12314
|
+
}
|
|
12315
|
+
|
|
12316
|
+
// packages/narrative/dist/tail.js
|
|
12317
|
+
import { open, stat } from "node:fs/promises";
|
|
12318
|
+
var NEWLINE = 10;
|
|
12319
|
+
function followTranscript(path, onLine, options = {}) {
|
|
12320
|
+
const intervalMs = options.intervalMs ?? 500;
|
|
12321
|
+
let offset = 0;
|
|
12322
|
+
let remainder = Buffer.alloc(0);
|
|
12323
|
+
let closed = false;
|
|
12324
|
+
let timer;
|
|
12325
|
+
async function poll() {
|
|
12326
|
+
try {
|
|
12327
|
+
const stats = await stat(path);
|
|
12328
|
+
if (stats.size < offset) {
|
|
12329
|
+
offset = 0;
|
|
12330
|
+
remainder = Buffer.alloc(0);
|
|
12331
|
+
}
|
|
12332
|
+
if (stats.size > offset) {
|
|
12333
|
+
const handle2 = await open(path, "r");
|
|
12334
|
+
try {
|
|
12335
|
+
const length = stats.size - offset;
|
|
12336
|
+
const buffer = Buffer.alloc(length);
|
|
12337
|
+
const { bytesRead } = await handle2.read(buffer, 0, length, offset);
|
|
12338
|
+
offset += bytesRead;
|
|
12339
|
+
emitLines(buffer.subarray(0, bytesRead));
|
|
12340
|
+
} finally {
|
|
12341
|
+
await handle2.close();
|
|
12342
|
+
}
|
|
12343
|
+
}
|
|
12344
|
+
} catch {
|
|
12345
|
+
}
|
|
12346
|
+
if (!closed)
|
|
12347
|
+
timer = setTimeout(poll, intervalMs);
|
|
12348
|
+
}
|
|
12349
|
+
function emitLines(chunk) {
|
|
12350
|
+
let data = remainder.length > 0 ? Buffer.concat([remainder, chunk]) : chunk;
|
|
12351
|
+
let newlineAt = data.indexOf(NEWLINE);
|
|
12352
|
+
while (newlineAt !== -1) {
|
|
12353
|
+
const line = data.subarray(0, newlineAt).toString("utf8");
|
|
12354
|
+
data = data.subarray(newlineAt + 1);
|
|
12355
|
+
if (!closed)
|
|
12356
|
+
onLine(line);
|
|
12357
|
+
newlineAt = data.indexOf(NEWLINE);
|
|
12358
|
+
}
|
|
12359
|
+
remainder = Buffer.from(data);
|
|
12360
|
+
}
|
|
12361
|
+
void poll();
|
|
12362
|
+
return {
|
|
12363
|
+
close() {
|
|
12364
|
+
closed = true;
|
|
12365
|
+
if (timer !== void 0)
|
|
12366
|
+
clearTimeout(timer);
|
|
12367
|
+
}
|
|
12368
|
+
};
|
|
12369
|
+
}
|
|
12370
|
+
|
|
12371
|
+
// packages/narrative/dist/textlist.js
|
|
12372
|
+
var CHECKLIST_LINE = /^\s*[-*]\s+\[([ xX])\]\s+(.+?)\s*$/;
|
|
12373
|
+
function parseTextChecklist(text) {
|
|
12374
|
+
const items = [];
|
|
12375
|
+
for (const line of text.split("\n")) {
|
|
12376
|
+
const match = CHECKLIST_LINE.exec(line);
|
|
12377
|
+
if (match === null)
|
|
12378
|
+
continue;
|
|
12379
|
+
const [, mark, content] = match;
|
|
12380
|
+
if (content === void 0 || content === "")
|
|
12381
|
+
continue;
|
|
12382
|
+
items.push({ content, checked: mark !== " " });
|
|
12383
|
+
}
|
|
12384
|
+
return items;
|
|
12385
|
+
}
|
|
12386
|
+
|
|
12387
|
+
// packages/narrative/dist/todos.js
|
|
12388
|
+
var TODO_STATUSES = ["pending", "in_progress", "completed"];
|
|
12389
|
+
function isTodoStatus(value) {
|
|
12390
|
+
return typeof value === "string" && TODO_STATUSES.includes(value);
|
|
12391
|
+
}
|
|
12392
|
+
function extractTodoSnapshots(lines) {
|
|
12393
|
+
const snapshots = [];
|
|
12394
|
+
const tasks = /* @__PURE__ */ new Map();
|
|
12395
|
+
const createResultIds = buildTaskCreateResultIndex(lines);
|
|
12396
|
+
for (const line of lines) {
|
|
12397
|
+
if (line.kind !== "assistant")
|
|
12398
|
+
continue;
|
|
12399
|
+
for (const block of line.blocks) {
|
|
12400
|
+
if (block.kind !== "tool_use")
|
|
12401
|
+
continue;
|
|
12402
|
+
let changed = false;
|
|
12403
|
+
if (block.name === "TodoWrite") {
|
|
12404
|
+
changed = applyTodoWrite(block.input, tasks);
|
|
12405
|
+
} else if (block.name === "TaskCreate") {
|
|
12406
|
+
changed = applyTaskCreate(block.input, block.id, createResultIds, tasks);
|
|
12407
|
+
} else if (block.name === "TaskUpdate") {
|
|
12408
|
+
changed = applyTaskUpdate(block.input, tasks);
|
|
12409
|
+
}
|
|
12410
|
+
if (changed)
|
|
12411
|
+
snapshots.push(snapshotOf(tasks, line.timestamp));
|
|
12412
|
+
}
|
|
12413
|
+
}
|
|
12414
|
+
return snapshots;
|
|
12415
|
+
}
|
|
12416
|
+
function buildTaskCreateResultIndex(lines) {
|
|
12417
|
+
const index = /* @__PURE__ */ new Map();
|
|
12418
|
+
for (const line of lines) {
|
|
12419
|
+
if (line.kind !== "user")
|
|
12420
|
+
continue;
|
|
12421
|
+
const result = line.toolUseResult;
|
|
12422
|
+
if (!isRecord2(result) || !isRecord2(result.task) || typeof result.task.id !== "string")
|
|
12423
|
+
continue;
|
|
12424
|
+
for (const toolResult of line.toolResults)
|
|
12425
|
+
index.set(toolResult.toolUseId, result.task.id);
|
|
12426
|
+
}
|
|
12427
|
+
return index;
|
|
12428
|
+
}
|
|
12429
|
+
function applyTodoWrite(input, tasks) {
|
|
12430
|
+
const todos = input.todos;
|
|
12431
|
+
if (!Array.isArray(todos))
|
|
12432
|
+
return false;
|
|
12433
|
+
tasks.clear();
|
|
12434
|
+
for (const raw of todos) {
|
|
12435
|
+
if (!isRecord2(raw) || typeof raw.content !== "string" || !isTodoStatus(raw.status))
|
|
12436
|
+
continue;
|
|
12437
|
+
const item = { id: raw.content, content: raw.content, status: raw.status };
|
|
12438
|
+
if (typeof raw.activeForm === "string")
|
|
12439
|
+
item.activeForm = raw.activeForm;
|
|
12440
|
+
tasks.set(item.id, item);
|
|
12441
|
+
}
|
|
12442
|
+
return true;
|
|
12443
|
+
}
|
|
12444
|
+
function applyTaskCreate(input, toolUseId, createResultIds, tasks) {
|
|
12445
|
+
if (typeof input.subject !== "string")
|
|
12446
|
+
return false;
|
|
12447
|
+
const id = createResultIds.get(toolUseId) ?? toolUseId;
|
|
12448
|
+
const item = { id, content: input.subject, status: "pending" };
|
|
12449
|
+
if (typeof input.activeForm === "string")
|
|
12450
|
+
item.activeForm = input.activeForm;
|
|
12451
|
+
tasks.set(id, item);
|
|
12452
|
+
return true;
|
|
12453
|
+
}
|
|
12454
|
+
function applyTaskUpdate(input, tasks) {
|
|
12455
|
+
if (typeof input.taskId !== "string")
|
|
12456
|
+
return false;
|
|
12457
|
+
const existing = tasks.get(input.taskId);
|
|
12458
|
+
if (existing === void 0)
|
|
12459
|
+
return false;
|
|
12460
|
+
if (isTodoStatus(input.status) && input.status !== existing.status) {
|
|
12461
|
+
existing.status = input.status;
|
|
12462
|
+
return true;
|
|
12463
|
+
}
|
|
12464
|
+
return false;
|
|
12465
|
+
}
|
|
12466
|
+
function snapshotOf(tasks, ts) {
|
|
12467
|
+
const snapshot = { todos: [...tasks.values()].map((item) => ({ ...item })) };
|
|
12468
|
+
if (ts !== void 0)
|
|
12469
|
+
snapshot.ts = ts;
|
|
12470
|
+
return snapshot;
|
|
12471
|
+
}
|
|
12472
|
+
function deriveTodoTimeline(snapshots) {
|
|
12473
|
+
const entries = /* @__PURE__ */ new Map();
|
|
12474
|
+
for (const snapshot of snapshots) {
|
|
12475
|
+
const present = /* @__PURE__ */ new Set();
|
|
12476
|
+
for (const todo of snapshot.todos) {
|
|
12477
|
+
present.add(todo.id);
|
|
12478
|
+
const existing = entries.get(todo.id);
|
|
12479
|
+
if (existing === void 0) {
|
|
12480
|
+
const entry = {
|
|
12481
|
+
id: todo.id,
|
|
12482
|
+
content: todo.content,
|
|
12483
|
+
spans: [newSpan(todo.status, snapshot.ts)]
|
|
12484
|
+
};
|
|
12485
|
+
if (todo.activeForm !== void 0)
|
|
12486
|
+
entry.activeForm = todo.activeForm;
|
|
12487
|
+
entries.set(todo.id, entry);
|
|
12488
|
+
continue;
|
|
12489
|
+
}
|
|
12490
|
+
existing.content = todo.content;
|
|
12491
|
+
if (todo.activeForm !== void 0)
|
|
12492
|
+
existing.activeForm = todo.activeForm;
|
|
12493
|
+
const last = existing.spans[existing.spans.length - 1];
|
|
12494
|
+
if (last !== void 0 && last.to === void 0 && last.status !== todo.status) {
|
|
12495
|
+
if (snapshot.ts !== void 0)
|
|
12496
|
+
last.to = snapshot.ts;
|
|
12497
|
+
existing.spans.push(newSpan(todo.status, snapshot.ts));
|
|
12498
|
+
}
|
|
12499
|
+
}
|
|
12500
|
+
for (const [id, entry] of entries) {
|
|
12501
|
+
if (present.has(id))
|
|
12502
|
+
continue;
|
|
12503
|
+
const last = entry.spans[entry.spans.length - 1];
|
|
12504
|
+
if (last !== void 0 && last.to === void 0 && snapshot.ts !== void 0) {
|
|
12505
|
+
last.to = snapshot.ts;
|
|
12506
|
+
}
|
|
12507
|
+
}
|
|
12508
|
+
}
|
|
12509
|
+
return [...entries.values()];
|
|
12510
|
+
}
|
|
12511
|
+
function newSpan(status, from) {
|
|
12512
|
+
const span = { status };
|
|
12513
|
+
if (from !== void 0)
|
|
12514
|
+
span.from = from;
|
|
12515
|
+
return span;
|
|
12516
|
+
}
|
|
12517
|
+
|
|
12518
|
+
// packages/narrative/dist/usage.js
|
|
12519
|
+
function turnUsage(line) {
|
|
12520
|
+
const usage = line.usage;
|
|
12521
|
+
if (usage === void 0)
|
|
12522
|
+
return void 0;
|
|
12523
|
+
const inputTokens = finiteNumber(usage.input_tokens);
|
|
12524
|
+
const cacheCreationInputTokens = finiteNumber(usage.cache_creation_input_tokens);
|
|
12525
|
+
const cacheReadInputTokens = finiteNumber(usage.cache_read_input_tokens);
|
|
12526
|
+
const outputTokens = finiteNumber(usage.output_tokens);
|
|
12527
|
+
if (inputTokens === void 0 || cacheCreationInputTokens === void 0 || cacheReadInputTokens === void 0 || outputTokens === void 0) {
|
|
12528
|
+
return void 0;
|
|
12529
|
+
}
|
|
12530
|
+
const result = {
|
|
12531
|
+
inputTokens,
|
|
12532
|
+
cacheCreationInputTokens,
|
|
12533
|
+
cacheReadInputTokens,
|
|
12534
|
+
outputTokens,
|
|
12535
|
+
contextTotal: {
|
|
12536
|
+
kind: "exact",
|
|
12537
|
+
value: inputTokens + cacheCreationInputTokens + cacheReadInputTokens
|
|
12538
|
+
}
|
|
12539
|
+
};
|
|
12540
|
+
const breakdown = usage.cache_creation;
|
|
12541
|
+
if (isRecord2(breakdown)) {
|
|
12542
|
+
const fiveMinute = finiteNumber(breakdown.ephemeral_5m_input_tokens);
|
|
12543
|
+
const oneHour = finiteNumber(breakdown.ephemeral_1h_input_tokens);
|
|
12544
|
+
if (fiveMinute !== void 0)
|
|
12545
|
+
result.cacheCreation5mTokens = fiveMinute;
|
|
12546
|
+
if (oneHour !== void 0)
|
|
12547
|
+
result.cacheCreation1hTokens = oneHour;
|
|
12548
|
+
}
|
|
12549
|
+
return result;
|
|
12550
|
+
}
|
|
12551
|
+
function finiteNumber(value) {
|
|
12552
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
12553
|
+
}
|
|
12554
|
+
var MODEL_PRICES = {
|
|
12555
|
+
"claude-fable-5": { inputPerMTok: 10, outputPerMTok: 50 },
|
|
12556
|
+
"claude-mythos-5": { inputPerMTok: 10, outputPerMTok: 50 },
|
|
12557
|
+
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
12558
|
+
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
12559
|
+
"claude-opus-4-7": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
12560
|
+
"claude-opus-4-6": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
12561
|
+
"claude-sonnet-5": { inputPerMTok: 3, outputPerMTok: 15 },
|
|
12562
|
+
"claude-sonnet-4-6": { inputPerMTok: 3, outputPerMTok: 15 },
|
|
12563
|
+
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 }
|
|
12564
|
+
};
|
|
12565
|
+
var CACHE_READ_MULTIPLIER = 0.1;
|
|
12566
|
+
var CACHE_WRITE_5M_MULTIPLIER = 1.25;
|
|
12567
|
+
var CACHE_WRITE_1H_MULTIPLIER = 2;
|
|
12568
|
+
function costUsd(usage, model) {
|
|
12569
|
+
const price = MODEL_PRICES[model];
|
|
12570
|
+
if (price === void 0)
|
|
12571
|
+
return void 0;
|
|
12572
|
+
const fiveMinute = usage.cacheCreation5mTokens;
|
|
12573
|
+
const oneHour = usage.cacheCreation1hTokens;
|
|
12574
|
+
const cacheWriteTokensPriced = fiveMinute !== void 0 || oneHour !== void 0 ? (fiveMinute ?? 0) * CACHE_WRITE_5M_MULTIPLIER + (oneHour ?? 0) * CACHE_WRITE_1H_MULTIPLIER : usage.cacheCreationInputTokens * CACHE_WRITE_5M_MULTIPLIER;
|
|
12575
|
+
return (usage.inputTokens * price.inputPerMTok + usage.cacheReadInputTokens * price.inputPerMTok * CACHE_READ_MULTIPLIER + cacheWriteTokensPriced * price.inputPerMTok + usage.outputTokens * price.outputPerMTok) / 1e6;
|
|
12576
|
+
}
|
|
12577
|
+
|
|
12578
|
+
// apps/cli/dist/narrative-service.js
|
|
12579
|
+
var MAX_SESSIONS = 8;
|
|
12580
|
+
var ENDED_AFTER_STOP_MS = 10 * 6e4;
|
|
12581
|
+
var TODO_TOOLS = /* @__PURE__ */ new Set(["TaskCreate", "TaskUpdate", "TodoWrite"]);
|
|
12582
|
+
var DETAIL_LIMIT = 80;
|
|
12583
|
+
var QUOTE_LIMIT = 160;
|
|
12584
|
+
var ERROR_BRIEF_LIMIT = 60;
|
|
12585
|
+
function createNarrativeService(options = {}) {
|
|
12586
|
+
const follow = options.followTranscripts ?? true;
|
|
12587
|
+
const intervalMs = options.intervalMs ?? 500;
|
|
12588
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
12589
|
+
const harnesses = /* @__PURE__ */ new Map();
|
|
12590
|
+
let closed = false;
|
|
12591
|
+
function sessionsOf(harness) {
|
|
12592
|
+
let sessions = harnesses.get(harness);
|
|
12593
|
+
if (sessions === void 0) {
|
|
12594
|
+
sessions = /* @__PURE__ */ new Map();
|
|
12595
|
+
harnesses.set(harness, sessions);
|
|
12596
|
+
}
|
|
12597
|
+
return sessions;
|
|
12598
|
+
}
|
|
12599
|
+
function newSession(id) {
|
|
12600
|
+
return {
|
|
12601
|
+
id,
|
|
12602
|
+
lines: [],
|
|
12603
|
+
follows: /* @__PURE__ */ new Map(),
|
|
12604
|
+
hookIntervals: [],
|
|
12605
|
+
waiting: null,
|
|
12606
|
+
waitEpisodes: [],
|
|
12607
|
+
dirty: true
|
|
12608
|
+
};
|
|
12609
|
+
}
|
|
12610
|
+
function closeSession(session) {
|
|
12611
|
+
for (const handle2 of session.follows.values())
|
|
12612
|
+
handle2.close();
|
|
12613
|
+
session.follows.clear();
|
|
12614
|
+
if (session.subagentTimer !== void 0)
|
|
12615
|
+
clearInterval(session.subagentTimer);
|
|
12616
|
+
session.subagentTimer = void 0;
|
|
12617
|
+
}
|
|
12618
|
+
function evictOne(sessions, nowIso) {
|
|
12619
|
+
let victimId;
|
|
12620
|
+
let victimAt = "\uFFFF";
|
|
12621
|
+
let victimEnded = false;
|
|
12622
|
+
for (const [id, session] of sessions) {
|
|
12623
|
+
const ended = isEnded(session, nowIso);
|
|
12624
|
+
const at = lastActivityOf(session);
|
|
12625
|
+
const better = victimEnded === ended ? at < victimAt : ended;
|
|
12626
|
+
if (victimId === void 0 || better) {
|
|
12627
|
+
victimId = id;
|
|
12628
|
+
victimAt = at;
|
|
12629
|
+
victimEnded = ended;
|
|
12630
|
+
}
|
|
12631
|
+
}
|
|
12632
|
+
if (victimId !== void 0) {
|
|
12633
|
+
const victim = sessions.get(victimId);
|
|
12634
|
+
if (victim)
|
|
12635
|
+
closeSession(victim);
|
|
12636
|
+
sessions.delete(victimId);
|
|
12637
|
+
}
|
|
12638
|
+
}
|
|
12639
|
+
function sessionOf(harness, id) {
|
|
12640
|
+
const sessions = sessionsOf(harness);
|
|
12641
|
+
let session = sessions.get(id);
|
|
12642
|
+
if (session === void 0) {
|
|
12643
|
+
if (sessions.size >= MAX_SESSIONS)
|
|
12644
|
+
evictOne(sessions, now().toISOString());
|
|
12645
|
+
session = newSession(id);
|
|
12646
|
+
sessions.set(id, session);
|
|
12647
|
+
}
|
|
12648
|
+
return session;
|
|
12649
|
+
}
|
|
12650
|
+
function resetTranscript(session, path) {
|
|
12651
|
+
closeSession(session);
|
|
12652
|
+
session.lines = [];
|
|
12653
|
+
session.hookIntervals = [];
|
|
12654
|
+
session.mockNote = void 0;
|
|
12655
|
+
session.transcriptPath = path;
|
|
12656
|
+
session.dirty = true;
|
|
12657
|
+
}
|
|
12658
|
+
function startFollowing(session, path) {
|
|
12659
|
+
if (closed || session.follows.has(path))
|
|
12660
|
+
return;
|
|
12661
|
+
session.follows.set(path, followTranscript(path, (line) => {
|
|
12662
|
+
session.lines.push(parseTranscriptLine(line));
|
|
12663
|
+
session.dirty = true;
|
|
12664
|
+
}, { intervalMs }));
|
|
12665
|
+
}
|
|
12666
|
+
function startSubagentDiscovery(session, transcriptPath) {
|
|
12667
|
+
const sessionDir = transcriptPath.replace(/\.jsonl$/, "");
|
|
12668
|
+
if (sessionDir === transcriptPath)
|
|
12669
|
+
return;
|
|
12670
|
+
const subagentsDir = join4(sessionDir, "subagents");
|
|
12671
|
+
const poll = async () => {
|
|
12672
|
+
try {
|
|
12673
|
+
const entries = await readdir(subagentsDir);
|
|
12674
|
+
for (const entry of entries) {
|
|
12675
|
+
if (/^agent-.*\.jsonl$/.test(entry))
|
|
12676
|
+
startFollowing(session, join4(subagentsDir, entry));
|
|
12677
|
+
}
|
|
12678
|
+
} catch {
|
|
12679
|
+
}
|
|
12680
|
+
};
|
|
12681
|
+
session.subagentTimer = setInterval(() => void poll(), intervalMs);
|
|
12682
|
+
void poll();
|
|
12683
|
+
}
|
|
12684
|
+
function handleEvent(event) {
|
|
12685
|
+
if (closed || !HARNESS_IDS.includes(event.harness))
|
|
12686
|
+
return;
|
|
12687
|
+
const payload = event.payload;
|
|
12688
|
+
const name = payload.hook_event_name;
|
|
12689
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
|
|
12690
|
+
const sessionId = typeof payload.session_id === "string" ? payload.session_id : void 0;
|
|
12691
|
+
const key = sessionId ?? transcriptPath ?? "__nosession__";
|
|
12692
|
+
const session = sessionOf(event.harness, key);
|
|
12693
|
+
session.lastActivity = event.received_at;
|
|
12694
|
+
const cwd = typeof payload.cwd === "string" ? payload.cwd : void 0;
|
|
12695
|
+
if (cwd !== void 0) {
|
|
12696
|
+
const base = pathBasename(cwd);
|
|
12697
|
+
if (base !== "")
|
|
12698
|
+
session.label = base;
|
|
12699
|
+
}
|
|
12700
|
+
if (follow && transcriptPath !== void 0) {
|
|
12701
|
+
if (session.transcriptPath !== transcriptPath) {
|
|
12702
|
+
resetTranscript(session, transcriptPath);
|
|
12703
|
+
startFollowing(session, transcriptPath);
|
|
12704
|
+
startSubagentDiscovery(session, transcriptPath);
|
|
12705
|
+
}
|
|
12706
|
+
}
|
|
12707
|
+
if (name === "SubagentStart") {
|
|
12708
|
+
const agentId = agentIdOf(payload);
|
|
12709
|
+
if (agentId !== void 0) {
|
|
12710
|
+
session.hookIntervals.push({ agentId, startTs: event.received_at });
|
|
12711
|
+
}
|
|
12712
|
+
} else if (name === "SubagentStop") {
|
|
12713
|
+
const agentId = agentIdOf(payload);
|
|
12714
|
+
if (agentId !== void 0) {
|
|
12715
|
+
const open2 = [...session.hookIntervals].reverse().find((i) => i.agentId === agentId && i.endTs === void 0);
|
|
12716
|
+
if (open2)
|
|
12717
|
+
open2.endTs = event.received_at;
|
|
12718
|
+
}
|
|
12719
|
+
}
|
|
12720
|
+
if (name === "PreToolUse" && typeof payload.tool_name === "string") {
|
|
12721
|
+
session.pendingHookTool = {
|
|
12722
|
+
toolName: payload.tool_name,
|
|
12723
|
+
detail: hookToolDetail(payload.tool_input),
|
|
12724
|
+
since: event.received_at
|
|
12725
|
+
};
|
|
12726
|
+
} else if (name === "PostToolUse" && typeof payload.tool_name === "string" || name === "Stop" || name === "SessionEnd") {
|
|
12727
|
+
session.pendingHookTool = void 0;
|
|
12728
|
+
}
|
|
12729
|
+
if (name === "PermissionRequest" || name === "Notification") {
|
|
12730
|
+
const kind = name === "PermissionRequest" ? "permission" : "notification";
|
|
12731
|
+
const message = waitingMessage(payload, kind === "permission" ? "\u7B49\u5F85\u6388\u6743" : "\u7B49\u5F85\u56DE\u5E94");
|
|
12732
|
+
if (session.waiting === null) {
|
|
12733
|
+
session.waiting = { kind, message, since: event.received_at };
|
|
12734
|
+
} else if (kind === "permission" || session.waiting.kind === kind) {
|
|
12735
|
+
session.waiting = { kind, message, since: session.waiting.since };
|
|
12736
|
+
}
|
|
12737
|
+
} else {
|
|
12738
|
+
if (session.waiting !== null) {
|
|
12739
|
+
session.waitEpisodes.push({
|
|
12740
|
+
...session.waiting,
|
|
12741
|
+
until: event.received_at,
|
|
12742
|
+
resolvedBy: name
|
|
12743
|
+
});
|
|
12744
|
+
session.waiting = null;
|
|
12745
|
+
}
|
|
12746
|
+
}
|
|
12747
|
+
if (name === "SessionEnd") {
|
|
12748
|
+
session.endedAt = event.received_at;
|
|
12749
|
+
} else if (name === "Stop") {
|
|
12750
|
+
session.stopAt = event.received_at;
|
|
12751
|
+
} else {
|
|
12752
|
+
session.stopAt = void 0;
|
|
12753
|
+
}
|
|
12754
|
+
session.lastEvent = { name, at: event.received_at };
|
|
12755
|
+
session.dirty = true;
|
|
12756
|
+
}
|
|
12757
|
+
function seedFixture(harness, rawLines, mockNote) {
|
|
12758
|
+
const lines = rawLines.filter((l) => l.trim() !== "").map((l) => parseTranscriptLine(l));
|
|
12759
|
+
const sessionLine = lines.find((l) => l.kind !== "unknown" && typeof l.sessionId === "string");
|
|
12760
|
+
const id = sessionLine?.sessionId ?? "__fixture__";
|
|
12761
|
+
const session = sessionOf(harness, id);
|
|
12762
|
+
session.lines = lines;
|
|
12763
|
+
session.mockNote = mockNote;
|
|
12764
|
+
for (const line of lines) {
|
|
12765
|
+
if (line.kind === "unknown")
|
|
12766
|
+
continue;
|
|
12767
|
+
const cwd = line.raw.cwd;
|
|
12768
|
+
if (typeof cwd === "string") {
|
|
12769
|
+
const base = pathBasename(cwd);
|
|
12770
|
+
if (base !== "")
|
|
12771
|
+
session.label = base;
|
|
12772
|
+
break;
|
|
12773
|
+
}
|
|
12774
|
+
}
|
|
12775
|
+
session.dirty = true;
|
|
12776
|
+
}
|
|
12777
|
+
function deriveCached(harness, session) {
|
|
12778
|
+
if (!session.dirty && session.cached !== void 0)
|
|
12779
|
+
return session.cached;
|
|
12780
|
+
session.cached = derive(harness, session, now().toISOString());
|
|
12781
|
+
session.dirty = false;
|
|
12782
|
+
return session.cached;
|
|
12783
|
+
}
|
|
12784
|
+
function getOverview(harness) {
|
|
12785
|
+
const nowIso = now().toISOString();
|
|
12786
|
+
const sessions = sessionsOf(harness);
|
|
12787
|
+
const infos = [...sessions.values()].map((session) => ({
|
|
12788
|
+
id: session.id,
|
|
12789
|
+
label: session.label ?? session.id.slice(0, 8),
|
|
12790
|
+
lastActivity: lastActivityOf(session),
|
|
12791
|
+
waiting: session.waiting !== null,
|
|
12792
|
+
ended: isEnded(session, nowIso)
|
|
12793
|
+
})).sort((a, b) => a.lastActivity < b.lastActivity ? 1 : a.lastActivity > b.lastActivity ? -1 : 0);
|
|
12794
|
+
const states = {};
|
|
12795
|
+
for (const [id, session] of sessions)
|
|
12796
|
+
states[id] = deriveCached(harness, session);
|
|
12797
|
+
return { harness, sessions: infos, states };
|
|
12798
|
+
}
|
|
12799
|
+
function getState(harness) {
|
|
12800
|
+
const overview = getOverview(harness);
|
|
12801
|
+
const first = overview.sessions[0];
|
|
12802
|
+
if (first !== void 0) {
|
|
12803
|
+
const state2 = overview.states[first.id];
|
|
12804
|
+
if (state2 !== void 0)
|
|
12805
|
+
return state2;
|
|
12806
|
+
}
|
|
12807
|
+
return emptyState(harness, now().toISOString());
|
|
12808
|
+
}
|
|
12809
|
+
function close() {
|
|
12810
|
+
closed = true;
|
|
12811
|
+
for (const sessions of harnesses.values()) {
|
|
12812
|
+
for (const session of sessions.values())
|
|
12813
|
+
closeSession(session);
|
|
12814
|
+
}
|
|
12815
|
+
}
|
|
12816
|
+
return { handleEvent, getState, getOverview, seedFixture, close };
|
|
12817
|
+
}
|
|
12818
|
+
function lastActivityOf(session) {
|
|
12819
|
+
if (session.lastActivity !== void 0)
|
|
12820
|
+
return session.lastActivity;
|
|
12821
|
+
let latest = "";
|
|
12822
|
+
for (const line of session.lines) {
|
|
12823
|
+
if (line.kind === "unknown")
|
|
12824
|
+
continue;
|
|
12825
|
+
const ts = line.timestamp;
|
|
12826
|
+
if (ts !== void 0 && ts > latest)
|
|
12827
|
+
latest = ts;
|
|
12828
|
+
}
|
|
12829
|
+
return latest;
|
|
12830
|
+
}
|
|
12831
|
+
function isEnded(session, nowIso) {
|
|
12832
|
+
if (session.endedAt !== void 0)
|
|
12833
|
+
return true;
|
|
12834
|
+
if (session.stopAt === void 0)
|
|
12835
|
+
return false;
|
|
12836
|
+
const last = Date.parse(lastActivityOf(session));
|
|
12837
|
+
const nowMs = Date.parse(nowIso);
|
|
12838
|
+
if (!Number.isFinite(last) || !Number.isFinite(nowMs))
|
|
12839
|
+
return false;
|
|
12840
|
+
return nowMs - last >= ENDED_AFTER_STOP_MS;
|
|
12841
|
+
}
|
|
12842
|
+
function emptyState(harness, nowIso) {
|
|
12843
|
+
return {
|
|
12844
|
+
harness,
|
|
12845
|
+
sessionId: null,
|
|
12846
|
+
transcriptPath: null,
|
|
12847
|
+
todos: [],
|
|
12848
|
+
traces: [],
|
|
12849
|
+
turnUsages: [],
|
|
12850
|
+
currentAction: null,
|
|
12851
|
+
waiting: null,
|
|
12852
|
+
agentQuote: null,
|
|
12853
|
+
lastPrompt: null,
|
|
12854
|
+
updatedAt: nowIso
|
|
12855
|
+
};
|
|
12856
|
+
}
|
|
12857
|
+
function pathBasename(path) {
|
|
12858
|
+
const parts = path.split(/[\\/]/).filter((p) => p !== "");
|
|
12859
|
+
return parts[parts.length - 1] ?? "";
|
|
12860
|
+
}
|
|
12861
|
+
function agentIdOf(payload) {
|
|
12862
|
+
const id = payload.agent_id ?? payload.agentId;
|
|
12863
|
+
return typeof id === "string" && id !== "" ? id : void 0;
|
|
12864
|
+
}
|
|
12865
|
+
function waitingMessage(payload, fallback) {
|
|
12866
|
+
if (typeof payload.message === "string" && payload.message !== "")
|
|
12867
|
+
return payload.message;
|
|
12868
|
+
if (typeof payload.tool_name === "string" && payload.tool_name !== "") {
|
|
12869
|
+
return `${fallback}:${payload.tool_name}`;
|
|
12870
|
+
}
|
|
12871
|
+
return fallback;
|
|
12872
|
+
}
|
|
12873
|
+
function derive(harness, session, nowIso) {
|
|
12874
|
+
const lines = sortedByTimestamp(session.lines);
|
|
12875
|
+
const timeline = deriveTodoTimeline(extractTodoSnapshots(lines));
|
|
12876
|
+
const allSteps = extractSteps(lines);
|
|
12877
|
+
const steps = allSteps.filter((s) => !TODO_TOOLS.has(s.toolName));
|
|
12878
|
+
const intervals = [...deriveSubagentIntervals(lines), ...session.hookIntervals];
|
|
12879
|
+
const attributed = attributeSteps(steps, timeline, intervals);
|
|
12880
|
+
const results = collectResults(lines);
|
|
12881
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
12882
|
+
for (const { step, attribution } of attributed) {
|
|
12883
|
+
const key = attribution.kind === "todo" ? attribution.todoId : null;
|
|
12884
|
+
const bucket = buckets.get(key);
|
|
12885
|
+
if (bucket)
|
|
12886
|
+
bucket.push(step);
|
|
12887
|
+
else
|
|
12888
|
+
buckets.set(key, [step]);
|
|
12889
|
+
}
|
|
12890
|
+
const waitNodes = /* @__PURE__ */ new Map();
|
|
12891
|
+
const pushWait = (episode, open2) => {
|
|
12892
|
+
const key = todoInProgressAt(timeline, episode.since);
|
|
12893
|
+
const node = {
|
|
12894
|
+
phase: "wait",
|
|
12895
|
+
label: episode.kind === "permission" ? "\u7B49\u5F85\u6388\u6743" : "\u7B49\u5F85\u56DE\u5E94",
|
|
12896
|
+
steps: [],
|
|
12897
|
+
startTs: episode.since,
|
|
12898
|
+
failed: false,
|
|
12899
|
+
wait: { kind: episode.kind, message: episode.message }
|
|
12900
|
+
};
|
|
12901
|
+
if (!open2) {
|
|
12902
|
+
const closed = episode;
|
|
12903
|
+
node.endTs = closed.until;
|
|
12904
|
+
const ms = Date.parse(closed.until) - Date.parse(closed.since);
|
|
12905
|
+
if (Number.isFinite(ms) && ms >= 0)
|
|
12906
|
+
node.durationMs = ms;
|
|
12907
|
+
if (node.wait)
|
|
12908
|
+
node.wait.resolvedBy = closed.resolvedBy;
|
|
12909
|
+
}
|
|
12910
|
+
const list = waitNodes.get(key);
|
|
12911
|
+
if (list)
|
|
12912
|
+
list.push(node);
|
|
12913
|
+
else
|
|
12914
|
+
waitNodes.set(key, [node]);
|
|
12915
|
+
};
|
|
12916
|
+
for (const episode of session.waitEpisodes)
|
|
12917
|
+
pushWait(episode, false);
|
|
12918
|
+
if (session.waiting !== null)
|
|
12919
|
+
pushWait(session.waiting, true);
|
|
12920
|
+
const traces = [];
|
|
12921
|
+
const agentsByTodo = /* @__PURE__ */ new Map();
|
|
12922
|
+
const orderedKeys = timeline.map((entry) => entry.id).filter((id) => buckets.has(id) || waitNodes.has(id));
|
|
12923
|
+
if (buckets.has(null) || waitNodes.has(null))
|
|
12924
|
+
orderedKeys.push(null);
|
|
12925
|
+
for (const key of orderedKeys) {
|
|
12926
|
+
const bucketSteps = buckets.get(key) ?? [];
|
|
12927
|
+
const nodes = consolidatePhases(bucketSteps).map((node) => ({
|
|
12928
|
+
phase: node.phase,
|
|
12929
|
+
label: PHASE_LABELS[node.phase],
|
|
12930
|
+
steps: node.steps.map((step) => stepView(step, results)),
|
|
12931
|
+
...node.startTs !== void 0 ? { startTs: node.startTs } : {},
|
|
12932
|
+
...node.endTs !== void 0 ? { endTs: node.endTs } : {},
|
|
12933
|
+
...node.durationMs !== void 0 ? { durationMs: node.durationMs } : {},
|
|
12934
|
+
failed: node.failed,
|
|
12935
|
+
...nodeEstTokens(node.steps, results)
|
|
12936
|
+
}));
|
|
12937
|
+
for (const waitNode of waitNodes.get(key) ?? [])
|
|
12938
|
+
insertByStartTs(nodes, waitNode);
|
|
12939
|
+
traces.push({ todoId: key, nodes });
|
|
12940
|
+
if (key !== null) {
|
|
12941
|
+
const agents = /* @__PURE__ */ new Set();
|
|
12942
|
+
for (const step of bucketSteps)
|
|
12943
|
+
if (step.agentId !== void 0)
|
|
12944
|
+
agents.add(step.agentId);
|
|
12945
|
+
agentsByTodo.set(key, agents);
|
|
12946
|
+
}
|
|
12947
|
+
}
|
|
12948
|
+
const todos = timeline.map((entry) => {
|
|
12949
|
+
const last = entry.spans[entry.spans.length - 1];
|
|
12950
|
+
return {
|
|
12951
|
+
id: entry.id,
|
|
12952
|
+
content: entry.content,
|
|
12953
|
+
...entry.activeForm !== void 0 ? { activeForm: entry.activeForm } : {},
|
|
12954
|
+
status: last?.status ?? "pending",
|
|
12955
|
+
agents: [...agentsByTodo.get(entry.id) ?? []]
|
|
12956
|
+
};
|
|
12957
|
+
});
|
|
12958
|
+
const textTodos = todos.length === 0 ? deriveTextTodos(lines) : void 0;
|
|
12959
|
+
return {
|
|
12960
|
+
harness,
|
|
12961
|
+
sessionId: session.id,
|
|
12962
|
+
transcriptPath: session.transcriptPath ?? null,
|
|
12963
|
+
...session.mockNote !== void 0 ? { __mock_note: session.mockNote } : {},
|
|
12964
|
+
todos,
|
|
12965
|
+
...textTodos !== void 0 && textTodos.length > 0 ? { textTodos } : {},
|
|
12966
|
+
traces,
|
|
12967
|
+
turnUsages: deriveTurnUsages(lines),
|
|
12968
|
+
currentAction: deriveCurrentAction(steps, session.lastEvent, session.pendingHookTool),
|
|
12969
|
+
waiting: session.waiting,
|
|
12970
|
+
agentQuote: deriveAgentQuote(lines),
|
|
12971
|
+
lastPrompt: deriveLastPrompt(lines),
|
|
12972
|
+
updatedAt: nowIso
|
|
12973
|
+
};
|
|
12974
|
+
}
|
|
12975
|
+
function todoInProgressAt(timeline, ts) {
|
|
12976
|
+
const active = timeline.filter((entry) => entry.spans.some((span) => span.status === "in_progress" && span.from !== void 0 && span.from <= ts && (span.to === void 0 || ts < span.to)));
|
|
12977
|
+
const only = active[0];
|
|
12978
|
+
return active.length === 1 && only !== void 0 ? only.id : null;
|
|
12979
|
+
}
|
|
12980
|
+
function insertByStartTs(nodes, node) {
|
|
12981
|
+
const ts = node.startTs;
|
|
12982
|
+
if (ts !== void 0) {
|
|
12983
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
12984
|
+
const at = nodes[i]?.startTs;
|
|
12985
|
+
if (at !== void 0 && at > ts) {
|
|
12986
|
+
nodes.splice(i, 0, node);
|
|
12987
|
+
return;
|
|
12988
|
+
}
|
|
12989
|
+
}
|
|
12990
|
+
}
|
|
12991
|
+
nodes.push(node);
|
|
12992
|
+
}
|
|
12993
|
+
function deriveTextTodos(lines) {
|
|
12994
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12995
|
+
const line = lines[i];
|
|
12996
|
+
if (line === void 0 || line.kind !== "assistant" || line.agentId !== void 0)
|
|
12997
|
+
continue;
|
|
12998
|
+
for (let j = line.blocks.length - 1; j >= 0; j--) {
|
|
12999
|
+
const block = line.blocks[j];
|
|
13000
|
+
if (block === void 0 || block.kind !== "text")
|
|
13001
|
+
continue;
|
|
13002
|
+
const items = parseTextChecklist(block.text);
|
|
13003
|
+
if (items.length > 0)
|
|
13004
|
+
return items;
|
|
13005
|
+
}
|
|
13006
|
+
}
|
|
13007
|
+
return void 0;
|
|
13008
|
+
}
|
|
13009
|
+
function sortedByTimestamp(lines) {
|
|
13010
|
+
return [...lines].sort((a, b) => {
|
|
13011
|
+
const ta = a.kind === "unknown" ? void 0 : a.timestamp;
|
|
13012
|
+
const tb = b.kind === "unknown" ? void 0 : b.timestamp;
|
|
13013
|
+
if (ta === void 0 || tb === void 0)
|
|
13014
|
+
return 0;
|
|
13015
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
13016
|
+
});
|
|
13017
|
+
}
|
|
13018
|
+
function collectResults(lines) {
|
|
13019
|
+
const results = /* @__PURE__ */ new Map();
|
|
13020
|
+
for (const line of lines) {
|
|
13021
|
+
if (line.kind !== "user")
|
|
13022
|
+
continue;
|
|
13023
|
+
for (const result of line.toolResults) {
|
|
13024
|
+
if (result.content === void 0)
|
|
13025
|
+
continue;
|
|
13026
|
+
const info = {};
|
|
13027
|
+
try {
|
|
13028
|
+
info.bytes = Buffer.byteLength(JSON.stringify(result.content), "utf8");
|
|
13029
|
+
} catch {
|
|
13030
|
+
}
|
|
13031
|
+
const brief = errorBrief(result.content);
|
|
13032
|
+
if (brief !== void 0)
|
|
13033
|
+
info.brief = brief;
|
|
13034
|
+
results.set(result.toolUseId, info);
|
|
13035
|
+
}
|
|
13036
|
+
}
|
|
13037
|
+
return results;
|
|
13038
|
+
}
|
|
13039
|
+
function stepView(step, results) {
|
|
13040
|
+
const view = {
|
|
13041
|
+
toolName: step.toolName,
|
|
13042
|
+
detail: stepDetail(step),
|
|
13043
|
+
isError: step.isError
|
|
13044
|
+
};
|
|
13045
|
+
if (step.ts !== void 0)
|
|
13046
|
+
view.ts = step.ts;
|
|
13047
|
+
if (step.endTs !== void 0)
|
|
13048
|
+
view.endTs = step.endTs;
|
|
13049
|
+
if (step.agentId !== void 0)
|
|
13050
|
+
view.agentId = step.agentId;
|
|
13051
|
+
if (step.isError) {
|
|
13052
|
+
const brief = results.get(step.toolUseId)?.brief;
|
|
13053
|
+
if (brief !== void 0)
|
|
13054
|
+
view.errorBrief = brief;
|
|
13055
|
+
}
|
|
13056
|
+
const est = stepEstTokens(step, results);
|
|
13057
|
+
if (est !== void 0)
|
|
13058
|
+
view.estTokens = est;
|
|
13059
|
+
return view;
|
|
13060
|
+
}
|
|
13061
|
+
function stepEstTokens(step, results) {
|
|
13062
|
+
let bytes = 0;
|
|
13063
|
+
try {
|
|
13064
|
+
bytes += Buffer.byteLength(JSON.stringify(step.toolInput), "utf8");
|
|
13065
|
+
} catch {
|
|
13066
|
+
}
|
|
13067
|
+
bytes += results.get(step.toolUseId)?.bytes ?? 0;
|
|
13068
|
+
if (bytes === 0)
|
|
13069
|
+
return void 0;
|
|
13070
|
+
return estimateTokens(bytes).value;
|
|
13071
|
+
}
|
|
13072
|
+
function nodeEstTokens(steps, results) {
|
|
13073
|
+
let sum = 0;
|
|
13074
|
+
let counted = false;
|
|
13075
|
+
for (const step of steps) {
|
|
13076
|
+
const est = stepEstTokens(step, results);
|
|
13077
|
+
if (est !== void 0) {
|
|
13078
|
+
sum += est;
|
|
13079
|
+
counted = true;
|
|
13080
|
+
}
|
|
13081
|
+
}
|
|
13082
|
+
return counted ? { estTokens: sum } : {};
|
|
13083
|
+
}
|
|
13084
|
+
var DETAIL_KEYS = ["command", "file_path", "description", "subject", "pattern", "prompt"];
|
|
13085
|
+
function inputDetail(input) {
|
|
13086
|
+
for (const key of DETAIL_KEYS) {
|
|
13087
|
+
const value = input[key];
|
|
13088
|
+
if (typeof value === "string" && value !== "")
|
|
13089
|
+
return truncate(value, DETAIL_LIMIT);
|
|
13090
|
+
}
|
|
13091
|
+
return "";
|
|
13092
|
+
}
|
|
13093
|
+
function stepDetail(step) {
|
|
13094
|
+
return inputDetail(step.toolInput);
|
|
13095
|
+
}
|
|
13096
|
+
function hookToolDetail(input) {
|
|
13097
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
13098
|
+
return "";
|
|
13099
|
+
return inputDetail(input);
|
|
13100
|
+
}
|
|
13101
|
+
function deriveTurnUsages(lines) {
|
|
13102
|
+
const byMessage = /* @__PURE__ */ new Map();
|
|
13103
|
+
let anonymous = 0;
|
|
13104
|
+
for (const line of lines) {
|
|
13105
|
+
if (line.kind !== "assistant")
|
|
13106
|
+
continue;
|
|
13107
|
+
const usage = turnUsage(line);
|
|
13108
|
+
if (usage === void 0)
|
|
13109
|
+
continue;
|
|
13110
|
+
const key = line.messageId ?? `__anon_${anonymous++}`;
|
|
13111
|
+
const view = {
|
|
13112
|
+
...line.messageId !== void 0 ? { messageId: line.messageId } : {},
|
|
13113
|
+
...line.model !== void 0 ? { model: line.model } : {},
|
|
13114
|
+
inputTokens: usage.inputTokens,
|
|
13115
|
+
cacheCreationInputTokens: usage.cacheCreationInputTokens,
|
|
13116
|
+
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
13117
|
+
outputTokens: usage.outputTokens,
|
|
13118
|
+
contextTotal: usage.contextTotal,
|
|
13119
|
+
...line.timestamp !== void 0 ? { ts: line.timestamp } : {}
|
|
13120
|
+
};
|
|
13121
|
+
if (line.model !== void 0) {
|
|
13122
|
+
const cost = costUsd(usage, line.model);
|
|
13123
|
+
if (cost !== void 0)
|
|
13124
|
+
view.costUsd = cost;
|
|
13125
|
+
}
|
|
13126
|
+
const existing = byMessage.get(key);
|
|
13127
|
+
if (existing?.ts !== void 0)
|
|
13128
|
+
view.ts = existing.ts;
|
|
13129
|
+
byMessage.set(key, view);
|
|
13130
|
+
}
|
|
13131
|
+
return [...byMessage.values()];
|
|
13132
|
+
}
|
|
13133
|
+
function deriveCurrentAction(steps, lastEvent, pendingHookTool) {
|
|
13134
|
+
const open2 = [...steps].reverse().find((s) => s.endTs === void 0);
|
|
13135
|
+
if (open2) {
|
|
13136
|
+
const action = { kind: "tool", toolName: open2.toolName, detail: stepDetail(open2) };
|
|
13137
|
+
if (open2.ts !== void 0)
|
|
13138
|
+
action.since = open2.ts;
|
|
13139
|
+
return action;
|
|
13140
|
+
}
|
|
13141
|
+
if (pendingHookTool) {
|
|
13142
|
+
return {
|
|
13143
|
+
kind: "tool",
|
|
13144
|
+
toolName: pendingHookTool.toolName,
|
|
13145
|
+
detail: pendingHookTool.detail,
|
|
13146
|
+
since: pendingHookTool.since
|
|
13147
|
+
};
|
|
13148
|
+
}
|
|
13149
|
+
if (lastEvent)
|
|
13150
|
+
return { kind: "event", name: lastEvent.name, at: lastEvent.at };
|
|
13151
|
+
return null;
|
|
13152
|
+
}
|
|
13153
|
+
function deriveAgentQuote(lines) {
|
|
13154
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
13155
|
+
const line = lines[i];
|
|
13156
|
+
if (line === void 0 || line.kind !== "assistant" || line.agentId !== void 0)
|
|
13157
|
+
continue;
|
|
13158
|
+
for (let j = line.blocks.length - 1; j >= 0; j--) {
|
|
13159
|
+
const block = line.blocks[j];
|
|
13160
|
+
if (block !== void 0 && block.kind === "text" && block.text.trim() !== "") {
|
|
13161
|
+
return truncate(block.text.trim(), QUOTE_LIMIT);
|
|
13162
|
+
}
|
|
13163
|
+
}
|
|
13164
|
+
}
|
|
13165
|
+
return null;
|
|
13166
|
+
}
|
|
13167
|
+
function deriveLastPrompt(lines) {
|
|
13168
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
13169
|
+
const line = lines[i];
|
|
13170
|
+
if (line === void 0 || line.kind !== "user" || line.agentId !== void 0)
|
|
13171
|
+
continue;
|
|
13172
|
+
if (line.toolResults.length > 0)
|
|
13173
|
+
continue;
|
|
13174
|
+
if (line.text === void 0 || line.text.trim() === "")
|
|
13175
|
+
continue;
|
|
13176
|
+
return truncate(line.text.trim(), DETAIL_LIMIT);
|
|
13177
|
+
}
|
|
13178
|
+
return null;
|
|
13179
|
+
}
|
|
13180
|
+
function truncate(text, limit) {
|
|
13181
|
+
return text.length <= limit ? text : `${text.slice(0, limit - 1)}\u2026`;
|
|
13182
|
+
}
|
|
13183
|
+
function errorBrief(content) {
|
|
13184
|
+
if (typeof content === "string") {
|
|
13185
|
+
const first = content.split("\n", 1)[0]?.trim();
|
|
13186
|
+
return first ? truncate(first, ERROR_BRIEF_LIMIT) : void 0;
|
|
13187
|
+
}
|
|
13188
|
+
if (Array.isArray(content)) {
|
|
13189
|
+
for (const block of content) {
|
|
13190
|
+
if (typeof block === "object" && block !== null && block.type === "text" && typeof block.text === "string") {
|
|
13191
|
+
return errorBrief(block.text);
|
|
13192
|
+
}
|
|
13193
|
+
}
|
|
13194
|
+
}
|
|
13195
|
+
return void 0;
|
|
11929
13196
|
}
|
|
11930
13197
|
|
|
11931
13198
|
// apps/cli/dist/registry.js
|
|
13199
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
11932
13200
|
async function importFactory(harness) {
|
|
11933
13201
|
const mod = await importAdapterModule(harness);
|
|
11934
13202
|
if (typeof mod.createAdapter !== "function") {
|
|
@@ -11947,7 +13215,7 @@ function importAdapterModule(harness) {
|
|
|
11947
13215
|
}
|
|
11948
13216
|
}
|
|
11949
13217
|
function fixtureScanOptions(harness) {
|
|
11950
|
-
if (!
|
|
13218
|
+
if (!existsSync10(fixturePath())) {
|
|
11951
13219
|
throw new Error("--fixtures \u9700\u8981\u4ED3\u5E93\u5185\u7684 packages/fixtures \u6570\u636E;\u53D1\u5E03\u5B89\u88C5\u5305/npx \u4EA7\u7269\u4E0D\u5305\u542B\u6837\u4F8B");
|
|
11952
13220
|
}
|
|
11953
13221
|
switch (harness) {
|
|
@@ -11956,7 +13224,7 @@ function fixtureScanOptions(harness) {
|
|
|
11956
13224
|
case "codex":
|
|
11957
13225
|
return { homeDir: fixturePath("codex", "home") };
|
|
11958
13226
|
case "dsh":
|
|
11959
|
-
return {};
|
|
13227
|
+
return { homeDir: fixturePath("dsh", "home") };
|
|
11960
13228
|
}
|
|
11961
13229
|
}
|
|
11962
13230
|
async function loadHarness(harness, mode) {
|
|
@@ -11971,6 +13239,94 @@ async function loadHarness(harness, mode) {
|
|
|
11971
13239
|
|
|
11972
13240
|
// apps/cli/dist/scan.js
|
|
11973
13241
|
init_dist();
|
|
13242
|
+
|
|
13243
|
+
// apps/cli/dist/telemetry.js
|
|
13244
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
13245
|
+
import { dirname as dirname11, join as join17 } from "node:path";
|
|
13246
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13247
|
+
var DEFAULT_TELEMETRY_SERVER = "https://api.getwingman.dev";
|
|
13248
|
+
var SEND_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
13249
|
+
var SEND_TIMEOUT_MS = 3e3;
|
|
13250
|
+
var MAX_ERROR_TYPES = 10;
|
|
13251
|
+
function emptyState2() {
|
|
13252
|
+
return {
|
|
13253
|
+
scans: 0,
|
|
13254
|
+
toggleApplies: 0,
|
|
13255
|
+
detected: { cc: false, codex: false, dsh: false },
|
|
13256
|
+
liveWired: { cc: false, codex: false, dsh: false },
|
|
13257
|
+
errorTypes: /* @__PURE__ */ new Set()
|
|
13258
|
+
};
|
|
13259
|
+
}
|
|
13260
|
+
var state = emptyState2();
|
|
13261
|
+
function recordDetect(harness, installed) {
|
|
13262
|
+
state.detected[harness] = installed;
|
|
13263
|
+
}
|
|
13264
|
+
function recordScan(config) {
|
|
13265
|
+
state.scans += 1;
|
|
13266
|
+
state.detected[config.harness] = true;
|
|
13267
|
+
state.liveWired[config.harness] = config.capabilities.live !== "none";
|
|
13268
|
+
}
|
|
13269
|
+
function recordHookInstall(harness) {
|
|
13270
|
+
state.liveWired[harness] = true;
|
|
13271
|
+
}
|
|
13272
|
+
function recordApply() {
|
|
13273
|
+
state.toggleApplies += 1;
|
|
13274
|
+
}
|
|
13275
|
+
function recordError(error) {
|
|
13276
|
+
if (state.errorTypes.size >= MAX_ERROR_TYPES)
|
|
13277
|
+
return;
|
|
13278
|
+
state.errorTypes.add(error instanceof Error ? error.constructor.name : typeof error);
|
|
13279
|
+
}
|
|
13280
|
+
function buildPayload(options) {
|
|
13281
|
+
return {
|
|
13282
|
+
app_version: options.version,
|
|
13283
|
+
platform: process.platform,
|
|
13284
|
+
arch: process.arch,
|
|
13285
|
+
harnesses: { ...state.detected },
|
|
13286
|
+
counts: {
|
|
13287
|
+
scans: state.scans,
|
|
13288
|
+
live_wired: Object.values(state.liveWired).filter(Boolean).length,
|
|
13289
|
+
toggle_applies: state.toggleApplies
|
|
13290
|
+
},
|
|
13291
|
+
license_state: options.licenseState,
|
|
13292
|
+
error_types: [...state.errorTypes].sort()
|
|
13293
|
+
};
|
|
13294
|
+
}
|
|
13295
|
+
function appVersion() {
|
|
13296
|
+
if (typeof __WINGMAN_VERSION__ === "string")
|
|
13297
|
+
return __WINGMAN_VERSION__;
|
|
13298
|
+
try {
|
|
13299
|
+
const pkgPath = join17(dirname11(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
13300
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
13301
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
13302
|
+
} catch {
|
|
13303
|
+
return "unknown";
|
|
13304
|
+
}
|
|
13305
|
+
}
|
|
13306
|
+
async function sendMetrics(payload, baseUrl) {
|
|
13307
|
+
const base = baseUrl ?? process.env.WINGMAN_TELEMETRY_SERVER ?? DEFAULT_TELEMETRY_SERVER;
|
|
13308
|
+
try {
|
|
13309
|
+
await fetch(`${base}/metrics`, {
|
|
13310
|
+
method: "POST",
|
|
13311
|
+
headers: { "content-type": "application/json" },
|
|
13312
|
+
body: JSON.stringify(payload),
|
|
13313
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
|
|
13314
|
+
});
|
|
13315
|
+
} catch {
|
|
13316
|
+
}
|
|
13317
|
+
}
|
|
13318
|
+
function startTelemetry(options = {}) {
|
|
13319
|
+
const send = () => {
|
|
13320
|
+
const licenseState = licenseStatus(options.homeDir === void 0 ? {} : { homeDir: options.homeDir }).state;
|
|
13321
|
+
void sendMetrics(buildPayload({ version: appVersion(), licenseState }), options.baseUrl);
|
|
13322
|
+
};
|
|
13323
|
+
send();
|
|
13324
|
+
const timer = setInterval(send, options.intervalMs ?? SEND_INTERVAL_MS);
|
|
13325
|
+
timer.unref();
|
|
13326
|
+
return () => clearInterval(timer);
|
|
13327
|
+
}
|
|
13328
|
+
|
|
13329
|
+
// apps/cli/dist/scan.js
|
|
11974
13330
|
async function scanValidated(adapter, options) {
|
|
11975
13331
|
const config = await adapter.scan(options);
|
|
11976
13332
|
const errors = validateUnifiedConfig(config);
|
|
@@ -11978,6 +13334,7 @@ async function scanValidated(adapter, options) {
|
|
|
11978
13334
|
throw new Error(`${adapter.harness} \u7684 scan \u8F93\u51FA\u4E0D\u7B26\u5408 UnifiedConfig \u5408\u540C:
|
|
11979
13335
|
- ${errors.join("\n- ")}`);
|
|
11980
13336
|
}
|
|
13337
|
+
recordScan(config);
|
|
11981
13338
|
return config;
|
|
11982
13339
|
}
|
|
11983
13340
|
|
|
@@ -11985,7 +13342,152 @@ async function scanValidated(adapter, options) {
|
|
|
11985
13342
|
init_dist();
|
|
11986
13343
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
11987
13344
|
import { createServer } from "node:http";
|
|
11988
|
-
import { extname, resolve as
|
|
13345
|
+
import { extname, resolve as resolve5, sep as sep2 } from "node:path";
|
|
13346
|
+
|
|
13347
|
+
// apps/cli/dist/suggest.js
|
|
13348
|
+
init_dist();
|
|
13349
|
+
var WAITING_TIMEOUT_MS = 6e4;
|
|
13350
|
+
var HISTORY_BLOAT_RATIO = 0.5;
|
|
13351
|
+
var HISTORY_BLOAT_MIN_CONTEXT = 5e4;
|
|
13352
|
+
function mcpServerName(nativeName) {
|
|
13353
|
+
for (const prefix of ["mcpServers.", "mcp_servers."]) {
|
|
13354
|
+
if (nativeName.startsWith(prefix) && nativeName.length > prefix.length) {
|
|
13355
|
+
return nativeName.slice(prefix.length);
|
|
13356
|
+
}
|
|
13357
|
+
}
|
|
13358
|
+
return void 0;
|
|
13359
|
+
}
|
|
13360
|
+
function displayName(item) {
|
|
13361
|
+
return mcpServerName(item.nativeName) ?? item.nativeName;
|
|
13362
|
+
}
|
|
13363
|
+
function fmtTok(value) {
|
|
13364
|
+
if (value < 1e3)
|
|
13365
|
+
return String(value);
|
|
13366
|
+
return `${(Math.round(value / 100) / 10).toFixed(1)}k`;
|
|
13367
|
+
}
|
|
13368
|
+
function fmtTokens(tokens) {
|
|
13369
|
+
const n = fmtTok(tokens.value);
|
|
13370
|
+
return tokens.kind === "estimate" ? `\u7EA6 ${n}` : n;
|
|
13371
|
+
}
|
|
13372
|
+
function fmtDuration(ms) {
|
|
13373
|
+
const seconds = Math.round(ms / 1e3);
|
|
13374
|
+
if (seconds < 60)
|
|
13375
|
+
return `${seconds}s`;
|
|
13376
|
+
const minutes = Math.floor(seconds / 60);
|
|
13377
|
+
return `${minutes}m${seconds % 60 === 0 ? "" : ` ${seconds % 60}s`}`;
|
|
13378
|
+
}
|
|
13379
|
+
function residentTotal(config) {
|
|
13380
|
+
let sum = 0;
|
|
13381
|
+
let counted = false;
|
|
13382
|
+
let kind = "exact";
|
|
13383
|
+
for (const items of Object.values(config.slots)) {
|
|
13384
|
+
for (const item of items) {
|
|
13385
|
+
if (!item.enabled || !item.tokens)
|
|
13386
|
+
continue;
|
|
13387
|
+
sum += item.tokens.value;
|
|
13388
|
+
counted = true;
|
|
13389
|
+
if (item.tokens.kind === "estimate")
|
|
13390
|
+
kind = "estimate";
|
|
13391
|
+
}
|
|
13392
|
+
}
|
|
13393
|
+
return counted ? { kind, value: sum } : void 0;
|
|
13394
|
+
}
|
|
13395
|
+
function stepToolNames(state2) {
|
|
13396
|
+
const names = [];
|
|
13397
|
+
for (const trace of state2.traces) {
|
|
13398
|
+
for (const node of trace.nodes) {
|
|
13399
|
+
for (const step of node.steps)
|
|
13400
|
+
names.push(step.toolName);
|
|
13401
|
+
}
|
|
13402
|
+
}
|
|
13403
|
+
return names;
|
|
13404
|
+
}
|
|
13405
|
+
function deriveSuggestions(state2, config, nowMs) {
|
|
13406
|
+
const suggestions = [];
|
|
13407
|
+
if (state2.waiting !== null) {
|
|
13408
|
+
const sinceMs = Date.parse(state2.waiting.since);
|
|
13409
|
+
if (Number.isFinite(sinceMs) && nowMs - sinceMs > WAITING_TIMEOUT_MS) {
|
|
13410
|
+
suggestions.push({
|
|
13411
|
+
id: "waiting-timeout",
|
|
13412
|
+
rule: "waiting-timeout",
|
|
13413
|
+
text: `\u5DF2\u7B49\u5F85 ${fmtDuration(nowMs - sinceMs)}:${state2.waiting.message}`,
|
|
13414
|
+
source: `\u6765\u6E90:${state2.waiting.kind === "permission" ? "PermissionRequest" : "Notification"} \u4E8B\u4EF6,${state2.waiting.since} \u8D77\u672A\u51B3;\u9608\u503C 60s`
|
|
13415
|
+
});
|
|
13416
|
+
}
|
|
13417
|
+
}
|
|
13418
|
+
if (config !== void 0) {
|
|
13419
|
+
const refSources = [
|
|
13420
|
+
...state2.lastPrompt !== null ? [{ file: "\u7528\u6237\u63D0\u793A", text: state2.lastPrompt }] : [],
|
|
13421
|
+
...state2.agentQuote !== null ? [{ file: "agent \u81EA\u8FF0", text: state2.agentQuote }] : []
|
|
13422
|
+
];
|
|
13423
|
+
if (refSources.length > 0) {
|
|
13424
|
+
for (const items of Object.values(config.slots)) {
|
|
13425
|
+
for (const item of items) {
|
|
13426
|
+
if (item.enabled || !item.toggleable)
|
|
13427
|
+
continue;
|
|
13428
|
+
const name = displayName(item);
|
|
13429
|
+
const hits = scanReferences(refSources, name);
|
|
13430
|
+
const hit = hits[0];
|
|
13431
|
+
if (hit === void 0)
|
|
13432
|
+
continue;
|
|
13433
|
+
suggestions.push({
|
|
13434
|
+
id: `reenable:${item.id}`,
|
|
13435
|
+
rule: "reenable",
|
|
13436
|
+
text: `\u68C0\u6D4B\u5230\u63D0\u53CA ${name}(\u5DF2\u5173),\u53EF\u4E00\u952E\u6253\u5F00`,
|
|
13437
|
+
source: `\u547D\u4E2D:${hit.file} \u2014\u2014 \u300C${hit.context}\u300D(scanReferences \u6574\u8BCD\u5339\u914D)`,
|
|
13438
|
+
action: { itemId: item.id, enable: true }
|
|
13439
|
+
});
|
|
13440
|
+
}
|
|
13441
|
+
}
|
|
13442
|
+
}
|
|
13443
|
+
const toolNames = stepToolNames(state2);
|
|
13444
|
+
if (toolNames.length > 0 && state2.turnUsages.length > 0) {
|
|
13445
|
+
for (const item of config.slots.tools) {
|
|
13446
|
+
if (!item.enabled || !item.toggleable)
|
|
13447
|
+
continue;
|
|
13448
|
+
const server = mcpServerName(item.nativeName);
|
|
13449
|
+
if (server === void 0)
|
|
13450
|
+
continue;
|
|
13451
|
+
const used = toolNames.some((n) => n.startsWith(`mcp__${server}__`));
|
|
13452
|
+
if (used)
|
|
13453
|
+
continue;
|
|
13454
|
+
const saving = item.tokens ? `,\u5173\u6389\u53EF\u7701 ${fmtTokens(item.tokens)} tok(\u5E38\u9A7B\u4F30\u7B97)` : ",\u53EF\u5173";
|
|
13455
|
+
suggestions.push({
|
|
13456
|
+
id: `idle-mcp:${item.id}`,
|
|
13457
|
+
rule: "idle-mcp",
|
|
13458
|
+
text: `${server} \u672C session \u672A\u4F7F\u7528${saving}`,
|
|
13459
|
+
source: [
|
|
13460
|
+
`\u5224\u5B9A:\u672C session \u5DE5\u5177\u6B65\u9AA4\u96F6\u547D\u4E2D mcp__${server}__*(transcript \u89C2\u5BDF)`,
|
|
13461
|
+
`\u6761\u76EE:${item.nativePath !== "" ? item.nativePath : item.id}`,
|
|
13462
|
+
item.tokens ? "\u7701\u989D = \u6761\u76EE\u5E38\u9A7B token(\u4F30\u7B97\u6807\u300C\u7EA6\u300D);\u5173\u505C\u8D70 plan/apply,\u5199\u524D\u5907\u4EFD\u53EF\u56DE\u9000" : "\u8BE5 harness \u672A\u63D0\u4F9B\u6B64\u6761\u76EE\u7684\u5E38\u9A7B\u5360\u7528\u6570,\u4E0D\u7F16\u7701\u989D;\u5173\u505C\u8D70 plan/apply,\u5199\u524D\u5907\u4EFD\u53EF\u56DE\u9000"
|
|
13463
|
+
].join("\n"),
|
|
13464
|
+
action: { itemId: item.id, enable: false }
|
|
13465
|
+
});
|
|
13466
|
+
}
|
|
13467
|
+
}
|
|
13468
|
+
const lastTurn = state2.turnUsages[state2.turnUsages.length - 1];
|
|
13469
|
+
const resident = residentTotal(config);
|
|
13470
|
+
if (lastTurn !== void 0 && resident !== void 0) {
|
|
13471
|
+
const ctx = lastTurn.contextTotal.value;
|
|
13472
|
+
const history = ctx - resident.value;
|
|
13473
|
+
if (ctx >= HISTORY_BLOAT_MIN_CONTEXT && history / ctx > HISTORY_BLOAT_RATIO) {
|
|
13474
|
+
const pct = Math.round(history / ctx * 100);
|
|
13475
|
+
suggestions.push({
|
|
13476
|
+
id: "history-bloat",
|
|
13477
|
+
rule: "history-bloat",
|
|
13478
|
+
text: `\u5BF9\u8BDD\u5386\u53F2\u7EA6\u5360\u672C\u8F6E context ${pct}%(\u7EA6 ${fmtTok(history)} / ${fmtTok(ctx)}),\u5F00\u65B0 session \u53EF\u590D\u4F4D`,
|
|
13479
|
+
source: [
|
|
13480
|
+
"\u4F30\u7B97 = \u672C\u8F6E usage \u5408\u8BA1(harness \u81EA\u62A5,\u7CBE\u786E)\u2212 \u914D\u7F6E\u5E38\u9A7B\u5408\u8BA1(\u4F30\u7B97,\u6807\u300C\u7EA6\u300D)",
|
|
13481
|
+
`\u9608\u503C:context \u2265 ${fmtTok(HISTORY_BLOAT_MIN_CONTEXT)} \u4E14\u5386\u53F2\u5360\u6BD4 > 50%`
|
|
13482
|
+
].join("\n")
|
|
13483
|
+
});
|
|
13484
|
+
}
|
|
13485
|
+
}
|
|
13486
|
+
}
|
|
13487
|
+
return suggestions;
|
|
13488
|
+
}
|
|
13489
|
+
|
|
13490
|
+
// apps/cli/dist/server.js
|
|
11989
13491
|
var CONTENT_TYPES = {
|
|
11990
13492
|
".html": "text/html; charset=utf-8",
|
|
11991
13493
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -11997,10 +13499,13 @@ var CONTENT_TYPES = {
|
|
|
11997
13499
|
".ico": "image/x-icon",
|
|
11998
13500
|
".woff2": "font/woff2"
|
|
11999
13501
|
};
|
|
13502
|
+
var ANATOMY_CACHE_TTL_MS = 3e4;
|
|
12000
13503
|
function createWingmanServer(options) {
|
|
12001
13504
|
const warn = options.warn ?? ((message) => console.error(message));
|
|
13505
|
+
const runtime = { anatomy: null };
|
|
12002
13506
|
return createServer((req, res) => {
|
|
12003
|
-
handle(req, res, options, warn).catch((error) => {
|
|
13507
|
+
handle(req, res, options, warn, runtime).catch((error) => {
|
|
13508
|
+
recordError(error);
|
|
12004
13509
|
warn(`[wingman] \u8BF7\u6C42\u5904\u7406\u5931\u8D25:${errorMessage(error)}`);
|
|
12005
13510
|
if (!res.headersSent)
|
|
12006
13511
|
sendText2(res, 500, "internal error");
|
|
@@ -12018,8 +13523,15 @@ function listen(server, port) {
|
|
|
12018
13523
|
});
|
|
12019
13524
|
});
|
|
12020
13525
|
}
|
|
12021
|
-
async function handle(req, res, options, warn) {
|
|
13526
|
+
async function handle(req, res, options, warn, runtime) {
|
|
12022
13527
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
13528
|
+
if (req.method !== "GET") {
|
|
13529
|
+
const origin = req.headers.origin;
|
|
13530
|
+
if (typeof origin === "string" && origin !== "" && !/^https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?$/.test(origin)) {
|
|
13531
|
+
sendJson(res, 403, { error: `\u62D2\u7EDD\u5F02\u6E90\u5199\u8BF7\u6C42(Origin: ${origin})` });
|
|
13532
|
+
return;
|
|
13533
|
+
}
|
|
13534
|
+
}
|
|
12023
13535
|
if (options.receiver && url.pathname === "/hook") {
|
|
12024
13536
|
options.receiver.handleHook(req, res);
|
|
12025
13537
|
return;
|
|
@@ -12033,7 +13545,7 @@ async function handle(req, res, options, warn) {
|
|
|
12033
13545
|
return;
|
|
12034
13546
|
}
|
|
12035
13547
|
if (url.pathname === "/api/action/apply" && req.method === "POST") {
|
|
12036
|
-
await handleActionApply(req, res, options);
|
|
13548
|
+
await handleActionApply(req, res, options, runtime);
|
|
12037
13549
|
return;
|
|
12038
13550
|
}
|
|
12039
13551
|
if (url.pathname === "/api/license/install" && req.method === "POST") {
|
|
@@ -12045,8 +13557,34 @@ async function handle(req, res, options, warn) {
|
|
|
12045
13557
|
return;
|
|
12046
13558
|
}
|
|
12047
13559
|
if (url.pathname === "/api/license") {
|
|
12048
|
-
const { state, detail } = licenseStatus(options.licenseOptions);
|
|
12049
|
-
sendJson(res, 200, { state, detail });
|
|
13560
|
+
const { state: state2, detail } = licenseStatus(options.licenseOptions);
|
|
13561
|
+
sendJson(res, 200, { state: state2, detail });
|
|
13562
|
+
return;
|
|
13563
|
+
}
|
|
13564
|
+
if (url.pathname === "/api/narrative") {
|
|
13565
|
+
if (!options.narrative) {
|
|
13566
|
+
sendText2(res, 404, "not found");
|
|
13567
|
+
return;
|
|
13568
|
+
}
|
|
13569
|
+
const harness = url.searchParams.get("harness");
|
|
13570
|
+
if (typeof harness !== "string" || !HARNESS_IDS.includes(harness)) {
|
|
13571
|
+
sendJson(res, 400, {
|
|
13572
|
+
error: `harness \u9700\u8981 ${HARNESS_IDS.join("|")}(?harness=<id>),\u5F97\u5230 ${String(harness)}`
|
|
13573
|
+
});
|
|
13574
|
+
return;
|
|
13575
|
+
}
|
|
13576
|
+
const overview = options.narrative.getOverview(harness);
|
|
13577
|
+
if (overview.sessions.length > 0) {
|
|
13578
|
+
const config = (await cachedAnatomy(options, warn, runtime)).find((c) => c.harness === harness);
|
|
13579
|
+
const nowMs = Date.now();
|
|
13580
|
+
const states = {};
|
|
13581
|
+
for (const [id, state2] of Object.entries(overview.states)) {
|
|
13582
|
+
const suggestions = deriveSuggestions(state2, config, nowMs);
|
|
13583
|
+
states[id] = suggestions.length > 0 ? { ...state2, suggestions } : state2;
|
|
13584
|
+
}
|
|
13585
|
+
overview.states = states;
|
|
13586
|
+
}
|
|
13587
|
+
sendJson(res, 200, overview);
|
|
12050
13588
|
return;
|
|
12051
13589
|
}
|
|
12052
13590
|
if (url.pathname === "/api/anatomy") {
|
|
@@ -12061,20 +13599,75 @@ async function handle(req, res, options, warn) {
|
|
|
12061
13599
|
}
|
|
12062
13600
|
await serveStatic(url.pathname, res, options.panelDistDir);
|
|
12063
13601
|
}
|
|
13602
|
+
async function cachedAnatomy(options, warn, runtime) {
|
|
13603
|
+
const nowMs = Date.now();
|
|
13604
|
+
if (runtime.anatomy !== null && nowMs - runtime.anatomy.at < ANATOMY_CACHE_TTL_MS) {
|
|
13605
|
+
return runtime.anatomy.configs;
|
|
13606
|
+
}
|
|
13607
|
+
const configs = await scanAll(options.harnesses, warn);
|
|
13608
|
+
runtime.anatomy = { at: nowMs, configs };
|
|
13609
|
+
return configs;
|
|
13610
|
+
}
|
|
12064
13611
|
async function scanAll(harnesses, warn) {
|
|
12065
13612
|
const configs = [];
|
|
12066
13613
|
for (const { harness, adapter, scanOptions } of harnesses) {
|
|
13614
|
+
let installed;
|
|
12067
13615
|
try {
|
|
12068
13616
|
const detected = await adapter.detect(scanOptions);
|
|
12069
|
-
|
|
12070
|
-
|
|
13617
|
+
recordDetect(harness, detected.installed);
|
|
13618
|
+
installed = detected.installed;
|
|
13619
|
+
} catch (error) {
|
|
13620
|
+
recordError(error);
|
|
13621
|
+
warn(`[wingman] ${harness} detect \u5931\u8D25,\u5DF2\u8DF3\u8FC7:${errorMessage(error)}`);
|
|
13622
|
+
continue;
|
|
13623
|
+
}
|
|
13624
|
+
if (!installed)
|
|
13625
|
+
continue;
|
|
13626
|
+
try {
|
|
12071
13627
|
configs.push(await scanValidated(adapter, scanOptions));
|
|
12072
13628
|
} catch (error) {
|
|
12073
|
-
|
|
13629
|
+
recordError(error);
|
|
13630
|
+
warn(`[wingman] ${harness} \u626B\u63CF\u5931\u8D25,\u964D\u7EA7\u663E\u793A:${errorMessage(error)}`);
|
|
13631
|
+
configs.push(degradedScanConfig(harness, error));
|
|
12074
13632
|
}
|
|
12075
13633
|
}
|
|
12076
13634
|
return configs;
|
|
12077
13635
|
}
|
|
13636
|
+
var DEGRADED_SOURCE = {
|
|
13637
|
+
cc: "bundled",
|
|
13638
|
+
codex: "user",
|
|
13639
|
+
dsh: "bundle"
|
|
13640
|
+
};
|
|
13641
|
+
function degradedScanConfig(harness, error) {
|
|
13642
|
+
return {
|
|
13643
|
+
harness,
|
|
13644
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13645
|
+
slots: {
|
|
13646
|
+
base: [
|
|
13647
|
+
{
|
|
13648
|
+
id: `${harness}:base:${DEGRADED_SOURCE[harness]}:scan-failed`,
|
|
13649
|
+
slot: "base",
|
|
13650
|
+
nativeName: `\u8BFB\u53D6\u914D\u7F6E\u5931\u8D25:${errorSummary(error)}`,
|
|
13651
|
+
nativePath: "",
|
|
13652
|
+
toggleable: false,
|
|
13653
|
+
enabled: true,
|
|
13654
|
+
source: DEGRADED_SOURCE[harness]
|
|
13655
|
+
}
|
|
13656
|
+
],
|
|
13657
|
+
rules: [],
|
|
13658
|
+
skills: [],
|
|
13659
|
+
tools: [],
|
|
13660
|
+
gates: [],
|
|
13661
|
+
history: []
|
|
13662
|
+
},
|
|
13663
|
+
capabilities: { live: "none", actions: [] }
|
|
13664
|
+
};
|
|
13665
|
+
}
|
|
13666
|
+
function errorSummary(error) {
|
|
13667
|
+
const first = errorMessage(error).split("\n", 1)[0] ?? "";
|
|
13668
|
+
const scrubbed = first.replace(/(?:[A-Za-z]:)?[\\/][^\s'"()|,;]+/g, "\u2026").trim();
|
|
13669
|
+
return scrubbed.length <= 120 ? scrubbed : `${scrubbed.slice(0, 119)}\u2026`;
|
|
13670
|
+
}
|
|
12078
13671
|
async function handleActionPlan(req, res, options) {
|
|
12079
13672
|
const body = await readJsonBody(req);
|
|
12080
13673
|
if (body === void 0) {
|
|
@@ -12098,7 +13691,7 @@ async function handleActionPlan(req, res, options) {
|
|
|
12098
13691
|
sendJson(res, 400, { error: errorMessage(error) });
|
|
12099
13692
|
}
|
|
12100
13693
|
}
|
|
12101
|
-
async function handleActionApply(req, res, options) {
|
|
13694
|
+
async function handleActionApply(req, res, options, runtime) {
|
|
12102
13695
|
if (options.readonlyActions) {
|
|
12103
13696
|
sendJson(res, 403, { error: "fixtures \u6A21\u5F0F\u53EA\u8BFB:\u52A8\u4F5C\u53EA\u5230 plan \u4E3A\u6B62,\u4E0D\u5199\u4EFB\u4F55\u6587\u4EF6" });
|
|
12104
13697
|
return;
|
|
@@ -12124,8 +13717,13 @@ async function handleActionApply(req, res, options) {
|
|
|
12124
13717
|
}
|
|
12125
13718
|
try {
|
|
12126
13719
|
const result = await loaded.adapter.applyAction(plan, loaded.scanOptions);
|
|
13720
|
+
if (result.ok) {
|
|
13721
|
+
recordApply();
|
|
13722
|
+
runtime.anatomy = null;
|
|
13723
|
+
}
|
|
12127
13724
|
sendJson(res, 200, result);
|
|
12128
13725
|
} catch (error) {
|
|
13726
|
+
recordError(error);
|
|
12129
13727
|
sendJson(res, 400, { error: errorMessage(error) });
|
|
12130
13728
|
}
|
|
12131
13729
|
}
|
|
@@ -12208,8 +13806,8 @@ async function serveStatic(pathname, res, panelDistDir) {
|
|
|
12208
13806
|
return;
|
|
12209
13807
|
}
|
|
12210
13808
|
const rel = decoded === "/" ? "/index.html" : decoded;
|
|
12211
|
-
const distDir =
|
|
12212
|
-
const filePath =
|
|
13809
|
+
const distDir = resolve5(panelDistDir);
|
|
13810
|
+
const filePath = resolve5(distDir, `.${rel}`);
|
|
12213
13811
|
if (filePath !== distDir && !filePath.startsWith(distDir + sep2)) {
|
|
12214
13812
|
sendText2(res, 403, "forbidden");
|
|
12215
13813
|
return;
|
|
@@ -12326,17 +13924,30 @@ async function runServe(args) {
|
|
|
12326
13924
|
`);
|
|
12327
13925
|
}
|
|
12328
13926
|
}
|
|
12329
|
-
const panelDistDir = panelDist === void 0 ?
|
|
13927
|
+
const panelDistDir = panelDist === void 0 ? resolve6(dirname12(fileURLToPath3(import.meta.url)), "../../panel/dist") : resolve6(panelDist);
|
|
13928
|
+
const narrative = createNarrativeService({ followTranscripts: !fixtures });
|
|
13929
|
+
if (fixtures) {
|
|
13930
|
+
try {
|
|
13931
|
+
const raw = readFileSync12(fixturePath("cc", "sessions", "session-todos.jsonl"), "utf8");
|
|
13932
|
+
narrative.seedFixture("cc", raw.split("\n"), "MOCK \u2014 fixtures \u6F14\u793A:\u53D9\u4E8B\u6570\u636E\u6765\u81EA packages/fixtures \u7684 session-todos.jsonl \u6837\u4F8B\u4F1A\u8BDD,\u975E\u672C\u673A\u771F\u5B9E transcript\u3002");
|
|
13933
|
+
} catch (error) {
|
|
13934
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13935
|
+
process.stderr.write(`[wingman] fixtures \u53D9\u4E8B\u6837\u4F8B\u52A0\u8F7D\u5931\u8D25,\u5DF2\u8DF3\u8FC7:${message}
|
|
13936
|
+
`);
|
|
13937
|
+
}
|
|
13938
|
+
}
|
|
12330
13939
|
const server = createWingmanServer({
|
|
12331
13940
|
harnesses,
|
|
12332
13941
|
panelDistDir,
|
|
12333
|
-
receiver: createReceiver(),
|
|
12334
|
-
readonlyActions: fixtures
|
|
13942
|
+
receiver: createReceiver({ onEvent: narrative.handleEvent }),
|
|
13943
|
+
readonlyActions: fixtures,
|
|
13944
|
+
narrative
|
|
12335
13945
|
});
|
|
12336
13946
|
const actualPort = await listen(server, port);
|
|
13947
|
+
startTelemetry();
|
|
12337
13948
|
const mode = fixtures ? "(fixtures \u6A21\u5F0F:\u6570\u636E\u6765\u81EA @wingman/fixtures \u6837\u4F8B,\u975E\u672C\u673A\u914D\u7F6E)" : "";
|
|
12338
13949
|
process.stdout.write(`Wingman \u9762\u677F:http://127.0.0.1:${actualPort}${mode}
|
|
12339
|
-
\u53EA\u7ED1 127.0.0.1,\u6570\u636E\
|
|
13950
|
+
\u53EA\u7ED1 127.0.0.1,\u9762\u677F\u4E0E\u6570\u636E\u90FD\u5728\u672C\u673A;\u533F\u540D\u4F7F\u7528\u7EDF\u8BA1\u4E0D\u542B\u5185\u5BB9\u4E0E\u8DEF\u5F84,\u660E\u7EC6 getwingman.dev/data\u3002Ctrl+C \u9000\u51FA\u3002
|
|
12340
13951
|
`);
|
|
12341
13952
|
return 0;
|
|
12342
13953
|
}
|
|
@@ -12364,6 +13975,7 @@ async function runHook(args) {
|
|
|
12364
13975
|
const { adapter } = await loadHarness(harness, { fixtures: false });
|
|
12365
13976
|
if (action === "install") {
|
|
12366
13977
|
await adapter.installHooks(port);
|
|
13978
|
+
recordHookInstall(harness);
|
|
12367
13979
|
process.stdout.write(`wingman:${harness} \u5B9E\u51B5\u63A5\u7EBF\u5B8C\u6210(POST http://127.0.0.1:${port}/hook?harness=${harness})\u3002
|
|
12368
13980
|
\u5199\u524D\u5DF2\u5907\u4EFD;\u91CD\u590D\u6267\u884C\u5E42\u7B49;\u89E3\u7EBF:wingman hook uninstall ${harness}
|
|
12369
13981
|
`);
|