harness-wingman 0.1.0 → 0.3.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/apps/cli/dist/main.js +1889 -294
- package/apps/panel/dist/assets/index-CHt8loKP.js +202 -0
- package/apps/panel/dist/assets/index-CP99dQS1.css +1 -0
- package/apps/panel/dist/index.html +2 -2
- package/package.json +1 -1
- 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
|
-
|
|
877
|
-
|
|
878
|
-
for (const f of walkFiles(projectDir, "CLAUDE.md", true))
|
|
917
|
+
const bareRoot = isBareProjectDir(projectDir, homeDir);
|
|
918
|
+
pushFile(join8(homeDir, ".claude", "CLAUDE.md"));
|
|
919
|
+
for (const f of listMdFiles(join8(homeDir, ".claude", "rules")))
|
|
879
920
|
pushFile(f);
|
|
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);
|
|
@@ -11733,10 +11825,10 @@ function isHookPayload(value) {
|
|
|
11733
11825
|
return typeof name === "string" && name.length > 0;
|
|
11734
11826
|
}
|
|
11735
11827
|
function readBody(req) {
|
|
11736
|
-
return new Promise((
|
|
11828
|
+
return new Promise((resolve7, reject) => {
|
|
11737
11829
|
const chunks = [];
|
|
11738
11830
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
11739
|
-
req.on("end", () =>
|
|
11831
|
+
req.on("end", () => resolve7(Buffer.concat(chunks).toString("utf8")));
|
|
11740
11832
|
req.on("error", reject);
|
|
11741
11833
|
});
|
|
11742
11834
|
}
|
|
@@ -11749,7 +11841,7 @@ function sendText(res, status, body) {
|
|
|
11749
11841
|
import { verify } from "node:crypto";
|
|
11750
11842
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
11751
11843
|
import { homedir } from "node:os";
|
|
11752
|
-
import { join } from "node:path";
|
|
11844
|
+
import { join as join2 } from "node:path";
|
|
11753
11845
|
var OFFLINE_GRACE_DAYS = 14;
|
|
11754
11846
|
var PROD_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAyU4yO0SJcflt/CV77wqVxIwCkM52CYGC6qrPGP8dMME=\n-----END PUBLIC KEY-----\n";
|
|
11755
11847
|
function checkLicense(options = {}) {
|
|
@@ -11763,7 +11855,7 @@ function licenseStatus(options = {}) {
|
|
|
11763
11855
|
const home = options.homeDir ?? homedir();
|
|
11764
11856
|
let raw;
|
|
11765
11857
|
try {
|
|
11766
|
-
raw = readFileSync(
|
|
11858
|
+
raw = readFileSync(join2(home, ".wingman", "license.json"), "utf8");
|
|
11767
11859
|
} catch {
|
|
11768
11860
|
return { state: "none", detail: "\u672A\u5B89\u88C5 license(anatomy \u4E0E\u5B9E\u51B5\u514D\u8D39\u53EF\u7528;\u5199\u52A8\u4F5C\u9700\u8BA2\u9605)" };
|
|
11769
11861
|
}
|
|
@@ -11832,9 +11924,9 @@ function installLicense(raw, options = {}) {
|
|
|
11832
11924
|
return { ok: false, error: status.detail };
|
|
11833
11925
|
}
|
|
11834
11926
|
}
|
|
11835
|
-
const dir =
|
|
11836
|
-
const licensePath =
|
|
11837
|
-
const tmpPath =
|
|
11927
|
+
const dir = join2(options.homeDir ?? homedir(), ".wingman");
|
|
11928
|
+
const licensePath = join2(dir, "license.json");
|
|
11929
|
+
const tmpPath = join2(dir, `.license.json.tmp-${process.pid}`);
|
|
11838
11930
|
try {
|
|
11839
11931
|
mkdirSync(dir, { recursive: true });
|
|
11840
11932
|
writeFileSync(tmpPath, raw);
|
|
@@ -11851,12 +11943,12 @@ function installLicense(raw, options = {}) {
|
|
|
11851
11943
|
// apps/cli/dist/license-refresh.js
|
|
11852
11944
|
import { readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
11853
11945
|
import { homedir as homedir2 } from "node:os";
|
|
11854
|
-
import { dirname, join as
|
|
11946
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
11855
11947
|
var DEFAULT_LICENSE_SERVER = "https://api.getwingman.dev";
|
|
11856
11948
|
async function refreshLicense(options = {}) {
|
|
11857
11949
|
const home = options.homeDir ?? homedir2();
|
|
11858
11950
|
const baseUrl = options.baseUrl ?? process.env.WINGMAN_LICENSE_SERVER ?? DEFAULT_LICENSE_SERVER;
|
|
11859
|
-
const licensePath =
|
|
11951
|
+
const licensePath = join3(home, ".wingman", "license.json");
|
|
11860
11952
|
let raw;
|
|
11861
11953
|
try {
|
|
11862
11954
|
raw = readFileSync2(licensePath, "utf8");
|
|
@@ -11898,7 +11990,7 @@ async function refreshLicense(options = {}) {
|
|
|
11898
11990
|
}
|
|
11899
11991
|
const next = `${JSON.stringify({ token: entitlement.token, signature: entitlement.signature })}
|
|
11900
11992
|
`;
|
|
11901
|
-
const tmpPath =
|
|
11993
|
+
const tmpPath = join3(dirname2(licensePath), `.license.json.tmp-${process.pid}`);
|
|
11902
11994
|
try {
|
|
11903
11995
|
writeFileSync2(tmpPath, next);
|
|
11904
11996
|
renameSync2(tmpPath, licensePath);
|
|
@@ -11917,18 +12009,1184 @@ function expSuffix(token) {
|
|
|
11917
12009
|
}
|
|
11918
12010
|
}
|
|
11919
12011
|
|
|
11920
|
-
// apps/cli/dist/
|
|
11921
|
-
|
|
12012
|
+
// apps/cli/dist/narrative-service.js
|
|
12013
|
+
init_dist();
|
|
12014
|
+
import { readdir } from "node:fs/promises";
|
|
12015
|
+
import { join as join4 } from "node:path";
|
|
11922
12016
|
|
|
11923
|
-
// packages/
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
12017
|
+
// packages/narrative/dist/attribute.js
|
|
12018
|
+
function extractSteps(lines) {
|
|
12019
|
+
const steps = [];
|
|
12020
|
+
const byToolUseId = /* @__PURE__ */ new Map();
|
|
12021
|
+
for (const line of lines) {
|
|
12022
|
+
if (line.kind === "assistant") {
|
|
12023
|
+
for (const block of line.blocks) {
|
|
12024
|
+
if (block.kind !== "tool_use")
|
|
12025
|
+
continue;
|
|
12026
|
+
const step = {
|
|
12027
|
+
toolUseId: block.id,
|
|
12028
|
+
toolName: block.name,
|
|
12029
|
+
toolInput: block.input,
|
|
12030
|
+
isError: false
|
|
12031
|
+
};
|
|
12032
|
+
if (line.timestamp !== void 0)
|
|
12033
|
+
step.ts = line.timestamp;
|
|
12034
|
+
if (line.agentId !== void 0)
|
|
12035
|
+
step.agentId = line.agentId;
|
|
12036
|
+
steps.push(step);
|
|
12037
|
+
byToolUseId.set(block.id, step);
|
|
12038
|
+
}
|
|
12039
|
+
} else if (line.kind === "user") {
|
|
12040
|
+
for (const result of line.toolResults) {
|
|
12041
|
+
const step = byToolUseId.get(result.toolUseId);
|
|
12042
|
+
if (step === void 0)
|
|
12043
|
+
continue;
|
|
12044
|
+
if (line.timestamp !== void 0)
|
|
12045
|
+
step.endTs = line.timestamp;
|
|
12046
|
+
if (result.isError)
|
|
12047
|
+
step.isError = true;
|
|
12048
|
+
}
|
|
12049
|
+
}
|
|
12050
|
+
}
|
|
12051
|
+
return steps;
|
|
12052
|
+
}
|
|
12053
|
+
function deriveSubagentIntervals(lines) {
|
|
12054
|
+
const agentSteps = extractSteps(lines).filter((step) => step.toolName === "Agent");
|
|
12055
|
+
return agentSteps.map((step) => {
|
|
12056
|
+
const interval = { agentId: step.toolUseId };
|
|
12057
|
+
if (step.ts !== void 0)
|
|
12058
|
+
interval.startTs = step.ts;
|
|
12059
|
+
if (step.endTs !== void 0)
|
|
12060
|
+
interval.endTs = step.endTs;
|
|
12061
|
+
return interval;
|
|
12062
|
+
});
|
|
12063
|
+
}
|
|
12064
|
+
function attributeSteps(steps, timeline, intervals = []) {
|
|
12065
|
+
return steps.map((step) => ({ step, attribution: attributeOne(step, timeline, intervals) }));
|
|
12066
|
+
}
|
|
12067
|
+
function attributeOne(step, timeline, intervals) {
|
|
12068
|
+
const ts = step.ts;
|
|
12069
|
+
if (ts === void 0)
|
|
12070
|
+
return { kind: "other", reason: "no-timestamp" };
|
|
12071
|
+
const active = timeline.filter((entry) => isInProgressAt(entry, ts));
|
|
12072
|
+
if (active.length === 0)
|
|
12073
|
+
return { kind: "other", reason: "no-active-todo" };
|
|
12074
|
+
const only = active[0];
|
|
12075
|
+
if (active.length === 1 && only !== void 0)
|
|
12076
|
+
return { kind: "todo", todoId: only.id };
|
|
12077
|
+
if (step.agentId !== void 0) {
|
|
12078
|
+
const todoIds = /* @__PURE__ */ new Set();
|
|
12079
|
+
for (const interval of intervals) {
|
|
12080
|
+
if (interval.agentId !== step.agentId)
|
|
12081
|
+
continue;
|
|
12082
|
+
const todoId = resolveIntervalTodo(interval, timeline);
|
|
12083
|
+
if (todoId !== void 0)
|
|
12084
|
+
todoIds.add(todoId);
|
|
12085
|
+
}
|
|
12086
|
+
if (todoIds.size === 1) {
|
|
12087
|
+
const [todoId] = todoIds;
|
|
12088
|
+
if (todoId !== void 0 && active.some((entry) => entry.id === todoId)) {
|
|
12089
|
+
return { kind: "todo", todoId };
|
|
12090
|
+
}
|
|
12091
|
+
}
|
|
12092
|
+
}
|
|
12093
|
+
return { kind: "other", reason: "ambiguous" };
|
|
12094
|
+
}
|
|
12095
|
+
function resolveIntervalTodo(interval, timeline) {
|
|
12096
|
+
const start = interval.startTs;
|
|
12097
|
+
if (start === void 0)
|
|
12098
|
+
return void 0;
|
|
12099
|
+
const active = timeline.filter((entry) => isInProgressAt(entry, start));
|
|
12100
|
+
const only = active[0];
|
|
12101
|
+
return active.length === 1 && only !== void 0 ? only.id : void 0;
|
|
12102
|
+
}
|
|
12103
|
+
function isInProgressAt(entry, ts) {
|
|
12104
|
+
return entry.spans.some((span) => span.status === "in_progress" && span.from !== void 0 && span.from <= ts && (span.to === void 0 || ts < span.to));
|
|
12105
|
+
}
|
|
12106
|
+
|
|
12107
|
+
// packages/narrative/dist/parse.js
|
|
12108
|
+
function parseTranscriptLine(line) {
|
|
12109
|
+
const trimmed = line.trim();
|
|
12110
|
+
if (trimmed === "")
|
|
12111
|
+
return { kind: "unknown", reason: "\u7A7A\u884C", rawText: line };
|
|
12112
|
+
let value;
|
|
12113
|
+
try {
|
|
12114
|
+
value = JSON.parse(trimmed);
|
|
12115
|
+
} catch {
|
|
12116
|
+
return { kind: "unknown", reason: "JSON \u89E3\u6790\u5931\u8D25", rawText: line };
|
|
12117
|
+
}
|
|
12118
|
+
if (!isRecord2(value))
|
|
12119
|
+
return { kind: "unknown", reason: "\u884C\u4E0D\u662F JSON \u5BF9\u8C61", rawText: line };
|
|
12120
|
+
const type = value.type;
|
|
12121
|
+
if (typeof type !== "string") {
|
|
12122
|
+
return { kind: "unknown", reason: "\u7F3A type \u5B57\u6BB5\u6216\u975E\u5B57\u7B26\u4E32", rawText: line };
|
|
12123
|
+
}
|
|
12124
|
+
const common = extractCommon(value);
|
|
12125
|
+
if (type === "user")
|
|
12126
|
+
return parseUser(value, common);
|
|
12127
|
+
if (type === "assistant")
|
|
12128
|
+
return parseAssistant(value, common);
|
|
12129
|
+
if (type === "attachment") {
|
|
12130
|
+
const attachment = {
|
|
12131
|
+
kind: "attachment",
|
|
12132
|
+
...common,
|
|
12133
|
+
attachment: isRecord2(value.attachment) ? value.attachment : {}
|
|
12134
|
+
};
|
|
12135
|
+
return attachment;
|
|
12136
|
+
}
|
|
12137
|
+
return { kind: "other", type, ...common };
|
|
12138
|
+
}
|
|
12139
|
+
function extractCommon(raw) {
|
|
12140
|
+
const common = { raw };
|
|
12141
|
+
if (typeof raw.uuid === "string")
|
|
12142
|
+
common.uuid = raw.uuid;
|
|
12143
|
+
if (typeof raw.parentUuid === "string")
|
|
12144
|
+
common.parentUuid = raw.parentUuid;
|
|
12145
|
+
if (typeof raw.timestamp === "string")
|
|
12146
|
+
common.timestamp = raw.timestamp;
|
|
12147
|
+
if (typeof raw.sessionId === "string")
|
|
12148
|
+
common.sessionId = raw.sessionId;
|
|
12149
|
+
if (typeof raw.agentId === "string")
|
|
12150
|
+
common.agentId = raw.agentId;
|
|
12151
|
+
if (typeof raw.isSidechain === "boolean")
|
|
12152
|
+
common.isSidechain = raw.isSidechain;
|
|
12153
|
+
return common;
|
|
12154
|
+
}
|
|
12155
|
+
function parseUser(raw, common) {
|
|
12156
|
+
const line = { kind: "user", ...common, toolResults: [] };
|
|
12157
|
+
const message = isRecord2(raw.message) ? raw.message : void 0;
|
|
12158
|
+
const content = message?.content;
|
|
12159
|
+
if (typeof content === "string") {
|
|
12160
|
+
line.text = content;
|
|
12161
|
+
} else if (Array.isArray(content)) {
|
|
12162
|
+
const texts = [];
|
|
12163
|
+
for (const block of content) {
|
|
12164
|
+
if (!isRecord2(block))
|
|
12165
|
+
continue;
|
|
12166
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
12167
|
+
const result = {
|
|
12168
|
+
toolUseId: block.tool_use_id,
|
|
12169
|
+
// 实测:成功结果常无 is_error 字段;仅显式 true 视为失败
|
|
12170
|
+
isError: block.is_error === true,
|
|
12171
|
+
content: block.content
|
|
12172
|
+
};
|
|
12173
|
+
line.toolResults.push(result);
|
|
12174
|
+
} else if (block.type === "text" && typeof block.text === "string") {
|
|
12175
|
+
texts.push(block.text);
|
|
12176
|
+
}
|
|
12177
|
+
}
|
|
12178
|
+
if (texts.length > 0)
|
|
12179
|
+
line.text = texts.join("\n");
|
|
12180
|
+
}
|
|
12181
|
+
if ("toolUseResult" in raw)
|
|
12182
|
+
line.toolUseResult = raw.toolUseResult;
|
|
12183
|
+
return line;
|
|
12184
|
+
}
|
|
12185
|
+
function parseAssistant(raw, common) {
|
|
12186
|
+
const line = { kind: "assistant", ...common, blocks: [] };
|
|
12187
|
+
const message = isRecord2(raw.message) ? raw.message : void 0;
|
|
12188
|
+
if (message === void 0)
|
|
12189
|
+
return line;
|
|
12190
|
+
if (typeof message.model === "string")
|
|
12191
|
+
line.model = message.model;
|
|
12192
|
+
if (typeof message.id === "string")
|
|
12193
|
+
line.messageId = message.id;
|
|
12194
|
+
if (isRecord2(message.usage))
|
|
12195
|
+
line.usage = message.usage;
|
|
12196
|
+
const content = message.content;
|
|
12197
|
+
if (!Array.isArray(content))
|
|
12198
|
+
return line;
|
|
12199
|
+
for (const block of content) {
|
|
12200
|
+
if (!isRecord2(block))
|
|
12201
|
+
continue;
|
|
12202
|
+
line.blocks.push(parseAssistantBlock(block));
|
|
12203
|
+
}
|
|
12204
|
+
return line;
|
|
12205
|
+
}
|
|
12206
|
+
function parseAssistantBlock(block) {
|
|
12207
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
12208
|
+
return { kind: "text", text: block.text };
|
|
12209
|
+
}
|
|
12210
|
+
if (block.type === "thinking" && typeof block.thinking === "string") {
|
|
12211
|
+
return { kind: "thinking", thinking: block.thinking };
|
|
12212
|
+
}
|
|
12213
|
+
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
12214
|
+
return {
|
|
12215
|
+
kind: "tool_use",
|
|
12216
|
+
id: block.id,
|
|
12217
|
+
name: block.name,
|
|
12218
|
+
input: isRecord2(block.input) ? block.input : {}
|
|
12219
|
+
};
|
|
12220
|
+
}
|
|
12221
|
+
return {
|
|
12222
|
+
kind: "other",
|
|
12223
|
+
type: typeof block.type === "string" ? block.type : "unknown",
|
|
12224
|
+
raw: block
|
|
12225
|
+
};
|
|
12226
|
+
}
|
|
12227
|
+
function isRecord2(value) {
|
|
12228
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12229
|
+
}
|
|
12230
|
+
|
|
12231
|
+
// packages/narrative/dist/phase.js
|
|
12232
|
+
var PHASE_LABELS = {
|
|
12233
|
+
inspect: "\u68C0\u67E5",
|
|
12234
|
+
modify: "\u4FEE\u6539",
|
|
12235
|
+
verify: "\u9A8C\u8BC1",
|
|
12236
|
+
other: "\u5176\u4ED6"
|
|
12237
|
+
};
|
|
12238
|
+
var PHASE_TOOL_RULES = {
|
|
12239
|
+
Read: "inspect",
|
|
12240
|
+
Grep: "inspect",
|
|
12241
|
+
Glob: "inspect",
|
|
12242
|
+
WebFetch: "inspect",
|
|
12243
|
+
Edit: "modify",
|
|
12244
|
+
Write: "modify",
|
|
12245
|
+
NotebookEdit: "modify"
|
|
12246
|
+
};
|
|
12247
|
+
var VERIFY_COMMAND_WORDS = [
|
|
12248
|
+
"test",
|
|
12249
|
+
"vitest",
|
|
12250
|
+
"jest",
|
|
12251
|
+
"pytest",
|
|
12252
|
+
"playwright",
|
|
12253
|
+
"build",
|
|
12254
|
+
"lint",
|
|
12255
|
+
"biome",
|
|
12256
|
+
"eslint",
|
|
12257
|
+
"tsc",
|
|
12258
|
+
"typecheck",
|
|
12259
|
+
"check"
|
|
12260
|
+
];
|
|
12261
|
+
var VERIFY_COMMAND_RE = new RegExp(`\\b(?:${VERIFY_COMMAND_WORDS.join("|")})\\b`);
|
|
12262
|
+
function classifyPhase(toolName, toolInput) {
|
|
12263
|
+
const ruled = PHASE_TOOL_RULES[toolName];
|
|
12264
|
+
if (ruled !== void 0)
|
|
12265
|
+
return ruled;
|
|
12266
|
+
if (toolName === "Bash") {
|
|
12267
|
+
const command = toolInput?.command;
|
|
12268
|
+
if (typeof command === "string" && VERIFY_COMMAND_RE.test(command))
|
|
12269
|
+
return "verify";
|
|
12270
|
+
}
|
|
12271
|
+
return "other";
|
|
12272
|
+
}
|
|
12273
|
+
function consolidatePhases(steps) {
|
|
12274
|
+
const nodes = [];
|
|
12275
|
+
for (const step of steps) {
|
|
12276
|
+
const phase = classifyPhase(step.toolName, step.toolInput);
|
|
12277
|
+
const last = nodes[nodes.length - 1];
|
|
12278
|
+
if (last !== void 0 && last.phase === phase) {
|
|
12279
|
+
last.steps.push(step);
|
|
12280
|
+
if (step.isError)
|
|
12281
|
+
last.failed = true;
|
|
12282
|
+
} else {
|
|
12283
|
+
const node = { phase, steps: [step], failed: step.isError };
|
|
12284
|
+
nodes.push(node);
|
|
12285
|
+
}
|
|
12286
|
+
}
|
|
12287
|
+
for (const node of nodes) {
|
|
12288
|
+
const first = node.steps[0];
|
|
12289
|
+
const lastStep = node.steps[node.steps.length - 1];
|
|
12290
|
+
if (first?.ts !== void 0)
|
|
12291
|
+
node.startTs = first.ts;
|
|
12292
|
+
const end = lastStep?.endTs ?? lastStep?.ts;
|
|
12293
|
+
if (end !== void 0)
|
|
12294
|
+
node.endTs = end;
|
|
12295
|
+
if (node.startTs !== void 0 && node.endTs !== void 0) {
|
|
12296
|
+
const start = Date.parse(node.startTs);
|
|
12297
|
+
const endMs = Date.parse(node.endTs);
|
|
12298
|
+
if (Number.isFinite(start) && Number.isFinite(endMs) && endMs >= start) {
|
|
12299
|
+
node.durationMs = endMs - start;
|
|
12300
|
+
}
|
|
12301
|
+
}
|
|
12302
|
+
}
|
|
12303
|
+
return nodes;
|
|
12304
|
+
}
|
|
12305
|
+
|
|
12306
|
+
// packages/narrative/dist/tail.js
|
|
12307
|
+
import { open, stat } from "node:fs/promises";
|
|
12308
|
+
var NEWLINE = 10;
|
|
12309
|
+
function followTranscript(path, onLine, options = {}) {
|
|
12310
|
+
const intervalMs = options.intervalMs ?? 500;
|
|
12311
|
+
let offset = 0;
|
|
12312
|
+
let remainder = Buffer.alloc(0);
|
|
12313
|
+
let closed = false;
|
|
12314
|
+
let timer;
|
|
12315
|
+
async function poll() {
|
|
12316
|
+
try {
|
|
12317
|
+
const stats = await stat(path);
|
|
12318
|
+
if (stats.size < offset) {
|
|
12319
|
+
offset = 0;
|
|
12320
|
+
remainder = Buffer.alloc(0);
|
|
12321
|
+
}
|
|
12322
|
+
if (stats.size > offset) {
|
|
12323
|
+
const handle2 = await open(path, "r");
|
|
12324
|
+
try {
|
|
12325
|
+
const length = stats.size - offset;
|
|
12326
|
+
const buffer = Buffer.alloc(length);
|
|
12327
|
+
const { bytesRead } = await handle2.read(buffer, 0, length, offset);
|
|
12328
|
+
offset += bytesRead;
|
|
12329
|
+
emitLines(buffer.subarray(0, bytesRead));
|
|
12330
|
+
} finally {
|
|
12331
|
+
await handle2.close();
|
|
12332
|
+
}
|
|
12333
|
+
}
|
|
12334
|
+
} catch {
|
|
12335
|
+
}
|
|
12336
|
+
if (!closed)
|
|
12337
|
+
timer = setTimeout(poll, intervalMs);
|
|
12338
|
+
}
|
|
12339
|
+
function emitLines(chunk) {
|
|
12340
|
+
let data = remainder.length > 0 ? Buffer.concat([remainder, chunk]) : chunk;
|
|
12341
|
+
let newlineAt = data.indexOf(NEWLINE);
|
|
12342
|
+
while (newlineAt !== -1) {
|
|
12343
|
+
const line = data.subarray(0, newlineAt).toString("utf8");
|
|
12344
|
+
data = data.subarray(newlineAt + 1);
|
|
12345
|
+
if (!closed)
|
|
12346
|
+
onLine(line);
|
|
12347
|
+
newlineAt = data.indexOf(NEWLINE);
|
|
12348
|
+
}
|
|
12349
|
+
remainder = Buffer.from(data);
|
|
12350
|
+
}
|
|
12351
|
+
void poll();
|
|
12352
|
+
return {
|
|
12353
|
+
close() {
|
|
12354
|
+
closed = true;
|
|
12355
|
+
if (timer !== void 0)
|
|
12356
|
+
clearTimeout(timer);
|
|
12357
|
+
}
|
|
12358
|
+
};
|
|
12359
|
+
}
|
|
12360
|
+
|
|
12361
|
+
// packages/narrative/dist/textlist.js
|
|
12362
|
+
var CHECKLIST_LINE = /^\s*[-*]\s+\[([ xX])\]\s+(.+?)\s*$/;
|
|
12363
|
+
function parseTextChecklist(text) {
|
|
12364
|
+
const items = [];
|
|
12365
|
+
for (const line of text.split("\n")) {
|
|
12366
|
+
const match = CHECKLIST_LINE.exec(line);
|
|
12367
|
+
if (match === null)
|
|
12368
|
+
continue;
|
|
12369
|
+
const [, mark, content] = match;
|
|
12370
|
+
if (content === void 0 || content === "")
|
|
12371
|
+
continue;
|
|
12372
|
+
items.push({ content, checked: mark !== " " });
|
|
12373
|
+
}
|
|
12374
|
+
return items;
|
|
12375
|
+
}
|
|
12376
|
+
|
|
12377
|
+
// packages/narrative/dist/todos.js
|
|
12378
|
+
var TODO_STATUSES = ["pending", "in_progress", "completed"];
|
|
12379
|
+
function isTodoStatus(value) {
|
|
12380
|
+
return typeof value === "string" && TODO_STATUSES.includes(value);
|
|
12381
|
+
}
|
|
12382
|
+
function extractTodoSnapshots(lines) {
|
|
12383
|
+
const snapshots = [];
|
|
12384
|
+
const tasks = /* @__PURE__ */ new Map();
|
|
12385
|
+
const createResultIds = buildTaskCreateResultIndex(lines);
|
|
12386
|
+
for (const line of lines) {
|
|
12387
|
+
if (line.kind !== "assistant")
|
|
12388
|
+
continue;
|
|
12389
|
+
for (const block of line.blocks) {
|
|
12390
|
+
if (block.kind !== "tool_use")
|
|
12391
|
+
continue;
|
|
12392
|
+
let changed = false;
|
|
12393
|
+
if (block.name === "TodoWrite") {
|
|
12394
|
+
changed = applyTodoWrite(block.input, tasks);
|
|
12395
|
+
} else if (block.name === "TaskCreate") {
|
|
12396
|
+
changed = applyTaskCreate(block.input, block.id, createResultIds, tasks);
|
|
12397
|
+
} else if (block.name === "TaskUpdate") {
|
|
12398
|
+
changed = applyTaskUpdate(block.input, tasks);
|
|
12399
|
+
}
|
|
12400
|
+
if (changed)
|
|
12401
|
+
snapshots.push(snapshotOf(tasks, line.timestamp));
|
|
12402
|
+
}
|
|
12403
|
+
}
|
|
12404
|
+
return snapshots;
|
|
12405
|
+
}
|
|
12406
|
+
function buildTaskCreateResultIndex(lines) {
|
|
12407
|
+
const index = /* @__PURE__ */ new Map();
|
|
12408
|
+
for (const line of lines) {
|
|
12409
|
+
if (line.kind !== "user")
|
|
12410
|
+
continue;
|
|
12411
|
+
const result = line.toolUseResult;
|
|
12412
|
+
if (!isRecord2(result) || !isRecord2(result.task) || typeof result.task.id !== "string")
|
|
12413
|
+
continue;
|
|
12414
|
+
for (const toolResult of line.toolResults)
|
|
12415
|
+
index.set(toolResult.toolUseId, result.task.id);
|
|
12416
|
+
}
|
|
12417
|
+
return index;
|
|
12418
|
+
}
|
|
12419
|
+
function applyTodoWrite(input, tasks) {
|
|
12420
|
+
const todos = input.todos;
|
|
12421
|
+
if (!Array.isArray(todos))
|
|
12422
|
+
return false;
|
|
12423
|
+
tasks.clear();
|
|
12424
|
+
for (const raw of todos) {
|
|
12425
|
+
if (!isRecord2(raw) || typeof raw.content !== "string" || !isTodoStatus(raw.status))
|
|
12426
|
+
continue;
|
|
12427
|
+
const item = { id: raw.content, content: raw.content, status: raw.status };
|
|
12428
|
+
if (typeof raw.activeForm === "string")
|
|
12429
|
+
item.activeForm = raw.activeForm;
|
|
12430
|
+
tasks.set(item.id, item);
|
|
12431
|
+
}
|
|
12432
|
+
return true;
|
|
12433
|
+
}
|
|
12434
|
+
function applyTaskCreate(input, toolUseId, createResultIds, tasks) {
|
|
12435
|
+
if (typeof input.subject !== "string")
|
|
12436
|
+
return false;
|
|
12437
|
+
const id = createResultIds.get(toolUseId) ?? toolUseId;
|
|
12438
|
+
const item = { id, content: input.subject, status: "pending" };
|
|
12439
|
+
if (typeof input.activeForm === "string")
|
|
12440
|
+
item.activeForm = input.activeForm;
|
|
12441
|
+
tasks.set(id, item);
|
|
12442
|
+
return true;
|
|
12443
|
+
}
|
|
12444
|
+
function applyTaskUpdate(input, tasks) {
|
|
12445
|
+
if (typeof input.taskId !== "string")
|
|
12446
|
+
return false;
|
|
12447
|
+
const existing = tasks.get(input.taskId);
|
|
12448
|
+
if (existing === void 0)
|
|
12449
|
+
return false;
|
|
12450
|
+
if (isTodoStatus(input.status) && input.status !== existing.status) {
|
|
12451
|
+
existing.status = input.status;
|
|
12452
|
+
return true;
|
|
12453
|
+
}
|
|
12454
|
+
return false;
|
|
12455
|
+
}
|
|
12456
|
+
function snapshotOf(tasks, ts) {
|
|
12457
|
+
const snapshot = { todos: [...tasks.values()].map((item) => ({ ...item })) };
|
|
12458
|
+
if (ts !== void 0)
|
|
12459
|
+
snapshot.ts = ts;
|
|
12460
|
+
return snapshot;
|
|
12461
|
+
}
|
|
12462
|
+
function deriveTodoTimeline(snapshots) {
|
|
12463
|
+
const entries = /* @__PURE__ */ new Map();
|
|
12464
|
+
for (const snapshot of snapshots) {
|
|
12465
|
+
const present = /* @__PURE__ */ new Set();
|
|
12466
|
+
for (const todo of snapshot.todos) {
|
|
12467
|
+
present.add(todo.id);
|
|
12468
|
+
const existing = entries.get(todo.id);
|
|
12469
|
+
if (existing === void 0) {
|
|
12470
|
+
const entry = {
|
|
12471
|
+
id: todo.id,
|
|
12472
|
+
content: todo.content,
|
|
12473
|
+
spans: [newSpan(todo.status, snapshot.ts)]
|
|
12474
|
+
};
|
|
12475
|
+
if (todo.activeForm !== void 0)
|
|
12476
|
+
entry.activeForm = todo.activeForm;
|
|
12477
|
+
entries.set(todo.id, entry);
|
|
12478
|
+
continue;
|
|
12479
|
+
}
|
|
12480
|
+
existing.content = todo.content;
|
|
12481
|
+
if (todo.activeForm !== void 0)
|
|
12482
|
+
existing.activeForm = todo.activeForm;
|
|
12483
|
+
const last = existing.spans[existing.spans.length - 1];
|
|
12484
|
+
if (last !== void 0 && last.to === void 0 && last.status !== todo.status) {
|
|
12485
|
+
if (snapshot.ts !== void 0)
|
|
12486
|
+
last.to = snapshot.ts;
|
|
12487
|
+
existing.spans.push(newSpan(todo.status, snapshot.ts));
|
|
12488
|
+
}
|
|
12489
|
+
}
|
|
12490
|
+
for (const [id, entry] of entries) {
|
|
12491
|
+
if (present.has(id))
|
|
12492
|
+
continue;
|
|
12493
|
+
const last = entry.spans[entry.spans.length - 1];
|
|
12494
|
+
if (last !== void 0 && last.to === void 0 && snapshot.ts !== void 0) {
|
|
12495
|
+
last.to = snapshot.ts;
|
|
12496
|
+
}
|
|
12497
|
+
}
|
|
12498
|
+
}
|
|
12499
|
+
return [...entries.values()];
|
|
12500
|
+
}
|
|
12501
|
+
function newSpan(status, from) {
|
|
12502
|
+
const span = { status };
|
|
12503
|
+
if (from !== void 0)
|
|
12504
|
+
span.from = from;
|
|
12505
|
+
return span;
|
|
12506
|
+
}
|
|
12507
|
+
|
|
12508
|
+
// packages/narrative/dist/usage.js
|
|
12509
|
+
function turnUsage(line) {
|
|
12510
|
+
const usage = line.usage;
|
|
12511
|
+
if (usage === void 0)
|
|
12512
|
+
return void 0;
|
|
12513
|
+
const inputTokens = finiteNumber(usage.input_tokens);
|
|
12514
|
+
const cacheCreationInputTokens = finiteNumber(usage.cache_creation_input_tokens);
|
|
12515
|
+
const cacheReadInputTokens = finiteNumber(usage.cache_read_input_tokens);
|
|
12516
|
+
const outputTokens = finiteNumber(usage.output_tokens);
|
|
12517
|
+
if (inputTokens === void 0 || cacheCreationInputTokens === void 0 || cacheReadInputTokens === void 0 || outputTokens === void 0) {
|
|
12518
|
+
return void 0;
|
|
12519
|
+
}
|
|
12520
|
+
const result = {
|
|
12521
|
+
inputTokens,
|
|
12522
|
+
cacheCreationInputTokens,
|
|
12523
|
+
cacheReadInputTokens,
|
|
12524
|
+
outputTokens,
|
|
12525
|
+
contextTotal: {
|
|
12526
|
+
kind: "exact",
|
|
12527
|
+
value: inputTokens + cacheCreationInputTokens + cacheReadInputTokens
|
|
12528
|
+
}
|
|
12529
|
+
};
|
|
12530
|
+
const breakdown = usage.cache_creation;
|
|
12531
|
+
if (isRecord2(breakdown)) {
|
|
12532
|
+
const fiveMinute = finiteNumber(breakdown.ephemeral_5m_input_tokens);
|
|
12533
|
+
const oneHour = finiteNumber(breakdown.ephemeral_1h_input_tokens);
|
|
12534
|
+
if (fiveMinute !== void 0)
|
|
12535
|
+
result.cacheCreation5mTokens = fiveMinute;
|
|
12536
|
+
if (oneHour !== void 0)
|
|
12537
|
+
result.cacheCreation1hTokens = oneHour;
|
|
12538
|
+
}
|
|
12539
|
+
return result;
|
|
12540
|
+
}
|
|
12541
|
+
function finiteNumber(value) {
|
|
12542
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
12543
|
+
}
|
|
12544
|
+
var MODEL_PRICES = {
|
|
12545
|
+
"claude-fable-5": { inputPerMTok: 10, outputPerMTok: 50 },
|
|
12546
|
+
"claude-mythos-5": { inputPerMTok: 10, outputPerMTok: 50 },
|
|
12547
|
+
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
12548
|
+
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
12549
|
+
"claude-opus-4-7": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
12550
|
+
"claude-opus-4-6": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
12551
|
+
"claude-sonnet-5": { inputPerMTok: 3, outputPerMTok: 15 },
|
|
12552
|
+
"claude-sonnet-4-6": { inputPerMTok: 3, outputPerMTok: 15 },
|
|
12553
|
+
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 }
|
|
12554
|
+
};
|
|
12555
|
+
var CACHE_READ_MULTIPLIER = 0.1;
|
|
12556
|
+
var CACHE_WRITE_5M_MULTIPLIER = 1.25;
|
|
12557
|
+
var CACHE_WRITE_1H_MULTIPLIER = 2;
|
|
12558
|
+
function costUsd(usage, model) {
|
|
12559
|
+
const price = MODEL_PRICES[model];
|
|
12560
|
+
if (price === void 0)
|
|
12561
|
+
return void 0;
|
|
12562
|
+
const fiveMinute = usage.cacheCreation5mTokens;
|
|
12563
|
+
const oneHour = usage.cacheCreation1hTokens;
|
|
12564
|
+
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;
|
|
12565
|
+
return (usage.inputTokens * price.inputPerMTok + usage.cacheReadInputTokens * price.inputPerMTok * CACHE_READ_MULTIPLIER + cacheWriteTokensPriced * price.inputPerMTok + usage.outputTokens * price.outputPerMTok) / 1e6;
|
|
12566
|
+
}
|
|
12567
|
+
|
|
12568
|
+
// apps/cli/dist/narrative-service.js
|
|
12569
|
+
var MAX_SESSIONS = 8;
|
|
12570
|
+
var ENDED_AFTER_STOP_MS = 10 * 6e4;
|
|
12571
|
+
var TODO_TOOLS = /* @__PURE__ */ new Set(["TaskCreate", "TaskUpdate", "TodoWrite"]);
|
|
12572
|
+
var DETAIL_LIMIT = 80;
|
|
12573
|
+
var QUOTE_LIMIT = 160;
|
|
12574
|
+
var ERROR_BRIEF_LIMIT = 60;
|
|
12575
|
+
function createNarrativeService(options = {}) {
|
|
12576
|
+
const follow = options.followTranscripts ?? true;
|
|
12577
|
+
const intervalMs = options.intervalMs ?? 500;
|
|
12578
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
12579
|
+
const harnesses = /* @__PURE__ */ new Map();
|
|
12580
|
+
let closed = false;
|
|
12581
|
+
function sessionsOf(harness) {
|
|
12582
|
+
let sessions = harnesses.get(harness);
|
|
12583
|
+
if (sessions === void 0) {
|
|
12584
|
+
sessions = /* @__PURE__ */ new Map();
|
|
12585
|
+
harnesses.set(harness, sessions);
|
|
12586
|
+
}
|
|
12587
|
+
return sessions;
|
|
12588
|
+
}
|
|
12589
|
+
function newSession(id) {
|
|
12590
|
+
return {
|
|
12591
|
+
id,
|
|
12592
|
+
lines: [],
|
|
12593
|
+
follows: /* @__PURE__ */ new Map(),
|
|
12594
|
+
hookIntervals: [],
|
|
12595
|
+
waiting: null,
|
|
12596
|
+
waitEpisodes: [],
|
|
12597
|
+
dirty: true
|
|
12598
|
+
};
|
|
12599
|
+
}
|
|
12600
|
+
function closeSession(session) {
|
|
12601
|
+
for (const handle2 of session.follows.values())
|
|
12602
|
+
handle2.close();
|
|
12603
|
+
session.follows.clear();
|
|
12604
|
+
if (session.subagentTimer !== void 0)
|
|
12605
|
+
clearInterval(session.subagentTimer);
|
|
12606
|
+
session.subagentTimer = void 0;
|
|
12607
|
+
}
|
|
12608
|
+
function evictOne(sessions, nowIso) {
|
|
12609
|
+
let victimId;
|
|
12610
|
+
let victimAt = "\uFFFF";
|
|
12611
|
+
let victimEnded = false;
|
|
12612
|
+
for (const [id, session] of sessions) {
|
|
12613
|
+
const ended = isEnded(session, nowIso);
|
|
12614
|
+
const at = lastActivityOf(session);
|
|
12615
|
+
const better = victimEnded === ended ? at < victimAt : ended;
|
|
12616
|
+
if (victimId === void 0 || better) {
|
|
12617
|
+
victimId = id;
|
|
12618
|
+
victimAt = at;
|
|
12619
|
+
victimEnded = ended;
|
|
12620
|
+
}
|
|
12621
|
+
}
|
|
12622
|
+
if (victimId !== void 0) {
|
|
12623
|
+
const victim = sessions.get(victimId);
|
|
12624
|
+
if (victim)
|
|
12625
|
+
closeSession(victim);
|
|
12626
|
+
sessions.delete(victimId);
|
|
12627
|
+
}
|
|
12628
|
+
}
|
|
12629
|
+
function sessionOf(harness, id) {
|
|
12630
|
+
const sessions = sessionsOf(harness);
|
|
12631
|
+
let session = sessions.get(id);
|
|
12632
|
+
if (session === void 0) {
|
|
12633
|
+
if (sessions.size >= MAX_SESSIONS)
|
|
12634
|
+
evictOne(sessions, now().toISOString());
|
|
12635
|
+
session = newSession(id);
|
|
12636
|
+
sessions.set(id, session);
|
|
12637
|
+
}
|
|
12638
|
+
return session;
|
|
12639
|
+
}
|
|
12640
|
+
function resetTranscript(session, path) {
|
|
12641
|
+
closeSession(session);
|
|
12642
|
+
session.lines = [];
|
|
12643
|
+
session.hookIntervals = [];
|
|
12644
|
+
session.mockNote = void 0;
|
|
12645
|
+
session.transcriptPath = path;
|
|
12646
|
+
session.dirty = true;
|
|
12647
|
+
}
|
|
12648
|
+
function startFollowing(session, path) {
|
|
12649
|
+
if (closed || session.follows.has(path))
|
|
12650
|
+
return;
|
|
12651
|
+
session.follows.set(path, followTranscript(path, (line) => {
|
|
12652
|
+
session.lines.push(parseTranscriptLine(line));
|
|
12653
|
+
session.dirty = true;
|
|
12654
|
+
}, { intervalMs }));
|
|
12655
|
+
}
|
|
12656
|
+
function startSubagentDiscovery(session, transcriptPath) {
|
|
12657
|
+
const sessionDir = transcriptPath.replace(/\.jsonl$/, "");
|
|
12658
|
+
if (sessionDir === transcriptPath)
|
|
12659
|
+
return;
|
|
12660
|
+
const subagentsDir = join4(sessionDir, "subagents");
|
|
12661
|
+
const poll = async () => {
|
|
12662
|
+
try {
|
|
12663
|
+
const entries = await readdir(subagentsDir);
|
|
12664
|
+
for (const entry of entries) {
|
|
12665
|
+
if (/^agent-.*\.jsonl$/.test(entry))
|
|
12666
|
+
startFollowing(session, join4(subagentsDir, entry));
|
|
12667
|
+
}
|
|
12668
|
+
} catch {
|
|
12669
|
+
}
|
|
12670
|
+
};
|
|
12671
|
+
session.subagentTimer = setInterval(() => void poll(), intervalMs);
|
|
12672
|
+
void poll();
|
|
12673
|
+
}
|
|
12674
|
+
function handleEvent(event) {
|
|
12675
|
+
if (closed || !HARNESS_IDS.includes(event.harness))
|
|
12676
|
+
return;
|
|
12677
|
+
const payload = event.payload;
|
|
12678
|
+
const name = payload.hook_event_name;
|
|
12679
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
|
|
12680
|
+
const sessionId = typeof payload.session_id === "string" ? payload.session_id : void 0;
|
|
12681
|
+
const key = sessionId ?? transcriptPath ?? "__nosession__";
|
|
12682
|
+
const session = sessionOf(event.harness, key);
|
|
12683
|
+
session.lastActivity = event.received_at;
|
|
12684
|
+
const cwd = typeof payload.cwd === "string" ? payload.cwd : void 0;
|
|
12685
|
+
if (cwd !== void 0) {
|
|
12686
|
+
const base = pathBasename(cwd);
|
|
12687
|
+
if (base !== "")
|
|
12688
|
+
session.label = base;
|
|
12689
|
+
}
|
|
12690
|
+
if (follow && transcriptPath !== void 0) {
|
|
12691
|
+
if (session.transcriptPath !== transcriptPath) {
|
|
12692
|
+
resetTranscript(session, transcriptPath);
|
|
12693
|
+
startFollowing(session, transcriptPath);
|
|
12694
|
+
startSubagentDiscovery(session, transcriptPath);
|
|
12695
|
+
}
|
|
12696
|
+
}
|
|
12697
|
+
if (name === "SubagentStart") {
|
|
12698
|
+
const agentId = agentIdOf(payload);
|
|
12699
|
+
if (agentId !== void 0) {
|
|
12700
|
+
session.hookIntervals.push({ agentId, startTs: event.received_at });
|
|
12701
|
+
}
|
|
12702
|
+
} else if (name === "SubagentStop") {
|
|
12703
|
+
const agentId = agentIdOf(payload);
|
|
12704
|
+
if (agentId !== void 0) {
|
|
12705
|
+
const open2 = [...session.hookIntervals].reverse().find((i) => i.agentId === agentId && i.endTs === void 0);
|
|
12706
|
+
if (open2)
|
|
12707
|
+
open2.endTs = event.received_at;
|
|
12708
|
+
}
|
|
12709
|
+
}
|
|
12710
|
+
if (name === "PreToolUse" && typeof payload.tool_name === "string") {
|
|
12711
|
+
session.pendingHookTool = {
|
|
12712
|
+
toolName: payload.tool_name,
|
|
12713
|
+
detail: hookToolDetail(payload.tool_input),
|
|
12714
|
+
since: event.received_at
|
|
12715
|
+
};
|
|
12716
|
+
} else if (name === "PostToolUse" && typeof payload.tool_name === "string" || name === "Stop" || name === "SessionEnd") {
|
|
12717
|
+
session.pendingHookTool = void 0;
|
|
12718
|
+
}
|
|
12719
|
+
if (name === "PermissionRequest" || name === "Notification") {
|
|
12720
|
+
const kind = name === "PermissionRequest" ? "permission" : "notification";
|
|
12721
|
+
const message = waitingMessage(payload, kind === "permission" ? "\u7B49\u5F85\u6388\u6743" : "\u7B49\u5F85\u56DE\u5E94");
|
|
12722
|
+
if (session.waiting === null) {
|
|
12723
|
+
session.waiting = { kind, message, since: event.received_at };
|
|
12724
|
+
} else if (kind === "permission" || session.waiting.kind === kind) {
|
|
12725
|
+
session.waiting = { kind, message, since: session.waiting.since };
|
|
12726
|
+
}
|
|
12727
|
+
} else {
|
|
12728
|
+
if (session.waiting !== null) {
|
|
12729
|
+
session.waitEpisodes.push({
|
|
12730
|
+
...session.waiting,
|
|
12731
|
+
until: event.received_at,
|
|
12732
|
+
resolvedBy: name
|
|
12733
|
+
});
|
|
12734
|
+
session.waiting = null;
|
|
12735
|
+
}
|
|
12736
|
+
}
|
|
12737
|
+
if (name === "SessionEnd") {
|
|
12738
|
+
session.endedAt = event.received_at;
|
|
12739
|
+
} else if (name === "Stop") {
|
|
12740
|
+
session.stopAt = event.received_at;
|
|
12741
|
+
} else {
|
|
12742
|
+
session.stopAt = void 0;
|
|
12743
|
+
}
|
|
12744
|
+
session.lastEvent = { name, at: event.received_at };
|
|
12745
|
+
session.dirty = true;
|
|
12746
|
+
}
|
|
12747
|
+
function seedFixture(harness, rawLines, mockNote) {
|
|
12748
|
+
const lines = rawLines.filter((l) => l.trim() !== "").map((l) => parseTranscriptLine(l));
|
|
12749
|
+
const sessionLine = lines.find((l) => l.kind !== "unknown" && typeof l.sessionId === "string");
|
|
12750
|
+
const id = sessionLine?.sessionId ?? "__fixture__";
|
|
12751
|
+
const session = sessionOf(harness, id);
|
|
12752
|
+
session.lines = lines;
|
|
12753
|
+
session.mockNote = mockNote;
|
|
12754
|
+
for (const line of lines) {
|
|
12755
|
+
if (line.kind === "unknown")
|
|
12756
|
+
continue;
|
|
12757
|
+
const cwd = line.raw.cwd;
|
|
12758
|
+
if (typeof cwd === "string") {
|
|
12759
|
+
const base = pathBasename(cwd);
|
|
12760
|
+
if (base !== "")
|
|
12761
|
+
session.label = base;
|
|
12762
|
+
break;
|
|
12763
|
+
}
|
|
12764
|
+
}
|
|
12765
|
+
session.dirty = true;
|
|
12766
|
+
}
|
|
12767
|
+
function deriveCached(harness, session) {
|
|
12768
|
+
if (!session.dirty && session.cached !== void 0)
|
|
12769
|
+
return session.cached;
|
|
12770
|
+
session.cached = derive(harness, session, now().toISOString());
|
|
12771
|
+
session.dirty = false;
|
|
12772
|
+
return session.cached;
|
|
12773
|
+
}
|
|
12774
|
+
function getOverview(harness) {
|
|
12775
|
+
const nowIso = now().toISOString();
|
|
12776
|
+
const sessions = sessionsOf(harness);
|
|
12777
|
+
const infos = [...sessions.values()].map((session) => ({
|
|
12778
|
+
id: session.id,
|
|
12779
|
+
label: session.label ?? session.id.slice(0, 8),
|
|
12780
|
+
lastActivity: lastActivityOf(session),
|
|
12781
|
+
waiting: session.waiting !== null,
|
|
12782
|
+
ended: isEnded(session, nowIso)
|
|
12783
|
+
})).sort((a, b) => a.lastActivity < b.lastActivity ? 1 : a.lastActivity > b.lastActivity ? -1 : 0);
|
|
12784
|
+
const states = {};
|
|
12785
|
+
for (const [id, session] of sessions)
|
|
12786
|
+
states[id] = deriveCached(harness, session);
|
|
12787
|
+
return { harness, sessions: infos, states };
|
|
12788
|
+
}
|
|
12789
|
+
function getState(harness) {
|
|
12790
|
+
const overview = getOverview(harness);
|
|
12791
|
+
const first = overview.sessions[0];
|
|
12792
|
+
if (first !== void 0) {
|
|
12793
|
+
const state2 = overview.states[first.id];
|
|
12794
|
+
if (state2 !== void 0)
|
|
12795
|
+
return state2;
|
|
12796
|
+
}
|
|
12797
|
+
return emptyState(harness, now().toISOString());
|
|
12798
|
+
}
|
|
12799
|
+
function close() {
|
|
12800
|
+
closed = true;
|
|
12801
|
+
for (const sessions of harnesses.values()) {
|
|
12802
|
+
for (const session of sessions.values())
|
|
12803
|
+
closeSession(session);
|
|
12804
|
+
}
|
|
12805
|
+
}
|
|
12806
|
+
return { handleEvent, getState, getOverview, seedFixture, close };
|
|
12807
|
+
}
|
|
12808
|
+
function lastActivityOf(session) {
|
|
12809
|
+
if (session.lastActivity !== void 0)
|
|
12810
|
+
return session.lastActivity;
|
|
12811
|
+
let latest = "";
|
|
12812
|
+
for (const line of session.lines) {
|
|
12813
|
+
if (line.kind === "unknown")
|
|
12814
|
+
continue;
|
|
12815
|
+
const ts = line.timestamp;
|
|
12816
|
+
if (ts !== void 0 && ts > latest)
|
|
12817
|
+
latest = ts;
|
|
12818
|
+
}
|
|
12819
|
+
return latest;
|
|
12820
|
+
}
|
|
12821
|
+
function isEnded(session, nowIso) {
|
|
12822
|
+
if (session.endedAt !== void 0)
|
|
12823
|
+
return true;
|
|
12824
|
+
if (session.stopAt === void 0)
|
|
12825
|
+
return false;
|
|
12826
|
+
const last = Date.parse(lastActivityOf(session));
|
|
12827
|
+
const nowMs = Date.parse(nowIso);
|
|
12828
|
+
if (!Number.isFinite(last) || !Number.isFinite(nowMs))
|
|
12829
|
+
return false;
|
|
12830
|
+
return nowMs - last >= ENDED_AFTER_STOP_MS;
|
|
12831
|
+
}
|
|
12832
|
+
function emptyState(harness, nowIso) {
|
|
12833
|
+
return {
|
|
12834
|
+
harness,
|
|
12835
|
+
sessionId: null,
|
|
12836
|
+
transcriptPath: null,
|
|
12837
|
+
todos: [],
|
|
12838
|
+
traces: [],
|
|
12839
|
+
turnUsages: [],
|
|
12840
|
+
currentAction: null,
|
|
12841
|
+
waiting: null,
|
|
12842
|
+
agentQuote: null,
|
|
12843
|
+
lastPrompt: null,
|
|
12844
|
+
updatedAt: nowIso
|
|
12845
|
+
};
|
|
12846
|
+
}
|
|
12847
|
+
function pathBasename(path) {
|
|
12848
|
+
const parts = path.split(/[\\/]/).filter((p) => p !== "");
|
|
12849
|
+
return parts[parts.length - 1] ?? "";
|
|
12850
|
+
}
|
|
12851
|
+
function agentIdOf(payload) {
|
|
12852
|
+
const id = payload.agent_id ?? payload.agentId;
|
|
12853
|
+
return typeof id === "string" && id !== "" ? id : void 0;
|
|
12854
|
+
}
|
|
12855
|
+
function waitingMessage(payload, fallback) {
|
|
12856
|
+
if (typeof payload.message === "string" && payload.message !== "")
|
|
12857
|
+
return payload.message;
|
|
12858
|
+
if (typeof payload.tool_name === "string" && payload.tool_name !== "") {
|
|
12859
|
+
return `${fallback}:${payload.tool_name}`;
|
|
12860
|
+
}
|
|
12861
|
+
return fallback;
|
|
12862
|
+
}
|
|
12863
|
+
function derive(harness, session, nowIso) {
|
|
12864
|
+
const lines = sortedByTimestamp(session.lines);
|
|
12865
|
+
const timeline = deriveTodoTimeline(extractTodoSnapshots(lines));
|
|
12866
|
+
const allSteps = extractSteps(lines);
|
|
12867
|
+
const steps = allSteps.filter((s) => !TODO_TOOLS.has(s.toolName));
|
|
12868
|
+
const intervals = [...deriveSubagentIntervals(lines), ...session.hookIntervals];
|
|
12869
|
+
const attributed = attributeSteps(steps, timeline, intervals);
|
|
12870
|
+
const results = collectResults(lines);
|
|
12871
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
12872
|
+
for (const { step, attribution } of attributed) {
|
|
12873
|
+
const key = attribution.kind === "todo" ? attribution.todoId : null;
|
|
12874
|
+
const bucket = buckets.get(key);
|
|
12875
|
+
if (bucket)
|
|
12876
|
+
bucket.push(step);
|
|
12877
|
+
else
|
|
12878
|
+
buckets.set(key, [step]);
|
|
12879
|
+
}
|
|
12880
|
+
const waitNodes = /* @__PURE__ */ new Map();
|
|
12881
|
+
const pushWait = (episode, open2) => {
|
|
12882
|
+
const key = todoInProgressAt(timeline, episode.since);
|
|
12883
|
+
const node = {
|
|
12884
|
+
phase: "wait",
|
|
12885
|
+
label: episode.kind === "permission" ? "\u7B49\u5F85\u6388\u6743" : "\u7B49\u5F85\u56DE\u5E94",
|
|
12886
|
+
steps: [],
|
|
12887
|
+
startTs: episode.since,
|
|
12888
|
+
failed: false,
|
|
12889
|
+
wait: { kind: episode.kind, message: episode.message }
|
|
12890
|
+
};
|
|
12891
|
+
if (!open2) {
|
|
12892
|
+
const closed = episode;
|
|
12893
|
+
node.endTs = closed.until;
|
|
12894
|
+
const ms = Date.parse(closed.until) - Date.parse(closed.since);
|
|
12895
|
+
if (Number.isFinite(ms) && ms >= 0)
|
|
12896
|
+
node.durationMs = ms;
|
|
12897
|
+
if (node.wait)
|
|
12898
|
+
node.wait.resolvedBy = closed.resolvedBy;
|
|
12899
|
+
}
|
|
12900
|
+
const list = waitNodes.get(key);
|
|
12901
|
+
if (list)
|
|
12902
|
+
list.push(node);
|
|
12903
|
+
else
|
|
12904
|
+
waitNodes.set(key, [node]);
|
|
12905
|
+
};
|
|
12906
|
+
for (const episode of session.waitEpisodes)
|
|
12907
|
+
pushWait(episode, false);
|
|
12908
|
+
if (session.waiting !== null)
|
|
12909
|
+
pushWait(session.waiting, true);
|
|
12910
|
+
const traces = [];
|
|
12911
|
+
const agentsByTodo = /* @__PURE__ */ new Map();
|
|
12912
|
+
const orderedKeys = timeline.map((entry) => entry.id).filter((id) => buckets.has(id) || waitNodes.has(id));
|
|
12913
|
+
if (buckets.has(null) || waitNodes.has(null))
|
|
12914
|
+
orderedKeys.push(null);
|
|
12915
|
+
for (const key of orderedKeys) {
|
|
12916
|
+
const bucketSteps = buckets.get(key) ?? [];
|
|
12917
|
+
const nodes = consolidatePhases(bucketSteps).map((node) => ({
|
|
12918
|
+
phase: node.phase,
|
|
12919
|
+
label: PHASE_LABELS[node.phase],
|
|
12920
|
+
steps: node.steps.map((step) => stepView(step, results)),
|
|
12921
|
+
...node.startTs !== void 0 ? { startTs: node.startTs } : {},
|
|
12922
|
+
...node.endTs !== void 0 ? { endTs: node.endTs } : {},
|
|
12923
|
+
...node.durationMs !== void 0 ? { durationMs: node.durationMs } : {},
|
|
12924
|
+
failed: node.failed,
|
|
12925
|
+
...nodeEstTokens(node.steps, results)
|
|
12926
|
+
}));
|
|
12927
|
+
for (const waitNode of waitNodes.get(key) ?? [])
|
|
12928
|
+
insertByStartTs(nodes, waitNode);
|
|
12929
|
+
traces.push({ todoId: key, nodes });
|
|
12930
|
+
if (key !== null) {
|
|
12931
|
+
const agents = /* @__PURE__ */ new Set();
|
|
12932
|
+
for (const step of bucketSteps)
|
|
12933
|
+
if (step.agentId !== void 0)
|
|
12934
|
+
agents.add(step.agentId);
|
|
12935
|
+
agentsByTodo.set(key, agents);
|
|
12936
|
+
}
|
|
12937
|
+
}
|
|
12938
|
+
const todos = timeline.map((entry) => {
|
|
12939
|
+
const last = entry.spans[entry.spans.length - 1];
|
|
12940
|
+
return {
|
|
12941
|
+
id: entry.id,
|
|
12942
|
+
content: entry.content,
|
|
12943
|
+
...entry.activeForm !== void 0 ? { activeForm: entry.activeForm } : {},
|
|
12944
|
+
status: last?.status ?? "pending",
|
|
12945
|
+
agents: [...agentsByTodo.get(entry.id) ?? []]
|
|
12946
|
+
};
|
|
12947
|
+
});
|
|
12948
|
+
const textTodos = todos.length === 0 ? deriveTextTodos(lines) : void 0;
|
|
12949
|
+
return {
|
|
12950
|
+
harness,
|
|
12951
|
+
sessionId: session.id,
|
|
12952
|
+
transcriptPath: session.transcriptPath ?? null,
|
|
12953
|
+
...session.mockNote !== void 0 ? { __mock_note: session.mockNote } : {},
|
|
12954
|
+
todos,
|
|
12955
|
+
...textTodos !== void 0 && textTodos.length > 0 ? { textTodos } : {},
|
|
12956
|
+
traces,
|
|
12957
|
+
turnUsages: deriveTurnUsages(lines),
|
|
12958
|
+
currentAction: deriveCurrentAction(steps, session.lastEvent, session.pendingHookTool),
|
|
12959
|
+
waiting: session.waiting,
|
|
12960
|
+
agentQuote: deriveAgentQuote(lines),
|
|
12961
|
+
lastPrompt: deriveLastPrompt(lines),
|
|
12962
|
+
updatedAt: nowIso
|
|
12963
|
+
};
|
|
12964
|
+
}
|
|
12965
|
+
function todoInProgressAt(timeline, ts) {
|
|
12966
|
+
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)));
|
|
12967
|
+
const only = active[0];
|
|
12968
|
+
return active.length === 1 && only !== void 0 ? only.id : null;
|
|
12969
|
+
}
|
|
12970
|
+
function insertByStartTs(nodes, node) {
|
|
12971
|
+
const ts = node.startTs;
|
|
12972
|
+
if (ts !== void 0) {
|
|
12973
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
12974
|
+
const at = nodes[i]?.startTs;
|
|
12975
|
+
if (at !== void 0 && at > ts) {
|
|
12976
|
+
nodes.splice(i, 0, node);
|
|
12977
|
+
return;
|
|
12978
|
+
}
|
|
12979
|
+
}
|
|
12980
|
+
}
|
|
12981
|
+
nodes.push(node);
|
|
12982
|
+
}
|
|
12983
|
+
function deriveTextTodos(lines) {
|
|
12984
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12985
|
+
const line = lines[i];
|
|
12986
|
+
if (line === void 0 || line.kind !== "assistant" || line.agentId !== void 0)
|
|
12987
|
+
continue;
|
|
12988
|
+
for (let j = line.blocks.length - 1; j >= 0; j--) {
|
|
12989
|
+
const block = line.blocks[j];
|
|
12990
|
+
if (block === void 0 || block.kind !== "text")
|
|
12991
|
+
continue;
|
|
12992
|
+
const items = parseTextChecklist(block.text);
|
|
12993
|
+
if (items.length > 0)
|
|
12994
|
+
return items;
|
|
12995
|
+
}
|
|
12996
|
+
}
|
|
12997
|
+
return void 0;
|
|
12998
|
+
}
|
|
12999
|
+
function sortedByTimestamp(lines) {
|
|
13000
|
+
return [...lines].sort((a, b) => {
|
|
13001
|
+
const ta = a.kind === "unknown" ? void 0 : a.timestamp;
|
|
13002
|
+
const tb = b.kind === "unknown" ? void 0 : b.timestamp;
|
|
13003
|
+
if (ta === void 0 || tb === void 0)
|
|
13004
|
+
return 0;
|
|
13005
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
13006
|
+
});
|
|
13007
|
+
}
|
|
13008
|
+
function collectResults(lines) {
|
|
13009
|
+
const results = /* @__PURE__ */ new Map();
|
|
13010
|
+
for (const line of lines) {
|
|
13011
|
+
if (line.kind !== "user")
|
|
13012
|
+
continue;
|
|
13013
|
+
for (const result of line.toolResults) {
|
|
13014
|
+
if (result.content === void 0)
|
|
13015
|
+
continue;
|
|
13016
|
+
const info = {};
|
|
13017
|
+
try {
|
|
13018
|
+
info.bytes = Buffer.byteLength(JSON.stringify(result.content), "utf8");
|
|
13019
|
+
} catch {
|
|
13020
|
+
}
|
|
13021
|
+
const brief = errorBrief(result.content);
|
|
13022
|
+
if (brief !== void 0)
|
|
13023
|
+
info.brief = brief;
|
|
13024
|
+
results.set(result.toolUseId, info);
|
|
13025
|
+
}
|
|
13026
|
+
}
|
|
13027
|
+
return results;
|
|
13028
|
+
}
|
|
13029
|
+
function stepView(step, results) {
|
|
13030
|
+
const view = {
|
|
13031
|
+
toolName: step.toolName,
|
|
13032
|
+
detail: stepDetail(step),
|
|
13033
|
+
isError: step.isError
|
|
13034
|
+
};
|
|
13035
|
+
if (step.ts !== void 0)
|
|
13036
|
+
view.ts = step.ts;
|
|
13037
|
+
if (step.endTs !== void 0)
|
|
13038
|
+
view.endTs = step.endTs;
|
|
13039
|
+
if (step.agentId !== void 0)
|
|
13040
|
+
view.agentId = step.agentId;
|
|
13041
|
+
if (step.isError) {
|
|
13042
|
+
const brief = results.get(step.toolUseId)?.brief;
|
|
13043
|
+
if (brief !== void 0)
|
|
13044
|
+
view.errorBrief = brief;
|
|
13045
|
+
}
|
|
13046
|
+
const est = stepEstTokens(step, results);
|
|
13047
|
+
if (est !== void 0)
|
|
13048
|
+
view.estTokens = est;
|
|
13049
|
+
return view;
|
|
13050
|
+
}
|
|
13051
|
+
function stepEstTokens(step, results) {
|
|
13052
|
+
let bytes = 0;
|
|
13053
|
+
try {
|
|
13054
|
+
bytes += Buffer.byteLength(JSON.stringify(step.toolInput), "utf8");
|
|
13055
|
+
} catch {
|
|
13056
|
+
}
|
|
13057
|
+
bytes += results.get(step.toolUseId)?.bytes ?? 0;
|
|
13058
|
+
if (bytes === 0)
|
|
13059
|
+
return void 0;
|
|
13060
|
+
return estimateTokens(bytes).value;
|
|
13061
|
+
}
|
|
13062
|
+
function nodeEstTokens(steps, results) {
|
|
13063
|
+
let sum = 0;
|
|
13064
|
+
let counted = false;
|
|
13065
|
+
for (const step of steps) {
|
|
13066
|
+
const est = stepEstTokens(step, results);
|
|
13067
|
+
if (est !== void 0) {
|
|
13068
|
+
sum += est;
|
|
13069
|
+
counted = true;
|
|
13070
|
+
}
|
|
13071
|
+
}
|
|
13072
|
+
return counted ? { estTokens: sum } : {};
|
|
13073
|
+
}
|
|
13074
|
+
var DETAIL_KEYS = ["command", "file_path", "description", "subject", "pattern", "prompt"];
|
|
13075
|
+
function inputDetail(input) {
|
|
13076
|
+
for (const key of DETAIL_KEYS) {
|
|
13077
|
+
const value = input[key];
|
|
13078
|
+
if (typeof value === "string" && value !== "")
|
|
13079
|
+
return truncate(value, DETAIL_LIMIT);
|
|
13080
|
+
}
|
|
13081
|
+
return "";
|
|
13082
|
+
}
|
|
13083
|
+
function stepDetail(step) {
|
|
13084
|
+
return inputDetail(step.toolInput);
|
|
13085
|
+
}
|
|
13086
|
+
function hookToolDetail(input) {
|
|
13087
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
13088
|
+
return "";
|
|
13089
|
+
return inputDetail(input);
|
|
13090
|
+
}
|
|
13091
|
+
function deriveTurnUsages(lines) {
|
|
13092
|
+
const byMessage = /* @__PURE__ */ new Map();
|
|
13093
|
+
let anonymous = 0;
|
|
13094
|
+
for (const line of lines) {
|
|
13095
|
+
if (line.kind !== "assistant")
|
|
13096
|
+
continue;
|
|
13097
|
+
const usage = turnUsage(line);
|
|
13098
|
+
if (usage === void 0)
|
|
13099
|
+
continue;
|
|
13100
|
+
const key = line.messageId ?? `__anon_${anonymous++}`;
|
|
13101
|
+
const view = {
|
|
13102
|
+
...line.messageId !== void 0 ? { messageId: line.messageId } : {},
|
|
13103
|
+
...line.model !== void 0 ? { model: line.model } : {},
|
|
13104
|
+
inputTokens: usage.inputTokens,
|
|
13105
|
+
cacheCreationInputTokens: usage.cacheCreationInputTokens,
|
|
13106
|
+
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
13107
|
+
outputTokens: usage.outputTokens,
|
|
13108
|
+
contextTotal: usage.contextTotal,
|
|
13109
|
+
...line.timestamp !== void 0 ? { ts: line.timestamp } : {}
|
|
13110
|
+
};
|
|
13111
|
+
if (line.model !== void 0) {
|
|
13112
|
+
const cost = costUsd(usage, line.model);
|
|
13113
|
+
if (cost !== void 0)
|
|
13114
|
+
view.costUsd = cost;
|
|
13115
|
+
}
|
|
13116
|
+
const existing = byMessage.get(key);
|
|
13117
|
+
if (existing?.ts !== void 0)
|
|
13118
|
+
view.ts = existing.ts;
|
|
13119
|
+
byMessage.set(key, view);
|
|
13120
|
+
}
|
|
13121
|
+
return [...byMessage.values()];
|
|
13122
|
+
}
|
|
13123
|
+
function deriveCurrentAction(steps, lastEvent, pendingHookTool) {
|
|
13124
|
+
const open2 = [...steps].reverse().find((s) => s.endTs === void 0);
|
|
13125
|
+
if (open2) {
|
|
13126
|
+
const action = { kind: "tool", toolName: open2.toolName, detail: stepDetail(open2) };
|
|
13127
|
+
if (open2.ts !== void 0)
|
|
13128
|
+
action.since = open2.ts;
|
|
13129
|
+
return action;
|
|
13130
|
+
}
|
|
13131
|
+
if (pendingHookTool) {
|
|
13132
|
+
return {
|
|
13133
|
+
kind: "tool",
|
|
13134
|
+
toolName: pendingHookTool.toolName,
|
|
13135
|
+
detail: pendingHookTool.detail,
|
|
13136
|
+
since: pendingHookTool.since
|
|
13137
|
+
};
|
|
13138
|
+
}
|
|
13139
|
+
if (lastEvent)
|
|
13140
|
+
return { kind: "event", name: lastEvent.name, at: lastEvent.at };
|
|
13141
|
+
return null;
|
|
13142
|
+
}
|
|
13143
|
+
function deriveAgentQuote(lines) {
|
|
13144
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
13145
|
+
const line = lines[i];
|
|
13146
|
+
if (line === void 0 || line.kind !== "assistant" || line.agentId !== void 0)
|
|
13147
|
+
continue;
|
|
13148
|
+
for (let j = line.blocks.length - 1; j >= 0; j--) {
|
|
13149
|
+
const block = line.blocks[j];
|
|
13150
|
+
if (block !== void 0 && block.kind === "text" && block.text.trim() !== "") {
|
|
13151
|
+
return truncate(block.text.trim(), QUOTE_LIMIT);
|
|
13152
|
+
}
|
|
13153
|
+
}
|
|
13154
|
+
}
|
|
13155
|
+
return null;
|
|
13156
|
+
}
|
|
13157
|
+
function deriveLastPrompt(lines) {
|
|
13158
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
13159
|
+
const line = lines[i];
|
|
13160
|
+
if (line === void 0 || line.kind !== "user" || line.agentId !== void 0)
|
|
13161
|
+
continue;
|
|
13162
|
+
if (line.toolResults.length > 0)
|
|
13163
|
+
continue;
|
|
13164
|
+
if (line.text === void 0 || line.text.trim() === "")
|
|
13165
|
+
continue;
|
|
13166
|
+
return truncate(line.text.trim(), DETAIL_LIMIT);
|
|
13167
|
+
}
|
|
13168
|
+
return null;
|
|
13169
|
+
}
|
|
13170
|
+
function truncate(text, limit) {
|
|
13171
|
+
return text.length <= limit ? text : `${text.slice(0, limit - 1)}\u2026`;
|
|
13172
|
+
}
|
|
13173
|
+
function errorBrief(content) {
|
|
13174
|
+
if (typeof content === "string") {
|
|
13175
|
+
const first = content.split("\n", 1)[0]?.trim();
|
|
13176
|
+
return first ? truncate(first, ERROR_BRIEF_LIMIT) : void 0;
|
|
13177
|
+
}
|
|
13178
|
+
if (Array.isArray(content)) {
|
|
13179
|
+
for (const block of content) {
|
|
13180
|
+
if (typeof block === "object" && block !== null && block.type === "text" && typeof block.text === "string") {
|
|
13181
|
+
return errorBrief(block.text);
|
|
13182
|
+
}
|
|
13183
|
+
}
|
|
13184
|
+
}
|
|
13185
|
+
return void 0;
|
|
11929
13186
|
}
|
|
11930
13187
|
|
|
11931
13188
|
// apps/cli/dist/registry.js
|
|
13189
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
11932
13190
|
async function importFactory(harness) {
|
|
11933
13191
|
const mod = await importAdapterModule(harness);
|
|
11934
13192
|
if (typeof mod.createAdapter !== "function") {
|
|
@@ -11947,7 +13205,7 @@ function importAdapterModule(harness) {
|
|
|
11947
13205
|
}
|
|
11948
13206
|
}
|
|
11949
13207
|
function fixtureScanOptions(harness) {
|
|
11950
|
-
if (!
|
|
13208
|
+
if (!existsSync10(fixturePath())) {
|
|
11951
13209
|
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
13210
|
}
|
|
11953
13211
|
switch (harness) {
|
|
@@ -11956,7 +13214,7 @@ function fixtureScanOptions(harness) {
|
|
|
11956
13214
|
case "codex":
|
|
11957
13215
|
return { homeDir: fixturePath("codex", "home") };
|
|
11958
13216
|
case "dsh":
|
|
11959
|
-
return {};
|
|
13217
|
+
return { homeDir: fixturePath("dsh", "home") };
|
|
11960
13218
|
}
|
|
11961
13219
|
}
|
|
11962
13220
|
async function loadHarness(harness, mode) {
|
|
@@ -11971,6 +13229,94 @@ async function loadHarness(harness, mode) {
|
|
|
11971
13229
|
|
|
11972
13230
|
// apps/cli/dist/scan.js
|
|
11973
13231
|
init_dist();
|
|
13232
|
+
|
|
13233
|
+
// apps/cli/dist/telemetry.js
|
|
13234
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
13235
|
+
import { dirname as dirname11, join as join17 } from "node:path";
|
|
13236
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13237
|
+
var DEFAULT_TELEMETRY_SERVER = "https://api.getwingman.dev";
|
|
13238
|
+
var SEND_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
13239
|
+
var SEND_TIMEOUT_MS = 3e3;
|
|
13240
|
+
var MAX_ERROR_TYPES = 10;
|
|
13241
|
+
function emptyState2() {
|
|
13242
|
+
return {
|
|
13243
|
+
scans: 0,
|
|
13244
|
+
toggleApplies: 0,
|
|
13245
|
+
detected: { cc: false, codex: false, dsh: false },
|
|
13246
|
+
liveWired: { cc: false, codex: false, dsh: false },
|
|
13247
|
+
errorTypes: /* @__PURE__ */ new Set()
|
|
13248
|
+
};
|
|
13249
|
+
}
|
|
13250
|
+
var state = emptyState2();
|
|
13251
|
+
function recordDetect(harness, installed) {
|
|
13252
|
+
state.detected[harness] = installed;
|
|
13253
|
+
}
|
|
13254
|
+
function recordScan(config) {
|
|
13255
|
+
state.scans += 1;
|
|
13256
|
+
state.detected[config.harness] = true;
|
|
13257
|
+
state.liveWired[config.harness] = config.capabilities.live !== "none";
|
|
13258
|
+
}
|
|
13259
|
+
function recordHookInstall(harness) {
|
|
13260
|
+
state.liveWired[harness] = true;
|
|
13261
|
+
}
|
|
13262
|
+
function recordApply() {
|
|
13263
|
+
state.toggleApplies += 1;
|
|
13264
|
+
}
|
|
13265
|
+
function recordError(error) {
|
|
13266
|
+
if (state.errorTypes.size >= MAX_ERROR_TYPES)
|
|
13267
|
+
return;
|
|
13268
|
+
state.errorTypes.add(error instanceof Error ? error.constructor.name : typeof error);
|
|
13269
|
+
}
|
|
13270
|
+
function buildPayload(options) {
|
|
13271
|
+
return {
|
|
13272
|
+
app_version: options.version,
|
|
13273
|
+
platform: process.platform,
|
|
13274
|
+
arch: process.arch,
|
|
13275
|
+
harnesses: { ...state.detected },
|
|
13276
|
+
counts: {
|
|
13277
|
+
scans: state.scans,
|
|
13278
|
+
live_wired: Object.values(state.liveWired).filter(Boolean).length,
|
|
13279
|
+
toggle_applies: state.toggleApplies
|
|
13280
|
+
},
|
|
13281
|
+
license_state: options.licenseState,
|
|
13282
|
+
error_types: [...state.errorTypes].sort()
|
|
13283
|
+
};
|
|
13284
|
+
}
|
|
13285
|
+
function appVersion() {
|
|
13286
|
+
if (typeof __WINGMAN_VERSION__ === "string")
|
|
13287
|
+
return __WINGMAN_VERSION__;
|
|
13288
|
+
try {
|
|
13289
|
+
const pkgPath = join17(dirname11(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
13290
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
13291
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
13292
|
+
} catch {
|
|
13293
|
+
return "unknown";
|
|
13294
|
+
}
|
|
13295
|
+
}
|
|
13296
|
+
async function sendMetrics(payload, baseUrl) {
|
|
13297
|
+
const base = baseUrl ?? process.env.WINGMAN_TELEMETRY_SERVER ?? DEFAULT_TELEMETRY_SERVER;
|
|
13298
|
+
try {
|
|
13299
|
+
await fetch(`${base}/metrics`, {
|
|
13300
|
+
method: "POST",
|
|
13301
|
+
headers: { "content-type": "application/json" },
|
|
13302
|
+
body: JSON.stringify(payload),
|
|
13303
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
|
|
13304
|
+
});
|
|
13305
|
+
} catch {
|
|
13306
|
+
}
|
|
13307
|
+
}
|
|
13308
|
+
function startTelemetry(options = {}) {
|
|
13309
|
+
const send = () => {
|
|
13310
|
+
const licenseState = licenseStatus(options.homeDir === void 0 ? {} : { homeDir: options.homeDir }).state;
|
|
13311
|
+
void sendMetrics(buildPayload({ version: appVersion(), licenseState }), options.baseUrl);
|
|
13312
|
+
};
|
|
13313
|
+
send();
|
|
13314
|
+
const timer = setInterval(send, options.intervalMs ?? SEND_INTERVAL_MS);
|
|
13315
|
+
timer.unref();
|
|
13316
|
+
return () => clearInterval(timer);
|
|
13317
|
+
}
|
|
13318
|
+
|
|
13319
|
+
// apps/cli/dist/scan.js
|
|
11974
13320
|
async function scanValidated(adapter, options) {
|
|
11975
13321
|
const config = await adapter.scan(options);
|
|
11976
13322
|
const errors = validateUnifiedConfig(config);
|
|
@@ -11978,6 +13324,7 @@ async function scanValidated(adapter, options) {
|
|
|
11978
13324
|
throw new Error(`${adapter.harness} \u7684 scan \u8F93\u51FA\u4E0D\u7B26\u5408 UnifiedConfig \u5408\u540C:
|
|
11979
13325
|
- ${errors.join("\n- ")}`);
|
|
11980
13326
|
}
|
|
13327
|
+
recordScan(config);
|
|
11981
13328
|
return config;
|
|
11982
13329
|
}
|
|
11983
13330
|
|
|
@@ -11985,7 +13332,152 @@ async function scanValidated(adapter, options) {
|
|
|
11985
13332
|
init_dist();
|
|
11986
13333
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
11987
13334
|
import { createServer } from "node:http";
|
|
11988
|
-
import { extname, resolve as
|
|
13335
|
+
import { extname, resolve as resolve5, sep as sep2 } from "node:path";
|
|
13336
|
+
|
|
13337
|
+
// apps/cli/dist/suggest.js
|
|
13338
|
+
init_dist();
|
|
13339
|
+
var WAITING_TIMEOUT_MS = 6e4;
|
|
13340
|
+
var HISTORY_BLOAT_RATIO = 0.5;
|
|
13341
|
+
var HISTORY_BLOAT_MIN_CONTEXT = 5e4;
|
|
13342
|
+
function mcpServerName(nativeName) {
|
|
13343
|
+
for (const prefix of ["mcpServers.", "mcp_servers."]) {
|
|
13344
|
+
if (nativeName.startsWith(prefix) && nativeName.length > prefix.length) {
|
|
13345
|
+
return nativeName.slice(prefix.length);
|
|
13346
|
+
}
|
|
13347
|
+
}
|
|
13348
|
+
return void 0;
|
|
13349
|
+
}
|
|
13350
|
+
function displayName(item) {
|
|
13351
|
+
return mcpServerName(item.nativeName) ?? item.nativeName;
|
|
13352
|
+
}
|
|
13353
|
+
function fmtTok(value) {
|
|
13354
|
+
if (value < 1e3)
|
|
13355
|
+
return String(value);
|
|
13356
|
+
return `${(Math.round(value / 100) / 10).toFixed(1)}k`;
|
|
13357
|
+
}
|
|
13358
|
+
function fmtTokens(tokens) {
|
|
13359
|
+
const n = fmtTok(tokens.value);
|
|
13360
|
+
return tokens.kind === "estimate" ? `\u7EA6 ${n}` : n;
|
|
13361
|
+
}
|
|
13362
|
+
function fmtDuration(ms) {
|
|
13363
|
+
const seconds = Math.round(ms / 1e3);
|
|
13364
|
+
if (seconds < 60)
|
|
13365
|
+
return `${seconds}s`;
|
|
13366
|
+
const minutes = Math.floor(seconds / 60);
|
|
13367
|
+
return `${minutes}m${seconds % 60 === 0 ? "" : ` ${seconds % 60}s`}`;
|
|
13368
|
+
}
|
|
13369
|
+
function residentTotal(config) {
|
|
13370
|
+
let sum = 0;
|
|
13371
|
+
let counted = false;
|
|
13372
|
+
let kind = "exact";
|
|
13373
|
+
for (const items of Object.values(config.slots)) {
|
|
13374
|
+
for (const item of items) {
|
|
13375
|
+
if (!item.enabled || !item.tokens)
|
|
13376
|
+
continue;
|
|
13377
|
+
sum += item.tokens.value;
|
|
13378
|
+
counted = true;
|
|
13379
|
+
if (item.tokens.kind === "estimate")
|
|
13380
|
+
kind = "estimate";
|
|
13381
|
+
}
|
|
13382
|
+
}
|
|
13383
|
+
return counted ? { kind, value: sum } : void 0;
|
|
13384
|
+
}
|
|
13385
|
+
function stepToolNames(state2) {
|
|
13386
|
+
const names = [];
|
|
13387
|
+
for (const trace of state2.traces) {
|
|
13388
|
+
for (const node of trace.nodes) {
|
|
13389
|
+
for (const step of node.steps)
|
|
13390
|
+
names.push(step.toolName);
|
|
13391
|
+
}
|
|
13392
|
+
}
|
|
13393
|
+
return names;
|
|
13394
|
+
}
|
|
13395
|
+
function deriveSuggestions(state2, config, nowMs) {
|
|
13396
|
+
const suggestions = [];
|
|
13397
|
+
if (state2.waiting !== null) {
|
|
13398
|
+
const sinceMs = Date.parse(state2.waiting.since);
|
|
13399
|
+
if (Number.isFinite(sinceMs) && nowMs - sinceMs > WAITING_TIMEOUT_MS) {
|
|
13400
|
+
suggestions.push({
|
|
13401
|
+
id: "waiting-timeout",
|
|
13402
|
+
rule: "waiting-timeout",
|
|
13403
|
+
text: `\u5DF2\u7B49\u5F85 ${fmtDuration(nowMs - sinceMs)}:${state2.waiting.message}`,
|
|
13404
|
+
source: `\u6765\u6E90:${state2.waiting.kind === "permission" ? "PermissionRequest" : "Notification"} \u4E8B\u4EF6,${state2.waiting.since} \u8D77\u672A\u51B3;\u9608\u503C 60s`
|
|
13405
|
+
});
|
|
13406
|
+
}
|
|
13407
|
+
}
|
|
13408
|
+
if (config !== void 0) {
|
|
13409
|
+
const refSources = [
|
|
13410
|
+
...state2.lastPrompt !== null ? [{ file: "\u7528\u6237\u63D0\u793A", text: state2.lastPrompt }] : [],
|
|
13411
|
+
...state2.agentQuote !== null ? [{ file: "agent \u81EA\u8FF0", text: state2.agentQuote }] : []
|
|
13412
|
+
];
|
|
13413
|
+
if (refSources.length > 0) {
|
|
13414
|
+
for (const items of Object.values(config.slots)) {
|
|
13415
|
+
for (const item of items) {
|
|
13416
|
+
if (item.enabled || !item.toggleable)
|
|
13417
|
+
continue;
|
|
13418
|
+
const name = displayName(item);
|
|
13419
|
+
const hits = scanReferences(refSources, name);
|
|
13420
|
+
const hit = hits[0];
|
|
13421
|
+
if (hit === void 0)
|
|
13422
|
+
continue;
|
|
13423
|
+
suggestions.push({
|
|
13424
|
+
id: `reenable:${item.id}`,
|
|
13425
|
+
rule: "reenable",
|
|
13426
|
+
text: `\u68C0\u6D4B\u5230\u63D0\u53CA ${name}(\u5DF2\u5173),\u53EF\u4E00\u952E\u6253\u5F00`,
|
|
13427
|
+
source: `\u547D\u4E2D:${hit.file} \u2014\u2014 \u300C${hit.context}\u300D(scanReferences \u6574\u8BCD\u5339\u914D)`,
|
|
13428
|
+
action: { itemId: item.id, enable: true }
|
|
13429
|
+
});
|
|
13430
|
+
}
|
|
13431
|
+
}
|
|
13432
|
+
}
|
|
13433
|
+
const toolNames = stepToolNames(state2);
|
|
13434
|
+
if (toolNames.length > 0 && state2.turnUsages.length > 0) {
|
|
13435
|
+
for (const item of config.slots.tools) {
|
|
13436
|
+
if (!item.enabled || !item.toggleable)
|
|
13437
|
+
continue;
|
|
13438
|
+
const server = mcpServerName(item.nativeName);
|
|
13439
|
+
if (server === void 0)
|
|
13440
|
+
continue;
|
|
13441
|
+
const used = toolNames.some((n) => n.startsWith(`mcp__${server}__`));
|
|
13442
|
+
if (used)
|
|
13443
|
+
continue;
|
|
13444
|
+
const saving = item.tokens ? `,\u5173\u6389\u53EF\u7701 ${fmtTokens(item.tokens)} tok(\u5E38\u9A7B\u4F30\u7B97)` : ",\u53EF\u5173";
|
|
13445
|
+
suggestions.push({
|
|
13446
|
+
id: `idle-mcp:${item.id}`,
|
|
13447
|
+
rule: "idle-mcp",
|
|
13448
|
+
text: `${server} \u672C session \u672A\u4F7F\u7528${saving}`,
|
|
13449
|
+
source: [
|
|
13450
|
+
`\u5224\u5B9A:\u672C session \u5DE5\u5177\u6B65\u9AA4\u96F6\u547D\u4E2D mcp__${server}__*(transcript \u89C2\u5BDF)`,
|
|
13451
|
+
`\u6761\u76EE:${item.nativePath !== "" ? item.nativePath : item.id}`,
|
|
13452
|
+
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"
|
|
13453
|
+
].join("\n"),
|
|
13454
|
+
action: { itemId: item.id, enable: false }
|
|
13455
|
+
});
|
|
13456
|
+
}
|
|
13457
|
+
}
|
|
13458
|
+
const lastTurn = state2.turnUsages[state2.turnUsages.length - 1];
|
|
13459
|
+
const resident = residentTotal(config);
|
|
13460
|
+
if (lastTurn !== void 0 && resident !== void 0) {
|
|
13461
|
+
const ctx = lastTurn.contextTotal.value;
|
|
13462
|
+
const history = ctx - resident.value;
|
|
13463
|
+
if (ctx >= HISTORY_BLOAT_MIN_CONTEXT && history / ctx > HISTORY_BLOAT_RATIO) {
|
|
13464
|
+
const pct = Math.round(history / ctx * 100);
|
|
13465
|
+
suggestions.push({
|
|
13466
|
+
id: "history-bloat",
|
|
13467
|
+
rule: "history-bloat",
|
|
13468
|
+
text: `\u5BF9\u8BDD\u5386\u53F2\u7EA6\u5360\u672C\u8F6E context ${pct}%(\u7EA6 ${fmtTok(history)} / ${fmtTok(ctx)}),\u5F00\u65B0 session \u53EF\u590D\u4F4D`,
|
|
13469
|
+
source: [
|
|
13470
|
+
"\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)",
|
|
13471
|
+
`\u9608\u503C:context \u2265 ${fmtTok(HISTORY_BLOAT_MIN_CONTEXT)} \u4E14\u5386\u53F2\u5360\u6BD4 > 50%`
|
|
13472
|
+
].join("\n")
|
|
13473
|
+
});
|
|
13474
|
+
}
|
|
13475
|
+
}
|
|
13476
|
+
}
|
|
13477
|
+
return suggestions;
|
|
13478
|
+
}
|
|
13479
|
+
|
|
13480
|
+
// apps/cli/dist/server.js
|
|
11989
13481
|
var CONTENT_TYPES = {
|
|
11990
13482
|
".html": "text/html; charset=utf-8",
|
|
11991
13483
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -11997,10 +13489,13 @@ var CONTENT_TYPES = {
|
|
|
11997
13489
|
".ico": "image/x-icon",
|
|
11998
13490
|
".woff2": "font/woff2"
|
|
11999
13491
|
};
|
|
13492
|
+
var ANATOMY_CACHE_TTL_MS = 3e4;
|
|
12000
13493
|
function createWingmanServer(options) {
|
|
12001
13494
|
const warn = options.warn ?? ((message) => console.error(message));
|
|
13495
|
+
const runtime = { anatomy: null };
|
|
12002
13496
|
return createServer((req, res) => {
|
|
12003
|
-
handle(req, res, options, warn).catch((error) => {
|
|
13497
|
+
handle(req, res, options, warn, runtime).catch((error) => {
|
|
13498
|
+
recordError(error);
|
|
12004
13499
|
warn(`[wingman] \u8BF7\u6C42\u5904\u7406\u5931\u8D25:${errorMessage(error)}`);
|
|
12005
13500
|
if (!res.headersSent)
|
|
12006
13501
|
sendText2(res, 500, "internal error");
|
|
@@ -12018,7 +13513,7 @@ function listen(server, port) {
|
|
|
12018
13513
|
});
|
|
12019
13514
|
});
|
|
12020
13515
|
}
|
|
12021
|
-
async function handle(req, res, options, warn) {
|
|
13516
|
+
async function handle(req, res, options, warn, runtime) {
|
|
12022
13517
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
12023
13518
|
if (options.receiver && url.pathname === "/hook") {
|
|
12024
13519
|
options.receiver.handleHook(req, res);
|
|
@@ -12033,7 +13528,7 @@ async function handle(req, res, options, warn) {
|
|
|
12033
13528
|
return;
|
|
12034
13529
|
}
|
|
12035
13530
|
if (url.pathname === "/api/action/apply" && req.method === "POST") {
|
|
12036
|
-
await handleActionApply(req, res, options);
|
|
13531
|
+
await handleActionApply(req, res, options, runtime);
|
|
12037
13532
|
return;
|
|
12038
13533
|
}
|
|
12039
13534
|
if (url.pathname === "/api/license/install" && req.method === "POST") {
|
|
@@ -12045,8 +13540,34 @@ async function handle(req, res, options, warn) {
|
|
|
12045
13540
|
return;
|
|
12046
13541
|
}
|
|
12047
13542
|
if (url.pathname === "/api/license") {
|
|
12048
|
-
const { state, detail } = licenseStatus(options.licenseOptions);
|
|
12049
|
-
sendJson(res, 200, { state, detail });
|
|
13543
|
+
const { state: state2, detail } = licenseStatus(options.licenseOptions);
|
|
13544
|
+
sendJson(res, 200, { state: state2, detail });
|
|
13545
|
+
return;
|
|
13546
|
+
}
|
|
13547
|
+
if (url.pathname === "/api/narrative") {
|
|
13548
|
+
if (!options.narrative) {
|
|
13549
|
+
sendText2(res, 404, "not found");
|
|
13550
|
+
return;
|
|
13551
|
+
}
|
|
13552
|
+
const harness = url.searchParams.get("harness");
|
|
13553
|
+
if (typeof harness !== "string" || !HARNESS_IDS.includes(harness)) {
|
|
13554
|
+
sendJson(res, 400, {
|
|
13555
|
+
error: `harness \u9700\u8981 ${HARNESS_IDS.join("|")}(?harness=<id>),\u5F97\u5230 ${String(harness)}`
|
|
13556
|
+
});
|
|
13557
|
+
return;
|
|
13558
|
+
}
|
|
13559
|
+
const overview = options.narrative.getOverview(harness);
|
|
13560
|
+
if (overview.sessions.length > 0) {
|
|
13561
|
+
const config = (await cachedAnatomy(options, warn, runtime)).find((c) => c.harness === harness);
|
|
13562
|
+
const nowMs = Date.now();
|
|
13563
|
+
const states = {};
|
|
13564
|
+
for (const [id, state2] of Object.entries(overview.states)) {
|
|
13565
|
+
const suggestions = deriveSuggestions(state2, config, nowMs);
|
|
13566
|
+
states[id] = suggestions.length > 0 ? { ...state2, suggestions } : state2;
|
|
13567
|
+
}
|
|
13568
|
+
overview.states = states;
|
|
13569
|
+
}
|
|
13570
|
+
sendJson(res, 200, overview);
|
|
12050
13571
|
return;
|
|
12051
13572
|
}
|
|
12052
13573
|
if (url.pathname === "/api/anatomy") {
|
|
@@ -12061,20 +13582,75 @@ async function handle(req, res, options, warn) {
|
|
|
12061
13582
|
}
|
|
12062
13583
|
await serveStatic(url.pathname, res, options.panelDistDir);
|
|
12063
13584
|
}
|
|
13585
|
+
async function cachedAnatomy(options, warn, runtime) {
|
|
13586
|
+
const nowMs = Date.now();
|
|
13587
|
+
if (runtime.anatomy !== null && nowMs - runtime.anatomy.at < ANATOMY_CACHE_TTL_MS) {
|
|
13588
|
+
return runtime.anatomy.configs;
|
|
13589
|
+
}
|
|
13590
|
+
const configs = await scanAll(options.harnesses, warn);
|
|
13591
|
+
runtime.anatomy = { at: nowMs, configs };
|
|
13592
|
+
return configs;
|
|
13593
|
+
}
|
|
12064
13594
|
async function scanAll(harnesses, warn) {
|
|
12065
13595
|
const configs = [];
|
|
12066
13596
|
for (const { harness, adapter, scanOptions } of harnesses) {
|
|
13597
|
+
let installed;
|
|
12067
13598
|
try {
|
|
12068
13599
|
const detected = await adapter.detect(scanOptions);
|
|
12069
|
-
|
|
12070
|
-
|
|
13600
|
+
recordDetect(harness, detected.installed);
|
|
13601
|
+
installed = detected.installed;
|
|
13602
|
+
} catch (error) {
|
|
13603
|
+
recordError(error);
|
|
13604
|
+
warn(`[wingman] ${harness} detect \u5931\u8D25,\u5DF2\u8DF3\u8FC7:${errorMessage(error)}`);
|
|
13605
|
+
continue;
|
|
13606
|
+
}
|
|
13607
|
+
if (!installed)
|
|
13608
|
+
continue;
|
|
13609
|
+
try {
|
|
12071
13610
|
configs.push(await scanValidated(adapter, scanOptions));
|
|
12072
13611
|
} catch (error) {
|
|
12073
|
-
|
|
13612
|
+
recordError(error);
|
|
13613
|
+
warn(`[wingman] ${harness} \u626B\u63CF\u5931\u8D25,\u964D\u7EA7\u663E\u793A:${errorMessage(error)}`);
|
|
13614
|
+
configs.push(degradedScanConfig(harness, error));
|
|
12074
13615
|
}
|
|
12075
13616
|
}
|
|
12076
13617
|
return configs;
|
|
12077
13618
|
}
|
|
13619
|
+
var DEGRADED_SOURCE = {
|
|
13620
|
+
cc: "bundled",
|
|
13621
|
+
codex: "user",
|
|
13622
|
+
dsh: "bundle"
|
|
13623
|
+
};
|
|
13624
|
+
function degradedScanConfig(harness, error) {
|
|
13625
|
+
return {
|
|
13626
|
+
harness,
|
|
13627
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13628
|
+
slots: {
|
|
13629
|
+
base: [
|
|
13630
|
+
{
|
|
13631
|
+
id: `${harness}:base:${DEGRADED_SOURCE[harness]}:scan-failed`,
|
|
13632
|
+
slot: "base",
|
|
13633
|
+
nativeName: `\u8BFB\u53D6\u914D\u7F6E\u5931\u8D25:${errorSummary(error)}`,
|
|
13634
|
+
nativePath: "",
|
|
13635
|
+
toggleable: false,
|
|
13636
|
+
enabled: true,
|
|
13637
|
+
source: DEGRADED_SOURCE[harness]
|
|
13638
|
+
}
|
|
13639
|
+
],
|
|
13640
|
+
rules: [],
|
|
13641
|
+
skills: [],
|
|
13642
|
+
tools: [],
|
|
13643
|
+
gates: [],
|
|
13644
|
+
history: []
|
|
13645
|
+
},
|
|
13646
|
+
capabilities: { live: "none", actions: [] }
|
|
13647
|
+
};
|
|
13648
|
+
}
|
|
13649
|
+
function errorSummary(error) {
|
|
13650
|
+
const first = errorMessage(error).split("\n", 1)[0] ?? "";
|
|
13651
|
+
const scrubbed = first.replace(/(?:[A-Za-z]:)?[\\/][^\s'"()|,;]+/g, "\u2026").trim();
|
|
13652
|
+
return scrubbed.length <= 120 ? scrubbed : `${scrubbed.slice(0, 119)}\u2026`;
|
|
13653
|
+
}
|
|
12078
13654
|
async function handleActionPlan(req, res, options) {
|
|
12079
13655
|
const body = await readJsonBody(req);
|
|
12080
13656
|
if (body === void 0) {
|
|
@@ -12098,7 +13674,7 @@ async function handleActionPlan(req, res, options) {
|
|
|
12098
13674
|
sendJson(res, 400, { error: errorMessage(error) });
|
|
12099
13675
|
}
|
|
12100
13676
|
}
|
|
12101
|
-
async function handleActionApply(req, res, options) {
|
|
13677
|
+
async function handleActionApply(req, res, options, runtime) {
|
|
12102
13678
|
if (options.readonlyActions) {
|
|
12103
13679
|
sendJson(res, 403, { error: "fixtures \u6A21\u5F0F\u53EA\u8BFB:\u52A8\u4F5C\u53EA\u5230 plan \u4E3A\u6B62,\u4E0D\u5199\u4EFB\u4F55\u6587\u4EF6" });
|
|
12104
13680
|
return;
|
|
@@ -12124,8 +13700,13 @@ async function handleActionApply(req, res, options) {
|
|
|
12124
13700
|
}
|
|
12125
13701
|
try {
|
|
12126
13702
|
const result = await loaded.adapter.applyAction(plan, loaded.scanOptions);
|
|
13703
|
+
if (result.ok) {
|
|
13704
|
+
recordApply();
|
|
13705
|
+
runtime.anatomy = null;
|
|
13706
|
+
}
|
|
12127
13707
|
sendJson(res, 200, result);
|
|
12128
13708
|
} catch (error) {
|
|
13709
|
+
recordError(error);
|
|
12129
13710
|
sendJson(res, 400, { error: errorMessage(error) });
|
|
12130
13711
|
}
|
|
12131
13712
|
}
|
|
@@ -12208,8 +13789,8 @@ async function serveStatic(pathname, res, panelDistDir) {
|
|
|
12208
13789
|
return;
|
|
12209
13790
|
}
|
|
12210
13791
|
const rel = decoded === "/" ? "/index.html" : decoded;
|
|
12211
|
-
const distDir =
|
|
12212
|
-
const filePath =
|
|
13792
|
+
const distDir = resolve5(panelDistDir);
|
|
13793
|
+
const filePath = resolve5(distDir, `.${rel}`);
|
|
12213
13794
|
if (filePath !== distDir && !filePath.startsWith(distDir + sep2)) {
|
|
12214
13795
|
sendText2(res, 403, "forbidden");
|
|
12215
13796
|
return;
|
|
@@ -12326,17 +13907,30 @@ async function runServe(args) {
|
|
|
12326
13907
|
`);
|
|
12327
13908
|
}
|
|
12328
13909
|
}
|
|
12329
|
-
const panelDistDir = panelDist === void 0 ?
|
|
13910
|
+
const panelDistDir = panelDist === void 0 ? resolve6(dirname12(fileURLToPath3(import.meta.url)), "../../panel/dist") : resolve6(panelDist);
|
|
13911
|
+
const narrative = createNarrativeService({ followTranscripts: !fixtures });
|
|
13912
|
+
if (fixtures) {
|
|
13913
|
+
try {
|
|
13914
|
+
const raw = readFileSync12(fixturePath("cc", "sessions", "session-todos.jsonl"), "utf8");
|
|
13915
|
+
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");
|
|
13916
|
+
} catch (error) {
|
|
13917
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13918
|
+
process.stderr.write(`[wingman] fixtures \u53D9\u4E8B\u6837\u4F8B\u52A0\u8F7D\u5931\u8D25,\u5DF2\u8DF3\u8FC7:${message}
|
|
13919
|
+
`);
|
|
13920
|
+
}
|
|
13921
|
+
}
|
|
12330
13922
|
const server = createWingmanServer({
|
|
12331
13923
|
harnesses,
|
|
12332
13924
|
panelDistDir,
|
|
12333
|
-
receiver: createReceiver(),
|
|
12334
|
-
readonlyActions: fixtures
|
|
13925
|
+
receiver: createReceiver({ onEvent: narrative.handleEvent }),
|
|
13926
|
+
readonlyActions: fixtures,
|
|
13927
|
+
narrative
|
|
12335
13928
|
});
|
|
12336
13929
|
const actualPort = await listen(server, port);
|
|
13930
|
+
startTelemetry();
|
|
12337
13931
|
const mode = fixtures ? "(fixtures \u6A21\u5F0F:\u6570\u636E\u6765\u81EA @wingman/fixtures \u6837\u4F8B,\u975E\u672C\u673A\u914D\u7F6E)" : "";
|
|
12338
13932
|
process.stdout.write(`Wingman \u9762\u677F:http://127.0.0.1:${actualPort}${mode}
|
|
12339
|
-
\u53EA\u7ED1 127.0.0.1,\u6570\u636E\
|
|
13933
|
+
\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
13934
|
`);
|
|
12341
13935
|
return 0;
|
|
12342
13936
|
}
|
|
@@ -12364,6 +13958,7 @@ async function runHook(args) {
|
|
|
12364
13958
|
const { adapter } = await loadHarness(harness, { fixtures: false });
|
|
12365
13959
|
if (action === "install") {
|
|
12366
13960
|
await adapter.installHooks(port);
|
|
13961
|
+
recordHookInstall(harness);
|
|
12367
13962
|
process.stdout.write(`wingman:${harness} \u5B9E\u51B5\u63A5\u7EBF\u5B8C\u6210(POST http://127.0.0.1:${port}/hook?harness=${harness})\u3002
|
|
12368
13963
|
\u5199\u524D\u5DF2\u5907\u4EFD;\u91CD\u590D\u6267\u884C\u5E42\u7B49;\u89E3\u7EBF:wingman hook uninstall ${harness}
|
|
12369
13964
|
`);
|