harness-wingman 0.4.3 → 0.5.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.
@@ -55,15 +55,134 @@ var init_feedback_url = __esm({
55
55
  }
56
56
  });
57
57
 
58
+ // packages/core/dist/plan-protocol.js
59
+ import { createHash } from "node:crypto";
60
+ function planProtocolBlockStart(version = PLAN_PROTOCOL_VERSION) {
61
+ return `<!-- >>> wingman plan protocol v${version} >>> -->`;
62
+ }
63
+ function buildPlanProtocolBlock(version = PLAN_PROTOCOL_VERSION, text) {
64
+ let body = normalizeMarkdownLineEndings(text ?? KNOWN_PROTOCOL_TEXT.get(version) ?? "");
65
+ if (body === "")
66
+ throw new Error(`\u672A\u77E5 Plan Protocol \u7248\u672C v${version}\uFF0C\u4E14\u672A\u63D0\u4F9B\u6B63\u6587`);
67
+ if (!body.endsWith("\n"))
68
+ body += "\n";
69
+ return `${planProtocolBlockStart(version)}
70
+ ${body}${PLAN_PROTOCOL_BLOCK_END}
71
+ `;
72
+ }
73
+ function decodeUtf8Strict(bytes, sourcePath = "<memory>") {
74
+ try {
75
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
76
+ } catch {
77
+ throw new Error(`\u76EE\u6807\u6587\u4EF6\u4E0D\u662F\u4E25\u683C UTF-8(${sourcePath})`);
78
+ }
79
+ }
80
+ function sha256Hex(contents) {
81
+ return createHash("sha256").update(contents).digest("hex");
82
+ }
83
+ function normalizeMarkdownLineEndings(text) {
84
+ return text.replaceAll("\r\n", "\n");
85
+ }
86
+ function preferredMarkdownLineEnding(text) {
87
+ const hasCrlf = text.includes("\r\n");
88
+ const hasBareLf = text.replaceAll("\r\n", "").includes("\n");
89
+ return hasCrlf && !hasBareLf ? "\r\n" : "\n";
90
+ }
91
+ function splitPlanProtocolBlock(text, sourcePath = "<memory>") {
92
+ const starts = [...text.matchAll(BLOCK_START)];
93
+ const ends = allIndexesOf(text, PLAN_PROTOCOL_BLOCK_END);
94
+ if (starts.length === 0 && ends.length === 0) {
95
+ return { before: text, after: "", remainder: text };
96
+ }
97
+ if (starts.length > 1 || ends.length > 1) {
98
+ throw new Error(`wingman plan protocol \u6807\u8BB0\u5757\u91CD\u590D(${sourcePath})`);
99
+ }
100
+ const start = starts[0];
101
+ const endMark = ends[0];
102
+ if (start === void 0 || endMark === void 0 || endMark < start.index) {
103
+ throw new Error(`wingman plan protocol \u6807\u8BB0\u5757\u6B8B\u7F3A(${sourcePath})`);
104
+ }
105
+ let end = endMark + PLAN_PROTOCOL_BLOCK_END.length;
106
+ if (text.startsWith("\r\n", end))
107
+ end += 2;
108
+ else if (text[end] === "\n")
109
+ end += 1;
110
+ const before = text.slice(0, start.index);
111
+ const after = text.slice(end);
112
+ return {
113
+ before,
114
+ after,
115
+ remainder: before + after,
116
+ block: text.slice(start.index, end),
117
+ version: Number(start[1])
118
+ };
119
+ }
120
+ function inspectPlanProtocolBlock(block) {
121
+ const normalized = normalizeMarkdownLineEndings(block);
122
+ const split = splitPlanProtocolBlock(normalized);
123
+ const version = split.version;
124
+ const knownText = version === void 0 ? void 0 : KNOWN_PROTOCOL_TEXT.get(version);
125
+ const knownBlock = version === void 0 || knownText === void 0 ? void 0 : buildPlanProtocolBlock(version, knownText);
126
+ const known = split.before === "" && split.after === "" && normalized === knownBlock;
127
+ return {
128
+ kind: known && version === PLAN_PROTOCOL_VERSION ? "current" : known ? "legacy" : "modified",
129
+ ...version !== void 0 ? { version } : {},
130
+ sha256: sha256Hex(normalized)
131
+ };
132
+ }
133
+ function inspectPlanProtocolFile(bytes, sourcePath = "<memory>") {
134
+ decodeUtf8Strict(bytes, sourcePath);
135
+ const sha256 = sha256Hex(bytes);
136
+ for (const [version, text] of KNOWN_PROTOCOL_TEXT) {
137
+ if (sha256 === sha256Hex(text)) {
138
+ return {
139
+ kind: version === PLAN_PROTOCOL_VERSION ? "current" : "legacy",
140
+ version,
141
+ sha256
142
+ };
143
+ }
144
+ }
145
+ return { kind: "modified", sha256 };
146
+ }
147
+ function allIndexesOf(text, needle) {
148
+ const indexes = [];
149
+ for (let at = text.indexOf(needle); at !== -1; at = text.indexOf(needle, at + needle.length)) {
150
+ indexes.push(at);
151
+ }
152
+ return indexes;
153
+ }
154
+ var PLAN_PROTOCOL_VERSION, PLAN_PROTOCOL_TEXT, PLAN_PROTOCOL_BLOCK_END, LEGACY_PLAN_PROTOCOL_TEXT, KNOWN_PROTOCOL_TEXT, BLOCK_START;
155
+ var init_plan_protocol = __esm({
156
+ "packages/core/dist/plan-protocol.js"() {
157
+ "use strict";
158
+ PLAN_PROTOCOL_VERSION = 1;
159
+ PLAN_PROTOCOL_TEXT = `Wingman plan protocol v1 \u2014 a behavioral convention, not a gate. Wingman only reads; it never replies to you.
160
+ - The project root is the directory that contains this instruction file (for Claude Code: the parent of \`.claude/\`).
161
+ - Before starting work, read \`<project root>/.wingman/plan.md\`. If it does not exist, create it: a \`# Title\` line, then a nested checkbox tree of steps, each step with an indented \`Verify:\` line describing how it will be checked.
162
+ - Mark the step you are working on \`[/]\` and add sub-steps under it as you learn more. Mark finished steps \`[x]\`, abandoned steps \`[-]\`, and steps that need the user's decision \`[?]\`.
163
+ - Re-read the file before every write; change only your own lines; never reorder or rewrite other entries. Use your file-editing tool, not shell redirection.
164
+ - Keep your in-session todo list as sub-steps of the \`[/]\` step; do not copy the whole file into it.
165
+ - Never put secrets, credentials, or personal data in the plan.
166
+ `;
167
+ PLAN_PROTOCOL_BLOCK_END = "<!-- <<< wingman plan protocol <<< -->";
168
+ LEGACY_PLAN_PROTOCOL_TEXT = "Wingman plan protocol v0 \u2014 legacy fixture text.\n";
169
+ KNOWN_PROTOCOL_TEXT = /* @__PURE__ */ new Map([
170
+ [0, LEGACY_PLAN_PROTOCOL_TEXT],
171
+ [PLAN_PROTOCOL_VERSION, PLAN_PROTOCOL_TEXT]
172
+ ]);
173
+ BLOCK_START = /<!-- >>> wingman plan protocol v(\d+) >>> -->/gu;
174
+ }
175
+ });
176
+
58
177
  // packages/core/dist/refscan.js
59
178
  function scanReferences(sources, name) {
60
179
  if (name.length === 0)
61
180
  return [];
62
181
  const hits = [];
63
182
  for (const source of sources) {
64
- const lines = source.text.split("\n");
65
- for (let i = 0; i < lines.length; i++) {
66
- const line = lines[i] ?? "";
183
+ const lines2 = source.text.split("\n");
184
+ for (let i = 0; i < lines2.length; i++) {
185
+ const line = lines2[i] ?? "";
67
186
  if (!lineMatches(line, name))
68
187
  continue;
69
188
  hits.push({
@@ -236,6 +355,7 @@ var init_dist = __esm({
236
355
  "packages/core/dist/index.js"() {
237
356
  "use strict";
238
357
  init_feedback_url();
358
+ init_plan_protocol();
239
359
  init_refscan();
240
360
  SLOT_IDS = ["base", "rules", "skills", "tools", "gates", "history"];
241
361
  HARNESS_IDS = ["cc", "codex", "dsh"];
@@ -263,7 +383,7 @@ var init_dist = __esm({
263
383
 
264
384
  // packages/adapter-cc/dist/walk.js
265
385
  import { readdirSync } from "node:fs";
266
- import { join as join5, parse, resolve } from "node:path";
386
+ import { join as join4, parse, resolve } from "node:path";
267
387
  function newWalkStats(entryBudget = MAX_WALK_ENTRIES) {
268
388
  return { unknownErrors: 0, truncated: false, entriesLeft: entryBudget };
269
389
  }
@@ -290,7 +410,7 @@ function walkFiles(dir, fileName, stats = newWalkStats()) {
290
410
  return;
291
411
  }
292
412
  stats.entriesLeft -= 1;
293
- const p = join5(d, entry.name);
413
+ const p = join4(d, entry.name);
294
414
  if (entry.isDirectory()) {
295
415
  if (entry.name.startsWith(".") || SKIP_DIR_NAMES.has(entry.name))
296
416
  continue;
@@ -307,7 +427,7 @@ function walkFiles(dir, fileName, stats = newWalkStats()) {
307
427
  return found;
308
428
  }
309
429
  function listMdFiles(dir, stats) {
310
- return safeReaddir(dir, stats).map((e) => e.name).filter((n) => n.endsWith(".md")).sort().map((n) => join5(dir, n));
430
+ return safeReaddir(dir, stats).map((e) => e.name).filter((n) => n.endsWith(".md")).sort().map((n) => join4(dir, n));
311
431
  }
312
432
  function isBareProjectDir(projectDir, homeDir) {
313
433
  const r = resolve(projectDir);
@@ -327,19 +447,19 @@ var init_walk = __esm({
327
447
  // packages/adapter-cc/dist/detect.js
328
448
  import { existsSync as existsSync2, readFileSync as readFileSync3, statSync } from "node:fs";
329
449
  import { homedir as homedir3 } from "node:os";
330
- import { join as join6 } from "node:path";
450
+ import { join as join5 } from "node:path";
331
451
  function probeVersion(homeDir, stats = newWalkStats()) {
332
- const root = join6(homeDir, ".claude", "projects");
452
+ const root = join5(homeDir, ".claude", "projects");
333
453
  let latest;
334
454
  for (const entry of safeReaddir(root, stats)) {
335
455
  if (!entry.isDirectory())
336
456
  continue;
337
- const dir = join6(root, entry.name);
457
+ const dir = join5(root, entry.name);
338
458
  for (const sub of safeReaddir(dir, stats)) {
339
459
  const name = sub.name;
340
460
  if (!name.endsWith(".jsonl"))
341
461
  continue;
342
- const file = join6(dir, name);
462
+ const file = join5(dir, name);
343
463
  let mtimeMs;
344
464
  try {
345
465
  mtimeMs = statSync(file).mtimeMs;
@@ -353,15 +473,15 @@ function probeVersion(homeDir, stats = newWalkStats()) {
353
473
  }
354
474
  if (!latest)
355
475
  return void 0;
356
- let lines;
476
+ let lines2;
357
477
  try {
358
- lines = readFileSync3(latest.file, "utf8").split("\n");
478
+ lines2 = readFileSync3(latest.file, "utf8").split("\n");
359
479
  } catch (error) {
360
480
  recordWalkError(error, stats);
361
481
  return void 0;
362
482
  }
363
- for (let i = lines.length - 1; i >= 0; i--) {
364
- const line = lines[i];
483
+ for (let i = lines2.length - 1; i >= 0; i--) {
484
+ const line = lines2[i];
365
485
  if (!line)
366
486
  continue;
367
487
  try {
@@ -375,7 +495,7 @@ function probeVersion(homeDir, stats = newWalkStats()) {
375
495
  }
376
496
  async function detectCc(options) {
377
497
  const homeDir = options?.homeDir ?? homedir3();
378
- const installed = existsSync2(join6(homeDir, ".claude")) || existsSync2(join6(homeDir, ".claude.json"));
498
+ const installed = existsSync2(join5(homeDir, ".claude")) || existsSync2(join5(homeDir, ".claude.json"));
379
499
  if (!installed)
380
500
  return { installed: false };
381
501
  const version = probeVersion(homeDir);
@@ -392,7 +512,7 @@ var init_detect = __esm({
392
512
  import { Buffer as Buffer2 } from "node:buffer";
393
513
  import { existsSync as existsSync3, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
394
514
  import { homedir as homedir4 } from "node:os";
395
- import { basename, dirname as dirname3, join as join7, resolve as resolve2, sep } from "node:path";
515
+ import { basename, dirname as dirname3, join as join6, resolve as resolve2, sep } from "node:path";
396
516
  function readJsonFile(file) {
397
517
  const text = readFileSync4(file, "utf8");
398
518
  try {
@@ -410,9 +530,9 @@ function recordField(v, key) {
410
530
  }
411
531
  function readSettingsChain(homeDir, projectDir) {
412
532
  const chain = [
413
- { file: join7(homeDir, ".claude", "settings.json"), source: "user" },
414
- { file: join7(projectDir, ".claude", "settings.json"), source: "project" },
415
- { file: join7(projectDir, ".claude", "settings.local.json"), source: "local" }
533
+ { file: join6(homeDir, ".claude", "settings.json"), source: "user" },
534
+ { file: join6(projectDir, ".claude", "settings.json"), source: "project" },
535
+ { file: join6(projectDir, ".claude", "settings.local.json"), source: "local" }
416
536
  ];
417
537
  const out = [];
418
538
  for (const { file, source } of chain) {
@@ -479,20 +599,20 @@ function buildRules(homeDir, projectDir, stats) {
479
599
  if (item !== void 0)
480
600
  items.push(item);
481
601
  };
482
- const userClaudeMd = join7(homeDir, ".claude", "CLAUDE.md");
602
+ const userClaudeMd = join6(homeDir, ".claude", "CLAUDE.md");
483
603
  if (existsSync3(userClaudeMd))
484
604
  append(userClaudeMd, "user");
485
- for (const f of listMdFiles(join7(homeDir, ".claude", "rules"), stats))
605
+ for (const f of listMdFiles(join6(homeDir, ".claude", "rules"), stats))
486
606
  append(f, "user");
487
607
  if (isBareProjectDir(projectDir, homeDir)) {
488
- const direct = join7(projectDir, "CLAUDE.md");
608
+ const direct = join6(projectDir, "CLAUDE.md");
489
609
  if (existsSync3(direct))
490
610
  append(direct, "project");
491
611
  } else {
492
612
  for (const f of walkFiles(projectDir, "CLAUDE.md", stats))
493
613
  append(f, "project");
494
614
  }
495
- for (const f of listMdFiles(join7(projectDir, ".claude", "rules"), stats)) {
615
+ for (const f of listMdFiles(join6(projectDir, ".claude", "rules"), stats)) {
496
616
  append(f, "project");
497
617
  }
498
618
  return items;
@@ -515,14 +635,14 @@ function parseSkillFrontmatter(content) {
515
635
  return out;
516
636
  }
517
637
  function buildSkills(homeDir, projectDir, overrides, stats) {
518
- const userRoot = join7(homeDir, ".claude", "skills");
519
- const syncedRoot = join7(userRoot, "synced");
638
+ const userRoot = join6(homeDir, ".claude", "skills");
639
+ const syncedRoot = join6(userRoot, "synced");
520
640
  const roots = [
521
641
  { root: userRoot, source: "user", exclude: syncedRoot },
522
642
  // projectDir 为家目录时,项目 skills 根与 user 根是同一目录,再走一遍会把每个
523
643
  // skill 重复列为 project 层;bare root 无项目上下文 → 不设项目 skills 根
524
- ...isBareProjectDir(projectDir, homeDir) ? [] : [{ root: join7(projectDir, ".claude", "skills"), source: "project" }],
525
- { root: join7(homeDir, ".claude", "plugins"), source: "plugin" },
644
+ ...isBareProjectDir(projectDir, homeDir) ? [] : [{ root: join6(projectDir, ".claude", "skills"), source: "project" }],
645
+ { root: join6(homeDir, ".claude", "plugins"), source: "plugin" },
526
646
  { root: syncedRoot, source: "synced" }
527
647
  ];
528
648
  const items = [];
@@ -556,7 +676,7 @@ function buildSkills(homeDir, projectDir, overrides, stats) {
556
676
  }
557
677
  function buildTools(homeDir, projectDir) {
558
678
  const items = [];
559
- const claudeJsonPath = join7(homeDir, ".claude.json");
679
+ const claudeJsonPath = join6(homeDir, ".claude.json");
560
680
  let claudeJson;
561
681
  if (existsSync3(claudeJsonPath))
562
682
  claudeJson = asRecord(readJsonFile(claudeJsonPath));
@@ -582,7 +702,7 @@ function buildTools(homeDir, projectDir) {
582
702
  for (const name of Object.keys(localServers).sort()) {
583
703
  items.push(mcpItem(name, "local", claudeJsonPath));
584
704
  }
585
- const mcpJsonPath = join7(projectDir, ".mcp.json");
705
+ const mcpJsonPath = join6(projectDir, ".mcp.json");
586
706
  if (existsSync3(mcpJsonPath)) {
587
707
  const projectServers = recordField(readJsonFile(mcpJsonPath), "mcpServers") ?? {};
588
708
  for (const name of Object.keys(projectServers).sort()) {
@@ -657,10 +777,10 @@ function lastContextTotal(file, stats) {
657
777
  return total;
658
778
  }
659
779
  function buildHistory(homeDir, projectDir, stats) {
660
- const dir = join7(homeDir, ".claude", "projects", projectSlug(projectDir));
780
+ const dir = join6(homeDir, ".claude", "projects", projectSlug(projectDir));
661
781
  const items = [];
662
782
  for (const name of safeReaddir(dir, stats).map((e) => e.name).filter((n) => n.endsWith(".jsonl")).sort()) {
663
- const file = join7(dir, name);
783
+ const file = join6(dir, name);
664
784
  const total = lastContextTotal(file, stats);
665
785
  if (total === void 0)
666
786
  continue;
@@ -773,7 +893,7 @@ var init_scan = __esm({
773
893
  // packages/adapter-cc/dist/actions.js
774
894
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
775
895
  import { homedir as homedir5 } from "node:os";
776
- import { basename as basename2, dirname as dirname4, join as join8, resolve as resolve3 } from "node:path";
896
+ import { basename as basename2, dirname as dirname4, join as join7, resolve as resolve3 } from "node:path";
777
897
  function asRecord2(v) {
778
898
  return typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
779
899
  }
@@ -797,13 +917,13 @@ function deepEqual(a, b) {
797
917
  }
798
918
  function backupDirPath(homeDir) {
799
919
  const ts = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
800
- const root = join8(homeDir, ".wingman", "backups");
801
- let dir = join8(root, ts);
920
+ const root = join7(homeDir, ".wingman", "backups");
921
+ let dir = join7(root, ts);
802
922
  for (let n = 2; existsSync4(dir); n++)
803
- dir = join8(root, `${ts}-${n}`);
923
+ dir = join7(root, `${ts}-${n}`);
804
924
  return dir;
805
925
  }
806
- function decodeUtf8Strict(buffer, file) {
926
+ function decodeUtf8Strict2(buffer, file) {
807
927
  const text = buffer.toString("utf8");
808
928
  if (!buffer.equals(Buffer.from(text, "utf8"))) {
809
929
  throw new Error(`\u62D2\u5199:${file} \u4E0D\u662F UTF-8 \u6587\u672C,\u672A\u77E5\u683C\u5F0F\u4E0D\u731C`);
@@ -812,15 +932,15 @@ function decodeUtf8Strict(buffer, file) {
812
932
  }
813
933
  function readSettingsSnapshots(homeDir, projectDir) {
814
934
  const files = [
815
- join8(homeDir, ".claude", "settings.json"),
816
- join8(projectDir, ".claude", "settings.json"),
817
- join8(projectDir, ".claude", "settings.local.json")
935
+ join7(homeDir, ".claude", "settings.json"),
936
+ join7(projectDir, ".claude", "settings.json"),
937
+ join7(projectDir, ".claude", "settings.local.json")
818
938
  ];
819
939
  const out = [];
820
940
  for (const file of files) {
821
941
  if (!existsSync4(file))
822
942
  continue;
823
- const raw = decodeUtf8Strict(readFileSync5(file), file);
943
+ const raw = decodeUtf8Strict2(readFileSync5(file), file);
824
944
  let parsed;
825
945
  try {
826
946
  parsed = JSON.parse(raw);
@@ -843,7 +963,7 @@ function overridesRecord(s) {
843
963
  return rec;
844
964
  }
845
965
  function skillTargetFile(item, homeDir, projectDir) {
846
- return item.source === "project" ? join8(projectDir, ".claude", "settings.json") : join8(homeDir, ".claude", "settings.json");
966
+ return item.source === "project" ? join7(projectDir, ".claude", "settings.json") : join7(homeDir, ".claude", "settings.json");
847
967
  }
848
968
  function planSkillToggle(item, enable, homeDir, projectDir) {
849
969
  const name = item.nativeName;
@@ -896,7 +1016,7 @@ function planSkillToggle(item, enable, homeDir, projectDir) {
896
1016
  });
897
1017
  }
898
1018
  function planMcpToggle(name, enable, homeDir, projectDir) {
899
- const file = join8(homeDir, ".claude.json");
1019
+ const file = join7(homeDir, ".claude.json");
900
1020
  if (!existsSync4(file)) {
901
1021
  throw new Error(`\u62D2\u7EDD:\u627E\u4E0D\u5230 ${file}(MCP \u8FDE\u63A5\u5F00\u5173\u6309\u9879\u76EE\u5B58\u653E\u4E8E\u8BE5\u6587\u4EF6)`);
902
1022
  }
@@ -978,20 +1098,20 @@ function collectRefSources(homeDir, projectDir, exclude) {
978
1098
  push(file, readFileSync5(file, "utf8"));
979
1099
  };
980
1100
  const bareRoot = isBareProjectDir(projectDir, homeDir);
981
- pushFile(join8(homeDir, ".claude", "CLAUDE.md"));
982
- for (const f of listMdFiles(join8(homeDir, ".claude", "rules")))
1101
+ pushFile(join7(homeDir, ".claude", "CLAUDE.md"));
1102
+ for (const f of listMdFiles(join7(homeDir, ".claude", "rules")))
983
1103
  pushFile(f);
984
1104
  if (bareRoot)
985
- pushFile(join8(projectDir, "CLAUDE.md"));
1105
+ pushFile(join7(projectDir, "CLAUDE.md"));
986
1106
  else
987
1107
  for (const f of walkFiles(projectDir, "CLAUDE.md"))
988
1108
  pushFile(f);
989
- for (const f of listMdFiles(join8(projectDir, ".claude", "rules")))
1109
+ for (const f of listMdFiles(join7(projectDir, ".claude", "rules")))
990
1110
  pushFile(f);
991
1111
  const skillRoots = [
992
- join8(homeDir, ".claude", "skills"),
993
- ...bareRoot ? [] : [join8(projectDir, ".claude", "skills")],
994
- join8(homeDir, ".claude", "plugins")
1112
+ join7(homeDir, ".claude", "skills"),
1113
+ ...bareRoot ? [] : [join7(projectDir, ".claude", "skills")],
1114
+ join7(homeDir, ".claude", "plugins")
995
1115
  ];
996
1116
  for (const root of skillRoots) {
997
1117
  for (const f of walkFiles(root, "SKILL.md")) {
@@ -1000,17 +1120,17 @@ function collectRefSources(homeDir, projectDir, exclude) {
1000
1120
  }
1001
1121
  }
1002
1122
  for (const dir of [
1003
- join8(homeDir, ".claude", "agents"),
1004
- join8(projectDir, ".claude", "agents"),
1005
- join8(homeDir, ".claude", "commands"),
1006
- join8(projectDir, ".claude", "commands")
1123
+ join7(homeDir, ".claude", "agents"),
1124
+ join7(projectDir, ".claude", "agents"),
1125
+ join7(homeDir, ".claude", "commands"),
1126
+ join7(projectDir, ".claude", "commands")
1007
1127
  ]) {
1008
1128
  for (const f of listMdFiles(dir))
1009
1129
  pushFile(f);
1010
1130
  }
1011
1131
  for (const s of readSettingsSnapshots(homeDir, projectDir))
1012
1132
  push(s.file, s.raw);
1013
- for (const file of [join8(projectDir, ".mcp.json"), join8(homeDir, ".claude.json")]) {
1133
+ for (const file of [join7(projectDir, ".mcp.json"), join7(homeDir, ".claude.json")]) {
1014
1134
  if (seen.has(file) || !existsSync4(file))
1015
1135
  continue;
1016
1136
  const raw = readFileSync5(file, "utf8");
@@ -1141,9 +1261,9 @@ async function applyActionCc(plan, _options) {
1141
1261
  target: w.file
1142
1262
  }));
1143
1263
  toBackup.forEach((w, i) => {
1144
- writeFileSync3(join8(plan.backupDir, `${i}-${basename2(w.file)}`), currentByFile.get(w.file));
1264
+ writeFileSync3(join7(plan.backupDir, `${i}-${basename2(w.file)}`), currentByFile.get(w.file));
1145
1265
  });
1146
- writeFileSync3(join8(plan.backupDir, "manifest.json"), serialize({
1266
+ writeFileSync3(join7(plan.backupDir, "manifest.json"), serialize({
1147
1267
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1148
1268
  harness: "cc",
1149
1269
  action: plan.action,
@@ -1209,7 +1329,7 @@ var init_actions = __esm({
1209
1329
  // packages/adapter-cc/dist/hooks.js
1210
1330
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
1211
1331
  import { homedir as homedir6 } from "node:os";
1212
- import { dirname as dirname5, join as join9 } from "node:path";
1332
+ import { dirname as dirname5, join as join8 } from "node:path";
1213
1333
  function wingmanUrl(port) {
1214
1334
  return `http://127.0.0.1:${port}${SIGNATURE}`;
1215
1335
  }
@@ -1221,10 +1341,10 @@ function isWingmanHandler(h) {
1221
1341
  return rec?.type === "http" && typeof rec.url === "string" && rec.url.includes(SIGNATURE);
1222
1342
  }
1223
1343
  function wingmanRoot(homeDir) {
1224
- return join9(homeDir, ".wingman");
1344
+ return join8(homeDir, ".wingman");
1225
1345
  }
1226
1346
  function statePath(homeDir) {
1227
- return join9(wingmanRoot(homeDir), "state", "cc-hooks.json");
1347
+ return join8(wingmanRoot(homeDir), "state", "cc-hooks.json");
1228
1348
  }
1229
1349
  function readState(homeDir) {
1230
1350
  const file = statePath(homeDir);
@@ -1259,10 +1379,10 @@ function writeState(homeDir, state) {
1259
1379
  }
1260
1380
  function makeBackupDir(homeDir) {
1261
1381
  const ts = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
1262
- const root = join9(wingmanRoot(homeDir), "backups");
1263
- let dir = join9(root, ts);
1382
+ const root = join8(wingmanRoot(homeDir), "backups");
1383
+ let dir = join8(root, ts);
1264
1384
  for (let n = 2; existsSync5(dir); n++)
1265
- dir = join9(root, `${ts}-${n}`);
1385
+ dir = join8(root, `${ts}-${n}`);
1266
1386
  mkdirSync3(dir, { recursive: true });
1267
1387
  return dir;
1268
1388
  }
@@ -1372,7 +1492,7 @@ function deepEqual2(a, b) {
1372
1492
  return false;
1373
1493
  }
1374
1494
  function targetPath(homeDir) {
1375
- return join9(homeDir, ".claude", "settings.json");
1495
+ return join8(homeDir, ".claude", "settings.json");
1376
1496
  }
1377
1497
  async function installHooksCc(port, options) {
1378
1498
  const homeDir = options?.homeDir ?? homedir6();
@@ -1394,7 +1514,7 @@ async function installHooksCc(port, options) {
1394
1514
  }
1395
1515
  const backupDir = makeBackupDir(homeDir);
1396
1516
  if (bytes !== null)
1397
- writeFileSync4(join9(backupDir, "settings.json"), bytes);
1517
+ writeFileSync4(join8(backupDir, "settings.json"), bytes);
1398
1518
  const prev = readState(homeDir);
1399
1519
  const now = (/* @__PURE__ */ new Date()).toISOString();
1400
1520
  writeState(homeDir, prev ? { ...prev, port, installedAt: now } : { target, original: bytes, backupDir, port, installedAt: now });
@@ -1426,7 +1546,7 @@ async function uninstallHooksCc(options) {
1426
1546
  }
1427
1547
  const backupDir = makeBackupDir(homeDir);
1428
1548
  if (bytes !== null)
1429
- writeFileSync4(join9(backupDir, "settings.json"), bytes);
1549
+ writeFileSync4(join8(backupDir, "settings.json"), bytes);
1430
1550
  if (state && state.original !== null) {
1431
1551
  const originalParsed = asRecord3(JSON.parse(state.original));
1432
1552
  if (originalParsed && deepEqual2(normalizeForCompare(settings), normalizeForCompare(originalParsed))) {
@@ -1463,12 +1583,217 @@ var init_hooks = __esm({
1463
1583
  }
1464
1584
  });
1465
1585
 
1586
+ // packages/adapter-cc/dist/protocol.js
1587
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync7, rmdirSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
1588
+ import { homedir as homedir7 } from "node:os";
1589
+ import { basename as basename3, dirname as dirname6, join as join9, resolve as resolve4 } from "node:path";
1590
+ function protocolStatusCc(options) {
1591
+ const paths = resolveOptions(options);
1592
+ if (!existsSync6(paths.targetPath)) {
1593
+ return { harness: "cc", state: "not_installed", targetPath: paths.targetPath };
1594
+ }
1595
+ try {
1596
+ return statusFromIdentity(paths.targetPath, inspectPlanProtocolFile(readFileSync7(paths.targetPath), paths.targetPath));
1597
+ } catch (error) {
1598
+ return {
1599
+ harness: "cc",
1600
+ state: "invalid",
1601
+ targetPath: paths.targetPath,
1602
+ reason: error.message
1603
+ };
1604
+ }
1605
+ }
1606
+ function installProtocolCc(options) {
1607
+ const paths = resolveOptions(options);
1608
+ const existing = readExisting(paths.targetPath);
1609
+ const previousState = stateForIdentity(existing?.identity);
1610
+ if (previousState === "installed") {
1611
+ return result(paths.targetPath, "install", false, previousState, existing?.text ?? "", true);
1612
+ }
1613
+ if (previousState === "modified")
1614
+ throw modifiedError(paths.targetPath, "\u5B89\u88C5");
1615
+ const before = existing?.text ?? "";
1616
+ const backupDir = nextBackupDir(paths.homeDir);
1617
+ const installedSha256 = sha256Hex(PLAN_PROTOCOL_TEXT);
1618
+ if (!paths.dryRun) {
1619
+ const claudeDir = join9(paths.projectDir, ".claude");
1620
+ const rulesDir = dirname6(paths.targetPath);
1621
+ const prior = readState2(paths.statePath);
1622
+ backupTarget(backupDir, paths.targetPath, existing?.bytes);
1623
+ writeState2(paths.statePath, {
1624
+ version: 1,
1625
+ harness: "cc",
1626
+ projectDir: paths.projectDir,
1627
+ targetPath: paths.targetPath,
1628
+ protocolVersion: PLAN_PROTOCOL_VERSION,
1629
+ installedSha256,
1630
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1631
+ backupDir,
1632
+ original: prior?.original ?? (existing === void 0 ? { existed: false } : { existed: true, base64: existing.bytes.toString("base64") }),
1633
+ createdClaudeDir: prior?.createdClaudeDir ?? !existsSync6(claudeDir),
1634
+ createdRulesDir: prior?.createdRulesDir ?? !existsSync6(rulesDir)
1635
+ });
1636
+ mkdirSync4(rulesDir, { recursive: true });
1637
+ writeFileSync5(paths.targetPath, PLAN_PROTOCOL_TEXT);
1638
+ }
1639
+ return {
1640
+ ...result(paths.targetPath, "install", true, previousState, before, true),
1641
+ state: "installed",
1642
+ version: PLAN_PROTOCOL_VERSION,
1643
+ sha256: installedSha256,
1644
+ after: PLAN_PROTOCOL_TEXT,
1645
+ backupDir,
1646
+ totalBytes: Buffer.byteLength(PLAN_PROTOCOL_TEXT)
1647
+ };
1648
+ }
1649
+ function uninstallProtocolCc(options) {
1650
+ const paths = resolveOptions(options);
1651
+ const existing = readExisting(paths.targetPath);
1652
+ if (existing === void 0) {
1653
+ return result(paths.targetPath, "uninstall", false, "not_installed", "", false);
1654
+ }
1655
+ const previousState = stateForIdentity(existing.identity);
1656
+ if (previousState === "modified")
1657
+ throw modifiedError(paths.targetPath, "\u5378\u8F7D");
1658
+ const state = readState2(paths.statePath);
1659
+ const restored = restoredOriginal(state);
1660
+ const after = restored?.toString("utf8") ?? "";
1661
+ const afterExists = restored !== void 0;
1662
+ const backupDir = nextBackupDir(paths.homeDir);
1663
+ if (!paths.dryRun) {
1664
+ backupTarget(backupDir, paths.targetPath, existing.bytes);
1665
+ if (restored === void 0)
1666
+ rmSync3(paths.targetPath, { force: true });
1667
+ else
1668
+ writeFileSync5(paths.targetPath, restored);
1669
+ if (restored === void 0)
1670
+ removeCreatedEmptyDirs(paths, state);
1671
+ rmSync3(paths.statePath, { force: true });
1672
+ }
1673
+ return {
1674
+ ...result(paths.targetPath, "uninstall", true, previousState, existing.text, afterExists),
1675
+ state: "not_installed",
1676
+ after,
1677
+ backupDir,
1678
+ totalBytes: restored?.byteLength ?? 0
1679
+ };
1680
+ }
1681
+ function resolveOptions(options) {
1682
+ const homeDir = resolve4(options?.homeDir ?? homedir7());
1683
+ const projectDir = resolve4(options?.projectDir ?? process.cwd());
1684
+ const targetPath2 = join9(projectDir, ".claude", "rules", "wingman-plan-protocol.md");
1685
+ const projectHash = sha256Hex(projectDir);
1686
+ return {
1687
+ homeDir,
1688
+ projectDir,
1689
+ dryRun: options?.dryRun ?? false,
1690
+ targetPath: targetPath2,
1691
+ statePath: join9(homeDir, ".wingman", "state", `plan-protocol-cc-${projectHash}.json`)
1692
+ };
1693
+ }
1694
+ function readExisting(targetPath2) {
1695
+ if (!existsSync6(targetPath2))
1696
+ return void 0;
1697
+ const bytes = readFileSync7(targetPath2);
1698
+ const identity = inspectPlanProtocolFile(bytes, targetPath2);
1699
+ return { bytes, text: bytes.toString("utf8"), identity };
1700
+ }
1701
+ function stateForIdentity(identity) {
1702
+ if (identity === void 0)
1703
+ return "not_installed";
1704
+ if (identity.kind === "current")
1705
+ return "installed";
1706
+ if (identity.kind === "legacy")
1707
+ return "outdated";
1708
+ return "modified";
1709
+ }
1710
+ function statusFromIdentity(targetPath2, identity) {
1711
+ return {
1712
+ harness: "cc",
1713
+ state: stateForIdentity(identity),
1714
+ targetPath: targetPath2,
1715
+ ...identity.version !== void 0 ? { version: identity.version } : {},
1716
+ sha256: identity.sha256,
1717
+ ...identity.kind === "modified" ? { reason: "\u53D7\u7BA1\u534F\u8BAE\u6587\u4EF6\u5DF2\u88AB\u4FEE\u6539" } : {}
1718
+ };
1719
+ }
1720
+ function result(targetPath2, operation, changed, previousState, before, afterExists) {
1721
+ return {
1722
+ harness: "cc",
1723
+ operation,
1724
+ changed,
1725
+ previousState,
1726
+ state: previousState,
1727
+ targetPath: targetPath2,
1728
+ before,
1729
+ after: before,
1730
+ afterExists,
1731
+ effectiveAt: "next_session",
1732
+ totalBytes: Buffer.byteLength(before)
1733
+ };
1734
+ }
1735
+ function modifiedError(targetPath2, operation) {
1736
+ return new Error(`\u53D7\u7BA1\u534F\u8BAE\u6587\u4EF6\u5DF2\u88AB\u4FEE\u6539(${targetPath2})\uFF0C\u62D2\u7EDD${operation}\u5E76\u4FDD\u7559\u7528\u6237\u5185\u5BB9`);
1737
+ }
1738
+ function nextBackupDir(homeDir) {
1739
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
1740
+ const root = join9(homeDir, ".wingman", "backups");
1741
+ let dir = join9(root, stamp);
1742
+ for (let n = 2; existsSync6(dir); n += 1)
1743
+ dir = join9(root, `${stamp}-${n}`);
1744
+ return dir;
1745
+ }
1746
+ function backupTarget(backupDir, targetPath2, bytes) {
1747
+ mkdirSync4(backupDir, { recursive: true });
1748
+ if (bytes !== void 0)
1749
+ writeFileSync5(join9(backupDir, basename3(targetPath2)), bytes);
1750
+ }
1751
+ function readState2(statePath2) {
1752
+ if (!existsSync6(statePath2))
1753
+ return void 0;
1754
+ try {
1755
+ const state = JSON.parse(readFileSync7(statePath2, "utf8"));
1756
+ return state.version === 1 && state.harness === "cc" && typeof state.targetPath === "string" ? state : void 0;
1757
+ } catch {
1758
+ return void 0;
1759
+ }
1760
+ }
1761
+ function writeState2(statePath2, state) {
1762
+ mkdirSync4(dirname6(statePath2), { recursive: true });
1763
+ writeFileSync5(statePath2, `${JSON.stringify(state, null, 2)}
1764
+ `);
1765
+ }
1766
+ function restoredOriginal(state) {
1767
+ if (state?.original.existed !== true)
1768
+ return void 0;
1769
+ return Buffer.from(state.original.base64 ?? "", "base64");
1770
+ }
1771
+ function removeCreatedEmptyDirs(paths, state) {
1772
+ const rulesDir = dirname6(paths.targetPath);
1773
+ const claudeDir = dirname6(rulesDir);
1774
+ if (state?.createdRulesDir && existsSync6(rulesDir) && readdirSync2(rulesDir).length === 0) {
1775
+ rmdirSync(rulesDir);
1776
+ }
1777
+ if (state?.createdClaudeDir && existsSync6(claudeDir) && readdirSync2(claudeDir).length === 0) {
1778
+ rmdirSync(claudeDir);
1779
+ }
1780
+ }
1781
+ var init_protocol = __esm({
1782
+ "packages/adapter-cc/dist/protocol.js"() {
1783
+ "use strict";
1784
+ init_dist();
1785
+ }
1786
+ });
1787
+
1466
1788
  // packages/adapter-cc/dist/index.js
1467
1789
  var dist_exports = {};
1468
1790
  __export(dist_exports, {
1469
1791
  WIRED_EVENTS: () => WIRED_EVENTS,
1470
1792
  createAdapter: () => createAdapter,
1471
- feedbackSignals: () => feedbackSignalsCc
1793
+ feedbackSignals: () => feedbackSignalsCc,
1794
+ installProtocolCc: () => installProtocolCc,
1795
+ protocolStatusCc: () => protocolStatusCc,
1796
+ uninstallProtocolCc: () => uninstallProtocolCc
1472
1797
  });
1473
1798
  function createAdapter(_options) {
1474
1799
  return {
@@ -1489,6 +1814,7 @@ var init_dist2 = __esm({
1489
1814
  init_hooks();
1490
1815
  init_scan();
1491
1816
  init_hooks();
1817
+ init_protocol();
1492
1818
  init_scan();
1493
1819
  }
1494
1820
  });
@@ -1592,15 +1918,15 @@ var init_date = __esm({
1592
1918
 
1593
1919
  // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/error.js
1594
1920
  function getLineColFromPtr(string, ptr) {
1595
- let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
1596
- return [lines.length, lines.pop().length + 1];
1921
+ let lines2 = string.slice(0, ptr).split(/\r\n|\n|\r/g);
1922
+ return [lines2.length, lines2.pop().length + 1];
1597
1923
  }
1598
1924
  function makeCodeBlock(string, line, column) {
1599
- let lines = string.split(/\r\n|\n|\r/g);
1925
+ let lines2 = string.split(/\r\n|\n|\r/g);
1600
1926
  let codeblock = "";
1601
1927
  let numberLen = (Math.log10(line + 1) | 0) + 1;
1602
1928
  for (let i = line - 1; i <= line + 1; i++) {
1603
- let l = lines[i - 1];
1929
+ let l = lines2[i - 1];
1604
1930
  if (!l)
1605
1931
  continue;
1606
1932
  codeblock += i.toString().padEnd(numberLen, " ");
@@ -2202,10 +2528,10 @@ var init_dist3 = __esm({
2202
2528
  });
2203
2529
 
2204
2530
  // packages/adapter-codex/dist/shared.js
2205
- import { homedir as homedir7 } from "node:os";
2531
+ import { homedir as homedir8 } from "node:os";
2206
2532
  import { join as join10 } from "node:path";
2207
2533
  function resolvePaths(options) {
2208
- const homeDir = options?.homeDir ?? homedir7();
2534
+ const homeDir = options?.homeDir ?? homedir8();
2209
2535
  const projectDir = options?.projectDir ?? process.cwd();
2210
2536
  return { homeDir, projectDir, codexHome: join10(homeDir, ".codex") };
2211
2537
  }
@@ -2519,8 +2845,8 @@ var init_toml_edit = __esm({
2519
2845
  });
2520
2846
 
2521
2847
  // packages/adapter-codex/dist/actions.js
2522
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
2523
- import { basename as basename3, dirname as dirname6, join as join11 } from "node:path";
2848
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync3, writeFileSync as writeFileSync6 } from "node:fs";
2849
+ import { basename as basename4, dirname as dirname7, join as join11 } from "node:path";
2524
2850
  async function planAction(action, options) {
2525
2851
  const paths = resolvePaths(options);
2526
2852
  const resolved = resolveToggle(action, paths);
@@ -2530,7 +2856,7 @@ async function planAction(action, options) {
2530
2856
  writes: [
2531
2857
  { file: resolved.file, kind: "modify", before: resolved.before, after: resolved.after }
2532
2858
  ],
2533
- backupDir: nextBackupDir(paths),
2859
+ backupDir: nextBackupDir2(paths),
2534
2860
  references: scanReferences(collectCorpus(paths), resolved.bareName),
2535
2861
  // §4b 地面真值:config.toml 启动时读,外改对运行中会话不生效 → 如实报 next_session
2536
2862
  effectiveAt: "next_session"
@@ -2550,10 +2876,10 @@ async function applyAction(plan, options) {
2550
2876
  };
2551
2877
  }
2552
2878
  const backupsRoot = join11(wingmanStateRoot(paths), "backups");
2553
- if (dirname6(plan.backupDir) !== backupsRoot) {
2879
+ if (dirname7(plan.backupDir) !== backupsRoot) {
2554
2880
  return { ok: false, error: `backupDir \u4E0D\u5728 wingman \u5907\u4EFD\u6839(${backupsRoot})\u4E0B,\u62D2\u7EDD\u6267\u884C` };
2555
2881
  }
2556
- const currentRaw = readFileSync7(write.file);
2882
+ const currentRaw = readFileSync8(write.file);
2557
2883
  if (!currentRaw.equals(Buffer.from(write.before, "utf8"))) {
2558
2884
  return { ok: false, error: `\u6587\u4EF6\u5728 plan \u4E4B\u540E\u88AB\u4FEE\u6539(${write.file}),\u62D2\u7EDD\u6267\u884C;\u8BF7\u91CD\u65B0 plan` };
2559
2885
  }
@@ -2564,19 +2890,19 @@ async function applyAction(plan, options) {
2564
2890
  if (resolved.after !== write.after) {
2565
2891
  return { ok: false, error: "plan \u4E0E\u672C adapter \u7684\u63A8\u6F14\u4E0D\u4E00\u81F4,\u62D2\u7EDD\u6267\u884C;\u8BF7\u91CD\u65B0 plan" };
2566
2892
  }
2567
- mkdirSync4(plan.backupDir, { recursive: true });
2568
- writeFileSync5(join11(plan.backupDir, basename3(write.file)), currentRaw);
2569
- writeFileSync5(write.file, write.after);
2893
+ mkdirSync5(plan.backupDir, { recursive: true });
2894
+ writeFileSync6(join11(plan.backupDir, basename4(write.file)), currentRaw);
2895
+ writeFileSync6(write.file, write.after);
2570
2896
  return { ok: true, backupDir: plan.backupDir };
2571
2897
  } catch (error) {
2572
2898
  return { ok: false, error: error.message };
2573
2899
  }
2574
2900
  }
2575
- function nextBackupDir(paths) {
2901
+ function nextBackupDir2(paths) {
2576
2902
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
2577
2903
  const base = join11(wingmanStateRoot(paths), "backups");
2578
2904
  let dir = join11(base, stamp);
2579
- for (let n = 1; existsSync6(dir); n += 1)
2905
+ for (let n = 1; existsSync7(dir); n += 1)
2580
2906
  dir = join11(base, `${stamp}-${n}`);
2581
2907
  return dir;
2582
2908
  }
@@ -2592,9 +2918,9 @@ function resolveToggle(action, paths) {
2592
2918
  const source = m[2];
2593
2919
  const nativeName = m[3];
2594
2920
  const file = source === "user" ? join11(paths.codexHome, "config.toml") : join11(paths.projectDir, ".codex", "config.toml");
2595
- if (!existsSync6(file))
2921
+ if (!existsSync7(file))
2596
2922
  throw new Error(`itemId \u627E\u4E0D\u5230:\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728(${file})`);
2597
- const before = readFileSync7(file, "utf8");
2923
+ const before = readFileSync8(file, "utf8");
2598
2924
  let table;
2599
2925
  try {
2600
2926
  table = parse2(before);
@@ -2833,14 +3159,14 @@ function collectCorpus(paths) {
2833
3159
  } catch {
2834
3160
  continue;
2835
3161
  }
2836
- sources.push({ file, text: readFileSync7(file, "utf8") });
3162
+ sources.push({ file, text: readFileSync8(file, "utf8") });
2837
3163
  }
2838
3164
  return sources;
2839
3165
  }
2840
3166
  function listFilesRecursive(dir) {
2841
3167
  let entries;
2842
3168
  try {
2843
- entries = readdirSync2(dir, { withFileTypes: true });
3169
+ entries = readdirSync3(dir, { withFileTypes: true });
2844
3170
  } catch {
2845
3171
  return [];
2846
3172
  }
@@ -2912,8 +3238,9 @@ var init_actions2 = __esm({
2912
3238
  });
2913
3239
 
2914
3240
  // packages/adapter-codex/dist/hooks.js
2915
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync8, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
2916
- import { basename as basename4, dirname as dirname7, join as join12 } from "node:path";
3241
+ import { spawnSync } from "node:child_process";
3242
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
3243
+ import { basename as basename5, dirname as dirname8, join as join12 } from "node:path";
2917
3244
  async function installHooks(port, options) {
2918
3245
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
2919
3246
  throw new Error(`\u975E\u6CD5\u7AEF\u53E3:${port}`);
@@ -2922,22 +3249,22 @@ async function installHooks(port, options) {
2922
3249
  const root = wingmanStateRoot(paths);
2923
3250
  const scriptPath = join12(root, FORWARD_SCRIPT_NAME);
2924
3251
  const configPath = join12(paths.codexHome, "config.toml");
2925
- const raw = existsSync7(configPath) ? readFileSync8(configPath) : void 0;
3252
+ const raw = existsSync8(configPath) ? readFileSync9(configPath) : void 0;
2926
3253
  const text = raw?.toString("utf8") ?? "";
2927
3254
  const { before, after, remainder, block } = splitManagedBlock(text, configPath);
2928
3255
  const lineEnding = preferredLineEnding(text);
2929
- const newBlock = buildManagedBlock(scriptPath).replaceAll("\n", lineEnding);
3256
+ const newBlock = buildManagedBlock(scriptPath, resolveNodeExecutable()).replaceAll("\n", lineEnding);
2930
3257
  const knownBlock = block === void 0 ? void 0 : inspectManagedBlock(block, scriptPath);
2931
3258
  if (block !== void 0 && knownBlock === void 0) {
2932
3259
  throw new Error(`Wingman Codex hook \u53D7\u7BA1\u5757\u5DF2\u88AB\u4FEE\u6539\u6216\u6B8B\u7F3A(${configPath}),\u62D2\u7EDD\u8C0E\u62A5\u63A5\u7EBF\u6210\u529F;\u8BF7\u5148\u4ECE <wingman\u6839>/backups/ \u6062\u590D\u5B8C\u6574\u5757,\u6216\u4EBA\u5DE5\u6838\u5BF9\u540E\u79FB\u9664\u8BE5\u53D7\u7BA1\u5757\u518D\u91CD\u65B0\u5B89\u88C5\u5E76\u4FE1\u4EFB`);
2933
3260
  }
2934
- mkdirSync5(root, { recursive: true });
2935
- writeFileSync6(scriptPath, FORWARD_SCRIPT);
3261
+ mkdirSync6(root, { recursive: true });
3262
+ writeFileSync7(scriptPath, FORWARD_SCRIPT);
2936
3263
  const statePath2 = hooksStatePath(root);
2937
- const prior = readState2(statePath2);
3264
+ const prior = readState3(statePath2);
2938
3265
  if (knownBlock !== void 0 && knownBlock.state === void 0) {
2939
3266
  if (prior?.port !== port || prior.configPath !== configPath || prior.forwardScriptPath !== scriptPath) {
2940
- writeState2(statePath2, {
3267
+ writeState3(statePath2, {
2941
3268
  ...prior,
2942
3269
  version: 1,
2943
3270
  port,
@@ -2951,7 +3278,7 @@ async function installHooks(port, options) {
2951
3278
  const hasStateOutsideBlock = assertWritable(remainder, configPath);
2952
3279
  const backupDir = raw === void 0 ? void 0 : backupFile(root, configPath, raw);
2953
3280
  if (knownBlock !== void 0) {
2954
- writeState2(statePath2, {
3281
+ writeState3(statePath2, {
2955
3282
  ...prior,
2956
3283
  version: 1,
2957
3284
  port,
@@ -2961,11 +3288,11 @@ async function installHooks(port, options) {
2961
3288
  ...backupDir !== void 0 ? { backupDir } : {}
2962
3289
  });
2963
3290
  const state = hasStateOutsideBlock ? "" : knownBlock.state;
2964
- writeFileSync6(configPath, before + knownBlock.managedBlock + state + after);
3291
+ writeFileSync7(configPath, before + knownBlock.managedBlock + state + after);
2965
3292
  return;
2966
3293
  }
2967
3294
  const original = prior?.original ?? (raw === void 0 ? { existed: false } : { existed: true, base64: raw.toString("base64") });
2968
- writeState2(statePath2, {
3295
+ writeState3(statePath2, {
2969
3296
  version: 1,
2970
3297
  port,
2971
3298
  configPath,
@@ -2977,8 +3304,8 @@ async function installHooks(port, options) {
2977
3304
  let base = remainder;
2978
3305
  if (base !== "" && !base.endsWith("\n"))
2979
3306
  base += lineEnding;
2980
- mkdirSync5(paths.codexHome, { recursive: true });
2981
- writeFileSync6(configPath, base + newBlock);
3307
+ mkdirSync6(paths.codexHome, { recursive: true });
3308
+ writeFileSync7(configPath, base + newBlock);
2982
3309
  }
2983
3310
  async function uninstallHooks(options) {
2984
3311
  const paths = resolvePaths(options);
@@ -2986,8 +3313,8 @@ async function uninstallHooks(options) {
2986
3313
  const scriptPath = join12(root, FORWARD_SCRIPT_NAME);
2987
3314
  const statePath2 = hooksStatePath(root);
2988
3315
  const configPath = join12(paths.codexHome, "config.toml");
2989
- const state = readState2(statePath2);
2990
- const raw = existsSync7(configPath) ? readFileSync8(configPath) : void 0;
3316
+ const state = readState3(statePath2);
3317
+ const raw = existsSync8(configPath) ? readFileSync9(configPath) : void 0;
2991
3318
  if (raw !== void 0) {
2992
3319
  const text = raw.toString("utf8");
2993
3320
  const { before, after, remainder, block } = splitManagedBlock(text, configPath);
@@ -3000,20 +3327,20 @@ async function uninstallHooks(options) {
3000
3327
  const preservedState = hasHooksState(remainder) ? "" : knownBlock.state ?? "";
3001
3328
  const restored = restoreTarget(before + preservedState + after, state);
3002
3329
  if (restored === null)
3003
- rmSync3(configPath);
3330
+ rmSync4(configPath);
3004
3331
  else
3005
- writeFileSync6(configPath, restored);
3332
+ writeFileSync7(configPath, restored);
3006
3333
  }
3007
3334
  }
3008
- rmSync3(scriptPath, { force: true });
3009
- rmSync3(statePath2, { force: true });
3335
+ rmSync4(scriptPath, { force: true });
3336
+ rmSync4(statePath2, { force: true });
3010
3337
  }
3011
3338
  function hasManagedHooks(paths) {
3012
3339
  const configPath = join12(paths.codexHome, "config.toml");
3013
- if (!existsSync7(configPath))
3340
+ if (!existsSync8(configPath))
3014
3341
  return false;
3015
3342
  try {
3016
- const { block } = splitManagedBlock(readFileSync8(configPath, "utf8"), configPath);
3343
+ const { block } = splitManagedBlock(readFileSync9(configPath, "utf8"), configPath);
3017
3344
  return block !== void 0 && inspectManagedBlock(block, join12(wingmanStateRoot(paths), FORWARD_SCRIPT_NAME)) !== void 0;
3018
3345
  } catch {
3019
3346
  return false;
@@ -3023,9 +3350,9 @@ function feedbackSignalsCodex(options) {
3023
3350
  const paths = resolvePaths(options);
3024
3351
  const configPath = join12(paths.codexHome, "config.toml");
3025
3352
  const wiredEvents = [];
3026
- if (existsSync7(configPath)) {
3353
+ if (existsSync8(configPath)) {
3027
3354
  try {
3028
- const { block } = splitManagedBlock(readFileSync8(configPath, "utf8"), configPath);
3355
+ const { block } = splitManagedBlock(readFileSync9(configPath, "utf8"), configPath);
3029
3356
  if (block !== void 0) {
3030
3357
  const knownBlock = inspectManagedBlock(block, join12(wingmanStateRoot(paths), FORWARD_SCRIPT_NAME));
3031
3358
  const hooks = knownBlock === void 0 ? void 0 : parse2(normalizeLineEndings(knownBlock.managedBlock)).hooks;
@@ -3051,39 +3378,68 @@ function restoreTarget(remainder, state) {
3051
3378
  if (!original.existed) {
3052
3379
  return remainder.trim() === "" ? null : remainder;
3053
3380
  }
3054
- const originalBytes = Buffer.from(original.base64 ?? "", "base64");
3055
- const originalText = originalBytes.toString("utf8");
3381
+ const originalBytes3 = Buffer.from(original.base64 ?? "", "base64");
3382
+ const originalText = originalBytes3.toString("utf8");
3056
3383
  try {
3057
3384
  if (deepEqual3(parse2(remainder), parse2(originalText))) {
3058
3385
  const separatorOnly = remainder === `${originalText}
3059
3386
  ` || remainder === `${originalText}\r
3060
3387
  `;
3061
3388
  if (separatorOnly || lineEndingStyle(remainder) === lineEndingStyle(originalText)) {
3062
- return originalBytes;
3389
+ return originalBytes3;
3063
3390
  }
3064
3391
  }
3065
3392
  } catch {
3066
3393
  }
3067
3394
  return remainder;
3068
3395
  }
3069
- function buildManagedBlock(scriptPath) {
3070
- return buildManagedBlockForCommand(`node "${scriptPath}"`);
3396
+ function resolveNodeExecutable(probe = {}) {
3397
+ const execPath = probe.execPath ?? process.execPath;
3398
+ if (isNodeExecutable(execPath))
3399
+ return execPath;
3400
+ const loginShellWhich = probe.loginShellWhich ?? defaultLoginShellWhich;
3401
+ try {
3402
+ const shellNode = loginShellWhich(probe.shell ?? process.env.SHELL ?? "/bin/sh")?.trim();
3403
+ if (shellNode !== void 0 && shellNode !== "node" && isNodeExecutable(shellNode)) {
3404
+ return shellNode;
3405
+ }
3406
+ } catch {
3407
+ }
3408
+ const exists = probe.exists ?? existsSync8;
3409
+ for (const candidate of ["/opt/homebrew/bin/node", "/usr/local/bin/node"]) {
3410
+ try {
3411
+ if (exists(candidate) && isNodeExecutable(candidate))
3412
+ return candidate;
3413
+ } catch {
3414
+ }
3415
+ }
3416
+ return "node";
3417
+ }
3418
+ function defaultLoginShellWhich(shell) {
3419
+ const result4 = spawnSync(shell, ["-lc", "command -v node"], {
3420
+ encoding: "utf8",
3421
+ timeout: 3e3
3422
+ });
3423
+ if (result4.error !== void 0)
3424
+ return void 0;
3425
+ return result4.stdout.trim() || void 0;
3426
+ }
3427
+ function buildManagedBlock(scriptPath, nodeExecutable = "node") {
3428
+ return buildManagedBlockForCommand(`${nodeExecutable} "${scriptPath}"`);
3071
3429
  }
3072
3430
  function buildManagedBlockForCommand(rawCommand) {
3073
3431
  const command = tomlBasicString(rawCommand);
3074
3432
  const handler = `[{ hooks = [{ type = "command", command = ${command} }] }]`;
3075
- const lines = [MANAGED_BLOCK_START, "[hooks]"];
3433
+ const lines2 = [MANAGED_BLOCK_START, "[hooks]"];
3076
3434
  for (const event of HOOK_EVENTS)
3077
- lines.push(`${event} = ${handler}`);
3078
- lines.push(MANAGED_BLOCK_END);
3079
- return `${lines.join("\n")}
3435
+ lines2.push(`${event} = ${handler}`);
3436
+ lines2.push(MANAGED_BLOCK_END);
3437
+ return `${lines2.join("\n")}
3080
3438
  `;
3081
3439
  }
3082
3440
  function inspectManagedBlock(block, scriptPath) {
3083
3441
  const inspected = separateCodexHooksState(block);
3084
3442
  const normalized = normalizeLineEndings(inspected.managedBlock);
3085
- if (normalized === buildManagedBlock(scriptPath))
3086
- return inspected;
3087
3443
  let table;
3088
3444
  try {
3089
3445
  table = parse2(normalized);
@@ -3102,17 +3458,25 @@ function inspectManagedBlock(block, scriptPath) {
3102
3458
  const command = handlers[0].command;
3103
3459
  if (typeof command !== "string")
3104
3460
  return void 0;
3105
- const prefix = `node "${scriptPath}" `;
3106
- if (!command.startsWith(prefix))
3107
- return void 0;
3108
- const legacyPortText = command.slice(prefix.length);
3109
- if (!/^[1-9]\d{0,4}$/.test(legacyPortText))
3110
- return void 0;
3111
- const legacyPort = Number(legacyPortText);
3112
- if (legacyPort > 65535 || String(legacyPort) !== legacyPortText)
3461
+ const match = /^(\S+) "(.+)"(?: (\d+))?$/.exec(command);
3462
+ if (match === null || match[2] !== scriptPath || !isNodeExecutable(match[1] ?? "")) {
3113
3463
  return void 0;
3464
+ }
3465
+ const legacyPortText = match[3];
3466
+ if (legacyPortText !== void 0) {
3467
+ if (!/^[1-9]\d{0,4}$/.test(legacyPortText))
3468
+ return void 0;
3469
+ const legacyPort = Number(legacyPortText);
3470
+ if (legacyPort > 65535 || String(legacyPort) !== legacyPortText)
3471
+ return void 0;
3472
+ }
3114
3473
  return normalized === buildManagedBlockForCommand(command) ? inspected : void 0;
3115
3474
  }
3475
+ function isNodeExecutable(executable) {
3476
+ if (executable === "node")
3477
+ return true;
3478
+ return /^(?:\/|[A-Za-z]:[\\/])[^\s"]*[\\/]node(?:\.exe)?$/.test(executable);
3479
+ }
3116
3480
  function separateCodexHooksState(block) {
3117
3481
  const stateHeader = /^[ \t]*\[[ \t]*hooks[ \t]*\.[ \t]*state(?:[ \t]*|[ \t]*\..+)\][ \t]*(?:#.*)?\r?$/m;
3118
3482
  const match = stateHeader.exec(block);
@@ -3211,27 +3575,27 @@ function hasHooksState(text) {
3211
3575
  function backupFile(root, filePath, contents) {
3212
3576
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
3213
3577
  let dir = join12(root, "backups", stamp);
3214
- for (let n = 1; existsSync7(dir); n += 1)
3578
+ for (let n = 1; existsSync8(dir); n += 1)
3215
3579
  dir = join12(root, "backups", `${stamp}-${n}`);
3216
- mkdirSync5(dir, { recursive: true });
3217
- writeFileSync6(join12(dir, basename4(filePath)), contents);
3580
+ mkdirSync6(dir, { recursive: true });
3581
+ writeFileSync7(join12(dir, basename5(filePath)), contents);
3218
3582
  return dir;
3219
3583
  }
3220
3584
  function hooksStatePath(root) {
3221
3585
  return join12(root, "state", "codex-hooks.json");
3222
3586
  }
3223
- function readState2(statePath2) {
3224
- if (!existsSync7(statePath2))
3587
+ function readState3(statePath2) {
3588
+ if (!existsSync8(statePath2))
3225
3589
  return void 0;
3226
3590
  try {
3227
- return JSON.parse(readFileSync8(statePath2, "utf8"));
3591
+ return JSON.parse(readFileSync9(statePath2, "utf8"));
3228
3592
  } catch {
3229
3593
  return void 0;
3230
3594
  }
3231
3595
  }
3232
- function writeState2(statePath2, state) {
3233
- mkdirSync5(dirname7(statePath2), { recursive: true });
3234
- writeFileSync6(statePath2, `${JSON.stringify(state, null, 2)}
3596
+ function writeState3(statePath2, state) {
3597
+ mkdirSync6(dirname8(statePath2), { recursive: true });
3598
+ writeFileSync7(statePath2, `${JSON.stringify(state, null, 2)}
3235
3599
  `);
3236
3600
  }
3237
3601
  function deepEqual3(a, b) {
@@ -3387,11 +3751,11 @@ process.stdin.on('end', async () => {
3387
3751
  });
3388
3752
 
3389
3753
  // packages/adapter-codex/dist/scan.js
3390
- import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
3391
- import { dirname as dirname8, isAbsolute, join as join13, resolve as resolve4 } from "node:path";
3754
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync4 } from "node:fs";
3755
+ import { dirname as dirname9, isAbsolute, join as join13, resolve as resolve5 } from "node:path";
3392
3756
  async function detect(options) {
3393
3757
  const { codexHome } = resolvePaths(options);
3394
- return { installed: existsSync8(codexHome) };
3758
+ return { installed: existsSync9(codexHome) };
3395
3759
  }
3396
3760
  async function scan(options) {
3397
3761
  const paths = resolvePaths(options);
@@ -3435,7 +3799,7 @@ function loadConfigLayers(paths) {
3435
3799
  for (const [source, path] of candidates) {
3436
3800
  if (fileSize(path) === void 0)
3437
3801
  continue;
3438
- const text = readFileSync9(path, "utf8");
3802
+ const text = readFileSync10(path, "utf8");
3439
3803
  try {
3440
3804
  layers.push({ source, path, table: parse2(text) });
3441
3805
  } catch (error) {
@@ -3482,7 +3846,7 @@ function collectRules(out, paths, layers) {
3482
3846
  }
3483
3847
  const instructionsFile = layer.table.model_instructions_file;
3484
3848
  if (typeof instructionsFile === "string") {
3485
- const filePath = isAbsolute(instructionsFile) ? instructionsFile : resolve4(dirname8(layer.path), instructionsFile);
3849
+ const filePath = isAbsolute(instructionsFile) ? instructionsFile : resolve5(dirname9(layer.path), instructionsFile);
3486
3850
  const item = {
3487
3851
  id: `codex:rules:${layer.source}:model_instructions_file`,
3488
3852
  slot: "rules",
@@ -3614,7 +3978,7 @@ function collectHistory(out, paths, layers) {
3614
3978
  function expandPath(value, paths) {
3615
3979
  if (value === "~" || value.startsWith("~/"))
3616
3980
  return join13(paths.homeDir, value.slice(2));
3617
- return isAbsolute(value) ? value : resolve4(paths.codexHome, value);
3981
+ return isAbsolute(value) ? value : resolve5(paths.codexHome, value);
3618
3982
  }
3619
3983
  function assertUniqueIds(slots) {
3620
3984
  const seen = /* @__PURE__ */ new Set();
@@ -3644,7 +4008,7 @@ function isDirectory(path) {
3644
4008
  }
3645
4009
  function directoryBytes(dir) {
3646
4010
  let total = 0;
3647
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
4011
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
3648
4012
  const entryPath = join13(dir, entry.name);
3649
4013
  if (entry.isDirectory())
3650
4014
  total += directoryBytes(entryPath);
@@ -3666,12 +4030,219 @@ var init_scan2 = __esm({
3666
4030
  }
3667
4031
  });
3668
4032
 
4033
+ // packages/adapter-codex/dist/protocol.js
4034
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync11, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
4035
+ import { basename as basename6, dirname as dirname10, join as join14, resolve as resolve6 } from "node:path";
4036
+ function protocolStatusCodex(options) {
4037
+ const paths = resolveOptions2(options);
4038
+ if (!existsSync10(paths.targetPath))
4039
+ return status(paths.targetPath, "not_installed");
4040
+ try {
4041
+ const file = readAgents(paths.targetPath);
4042
+ if (file.identity === void 0)
4043
+ return status(paths.targetPath, "not_installed");
4044
+ const state = stateForIdentity2(file.identity);
4045
+ return status(paths.targetPath, state, file.identity);
4046
+ } catch (error) {
4047
+ return status(paths.targetPath, "invalid", void 0, error.message);
4048
+ }
4049
+ }
4050
+ function installProtocolCodex(options) {
4051
+ const paths = resolveOptions2(options);
4052
+ const existing = existsSync10(paths.targetPath) ? readAgents(paths.targetPath) : void 0;
4053
+ const previousState = stateForIdentity2(existing?.identity);
4054
+ if (previousState === "installed") {
4055
+ return result2(paths.targetPath, "install", false, previousState, existing?.text ?? "", true);
4056
+ }
4057
+ if (previousState === "modified")
4058
+ throw modifiedError2(paths.targetPath, "\u5B89\u88C5");
4059
+ const before = existing?.text ?? "";
4060
+ const lineEnding = preferredMarkdownLineEnding(before);
4061
+ const block = buildPlanProtocolBlock().replaceAll("\n", lineEnding);
4062
+ const after = existing?.split.block ? existing.split.before + block + existing.split.after : appendBlock(before, block, lineEnding);
4063
+ const installedRemainder = splitPlanProtocolBlock(after, paths.targetPath).remainder;
4064
+ const backupDir = nextBackupDir3(paths.homeDir);
4065
+ if (!paths.dryRun) {
4066
+ const prior = readState4(paths.statePath, paths.targetPath);
4067
+ backupTarget2(backupDir, paths.targetPath, existing?.bytes);
4068
+ writeState4(paths.statePath, {
4069
+ version: 1,
4070
+ projectDir: paths.projectDir,
4071
+ targetPath: paths.targetPath,
4072
+ protocolVersion: PLAN_PROTOCOL_VERSION,
4073
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4074
+ backupDir,
4075
+ installedRemainderSha256: sha256Hex(installedRemainder),
4076
+ original: prior?.original ?? (existing === void 0 ? { existed: false } : { existed: true, base64: existing.bytes.toString("base64") })
4077
+ });
4078
+ writeFileSync8(paths.targetPath, after);
4079
+ }
4080
+ return {
4081
+ ...result2(paths.targetPath, "install", true, previousState, before, true),
4082
+ state: "installed",
4083
+ version: PLAN_PROTOCOL_VERSION,
4084
+ after,
4085
+ backupDir,
4086
+ totalBytes: Buffer.byteLength(after)
4087
+ };
4088
+ }
4089
+ function uninstallProtocolCodex(options) {
4090
+ const paths = resolveOptions2(options);
4091
+ if (!existsSync10(paths.targetPath)) {
4092
+ return result2(paths.targetPath, "uninstall", false, "not_installed", "", false);
4093
+ }
4094
+ const existing = readAgents(paths.targetPath);
4095
+ if (existing.identity === void 0) {
4096
+ return result2(paths.targetPath, "uninstall", false, "not_installed", existing.text, true);
4097
+ }
4098
+ const previousState = stateForIdentity2(existing.identity);
4099
+ if (previousState === "modified")
4100
+ throw modifiedError2(paths.targetPath, "\u5378\u8F7D");
4101
+ const state = readState4(paths.statePath, paths.targetPath);
4102
+ const canRestore = state !== void 0 && sha256Hex(existing.split.remainder) === state.installedRemainderSha256;
4103
+ const restored = canRestore ? originalBytes(state) : void 0;
4104
+ const deleteTarget = canRestore ? state?.original.existed === false : existing.split.remainder.trim() === "";
4105
+ const after = deleteTarget ? "" : restored?.toString("utf8") ?? existing.split.remainder;
4106
+ const backupDir = nextBackupDir3(paths.homeDir);
4107
+ if (!paths.dryRun) {
4108
+ backupTarget2(backupDir, paths.targetPath, existing.bytes);
4109
+ if (deleteTarget)
4110
+ rmSync5(paths.targetPath, { force: true });
4111
+ else
4112
+ writeFileSync8(paths.targetPath, restored ?? existing.split.remainder);
4113
+ rmSync5(paths.statePath, { force: true });
4114
+ }
4115
+ return {
4116
+ ...result2(paths.targetPath, "uninstall", true, previousState, existing.text, !deleteTarget),
4117
+ state: "not_installed",
4118
+ message: canRestore ? SHARED_MESSAGE : `${SHARED_MESSAGE}\uFF1B\u5DF2\u79FB\u9664\u5757\u5E76\u4FDD\u7559\u7528\u6237\u6539\u52A8`,
4119
+ after,
4120
+ backupDir,
4121
+ totalBytes: Buffer.byteLength(after),
4122
+ restoredOriginal: canRestore
4123
+ };
4124
+ }
4125
+ function resolveOptions2(options) {
4126
+ const base = resolvePaths(options);
4127
+ const homeDir = resolve6(base.homeDir);
4128
+ const projectDir = resolve6(base.projectDir);
4129
+ const targetPath2 = join14(projectDir, "AGENTS.md");
4130
+ const statePath2 = join14(wingmanStateRoot({ ...base, homeDir, projectDir }), "state", `plan-protocol-agents-${sha256Hex(projectDir)}.json`);
4131
+ return { homeDir, projectDir, targetPath: targetPath2, statePath: statePath2, dryRun: options?.dryRun ?? false };
4132
+ }
4133
+ function readAgents(targetPath2) {
4134
+ const bytes = readFileSync11(targetPath2);
4135
+ const text = decodeUtf8Strict(bytes, targetPath2);
4136
+ let split;
4137
+ try {
4138
+ split = splitPlanProtocolBlock(text, targetPath2);
4139
+ } catch (error) {
4140
+ throw new Error(`${targetPath2}: ${error.message}`);
4141
+ }
4142
+ return {
4143
+ bytes,
4144
+ text,
4145
+ split,
4146
+ ...split.block !== void 0 ? { identity: inspectPlanProtocolBlock(split.block) } : {}
4147
+ };
4148
+ }
4149
+ function appendBlock(text, block, lineEnding) {
4150
+ if (text === "")
4151
+ return block;
4152
+ let base = text;
4153
+ if (!base.endsWith("\n"))
4154
+ base += lineEnding;
4155
+ if (!base.endsWith(`${lineEnding}${lineEnding}`))
4156
+ base += lineEnding;
4157
+ return base + block;
4158
+ }
4159
+ function stateForIdentity2(identity) {
4160
+ if (identity === void 0)
4161
+ return "not_installed";
4162
+ if (identity.kind === "current")
4163
+ return "installed";
4164
+ if (identity.kind === "legacy")
4165
+ return "outdated";
4166
+ return "modified";
4167
+ }
4168
+ function status(targetPath2, state, identity, reason) {
4169
+ return {
4170
+ harness: "codex",
4171
+ state,
4172
+ targetPath: targetPath2,
4173
+ shared: true,
4174
+ message: SHARED_MESSAGE,
4175
+ ...identity?.version !== void 0 ? { version: identity.version } : {},
4176
+ ...reason !== void 0 ? { reason } : {}
4177
+ };
4178
+ }
4179
+ function result2(targetPath2, operation, changed, previousState, before, afterExists) {
4180
+ return {
4181
+ ...status(targetPath2, previousState),
4182
+ operation,
4183
+ changed,
4184
+ previousState,
4185
+ before,
4186
+ after: before,
4187
+ afterExists,
4188
+ effectiveAt: "next_session",
4189
+ totalBytes: Buffer.byteLength(before)
4190
+ };
4191
+ }
4192
+ function modifiedError2(targetPath2, operation) {
4193
+ return new Error(`${targetPath2}: \u53D7\u7BA1\u5757\u5DF2\u88AB\u4FEE\u6539\uFF0C\u62D2\u7EDD${operation}\u5E76\u4FDD\u7559\u7528\u6237\u5185\u5BB9`);
4194
+ }
4195
+ function nextBackupDir3(homeDir) {
4196
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
4197
+ const root = join14(homeDir, ".wingman", "backups");
4198
+ let dir = join14(root, stamp);
4199
+ for (let n = 2; existsSync10(dir); n += 1)
4200
+ dir = join14(root, `${stamp}-${n}`);
4201
+ return dir;
4202
+ }
4203
+ function backupTarget2(backupDir, targetPath2, bytes) {
4204
+ mkdirSync7(backupDir, { recursive: true });
4205
+ if (bytes !== void 0)
4206
+ writeFileSync8(join14(backupDir, basename6(targetPath2)), bytes);
4207
+ }
4208
+ function readState4(statePath2, targetPath2) {
4209
+ if (!existsSync10(statePath2))
4210
+ return void 0;
4211
+ try {
4212
+ const value = JSON.parse(readFileSync11(statePath2, "utf8"));
4213
+ return value.version === 1 && value.targetPath === targetPath2 ? value : void 0;
4214
+ } catch {
4215
+ return void 0;
4216
+ }
4217
+ }
4218
+ function writeState4(statePath2, state) {
4219
+ mkdirSync7(dirname10(statePath2), { recursive: true });
4220
+ writeFileSync8(statePath2, `${JSON.stringify(state, null, 2)}
4221
+ `);
4222
+ }
4223
+ function originalBytes(state) {
4224
+ return state.original.existed ? Buffer.from(state.original.base64 ?? "", "base64") : void 0;
4225
+ }
4226
+ var SHARED_MESSAGE;
4227
+ var init_protocol2 = __esm({
4228
+ "packages/adapter-codex/dist/protocol.js"() {
4229
+ "use strict";
4230
+ init_dist();
4231
+ init_shared();
4232
+ SHARED_MESSAGE = "\u6B64 AGENTS.md \u534F\u8BAE\u5757\u7531 Codex \u4E0E dsh \u5171\u7528";
4233
+ }
4234
+ });
4235
+
3669
4236
  // packages/adapter-codex/dist/index.js
3670
4237
  var dist_exports2 = {};
3671
4238
  __export(dist_exports2, {
3672
4239
  HOOK_EVENTS: () => HOOK_EVENTS,
3673
4240
  createAdapter: () => createAdapter2,
3674
- feedbackSignals: () => feedbackSignalsCodex
4241
+ feedbackSignals: () => feedbackSignalsCodex,
4242
+ installProtocolCodex: () => installProtocolCodex,
4243
+ protocolStatusCodex: () => protocolStatusCodex,
4244
+ resolveNodeExecutable: () => resolveNodeExecutable,
4245
+ uninstallProtocolCodex: () => uninstallProtocolCodex
3675
4246
  });
3676
4247
  function createAdapter2(_options) {
3677
4248
  return {
@@ -3691,6 +4262,7 @@ var init_dist4 = __esm({
3691
4262
  init_hooks2();
3692
4263
  init_scan2();
3693
4264
  init_hooks2();
4265
+ init_protocol2();
3694
4266
  }
3695
4267
  });
3696
4268
 
@@ -4053,7 +4625,7 @@ var require_directives = __commonJS({
4053
4625
  return tag[0] === "!" ? tag : `!<${tag}>`;
4054
4626
  }
4055
4627
  toString(doc) {
4056
- const lines = this.yaml.explicit ? [`%YAML ${this.yaml.version || "1.2"}`] : [];
4628
+ const lines2 = this.yaml.explicit ? [`%YAML ${this.yaml.version || "1.2"}`] : [];
4057
4629
  const tagEntries = Object.entries(this.tags);
4058
4630
  let tagNames;
4059
4631
  if (doc && tagEntries.length > 0 && identity.isNode(doc.contents)) {
@@ -4069,9 +4641,9 @@ var require_directives = __commonJS({
4069
4641
  if (handle2 === "!!" && prefix === "tag:yaml.org,2002:")
4070
4642
  continue;
4071
4643
  if (!doc || tagNames.some((tn) => tn.startsWith(prefix)))
4072
- lines.push(`%TAG ${handle2} ${prefix}`);
4644
+ lines2.push(`%TAG ${handle2} ${prefix}`);
4073
4645
  }
4074
- return lines.join("\n");
4646
+ return lines2.join("\n");
4075
4647
  }
4076
4648
  };
4077
4649
  Directives.defaultYaml = { explicit: false, version: "1.2" };
@@ -5530,22 +6102,22 @@ var require_stringifyCollection = __commonJS({
5530
6102
  const { indent, options: { commentString } } = ctx;
5531
6103
  const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null });
5532
6104
  let chompKeep = false;
5533
- const lines = [];
6105
+ const lines2 = [];
5534
6106
  for (let i = 0; i < items.length; ++i) {
5535
6107
  const item = items[i];
5536
6108
  let comment2 = null;
5537
6109
  if (identity.isNode(item)) {
5538
6110
  if (!chompKeep && item.spaceBefore)
5539
- lines.push("");
5540
- addCommentBefore(ctx, lines, item.commentBefore, chompKeep);
6111
+ lines2.push("");
6112
+ addCommentBefore(ctx, lines2, item.commentBefore, chompKeep);
5541
6113
  if (item.comment)
5542
6114
  comment2 = item.comment;
5543
6115
  } else if (identity.isPair(item)) {
5544
6116
  const ik = identity.isNode(item.key) ? item.key : null;
5545
6117
  if (ik) {
5546
6118
  if (!chompKeep && ik.spaceBefore)
5547
- lines.push("");
5548
- addCommentBefore(ctx, lines, ik.commentBefore, chompKeep);
6119
+ lines2.push("");
6120
+ addCommentBefore(ctx, lines2, ik.commentBefore, chompKeep);
5549
6121
  }
5550
6122
  }
5551
6123
  chompKeep = false;
@@ -5554,15 +6126,15 @@ var require_stringifyCollection = __commonJS({
5554
6126
  str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2));
5555
6127
  if (chompKeep && comment2)
5556
6128
  chompKeep = false;
5557
- lines.push(blockItemPrefix + str2);
6129
+ lines2.push(blockItemPrefix + str2);
5558
6130
  }
5559
6131
  let str;
5560
- if (lines.length === 0) {
6132
+ if (lines2.length === 0) {
5561
6133
  str = flowChars.start + flowChars.end;
5562
6134
  } else {
5563
- str = lines[0];
5564
- for (let i = 1; i < lines.length; ++i) {
5565
- const line = lines[i];
6135
+ str = lines2[0];
6136
+ for (let i = 1; i < lines2.length; ++i) {
6137
+ const line = lines2[i];
5566
6138
  str += line ? `
5567
6139
  ${indent}${line}` : "\n";
5568
6140
  }
@@ -5585,22 +6157,22 @@ ${indent}${line}` : "\n";
5585
6157
  });
5586
6158
  let reqNewline = false;
5587
6159
  let linesAtValue = 0;
5588
- const lines = [];
6160
+ const lines2 = [];
5589
6161
  for (let i = 0; i < items.length; ++i) {
5590
6162
  const item = items[i];
5591
6163
  let comment = null;
5592
6164
  if (identity.isNode(item)) {
5593
6165
  if (item.spaceBefore)
5594
- lines.push("");
5595
- addCommentBefore(ctx, lines, item.commentBefore, false);
6166
+ lines2.push("");
6167
+ addCommentBefore(ctx, lines2, item.commentBefore, false);
5596
6168
  if (item.comment)
5597
6169
  comment = item.comment;
5598
6170
  } else if (identity.isPair(item)) {
5599
6171
  const ik = identity.isNode(item.key) ? item.key : null;
5600
6172
  if (ik) {
5601
6173
  if (ik.spaceBefore)
5602
- lines.push("");
5603
- addCommentBefore(ctx, lines, ik.commentBefore, false);
6174
+ lines2.push("");
6175
+ addCommentBefore(ctx, lines2, ik.commentBefore, false);
5604
6176
  if (ik.comment)
5605
6177
  reqNewline = true;
5606
6178
  }
@@ -5617,12 +6189,12 @@ ${indent}${line}` : "\n";
5617
6189
  if (comment)
5618
6190
  reqNewline = true;
5619
6191
  let str = stringify2.stringify(item, itemCtx, () => comment = null);
5620
- reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
6192
+ reqNewline || (reqNewline = lines2.length > linesAtValue || str.includes("\n"));
5621
6193
  if (i < items.length - 1) {
5622
6194
  str += ",";
5623
6195
  } else if (ctx.options.trailingComma) {
5624
6196
  if (ctx.options.lineWidth > 0) {
5625
- reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth);
6197
+ reqNewline || (reqNewline = lines2.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth);
5626
6198
  }
5627
6199
  if (reqNewline) {
5628
6200
  str += ",";
@@ -5630,35 +6202,35 @@ ${indent}${line}` : "\n";
5630
6202
  }
5631
6203
  if (comment)
5632
6204
  str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
5633
- lines.push(str);
5634
- linesAtValue = lines.length;
6205
+ lines2.push(str);
6206
+ linesAtValue = lines2.length;
5635
6207
  }
5636
6208
  const { start, end } = flowChars;
5637
- if (lines.length === 0) {
6209
+ if (lines2.length === 0) {
5638
6210
  return start + end;
5639
6211
  } else {
5640
6212
  if (!reqNewline) {
5641
- const len = lines.reduce((sum, line) => sum + line.length + 2, 2);
6213
+ const len = lines2.reduce((sum, line) => sum + line.length + 2, 2);
5642
6214
  reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth;
5643
6215
  }
5644
6216
  if (reqNewline) {
5645
6217
  let str = start;
5646
- for (const line of lines)
6218
+ for (const line of lines2)
5647
6219
  str += line ? `
5648
6220
  ${indentStep}${indent}${line}` : "\n";
5649
6221
  return `${str}
5650
6222
  ${indent}${end}`;
5651
6223
  } else {
5652
- return `${start}${fcPadding}${lines.join(" ")}${fcPadding}${end}`;
6224
+ return `${start}${fcPadding}${lines2.join(" ")}${fcPadding}${end}`;
5653
6225
  }
5654
6226
  }
5655
6227
  }
5656
- function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) {
6228
+ function addCommentBefore({ indent, options: { commentString } }, lines2, comment, chompKeep) {
5657
6229
  if (comment && chompKeep)
5658
6230
  comment = comment.replace(/^\n+/, "");
5659
6231
  if (comment) {
5660
6232
  const ic = stringifyComment.indentComment(commentString(comment), indent);
5661
- lines.push(ic.trimStart());
6233
+ lines2.push(ic.trimStart());
5662
6234
  }
5663
6235
  }
5664
6236
  exports.stringifyCollection = stringifyCollection;
@@ -6296,11 +6868,11 @@ var require_binary = __commonJS({
6296
6868
  if (type !== Scalar.Scalar.QUOTE_DOUBLE) {
6297
6869
  const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth);
6298
6870
  const n = Math.ceil(str.length / lineWidth);
6299
- const lines = new Array(n);
6871
+ const lines2 = new Array(n);
6300
6872
  for (let i = 0, o = 0; i < n; ++i, o += lineWidth) {
6301
- lines[i] = str.substr(o, lineWidth);
6873
+ lines2[i] = str.substr(o, lineWidth);
6302
6874
  }
6303
- str = lines.join(type === Scalar.Scalar.BLOCK_LITERAL ? "\n" : " ");
6875
+ str = lines2.join(type === Scalar.Scalar.BLOCK_LITERAL ? "\n" : " ");
6304
6876
  }
6305
6877
  return stringifyString.stringifyString({ comment, type, value: str }, ctx, onComment, onChompKeep);
6306
6878
  }
@@ -6980,35 +7552,35 @@ var require_stringifyDocument = __commonJS({
6980
7552
  var stringify2 = require_stringify();
6981
7553
  var stringifyComment = require_stringifyComment();
6982
7554
  function stringifyDocument(doc, options) {
6983
- const lines = [];
7555
+ const lines2 = [];
6984
7556
  let hasDirectives = options.directives === true;
6985
7557
  if (options.directives !== false && doc.directives) {
6986
7558
  const dir = doc.directives.toString(doc);
6987
7559
  if (dir) {
6988
- lines.push(dir);
7560
+ lines2.push(dir);
6989
7561
  hasDirectives = true;
6990
7562
  } else if (doc.directives.docStart)
6991
7563
  hasDirectives = true;
6992
7564
  }
6993
7565
  if (hasDirectives)
6994
- lines.push("---");
7566
+ lines2.push("---");
6995
7567
  const ctx = stringify2.createStringifyContext(doc, options);
6996
7568
  const { commentString } = ctx.options;
6997
7569
  if (doc.commentBefore) {
6998
- if (lines.length !== 1)
6999
- lines.unshift("");
7570
+ if (lines2.length !== 1)
7571
+ lines2.unshift("");
7000
7572
  const cs = commentString(doc.commentBefore);
7001
- lines.unshift(stringifyComment.indentComment(cs, ""));
7573
+ lines2.unshift(stringifyComment.indentComment(cs, ""));
7002
7574
  }
7003
7575
  let chompKeep = false;
7004
7576
  let contentComment = null;
7005
7577
  if (doc.contents) {
7006
7578
  if (identity.isNode(doc.contents)) {
7007
7579
  if (doc.contents.spaceBefore && hasDirectives)
7008
- lines.push("");
7580
+ lines2.push("");
7009
7581
  if (doc.contents.commentBefore) {
7010
7582
  const cs = commentString(doc.contents.commentBefore);
7011
- lines.push(stringifyComment.indentComment(cs, ""));
7583
+ lines2.push(stringifyComment.indentComment(cs, ""));
7012
7584
  }
7013
7585
  ctx.forceBlockIndent = !!doc.comment;
7014
7586
  contentComment = doc.contents.comment;
@@ -7017,36 +7589,36 @@ var require_stringifyDocument = __commonJS({
7017
7589
  let body = stringify2.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
7018
7590
  if (contentComment)
7019
7591
  body += stringifyComment.lineComment(body, "", commentString(contentComment));
7020
- if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") {
7021
- lines[lines.length - 1] = `--- ${body}`;
7592
+ if ((body[0] === "|" || body[0] === ">") && lines2[lines2.length - 1] === "---") {
7593
+ lines2[lines2.length - 1] = `--- ${body}`;
7022
7594
  } else
7023
- lines.push(body);
7595
+ lines2.push(body);
7024
7596
  } else {
7025
- lines.push(stringify2.stringify(doc.contents, ctx));
7597
+ lines2.push(stringify2.stringify(doc.contents, ctx));
7026
7598
  }
7027
7599
  if (doc.directives?.docEnd) {
7028
7600
  if (doc.comment) {
7029
7601
  const cs = commentString(doc.comment);
7030
7602
  if (cs.includes("\n")) {
7031
- lines.push("...");
7032
- lines.push(stringifyComment.indentComment(cs, ""));
7603
+ lines2.push("...");
7604
+ lines2.push(stringifyComment.indentComment(cs, ""));
7033
7605
  } else {
7034
- lines.push(`... ${cs}`);
7606
+ lines2.push(`... ${cs}`);
7035
7607
  }
7036
7608
  } else {
7037
- lines.push("...");
7609
+ lines2.push("...");
7038
7610
  }
7039
7611
  } else {
7040
7612
  let dc = doc.comment;
7041
7613
  if (dc && chompKeep)
7042
7614
  dc = dc.replace(/^\n+/, "");
7043
7615
  if (dc) {
7044
- if ((!chompKeep || contentComment) && lines[lines.length - 1] !== "")
7045
- lines.push("");
7046
- lines.push(stringifyComment.indentComment(commentString(dc), ""));
7616
+ if ((!chompKeep || contentComment) && lines2[lines2.length - 1] !== "")
7617
+ lines2.push("");
7618
+ lines2.push(stringifyComment.indentComment(commentString(dc), ""));
7047
7619
  }
7048
7620
  }
7049
- return lines.join("\n") + "\n";
7621
+ return lines2.join("\n") + "\n";
7050
7622
  }
7051
7623
  exports.stringifyDocument = stringifyDocument;
7052
7624
  }
@@ -8108,17 +8680,17 @@ var require_resolve_block_scalar = __commonJS({
8108
8680
  if (!header)
8109
8681
  return { value: "", type: null, comment: "", range: [start, start, start] };
8110
8682
  const type = header.mode === ">" ? Scalar.Scalar.BLOCK_FOLDED : Scalar.Scalar.BLOCK_LITERAL;
8111
- const lines = scalar.source ? splitLines(scalar.source) : [];
8112
- let chompStart = lines.length;
8113
- for (let i = lines.length - 1; i >= 0; --i) {
8114
- const content = lines[i][1];
8683
+ const lines2 = scalar.source ? splitLines(scalar.source) : [];
8684
+ let chompStart = lines2.length;
8685
+ for (let i = lines2.length - 1; i >= 0; --i) {
8686
+ const content = lines2[i][1];
8115
8687
  if (content === "" || content === "\r")
8116
8688
  chompStart = i;
8117
8689
  else
8118
8690
  break;
8119
8691
  }
8120
8692
  if (chompStart === 0) {
8121
- const value2 = header.chomp === "+" && lines.length > 0 ? "\n".repeat(Math.max(1, lines.length - 1)) : "";
8693
+ const value2 = header.chomp === "+" && lines2.length > 0 ? "\n".repeat(Math.max(1, lines2.length - 1)) : "";
8122
8694
  let end2 = start + header.length;
8123
8695
  if (scalar.source)
8124
8696
  end2 += scalar.source.length;
@@ -8128,7 +8700,7 @@ var require_resolve_block_scalar = __commonJS({
8128
8700
  let offset = scalar.offset + header.length;
8129
8701
  let contentStart = 0;
8130
8702
  for (let i = 0; i < chompStart; ++i) {
8131
- const [indent, content] = lines[i];
8703
+ const [indent, content] = lines2[i];
8132
8704
  if (content === "" || content === "\r") {
8133
8705
  if (header.indent === 0 && indent.length > trimIndent)
8134
8706
  trimIndent = indent.length;
@@ -8148,17 +8720,17 @@ var require_resolve_block_scalar = __commonJS({
8148
8720
  }
8149
8721
  offset += indent.length + content.length + 1;
8150
8722
  }
8151
- for (let i = lines.length - 1; i >= chompStart; --i) {
8152
- if (lines[i][0].length > trimIndent)
8723
+ for (let i = lines2.length - 1; i >= chompStart; --i) {
8724
+ if (lines2[i][0].length > trimIndent)
8153
8725
  chompStart = i + 1;
8154
8726
  }
8155
8727
  let value = "";
8156
8728
  let sep3 = "";
8157
8729
  let prevMoreIndented = false;
8158
8730
  for (let i = 0; i < contentStart; ++i)
8159
- value += lines[i][0].slice(trimIndent) + "\n";
8731
+ value += lines2[i][0].slice(trimIndent) + "\n";
8160
8732
  for (let i = contentStart; i < chompStart; ++i) {
8161
- let [indent, content] = lines[i];
8733
+ let [indent, content] = lines2[i];
8162
8734
  offset += indent.length + content.length + 1;
8163
8735
  const crlf = content[content.length - 1] === "\r";
8164
8736
  if (crlf)
@@ -8195,8 +8767,8 @@ var require_resolve_block_scalar = __commonJS({
8195
8767
  case "-":
8196
8768
  break;
8197
8769
  case "+":
8198
- for (let i = chompStart; i < lines.length; ++i)
8199
- value += "\n" + lines[i][0].slice(trimIndent);
8770
+ for (let i = chompStart; i < lines2.length; ++i)
8771
+ value += "\n" + lines2[i][0].slice(trimIndent);
8200
8772
  if (value[value.length - 1] !== "\n")
8201
8773
  value += "\n";
8202
8774
  break;
@@ -8271,10 +8843,10 @@ var require_resolve_block_scalar = __commonJS({
8271
8843
  const first = split[0];
8272
8844
  const m = first.match(/^( *)/);
8273
8845
  const line0 = m?.[1] ? [m[1], first.slice(m[1].length)] : ["", first];
8274
- const lines = [line0];
8846
+ const lines2 = [line0];
8275
8847
  for (let i = 1; i < split.length; i += 2)
8276
- lines.push([split[i], split[i + 1]]);
8277
- return lines;
8848
+ lines2.push([split[i], split[i + 1]]);
8849
+ return lines2;
8278
8850
  }
8279
8851
  exports.resolveBlockScalar = resolveBlockScalar;
8280
8852
  }
@@ -11025,9 +11597,9 @@ var require_dist = __commonJS({
11025
11597
  function parseHeader2(commentBefore) {
11026
11598
  if (!commentBefore)
11027
11599
  return void 0;
11028
- const lines = commentBefore.split("\n");
11029
- for (let i = lines.length - 1; i >= 0; i -= 1) {
11030
- const match = lines[i]?.match(/^\s*==\s*(.+?)\s*$/);
11600
+ const lines2 = commentBefore.split("\n");
11601
+ for (let i = lines2.length - 1; i >= 0; i -= 1) {
11602
+ const match = lines2[i]?.match(/^\s*==\s*(.+?)\s*$/);
11031
11603
  if (!match?.[1])
11032
11604
  continue;
11033
11605
  const text = match[1];
@@ -11230,32 +11802,32 @@ var init_dump_config = __esm({
11230
11802
  // packages/adapter-dsh/dist/hooks.js
11231
11803
  import { Buffer as Buffer3 } from "node:buffer";
11232
11804
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
11233
- import { homedir as homedir8 } from "node:os";
11234
- import { dirname as dirname9, join as join14 } from "node:path";
11805
+ import { homedir as homedir9 } from "node:os";
11806
+ import { dirname as dirname11, join as join15 } from "node:path";
11235
11807
  function feedbackSignalsDsh(_options) {
11236
11808
  return { wiredEvents: [], feedback: "unavailable" };
11237
11809
  }
11238
11810
  function resolveDshHome(options) {
11239
11811
  if (options?.homeDir !== void 0)
11240
- return join14(options.homeDir, ".dsh");
11812
+ return join15(options.homeDir, ".dsh");
11241
11813
  const envHome = process.env.DSH_HOME;
11242
11814
  if (envHome !== void 0 && envHome !== "")
11243
11815
  return envHome;
11244
- return join14(homedir8(), ".dsh");
11816
+ return join15(homedir9(), ".dsh");
11245
11817
  }
11246
11818
  function resolveDshHookPaths(options) {
11247
- const home = options?.homeDir ?? homedir8();
11819
+ const home = options?.homeDir ?? homedir9();
11248
11820
  const dshHome = resolveDshHome(options);
11249
- const wingmanRoot2 = join14(home, ".wingman");
11250
- const hooksDir = join14(wingmanRoot2, "dsh");
11821
+ const wingmanRoot2 = join15(home, ".wingman");
11822
+ const hooksDir = join15(wingmanRoot2, "dsh");
11251
11823
  return {
11252
11824
  dshHome,
11253
- patchFile: join14(dshHome, "cordis.patch.yml"),
11825
+ patchFile: join15(dshHome, "cordis.patch.yml"),
11254
11826
  wingmanRoot: wingmanRoot2,
11255
- hooksConfigFile: join14(hooksDir, "hooks-claude-code.json"),
11256
- forwarderFile: join14(hooksDir, "forward.mjs"),
11257
- stateFile: join14(wingmanRoot2, "state", "dsh-hooks.json"),
11258
- backupsRoot: join14(wingmanRoot2, "backups")
11827
+ hooksConfigFile: join15(hooksDir, "hooks-claude-code.json"),
11828
+ forwarderFile: join15(hooksDir, "forward.mjs"),
11829
+ stateFile: join15(wingmanRoot2, "state", "dsh-hooks.json"),
11830
+ backupsRoot: join15(wingmanRoot2, "backups")
11259
11831
  };
11260
11832
  }
11261
11833
  function yamlSingleQuote(value) {
@@ -11295,7 +11867,7 @@ async function readIfExists(file) {
11295
11867
  throw error;
11296
11868
  }
11297
11869
  }
11298
- function decodeUtf8Strict2(buffer, file) {
11870
+ function decodeUtf8Strict3(buffer, file) {
11299
11871
  const text = buffer.toString("utf8");
11300
11872
  if (!buffer.equals(Buffer3.from(text, "utf8"))) {
11301
11873
  throw new Error(`\u62D2\u5199:${file} \u4E0D\u662F UTF-8 \u6587\u672C,\u672A\u77E5\u683C\u5F0F\u4E0D\u731C`);
@@ -11328,10 +11900,10 @@ function tryParsePatchEntries(text, file) {
11328
11900
  }
11329
11901
  }
11330
11902
  function stripLiveHooksBlocks(text, file) {
11331
- const lines = text.split("\n");
11903
+ const lines2 = text.split("\n");
11332
11904
  const kept = [];
11333
11905
  let inBlock = false;
11334
- for (const line of lines) {
11906
+ for (const line of lines2) {
11335
11907
  const trimmed = line.trim();
11336
11908
  if (!inBlock) {
11337
11909
  if (trimmed.startsWith(HOOKS_MARKER_BEGIN_PREFIX)) {
@@ -11361,11 +11933,11 @@ function composeInstalledText(baseText, entryCount, block) {
11361
11933
  return block;
11362
11934
  let base = baseText;
11363
11935
  if (entryCount === 0) {
11364
- const lines = base.split("\n");
11365
- const rootIndex = lines.findIndex((line) => /^\s*\[\]\s*$/.test(line));
11936
+ const lines2 = base.split("\n");
11937
+ const rootIndex = lines2.findIndex((line) => /^\s*\[\]\s*$/.test(line));
11366
11938
  if (rootIndex !== -1) {
11367
- lines.splice(rootIndex, 1);
11368
- base = lines.join("\n");
11939
+ lines2.splice(rootIndex, 1);
11940
+ base = lines2.join("\n");
11369
11941
  }
11370
11942
  }
11371
11943
  if (base === "")
@@ -11374,7 +11946,7 @@ function composeInstalledText(baseText, entryCount, block) {
11374
11946
  base += "\n";
11375
11947
  return base + block;
11376
11948
  }
11377
- async function readState3(paths) {
11949
+ async function readState5(paths) {
11378
11950
  const buffer = await readIfExists(paths.stateFile);
11379
11951
  if (buffer === void 0)
11380
11952
  return void 0;
@@ -11383,19 +11955,19 @@ async function readState3(paths) {
11383
11955
  throw new Error(`\u672A\u77E5\u7684\u63A5\u7EBF\u72B6\u6001\u7248\u672C:${String(state.version)},\u4E0D\u731C`);
11384
11956
  return state;
11385
11957
  }
11386
- async function writeState3(paths, state) {
11387
- await mkdir(dirname9(paths.stateFile), { recursive: true });
11958
+ async function writeState5(paths, state) {
11959
+ await mkdir(dirname11(paths.stateFile), { recursive: true });
11388
11960
  await writeFile(paths.stateFile, `${JSON.stringify(state, void 0, 2)}
11389
11961
  `, "utf8");
11390
11962
  }
11391
11963
  async function backupPatchFile(paths, bytes) {
11392
- const dir = join14(paths.backupsRoot, (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
11964
+ const dir = join15(paths.backupsRoot, (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
11393
11965
  await mkdir(dir, { recursive: true });
11394
- await writeFile(join14(dir, "cordis.patch.yml"), bytes);
11966
+ await writeFile(join15(dir, "cordis.patch.yml"), bytes);
11395
11967
  return dir;
11396
11968
  }
11397
11969
  async function writeOwnedFiles(paths, url) {
11398
- await mkdir(dirname9(paths.hooksConfigFile), { recursive: true });
11970
+ await mkdir(dirname11(paths.hooksConfigFile), { recursive: true });
11399
11971
  await writeFile(paths.forwarderFile, FORWARDER_SOURCE, "utf8");
11400
11972
  await writeFile(paths.hooksConfigFile, buildHooksConfig(paths.forwarderFile, url), "utf8");
11401
11973
  }
@@ -11407,7 +11979,7 @@ async function installDshHooks(port, options) {
11407
11979
  const url = `http://127.0.0.1:${port}/hook?harness=dsh`;
11408
11980
  const block = buildPatchBlock(paths.hooksConfigFile);
11409
11981
  const currentBuffer = await readIfExists(paths.patchFile);
11410
- const currentText = currentBuffer === void 0 ? void 0 : decodeUtf8Strict2(currentBuffer, paths.patchFile);
11982
+ const currentText = currentBuffer === void 0 ? void 0 : decodeUtf8Strict3(currentBuffer, paths.patchFile);
11411
11983
  const surgicalText = currentText === void 0 ? void 0 : stripLiveHooksBlocks(currentText, paths.patchFile);
11412
11984
  const entriesBefore = surgicalText === void 0 ? [] : parsePatchEntries(surgicalText, paths.patchFile);
11413
11985
  if (hasWingmanRow(entriesBefore)) {
@@ -11420,11 +11992,11 @@ async function installDshHooks(port, options) {
11420
11992
  throw new Error(`\u62D2\u5199:${paths.patchFile} \u8FFD\u52A0\u540E\u5F62\u72B6\u6821\u9A8C\u5931\u8D25(\u539F\u6587\u4EF6\u5F62\u6001\u65E0\u6CD5\u5B89\u5168\u8FFD\u52A0),\u4E0D\u731C`);
11421
11993
  }
11422
11994
  await writeOwnedFiles(paths, url);
11423
- const previousState = await readState3(paths);
11995
+ const previousState = await readState5(paths);
11424
11996
  if (composed === currentText) {
11425
11997
  if (previousState === void 0) {
11426
11998
  const recovered = surgicalText ?? "";
11427
- await writeState3(paths, {
11999
+ await writeState5(paths, {
11428
12000
  version: 1,
11429
12001
  port,
11430
12002
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11438,7 +12010,7 @@ async function installDshHooks(port, options) {
11438
12010
  if (currentBuffer !== void 0)
11439
12011
  backupDir = await backupPatchFile(paths, currentBuffer);
11440
12012
  const original = previousState?.original ?? (currentBuffer === void 0 ? { existed: false } : { existed: true, bytesBase64: currentBuffer.toString("base64") });
11441
- await writeState3(paths, {
12013
+ await writeState5(paths, {
11442
12014
  version: 1,
11443
12015
  port,
11444
12016
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11451,10 +12023,10 @@ async function installDshHooks(port, options) {
11451
12023
  }
11452
12024
  async function uninstallDshHooks(options) {
11453
12025
  const paths = resolveDshHookPaths(options);
11454
- const state = await readState3(paths);
12026
+ const state = await readState5(paths);
11455
12027
  const currentBuffer = await readIfExists(paths.patchFile);
11456
12028
  if (currentBuffer !== void 0) {
11457
- const currentText = decodeUtf8Strict2(currentBuffer, paths.patchFile);
12029
+ const currentText = decodeUtf8Strict3(currentBuffer, paths.patchFile);
11458
12030
  if (currentText.includes(HOOKS_MARKER_BEGIN_PREFIX)) {
11459
12031
  const surgical = stripLiveHooksBlocks(currentText, paths.patchFile);
11460
12032
  const restored = resolveRestoredText(surgical, state, paths);
@@ -11543,18 +12115,18 @@ if (url !== undefined) {
11543
12115
  import { Buffer as Buffer4 } from "node:buffer";
11544
12116
  import { randomBytes } from "node:crypto";
11545
12117
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
11546
- import { dirname as dirname10, isAbsolute as isAbsolute2, join as join15, relative } from "node:path";
12118
+ import { dirname as dirname12, isAbsolute as isAbsolute2, join as join16, relative } from "node:path";
11547
12119
  function buildToggleBlock(instanceId, flags) {
11548
- const lines = [`${TOGGLE_BEGIN_PREFIX}${instanceId}${TOGGLE_BEGIN_SUFFIX}`];
12120
+ const lines2 = [`${TOGGLE_BEGIN_PREFIX}${instanceId}${TOGGLE_BEGIN_SUFFIX}`];
11549
12121
  if (flags.fileCreated)
11550
- lines.push(`${FLAG_PREFIX}${FLAG_FILE_CREATED}`);
12122
+ lines2.push(`${FLAG_PREFIX}${FLAG_FILE_CREATED}`);
11551
12123
  if (flags.newlineAdded)
11552
- lines.push(`${FLAG_PREFIX}${FLAG_NEWLINE_ADDED}`);
12124
+ lines2.push(`${FLAG_PREFIX}${FLAG_NEWLINE_ADDED}`);
11553
12125
  if (flags.emptyRootLine !== void 0) {
11554
- lines.push(`${FLAG_PREFIX}${FLAG_EMPTY_ROOT_LINE}${flags.emptyRootLine}`);
12126
+ lines2.push(`${FLAG_PREFIX}${FLAG_EMPTY_ROOT_LINE}${flags.emptyRootLine}`);
11555
12127
  }
11556
- lines.push(`- id: ${yamlSingleQuote(instanceId)}`, " disabled: true", `${TOGGLE_END_PREFIX}${instanceId}${TOGGLE_END_SUFFIX}`, "");
11557
- return lines.join("\n");
12128
+ lines2.push(`- id: ${yamlSingleQuote(instanceId)}`, " disabled: true", `${TOGGLE_END_PREFIX}${instanceId}${TOGGLE_END_SUFFIX}`, "");
12129
+ return lines2.join("\n");
11558
12130
  }
11559
12131
  function parseBeginMarker(trimmed) {
11560
12132
  if (!trimmed.startsWith(TOGGLE_BEGIN_PREFIX))
@@ -11655,10 +12227,10 @@ function stripAllToggleBlocks(text, blocks) {
11655
12227
  return out + text.slice(cursor);
11656
12228
  }
11657
12229
  function stripHooksBlocks(text) {
11658
- const lines = text.split("\n");
12230
+ const lines2 = text.split("\n");
11659
12231
  const kept = [];
11660
12232
  let inBlock = false;
11661
- for (const line of lines) {
12233
+ for (const line of lines2) {
11662
12234
  const trimmed = line.trim();
11663
12235
  if (!inBlock) {
11664
12236
  if (trimmed.startsWith(HOOKS_MARKER_BEGIN_PREFIX))
@@ -11679,8 +12251,8 @@ function referencesInstanceId(entries, instanceId) {
11679
12251
  return Array.isArray(candidate.insert) && candidate.insert.some((row) => row.id === instanceId);
11680
12252
  });
11681
12253
  }
11682
- function emptyRootLineIndex(lines) {
11683
- return lines.findIndex((line) => /^\s*\[\]\s*$/.test(line));
12254
+ function emptyRootLineIndex(lines2) {
12255
+ return lines2.findIndex((line) => /^\s*\[\]\s*$/.test(line));
11684
12256
  }
11685
12257
  function composeToggleOff(current, instanceId, entryCount) {
11686
12258
  if (current === void 0) {
@@ -11689,12 +12261,12 @@ function composeToggleOff(current, instanceId, entryCount) {
11689
12261
  let base = current;
11690
12262
  const flags = {};
11691
12263
  if (entryCount === 0) {
11692
- const lines = base.split("\n");
11693
- const rootIndex = emptyRootLineIndex(lines);
12264
+ const lines2 = base.split("\n");
12265
+ const rootIndex = emptyRootLineIndex(lines2);
11694
12266
  if (rootIndex !== -1) {
11695
12267
  flags.emptyRootLine = rootIndex;
11696
- lines.splice(rootIndex, 1);
11697
- base = lines.join("\n");
12268
+ lines2.splice(rootIndex, 1);
12269
+ base = lines2.join("\n");
11698
12270
  }
11699
12271
  }
11700
12272
  if (base !== "" && !base.endsWith("\n")) {
@@ -11716,9 +12288,9 @@ function composeToggleOn(current, block, file) {
11716
12288
  const entriesAfter = parsePatchEntries(removed, file);
11717
12289
  if (block.emptyRootLine !== void 0) {
11718
12290
  if (entriesAfter.length === 0) {
11719
- const lines = removed.split("\n");
11720
- lines.splice(Math.min(block.emptyRootLine, lines.length), 0, "[]");
11721
- removed = lines.join("\n");
12291
+ const lines2 = removed.split("\n");
12292
+ lines2.splice(Math.min(block.emptyRootLine, lines2.length), 0, "[]");
12293
+ removed = lines2.join("\n");
11722
12294
  } else {
11723
12295
  removed = transferEmptyRootFlag(removed, block.emptyRootLine, file);
11724
12296
  }
@@ -11803,7 +12375,7 @@ async function planDshToggle(action, dump, options) {
11803
12375
  }
11804
12376
  const paths = resolveDshHookPaths(options);
11805
12377
  const buffer = await readIfExists(paths.patchFile);
11806
- const text = buffer === void 0 ? void 0 : decodeUtf8Strict2(buffer, paths.patchFile);
12378
+ const text = buffer === void 0 ? void 0 : decodeUtf8Strict3(buffer, paths.patchFile);
11807
12379
  const blocks = text === void 0 ? [] : findToggleBlocks(text, paths.patchFile);
11808
12380
  const myBlock = blocks.find((b) => b.instanceId === row.id);
11809
12381
  const entriesAll = text === void 0 ? [] : parsePatchEntries(text, paths.patchFile);
@@ -11849,7 +12421,7 @@ async function planDshToggle(action, dump, options) {
11849
12421
  harness: "dsh",
11850
12422
  action: toggle,
11851
12423
  writes: [change],
11852
- backupDir: join15(paths.backupsRoot, backupDirName()),
12424
+ backupDir: join16(paths.backupsRoot, backupDirName()),
11853
12425
  references: scanDshReferences(paths.patchFile, text, dump, row),
11854
12426
  // 生效时机:patch 层在 boot 时组装(composeProfile),保证下次启动生效;钉定版本
11855
12427
  // 还会 watchUserPatches 热重载家目录层,但该监听失败被静默吞掉(suppressShutdownError)
@@ -11909,12 +12481,12 @@ async function applyDshToggle(plan, options) {
11909
12481
  }
11910
12482
  await mkdir2(plan.backupDir, { recursive: true });
11911
12483
  if (buffer !== void 0) {
11912
- await writeFile2(join15(plan.backupDir, "cordis.patch.yml"), buffer);
12484
+ await writeFile2(join16(plan.backupDir, "cordis.patch.yml"), buffer);
11913
12485
  }
11914
12486
  if (change.kind === "delete") {
11915
12487
  await rm2(paths.patchFile);
11916
12488
  } else {
11917
- await mkdir2(dirname10(paths.patchFile), { recursive: true });
12489
+ await mkdir2(dirname12(paths.patchFile), { recursive: true });
11918
12490
  await writeFile2(paths.patchFile, change.after ?? "", "utf8");
11919
12491
  }
11920
12492
  return { ok: true, backupDir: plan.backupDir };
@@ -11942,6 +12514,254 @@ var init_toggle = __esm({
11942
12514
  }
11943
12515
  });
11944
12516
 
12517
+ // packages/adapter-dsh/dist/protocol.js
12518
+ import { execFileSync } from "node:child_process";
12519
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync12, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "node:fs";
12520
+ import { homedir as homedir10 } from "node:os";
12521
+ import { basename as basename7, dirname as dirname13, join as join17, resolve as resolve7 } from "node:path";
12522
+ function protocolStatusDsh(options) {
12523
+ const paths = resolveOptions3(options);
12524
+ if (!existsSync11(paths.targetPath))
12525
+ return status2(paths.targetPath, "not_installed", options);
12526
+ try {
12527
+ const file = readAgents2(paths.targetPath);
12528
+ if (file.identity === void 0)
12529
+ return status2(paths.targetPath, "not_installed", options);
12530
+ const state = stateForIdentity3(file.identity);
12531
+ return status2(paths.targetPath, state, options, file.identity);
12532
+ } catch (error) {
12533
+ return status2(paths.targetPath, "invalid", options, void 0, error.message);
12534
+ }
12535
+ }
12536
+ function installProtocolDsh(options) {
12537
+ const paths = resolveOptions3(options);
12538
+ const existing = existsSync11(paths.targetPath) ? readAgents2(paths.targetPath) : void 0;
12539
+ const previousState = stateForIdentity3(existing?.identity);
12540
+ if (previousState === "installed") {
12541
+ return overlayLoadState(result3(paths.targetPath, "install", false, previousState, existing?.text ?? "", true), options);
12542
+ }
12543
+ if (previousState === "modified")
12544
+ throw modifiedError3(paths.targetPath, "\u5B89\u88C5");
12545
+ const before = existing?.text ?? "";
12546
+ const lineEnding = preferredMarkdownLineEnding(before);
12547
+ const block = buildPlanProtocolBlock().replaceAll("\n", lineEnding);
12548
+ const after = existing?.split.block ? existing.split.before + block + existing.split.after : appendBlock2(before, block, lineEnding);
12549
+ const installedRemainder = splitPlanProtocolBlock(after, paths.targetPath).remainder;
12550
+ const backupDir = nextBackupDir4(paths.homeDir);
12551
+ if (!paths.dryRun) {
12552
+ const prior = readState6(paths.statePath, paths.targetPath);
12553
+ backupTarget3(backupDir, paths.targetPath, existing?.bytes);
12554
+ writeState6(paths.statePath, {
12555
+ version: 1,
12556
+ projectDir: paths.projectDir,
12557
+ targetPath: paths.targetPath,
12558
+ protocolVersion: PLAN_PROTOCOL_VERSION,
12559
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
12560
+ backupDir,
12561
+ installedRemainderSha256: sha256Hex(installedRemainder),
12562
+ original: prior?.original ?? (existing === void 0 ? { existed: false } : { existed: true, base64: existing.bytes.toString("base64") })
12563
+ });
12564
+ writeFileSync9(paths.targetPath, after);
12565
+ }
12566
+ return overlayLoadState({
12567
+ ...result3(paths.targetPath, "install", true, previousState, before, true),
12568
+ state: "installed",
12569
+ version: PLAN_PROTOCOL_VERSION,
12570
+ after,
12571
+ backupDir,
12572
+ totalBytes: Buffer.byteLength(after)
12573
+ }, options);
12574
+ }
12575
+ function uninstallProtocolDsh(options) {
12576
+ const paths = resolveOptions3(options);
12577
+ if (!existsSync11(paths.targetPath)) {
12578
+ return result3(paths.targetPath, "uninstall", false, "not_installed", "", false);
12579
+ }
12580
+ const existing = readAgents2(paths.targetPath);
12581
+ if (existing.identity === void 0) {
12582
+ return result3(paths.targetPath, "uninstall", false, "not_installed", existing.text, true);
12583
+ }
12584
+ const previousState = stateForIdentity3(existing.identity);
12585
+ if (previousState === "modified")
12586
+ throw modifiedError3(paths.targetPath, "\u5378\u8F7D");
12587
+ const state = readState6(paths.statePath, paths.targetPath);
12588
+ const canRestore = state !== void 0 && sha256Hex(existing.split.remainder) === state.installedRemainderSha256;
12589
+ const restored = canRestore ? originalBytes2(state) : void 0;
12590
+ const deleteTarget = canRestore ? state?.original.existed === false : existing.split.remainder.trim() === "";
12591
+ const after = deleteTarget ? "" : restored?.toString("utf8") ?? existing.split.remainder;
12592
+ const backupDir = nextBackupDir4(paths.homeDir);
12593
+ if (!paths.dryRun) {
12594
+ backupTarget3(backupDir, paths.targetPath, existing.bytes);
12595
+ if (deleteTarget)
12596
+ rmSync6(paths.targetPath, { force: true });
12597
+ else
12598
+ writeFileSync9(paths.targetPath, restored ?? existing.split.remainder);
12599
+ rmSync6(paths.statePath, { force: true });
12600
+ }
12601
+ return {
12602
+ ...result3(paths.targetPath, "uninstall", true, previousState, existing.text, !deleteTarget),
12603
+ state: "not_installed",
12604
+ message: canRestore ? `${SHARED_MESSAGE2}\uFF1B\u5378\u8F7D\u540C\u65F6\u5F71\u54CD Codex \u4E0E dsh` : `${SHARED_MESSAGE2}\uFF1B\u5378\u8F7D\u540C\u65F6\u5F71\u54CD Codex \u4E0E dsh\uFF1B\u5DF2\u4FDD\u7559\u7528\u6237\u6539\u52A8`,
12605
+ after,
12606
+ backupDir,
12607
+ totalBytes: Buffer.byteLength(after),
12608
+ restoredOriginal: canRestore
12609
+ };
12610
+ }
12611
+ function resolveOptions3(options) {
12612
+ const homeDir = resolve7(options?.homeDir ?? homedir10());
12613
+ const projectDir = resolve7(options?.projectDir ?? process.cwd());
12614
+ const targetPath2 = join17(projectDir, "AGENTS.md");
12615
+ return {
12616
+ homeDir,
12617
+ projectDir,
12618
+ targetPath: targetPath2,
12619
+ statePath: join17(homeDir, ".wingman", "state", `plan-protocol-agents-${sha256Hex(projectDir)}.json`),
12620
+ dryRun: options?.dryRun ?? false
12621
+ };
12622
+ }
12623
+ function readAgents2(targetPath2) {
12624
+ const bytes = readFileSync12(targetPath2);
12625
+ const text = decodeUtf8Strict(bytes, targetPath2);
12626
+ let split;
12627
+ try {
12628
+ split = splitPlanProtocolBlock(text, targetPath2);
12629
+ } catch (error) {
12630
+ throw new Error(`${targetPath2}: ${error.message}`);
12631
+ }
12632
+ return {
12633
+ bytes,
12634
+ text,
12635
+ split,
12636
+ ...split.block !== void 0 ? { identity: inspectPlanProtocolBlock(split.block) } : {}
12637
+ };
12638
+ }
12639
+ function appendBlock2(text, block, lineEnding) {
12640
+ if (text === "")
12641
+ return block;
12642
+ let base = text;
12643
+ if (!base.endsWith("\n"))
12644
+ base += lineEnding;
12645
+ if (!base.endsWith(`${lineEnding}${lineEnding}`))
12646
+ base += lineEnding;
12647
+ return base + block;
12648
+ }
12649
+ function stateForIdentity3(identity) {
12650
+ if (identity === void 0)
12651
+ return "not_installed";
12652
+ if (identity.kind === "current")
12653
+ return "installed";
12654
+ if (identity.kind === "legacy")
12655
+ return "outdated";
12656
+ return "modified";
12657
+ }
12658
+ function status2(targetPath2, state, options, identity, reason) {
12659
+ const disabled = state === "installed" ? agentInstructionsDisabled(options) : void 0;
12660
+ const loadedState = disabled === true ? "installed_not_loaded" : state;
12661
+ return {
12662
+ harness: "dsh",
12663
+ state: loadedState,
12664
+ targetPath: targetPath2,
12665
+ shared: true,
12666
+ ...disabled !== void 0 ? { pluginDisabled: disabled } : {},
12667
+ message: disabled === true ? `${SHARED_MESSAGE2}\uFF1B${DISABLED_MESSAGE}` : SHARED_MESSAGE2,
12668
+ ...identity?.version !== void 0 ? { version: identity.version } : {},
12669
+ ...reason !== void 0 ? { reason } : {}
12670
+ };
12671
+ }
12672
+ function result3(targetPath2, operation, changed, previousState, before, afterExists) {
12673
+ return {
12674
+ harness: "dsh",
12675
+ state: previousState,
12676
+ targetPath: targetPath2,
12677
+ shared: true,
12678
+ message: SHARED_MESSAGE2,
12679
+ operation,
12680
+ changed,
12681
+ previousState,
12682
+ before,
12683
+ after: before,
12684
+ afterExists,
12685
+ effectiveAt: "next_session",
12686
+ totalBytes: Buffer.byteLength(before)
12687
+ };
12688
+ }
12689
+ function overlayLoadState(resultValue, options) {
12690
+ const disabled = agentInstructionsDisabled(options);
12691
+ if (disabled === void 0)
12692
+ return resultValue;
12693
+ return {
12694
+ ...resultValue,
12695
+ state: disabled ? "installed_not_loaded" : "installed",
12696
+ pluginDisabled: disabled,
12697
+ message: disabled ? `${SHARED_MESSAGE2}\uFF1B${DISABLED_MESSAGE}` : SHARED_MESSAGE2
12698
+ };
12699
+ }
12700
+ function agentInstructionsDisabled(options) {
12701
+ let text;
12702
+ try {
12703
+ if (options?.dumpConfigText !== void 0)
12704
+ text = options.dumpConfigText;
12705
+ else if (options?.dumpConfigPath !== void 0)
12706
+ text = readFileSync12(options.dumpConfigPath, "utf8");
12707
+ else {
12708
+ text = execFileSync("dsh", ["--profile", options?.profile ?? "web", "--dump-config"], {
12709
+ encoding: "utf8",
12710
+ timeout: 1e4,
12711
+ maxBuffer: 16 * 1024 * 1024
12712
+ });
12713
+ }
12714
+ const row = parseDumpConfig(text).find((entry) => entry.id === "agent-instructions" && entry.name === "@deepseek-ai/dsh-agent-instructions");
12715
+ return row?.disabled === true;
12716
+ } catch {
12717
+ return void 0;
12718
+ }
12719
+ }
12720
+ function modifiedError3(targetPath2, operation) {
12721
+ return new Error(`${targetPath2}: \u53D7\u7BA1\u5757\u5DF2\u88AB\u4FEE\u6539\uFF0C\u62D2\u7EDD${operation}\u5E76\u4FDD\u7559\u7528\u6237\u5185\u5BB9`);
12722
+ }
12723
+ function nextBackupDir4(homeDir) {
12724
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
12725
+ const root = join17(homeDir, ".wingman", "backups");
12726
+ let dir = join17(root, stamp);
12727
+ for (let n = 2; existsSync11(dir); n += 1)
12728
+ dir = join17(root, `${stamp}-${n}`);
12729
+ return dir;
12730
+ }
12731
+ function backupTarget3(backupDir, targetPath2, bytes) {
12732
+ mkdirSync8(backupDir, { recursive: true });
12733
+ if (bytes !== void 0)
12734
+ writeFileSync9(join17(backupDir, basename7(targetPath2)), bytes);
12735
+ }
12736
+ function readState6(statePath2, targetPath2) {
12737
+ if (!existsSync11(statePath2))
12738
+ return void 0;
12739
+ try {
12740
+ const value = JSON.parse(readFileSync12(statePath2, "utf8"));
12741
+ return value.version === 1 && value.targetPath === targetPath2 ? value : void 0;
12742
+ } catch {
12743
+ return void 0;
12744
+ }
12745
+ }
12746
+ function writeState6(statePath2, state) {
12747
+ mkdirSync8(dirname13(statePath2), { recursive: true });
12748
+ writeFileSync9(statePath2, `${JSON.stringify(state, null, 2)}
12749
+ `);
12750
+ }
12751
+ function originalBytes2(state) {
12752
+ return state.original.existed ? Buffer.from(state.original.base64 ?? "", "base64") : void 0;
12753
+ }
12754
+ var SHARED_MESSAGE2, DISABLED_MESSAGE;
12755
+ var init_protocol3 = __esm({
12756
+ "packages/adapter-dsh/dist/protocol.js"() {
12757
+ "use strict";
12758
+ init_dist();
12759
+ init_dump_config();
12760
+ SHARED_MESSAGE2 = "\u6B64 AGENTS.md \u534F\u8BAE\u5757\u7531 Codex \u4E0E dsh \u5171\u7528";
12761
+ DISABLED_MESSAGE = "dsh \u6307\u4EE4\u52A0\u8F7D\u63D2\u4EF6\u5DF2\u7981\u7528\uFF0C\u89C1\u63A7\u5236\u9875\u5F00\u5173";
12762
+ }
12763
+ });
12764
+
11945
12765
  // packages/adapter-dsh/dist/index.js
11946
12766
  var dist_exports3 = {};
11947
12767
  __export(dist_exports3, {
@@ -11949,20 +12769,23 @@ __export(dist_exports3, {
11949
12769
  createAdapter: () => createAdapter3,
11950
12770
  detectDshLiveWiring: () => detectDshLiveWiring,
11951
12771
  feedbackSignals: () => feedbackSignalsDsh,
12772
+ installProtocolDsh: () => installProtocolDsh,
11952
12773
  parseDumpConfig: () => parseDumpConfig,
12774
+ protocolStatusDsh: () => protocolStatusDsh,
11953
12775
  resolveDshHome: () => resolveDshHome,
11954
- resolveDshHookPaths: () => resolveDshHookPaths
12776
+ resolveDshHookPaths: () => resolveDshHookPaths,
12777
+ uninstallProtocolDsh: () => uninstallProtocolDsh
11955
12778
  });
11956
12779
  import { execFile } from "node:child_process";
11957
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
12780
+ import { existsSync as existsSync12, readFileSync as readFileSync13 } from "node:fs";
11958
12781
  import { readFile as readFile2 } from "node:fs/promises";
11959
12782
  import { createRequire } from "node:module";
11960
- import { join as join16 } from "node:path";
12783
+ import { join as join18 } from "node:path";
11961
12784
  import { promisify } from "node:util";
11962
12785
  function resolvePinnedDshVersion() {
11963
12786
  try {
11964
12787
  const require2 = createRequire(import.meta.url);
11965
- const pkg = JSON.parse(readFileSync10(require2.resolve("@deepseek-ai/dsh/package.json"), "utf8"));
12788
+ const pkg = JSON.parse(readFileSync13(require2.resolve("@deepseek-ai/dsh/package.json"), "utf8"));
11966
12789
  return typeof pkg.version === "string" ? pkg.version : void 0;
11967
12790
  } catch {
11968
12791
  return void 0;
@@ -12019,7 +12842,7 @@ function createAdapter3(options = {}) {
12019
12842
  // 版本尽力:钉定 devDependency 可解析时用其版本(仓库开发形态);发布产物无此依赖
12020
12843
  // → 省略,不跑命令探版本。
12021
12844
  async detect(options2) {
12022
- if (!existsSync9(resolveDshHome(options2)))
12845
+ if (!existsSync12(resolveDshHome(options2)))
12023
12846
  return { installed: false };
12024
12847
  const version = resolvePinnedDshVersion();
12025
12848
  return version !== void 0 ? { installed: true, version } : { installed: true };
@@ -12052,7 +12875,7 @@ function createAdapter3(options = {}) {
12052
12875
  },
12053
12876
  async planAction(action, options2) {
12054
12877
  const text = await loadDumpText();
12055
- const file = dumpConfigPath ?? join16(resolveDshHookPaths(options2).dshHome, `dump-config(${profile})`);
12878
+ const file = dumpConfigPath ?? join18(resolveDshHookPaths(options2).dshHome, `dump-config(${profile})`);
12056
12879
  return await planDshToggle(action, { text, file }, options2);
12057
12880
  },
12058
12881
  async applyAction(plan, options2) {
@@ -12075,14 +12898,16 @@ var init_dist5 = __esm({
12075
12898
  init_toggle();
12076
12899
  init_dump_config();
12077
12900
  init_hooks3();
12901
+ init_protocol3();
12078
12902
  execFileAsync = promisify(execFile);
12079
12903
  }
12080
12904
  });
12081
12905
 
12082
12906
  // apps/cli/dist/main.js
12083
12907
  init_dist();
12084
- import { readFileSync as readFileSync12 } from "node:fs";
12085
- import { dirname as dirname12, resolve as resolve6 } from "node:path";
12908
+ import { readFileSync as readFileSync15 } from "node:fs";
12909
+ import { homedir as homedir13 } from "node:os";
12910
+ import { dirname as dirname16, resolve as resolve11 } from "node:path";
12086
12911
  import { fileURLToPath as fileURLToPath2 } from "node:url";
12087
12912
 
12088
12913
  // packages/fixtures/dist/index.js
@@ -12210,7 +13035,7 @@ function isHookPayload(value) {
12210
13035
  }
12211
13036
  var HOOK_BODY_LIMIT = 1024 * 1024;
12212
13037
  function readBody(req) {
12213
- return new Promise((resolve7, reject) => {
13038
+ return new Promise((resolve12, reject) => {
12214
13039
  const chunks = [];
12215
13040
  let size = 0;
12216
13041
  req.on("data", (chunk) => {
@@ -12222,16 +13047,16 @@ function readBody(req) {
12222
13047
  }
12223
13048
  chunks.push(chunk);
12224
13049
  });
12225
- req.on("end", () => resolve7(Buffer.concat(chunks).toString("utf8")));
13050
+ req.on("end", () => resolve12(Buffer.concat(chunks).toString("utf8")));
12226
13051
  req.on("error", reject);
12227
13052
  });
12228
13053
  }
12229
- function sendJson(res, status, body) {
12230
- res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
13054
+ function sendJson(res, status3, body) {
13055
+ res.writeHead(status3, { "content-type": "application/json; charset=utf-8" });
12231
13056
  res.end(JSON.stringify(body));
12232
13057
  }
12233
- function sendText(res, status, body) {
12234
- res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
13058
+ function sendText(res, status3, body) {
13059
+ res.writeHead(status3, { "content-type": "text/plain; charset=utf-8" });
12235
13060
  res.end(body);
12236
13061
  }
12237
13062
 
@@ -12246,8 +13071,8 @@ function checkLicense(options = {}) {
12246
13071
  const publicKeyPem = options.publicKeyPem ?? PROD_PUBLIC_KEY_PEM;
12247
13072
  if (publicKeyPem === "")
12248
13073
  return true;
12249
- const status = licenseStatus({ ...options, publicKeyPem });
12250
- return status.state === "active" || status.state === "grace";
13074
+ const status3 = licenseStatus({ ...options, publicKeyPem });
13075
+ return status3.state === "active" || status3.state === "grace";
12251
13076
  }
12252
13077
  function licenseStatus(options = {}) {
12253
13078
  const home = options.homeDir ?? homedir();
@@ -12336,9 +13161,9 @@ function installLicense(raw, options = {}) {
12336
13161
  }
12337
13162
  const publicKeyPem = options.publicKeyPem ?? PROD_PUBLIC_KEY_PEM;
12338
13163
  if (publicKeyPem !== "") {
12339
- const status = verifyEntitlement(raw, options.now ?? /* @__PURE__ */ new Date(), publicKeyPem);
12340
- if (status.state !== "active" && status.state !== "grace") {
12341
- return { ok: false, error: status.detail };
13164
+ const status3 = verifyEntitlement(raw, options.now ?? /* @__PURE__ */ new Date(), publicKeyPem);
13165
+ if (status3.state !== "active" && status3.state !== "grace") {
13166
+ return { ok: false, error: status3.detail };
12342
13167
  }
12343
13168
  }
12344
13169
  const dir = join2(options.homeDir ?? homedir(), ".wingman");
@@ -12429,11 +13254,11 @@ async function refreshLicense(options = {}) {
12429
13254
  `;
12430
13255
  const publicKeyPem = options.publicKeyPem ?? PROD_PUBLIC_KEY_PEM;
12431
13256
  if (publicKeyPem !== "") {
12432
- const status = verifyEntitlement(next, options.now ?? /* @__PURE__ */ new Date(), publicKeyPem);
12433
- if (status.state !== "active" && status.state !== "grace") {
13257
+ const status3 = verifyEntitlement(next, options.now ?? /* @__PURE__ */ new Date(), publicKeyPem);
13258
+ if (status3.state !== "active" && status3.state !== "grace") {
12434
13259
  return {
12435
13260
  ok: false,
12436
- message: `\u7B7E\u53D1\u7AEF\u8FD4\u56DE\u7684 license \u65E0\u6548:${status.detail};\u539F license \u672A\u52A8`,
13261
+ message: `\u7B7E\u53D1\u7AEF\u8FD4\u56DE\u7684 license \u65E0\u6548:${status3.detail};\u539F license \u672A\u52A8`,
12437
13262
  reason: "invalid-response"
12438
13263
  };
12439
13264
  }
@@ -12478,15 +13303,18 @@ function expSuffix(token) {
12478
13303
  }
12479
13304
 
12480
13305
  // apps/cli/dist/narrative-service.js
13306
+ init_dist2();
13307
+ init_dist4();
13308
+ init_dist5();
12481
13309
  init_dist();
12482
13310
  import { readdir } from "node:fs/promises";
12483
- import { join as join4 } from "node:path";
13311
+ import { isAbsolute as isAbsolute4, join as join20, resolve as resolve9 } from "node:path";
12484
13312
 
12485
13313
  // packages/narrative/dist/attribute.js
12486
- function extractSteps(lines) {
13314
+ function extractSteps(lines2) {
12487
13315
  const steps = [];
12488
13316
  const byToolUseId = /* @__PURE__ */ new Map();
12489
- for (const line of lines) {
13317
+ for (const line of lines2) {
12490
13318
  if (line.kind === "assistant") {
12491
13319
  for (const block of line.blocks) {
12492
13320
  if (block.kind !== "tool_use")
@@ -12505,21 +13333,21 @@ function extractSteps(lines) {
12505
13333
  byToolUseId.set(block.id, step);
12506
13334
  }
12507
13335
  } else if (line.kind === "user") {
12508
- for (const result of line.toolResults) {
12509
- const step = byToolUseId.get(result.toolUseId);
13336
+ for (const result4 of line.toolResults) {
13337
+ const step = byToolUseId.get(result4.toolUseId);
12510
13338
  if (step === void 0)
12511
13339
  continue;
12512
13340
  if (line.timestamp !== void 0)
12513
13341
  step.endTs = line.timestamp;
12514
- if (result.isError)
13342
+ if (result4.isError)
12515
13343
  step.isError = true;
12516
13344
  }
12517
13345
  }
12518
13346
  }
12519
13347
  return steps;
12520
13348
  }
12521
- function deriveSubagentIntervals(lines) {
12522
- const agentSteps = extractSteps(lines).filter((step) => step.toolName === "Agent");
13349
+ function deriveSubagentIntervals(lines2) {
13350
+ const agentSteps = extractSteps(lines2).filter((step) => step.toolName === "Agent");
12523
13351
  return agentSteps.map((step) => {
12524
13352
  const interval = { agentId: step.toolUseId };
12525
13353
  if (step.ts !== void 0)
@@ -12683,17 +13511,17 @@ function parseResponseItem(payload, timestamp) {
12683
13511
  return call;
12684
13512
  }
12685
13513
  if (payload !== void 0 && (payloadType === "function_call_output" || payloadType === "custom_tool_call_output")) {
12686
- const result = { kind: "tool_result", resultType: payloadType };
12687
- assignString(result, "timestamp", timestamp);
12688
- assignString(result, "callId", optionalString(payload.call_id));
12689
- assignString(result, "itemId", optionalString(payload.id));
12690
- assignString(result, "turnId", extractTurnId(payload));
13514
+ const result4 = { kind: "tool_result", resultType: payloadType };
13515
+ assignString(result4, "timestamp", timestamp);
13516
+ assignString(result4, "callId", optionalString(payload.call_id));
13517
+ assignString(result4, "itemId", optionalString(payload.id));
13518
+ assignString(result4, "turnId", extractTurnId(payload));
12691
13519
  if (payload.output !== void 0) {
12692
- result.output = payload.output;
13520
+ result4.output = payload.output;
12693
13521
  if (codexToolResultIsError(payload.output))
12694
- result.isError = true;
13522
+ result4.isError = true;
12695
13523
  }
12696
- return result;
13524
+ return result4;
12697
13525
  }
12698
13526
  return otherLine("response_item", payload, timestamp);
12699
13527
  }
@@ -12872,13 +13700,13 @@ function parseUser(raw, common) {
12872
13700
  if (!isRecord3(block))
12873
13701
  continue;
12874
13702
  if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
12875
- const result = {
13703
+ const result4 = {
12876
13704
  toolUseId: block.tool_use_id,
12877
13705
  // 实测:成功结果常无 is_error 字段;仅显式 true 视为失败
12878
13706
  isError: block.is_error === true,
12879
13707
  content: block.content
12880
13708
  };
12881
- line.toolResults.push(result);
13709
+ line.toolResults.push(result4);
12882
13710
  } else if (block.type === "text" && typeof block.text === "string") {
12883
13711
  texts.push(block.text);
12884
13712
  }
@@ -13011,8 +13839,283 @@ function consolidatePhases(steps) {
13011
13839
  return nodes;
13012
13840
  }
13013
13841
 
13014
- // packages/narrative/dist/tail.js
13015
- import { open, stat } from "node:fs/promises";
13842
+ // packages/narrative/dist/project-plan.js
13843
+ var PROJECT_PLAN_MAX_BYTES = 1024 * 1024;
13844
+ var PROJECT_PLAN_MAX_LINES = 2e4;
13845
+ var PROJECT_PLAN_MAX_NODES = 2e3;
13846
+ var VERSION_LINE = /^<!-- wingman-plan: (\d+) -->$/u;
13847
+ var H1_LINE = /^#\s+(.+?)\s*$/u;
13848
+ var GOAL_LINE = /^(?:目标|Goal):\s*(.+?)\s*$/u;
13849
+ var STEP_LINE = /^([ \t]*)[-*+]\s+\[([ /xX?-])\]\s+(.+?)\s*$/u;
13850
+ var VERIFY_LINE = /^([ \t]+)(?:验收|Verify):\s*(.+?)\s*$/u;
13851
+ var FENCE_LINE = /^\s*(```|~~~)/u;
13852
+ function parseProjectPlan(input, facts = {}) {
13853
+ const fileProblem = validateFileFacts(facts);
13854
+ if (fileProblem !== void 0)
13855
+ return unavailable(fileProblem);
13856
+ const byteLength = typeof input === "string" ? new TextEncoder().encode(input).byteLength : input.byteLength;
13857
+ if (byteLength > PROJECT_PLAN_MAX_BYTES)
13858
+ return unavailable("\u8BA1\u5212\u6587\u4EF6\u8D85\u8FC7 1 MiB");
13859
+ let text;
13860
+ try {
13861
+ text = typeof input === "string" ? input : new TextDecoder("utf-8", { fatal: true }).decode(input);
13862
+ } catch {
13863
+ return unavailable("\u8BA1\u5212\u6587\u4EF6\u4E0D\u662F\u4E25\u683C UTF-8");
13864
+ }
13865
+ if (text.charCodeAt(0) === 65279)
13866
+ text = text.slice(1);
13867
+ text = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
13868
+ const lines2 = text.split("\n");
13869
+ if (lines2.at(-1) === "")
13870
+ lines2.pop();
13871
+ if (lines2.length > PROJECT_PLAN_MAX_LINES) {
13872
+ return unavailable("\u8BA1\u5212\u6587\u4EF6\u8D85\u8FC7 20000 \u884C", PROJECT_PLAN_MAX_LINES + 1);
13873
+ }
13874
+ const versionMatch = VERSION_LINE.exec(lines2[0] ?? "");
13875
+ if (versionMatch !== null && Number(versionMatch[1]) > 1) {
13876
+ return unavailable("\u683C\u5F0F\u7248\u672C\u4E0D\u53D7\u652F\u6301", 1);
13877
+ }
13878
+ for (const [index, line] of lines2.entries()) {
13879
+ if (line.includes("<<<<<<<") || line.includes(">>>>>>>")) {
13880
+ return unavailable("\u8BA1\u5212\u6587\u4EF6\u542B git \u51B2\u7A81\u6807\u8BB0", index + 1);
13881
+ }
13882
+ }
13883
+ let title;
13884
+ let goal;
13885
+ let ignoredLineCount = 0;
13886
+ let fence;
13887
+ let stepCount = 0;
13888
+ let lastStep;
13889
+ const nodes = [];
13890
+ const stack = [];
13891
+ for (const [index, line] of lines2.entries()) {
13892
+ const lineNumber = index + 1;
13893
+ const fenceMatch = FENCE_LINE.exec(line);
13894
+ if (fence !== void 0) {
13895
+ ignoredLineCount += 1;
13896
+ if (fenceMatch?.[1] === fence)
13897
+ fence = void 0;
13898
+ lastStep = void 0;
13899
+ continue;
13900
+ }
13901
+ if (fenceMatch !== null) {
13902
+ fence = fenceMatch[1];
13903
+ ignoredLineCount += 1;
13904
+ lastStep = void 0;
13905
+ continue;
13906
+ }
13907
+ if (lineNumber === 1 && versionMatch !== null)
13908
+ continue;
13909
+ const headingMatch = H1_LINE.exec(line);
13910
+ if (headingMatch !== null) {
13911
+ if (title === void 0)
13912
+ title = headingMatch[1];
13913
+ else
13914
+ ignoredLineCount += 1;
13915
+ lastStep = void 0;
13916
+ continue;
13917
+ }
13918
+ const goalMatch = GOAL_LINE.exec(line);
13919
+ if (stepCount === 0 && goal === void 0 && goalMatch !== null) {
13920
+ goal = goalMatch[1];
13921
+ lastStep = void 0;
13922
+ continue;
13923
+ }
13924
+ const verifyMatch = VERIFY_LINE.exec(line);
13925
+ if (verifyMatch !== null && lastStep !== void 0 && lastStep.line === lineNumber - 1 && indentColumns(verifyMatch[1] ?? "") > lastStep.indent) {
13926
+ lastStep.node.verification = verifyMatch[2] ?? "";
13927
+ continue;
13928
+ }
13929
+ const stepMatch = STEP_LINE.exec(line);
13930
+ if (stepMatch !== null) {
13931
+ stepCount += 1;
13932
+ if (stepCount > PROJECT_PLAN_MAX_NODES) {
13933
+ return unavailable("\u8BA1\u5212\u8282\u70B9\u8D85\u8FC7 2000 \u4E2A", lineNumber);
13934
+ }
13935
+ const indent = indentColumns(stepMatch[1] ?? "");
13936
+ const node = {
13937
+ key: String(lineNumber),
13938
+ text: stepMatch[3] ?? "",
13939
+ status: statusForMark(stepMatch[2] ?? " "),
13940
+ line: lineNumber,
13941
+ children: []
13942
+ };
13943
+ while (stack.length > 0 && (stack.at(-1)?.indent ?? -1) >= indent)
13944
+ stack.pop();
13945
+ const parent = stack.at(-1)?.node;
13946
+ if (parent === void 0)
13947
+ nodes.push(node);
13948
+ else
13949
+ parent.children.push(node);
13950
+ stack.push({ indent, node });
13951
+ lastStep = { indent, line: lineNumber, node };
13952
+ continue;
13953
+ }
13954
+ ignoredLineCount += 1;
13955
+ lastStep = void 0;
13956
+ }
13957
+ assignNodeKeys(nodes);
13958
+ return {
13959
+ availability: "available",
13960
+ version: 1,
13961
+ ...title === void 0 ? {} : { title },
13962
+ ...goal === void 0 ? {} : { goal },
13963
+ ignoredLineCount,
13964
+ nodes
13965
+ };
13966
+ }
13967
+ function projectPlanStructureKey(nodes) {
13968
+ return JSON.stringify(structureOf(nodes));
13969
+ }
13970
+ function structureOf(nodes) {
13971
+ return nodes.map((node) => [node.text, structureOf(node.children)]);
13972
+ }
13973
+ function deriveProjectPlanSummary(nodes) {
13974
+ const currentPoints = [];
13975
+ const checkpoints = [];
13976
+ const leaves = [];
13977
+ const contradictions = [];
13978
+ visitNodes(nodes, [], (node, path) => {
13979
+ if (node.children.length === 0)
13980
+ leaves.push(node);
13981
+ if (node.status === "checkpoint")
13982
+ checkpoints.push(pathFact(node, path));
13983
+ if (node.status === "in_progress" && !hasStatus(node.children, "in_progress")) {
13984
+ currentPoints.push(pathFact(node, path));
13985
+ }
13986
+ const contradiction = contradictionFor(node);
13987
+ if (contradiction !== void 0)
13988
+ contradictions.push({ key: node.key, text: contradiction });
13989
+ });
13990
+ const counts = {
13991
+ pending: leaves.filter((node) => node.status === "pending").length,
13992
+ inProgress: leaves.filter((node) => node.status === "in_progress").length,
13993
+ completed: leaves.filter((node) => node.status === "completed").length,
13994
+ cancelled: leaves.filter((node) => node.status === "cancelled").length,
13995
+ checkpoint: leaves.filter((node) => node.status === "checkpoint").length
13996
+ };
13997
+ const allLeavesDone = leaves.length > 0 && leaves.every((node) => node.status === "completed" || node.status === "cancelled");
13998
+ return {
13999
+ currentPointLabel: currentPoints.length === 0 ? "\u8BA1\u5212\u672A\u6807\u8BB0\u8FDB\u884C\u4E2D\u6B65\u9AA4" : currentPoints.length === 1 ? "\u5F53\u524D\u70B9" : `\u5E76\u884C ${currentPoints.length} \u9879`,
14000
+ currentPoints,
14001
+ checkpoints,
14002
+ counts,
14003
+ allLeavesDone,
14004
+ ...allLeavesDone ? { completionLabel: "Agent \u6807\u8BB0\u9879\u76EE\u6B65\u9AA4\u5168\u90E8\u5B8C\u6210" } : {},
14005
+ contradictions
14006
+ };
14007
+ }
14008
+ function deriveProjectReconciliationFacts(nodes, sessions, now) {
14009
+ const facts = [];
14010
+ const nodesByKey = /* @__PURE__ */ new Map();
14011
+ visitNodes(nodes, [], (node) => nodesByKey.set(node.key, node));
14012
+ if (sessions.some((session) => session.sessionPlanAllCompleted === true && session.attachedTo !== void 0 && nodesByKey.get(session.attachedTo)?.status === "in_progress")) {
14013
+ facts.push("\u4F1A\u8BDD\u8BA1\u5212\u5DF2\u5168\u90E8\u5B8C\u6210\uFF0C\u9879\u76EE\u6B65\u9AA4\u4ECD\u4E3A\u8FDB\u884C\u4E2D");
14014
+ }
14015
+ const activeAttachments = new Set(sessions.filter((session) => !session.ended && session.attachedTo !== void 0).map((session) => session.attachedTo));
14016
+ visitNodes(nodes, [], (node) => {
14017
+ if (node.status === "in_progress" && !activeAttachments.has(node.key)) {
14018
+ facts.push(`\u300C${node.text}\u300D\u6B65\u9AA4\u4E0B\u5F53\u524D\u6CA1\u6709\u89C2\u5BDF\u5230\u6D3B\u8DC3\u4F1A\u8BDD`);
14019
+ }
14020
+ });
14021
+ const nowMs = Date.parse(now);
14022
+ const recentWriters = new Set(sessions.filter((session) => {
14023
+ if (session.wrotePlanAt === void 0)
14024
+ return false;
14025
+ const age = nowMs - Date.parse(session.wrotePlanAt);
14026
+ return Number.isFinite(age) && age >= 0 && age <= 6e4;
14027
+ }).map((session) => session.id));
14028
+ if (recentWriters.size > 1) {
14029
+ facts.push(`\u6700\u8FD1 60 \u79D2\u5185 ${recentWriters.size} \u4E2A\u4F1A\u8BDD\u5199\u5165\u6B64\u6587\u4EF6`);
14030
+ }
14031
+ return facts;
14032
+ }
14033
+ function validateFileFacts(facts) {
14034
+ if (facts.isRegularFile === false)
14035
+ return "\u8BA1\u5212\u6587\u4EF6\u4E0D\u662F\u666E\u901A\u6587\u4EF6";
14036
+ if (facts.resolvedInsideRoot === false)
14037
+ return "\u8F6F\u94FE\u89E3\u6790\u5230\u9879\u76EE\u6839\u4E4B\u5916";
14038
+ return void 0;
14039
+ }
14040
+ function unavailable(reason, line) {
14041
+ return { availability: "unavailable", reason, ...line === void 0 ? {} : { line } };
14042
+ }
14043
+ function indentColumns(indent) {
14044
+ let columns = 0;
14045
+ for (const character of indent)
14046
+ columns += character === " " ? 4 : 1;
14047
+ return columns;
14048
+ }
14049
+ function statusForMark(mark) {
14050
+ if (mark === "/")
14051
+ return "in_progress";
14052
+ if (mark === "x" || mark === "X")
14053
+ return "completed";
14054
+ if (mark === "-")
14055
+ return "cancelled";
14056
+ if (mark === "?")
14057
+ return "checkpoint";
14058
+ return "pending";
14059
+ }
14060
+ function assignNodeKeys(nodes, parentPath = []) {
14061
+ const siblingCounts = /* @__PURE__ */ new Map();
14062
+ for (const node of nodes) {
14063
+ const normalizedText = node.text.trim().replace(/\s+/gu, " ");
14064
+ const occurrence = (siblingCounts.get(normalizedText) ?? 0) + 1;
14065
+ siblingCounts.set(normalizedText, occurrence);
14066
+ const segment = occurrence === 1 ? normalizedText : `${normalizedText}#${occurrence}`;
14067
+ const path = [...parentPath, segment];
14068
+ node.key = path.join(" > ");
14069
+ assignNodeKeys(node.children, path);
14070
+ }
14071
+ }
14072
+ function visitNodes(nodes, parentPath, visit) {
14073
+ for (const node of nodes) {
14074
+ const path = [...parentPath, node.text];
14075
+ visit(node, path);
14076
+ visitNodes(node.children, path, visit);
14077
+ }
14078
+ }
14079
+ function pathFact(node, path) {
14080
+ return { key: node.key, path, text: node.text };
14081
+ }
14082
+ function hasStatus(nodes, status3) {
14083
+ return nodes.some((node) => node.status === status3 || hasStatus(node.children, status3));
14084
+ }
14085
+ function contradictionFor(node) {
14086
+ if (node.status === "completed") {
14087
+ const child = node.children.find((candidate) => candidate.status === "pending" || candidate.status === "in_progress");
14088
+ if (child !== void 0) {
14089
+ return `\u7236\u6B65\u9AA4\u6807\u8BB0\u5B8C\u6210\uFF0C\u4F46\u540E\u4EE3\u4ECD\u6807\u8BB0\u4E3A${statusLabel(child.status)}`;
14090
+ }
14091
+ }
14092
+ if (node.status === "cancelled") {
14093
+ const child = node.children.find((candidate) => candidate.status === "in_progress");
14094
+ if (child !== void 0)
14095
+ return "\u7236\u6B65\u9AA4\u5DF2\u53D6\u6D88\uFF0C\u4F46\u540E\u4EE3\u4ECD\u6807\u8BB0\u4E3A\u8FDB\u884C\u4E2D";
14096
+ }
14097
+ if (node.status === "pending") {
14098
+ const child = node.children.find((candidate) => candidate.status === "in_progress" || candidate.status === "completed");
14099
+ if (child !== void 0) {
14100
+ return `\u7236\u6B65\u9AA4\u4ECD\u5F85\u505A\uFF0C\u4F46\u540E\u4EE3\u5DF2\u6807\u8BB0${statusLabel(child.status)}`;
14101
+ }
14102
+ }
14103
+ return void 0;
14104
+ }
14105
+ function statusLabel(status3) {
14106
+ if (status3 === "pending")
14107
+ return "\u5F85\u505A";
14108
+ if (status3 === "in_progress")
14109
+ return "\u8FDB\u884C\u4E2D";
14110
+ if (status3 === "completed")
14111
+ return "\u5B8C\u6210";
14112
+ if (status3 === "cancelled")
14113
+ return "\u5DF2\u53D6\u6D88";
14114
+ return "\u68C0\u67E5\u70B9";
14115
+ }
14116
+
14117
+ // packages/narrative/dist/tail.js
14118
+ import { open, stat } from "node:fs/promises";
13016
14119
  var NEWLINE = 10;
13017
14120
  function followTranscript(path, onLine, options = {}) {
13018
14121
  const intervalMs = options.intervalMs ?? 500;
@@ -13087,11 +14190,11 @@ var TODO_STATUSES = ["pending", "in_progress", "completed"];
13087
14190
  function isTodoStatus(value) {
13088
14191
  return typeof value === "string" && TODO_STATUSES.includes(value);
13089
14192
  }
13090
- function extractTodoSnapshots(lines) {
14193
+ function extractTodoSnapshots(lines2) {
13091
14194
  const snapshots = [];
13092
14195
  const tasks = /* @__PURE__ */ new Map();
13093
- const createResultIds = buildTaskCreateResultIndex(lines);
13094
- for (const line of lines) {
14196
+ const createResultIds = buildTaskCreateResultIndex(lines2);
14197
+ for (const line of lines2) {
13095
14198
  if (line.kind !== "assistant")
13096
14199
  continue;
13097
14200
  for (const block of line.blocks) {
@@ -13111,16 +14214,16 @@ function extractTodoSnapshots(lines) {
13111
14214
  }
13112
14215
  return snapshots;
13113
14216
  }
13114
- function buildTaskCreateResultIndex(lines) {
14217
+ function buildTaskCreateResultIndex(lines2) {
13115
14218
  const index = /* @__PURE__ */ new Map();
13116
- for (const line of lines) {
14219
+ for (const line of lines2) {
13117
14220
  if (line.kind !== "user")
13118
14221
  continue;
13119
- const result = line.toolUseResult;
13120
- if (!isRecord3(result) || !isRecord3(result.task) || typeof result.task.id !== "string")
14222
+ const result4 = line.toolUseResult;
14223
+ if (!isRecord3(result4) || !isRecord3(result4.task) || typeof result4.task.id !== "string")
13121
14224
  continue;
13122
14225
  for (const toolResult of line.toolResults)
13123
- index.set(toolResult.toolUseId, result.task.id);
14226
+ index.set(toolResult.toolUseId, result4.task.id);
13124
14227
  }
13125
14228
  return index;
13126
14229
  }
@@ -13235,8 +14338,8 @@ function deriveTodoTimeline(snapshots) {
13235
14338
  }
13236
14339
  return [...entries.values()];
13237
14340
  }
13238
- function newSpan(status, from) {
13239
- const span = { status };
14341
+ function newSpan(status3, from) {
14342
+ const span = { status: status3 };
13240
14343
  if (from !== void 0)
13241
14344
  span.from = from;
13242
14345
  return span;
@@ -13254,7 +14357,7 @@ function turnUsage(line) {
13254
14357
  if (inputTokens === void 0 || cacheCreationInputTokens === void 0 || cacheReadInputTokens === void 0 || outputTokens === void 0) {
13255
14358
  return void 0;
13256
14359
  }
13257
- const result = {
14360
+ const result4 = {
13258
14361
  inputTokens,
13259
14362
  cacheCreationInputTokens,
13260
14363
  cacheReadInputTokens,
@@ -13269,11 +14372,11 @@ function turnUsage(line) {
13269
14372
  const fiveMinute = finiteNumber(breakdown.ephemeral_5m_input_tokens);
13270
14373
  const oneHour = finiteNumber(breakdown.ephemeral_1h_input_tokens);
13271
14374
  if (fiveMinute !== void 0)
13272
- result.cacheCreation5mTokens = fiveMinute;
14375
+ result4.cacheCreation5mTokens = fiveMinute;
13273
14376
  if (oneHour !== void 0)
13274
- result.cacheCreation1hTokens = oneHour;
14377
+ result4.cacheCreation1hTokens = oneHour;
13275
14378
  }
13276
- return result;
14379
+ return result4;
13277
14380
  }
13278
14381
  function finiteNumber(value) {
13279
14382
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
@@ -13337,9 +14440,9 @@ function derivePlanRevisions(observations) {
13337
14440
  }
13338
14441
  return revisions;
13339
14442
  }
13340
- function deriveTaskPlan(harness, lines = [], codexLines = []) {
14443
+ function deriveTaskPlan(harness, lines2 = [], codexLines = []) {
13341
14444
  if (harness === "cc")
13342
- return deriveClaudeTaskPlan(lines);
14445
+ return deriveClaudeTaskPlan(lines2);
13343
14446
  if (harness === "codex")
13344
14447
  return deriveCodexTaskPlan(codexLines);
13345
14448
  return {
@@ -13348,16 +14451,62 @@ function deriveTaskPlan(harness, lines = [], codexLines = []) {
13348
14451
  reason: "dsh \u6CA1\u6709\u5411 Wingman hooks \u66B4\u9732\u7A33\u5B9A\u7684\u672C\u5730 todo projection"
13349
14452
  };
13350
14453
  }
13351
- function deriveClaudeTaskPlan(lines) {
14454
+ function observeTranscriptVersion(lines2) {
14455
+ let version;
14456
+ for (const line of lines2) {
14457
+ if (line.kind === "unknown")
14458
+ continue;
14459
+ const candidate = line.raw.version;
14460
+ if (typeof candidate === "string" && /^\d+\.\d+\.\d+$/u.test(candidate)) {
14461
+ version = candidate;
14462
+ }
14463
+ }
14464
+ return version;
14465
+ }
14466
+ function observeAssistantModel(lines2) {
14467
+ let model;
14468
+ for (const line of lines2) {
14469
+ if (line.kind === "assistant" && line.model !== void 0 && line.model !== "<synthetic>") {
14470
+ model = line.model;
14471
+ }
14472
+ }
14473
+ return model;
14474
+ }
14475
+ function compareVersions(a, b) {
14476
+ const left = a.split(".").map(Number);
14477
+ const right = b.split(".").map(Number);
14478
+ for (let index = 0; index < 3; index += 1) {
14479
+ if ((left[index] ?? 0) > (right[index] ?? 0))
14480
+ return 1;
14481
+ if ((left[index] ?? 0) < (right[index] ?? 0))
14482
+ return -1;
14483
+ }
14484
+ return 0;
14485
+ }
14486
+ function planToolGateFor(lines2) {
14487
+ const harnessVersion = observeTranscriptVersion(lines2);
14488
+ if (harnessVersion === void 0 || compareVersions(harnessVersion, "2.1.233") < 0) {
14489
+ return void 0;
14490
+ }
14491
+ const model = observeAssistantModel(lines2);
14492
+ return {
14493
+ harnessVersion,
14494
+ ...model === void 0 ? {} : { model },
14495
+ affected: model !== void 0 && modelHasGatedPlanTools(model) ? "known" : "unknown",
14496
+ since: "2.1.233",
14497
+ enable: "CLAUDE_CODE_ENABLE_TODO_TOOLS=1"
14498
+ };
14499
+ }
14500
+ function deriveClaudeTaskPlan(lines2) {
13352
14501
  const source = "Claude Code TaskCreate/TaskUpdate/TodoWrite";
13353
- if (latestClaudePlanPayloadValid(lines) === false) {
14502
+ if (latestClaudePlanPayloadValid(lines2) === false) {
13354
14503
  return {
13355
14504
  availability: "unavailable",
13356
14505
  source,
13357
14506
  reason: "Claude Code \u8BA1\u5212\u5DE5\u5177\u53C2\u6570\u65E0\u6CD5\u89E3\u6790"
13358
14507
  };
13359
14508
  }
13360
- const observations = extractTodoSnapshots(lines).map((snapshot) => ({
14509
+ const observations = extractTodoSnapshots(lines2).map((snapshot) => ({
13361
14510
  ...snapshot.ts === void 0 ? {} : { observedAt: snapshot.ts },
13362
14511
  steps: snapshot.todos.map((todo) => ({
13363
14512
  text: todo.content,
@@ -13365,13 +14514,28 @@ function deriveClaudeTaskPlan(lines) {
13365
14514
  status: todo.status
13366
14515
  }))
13367
14516
  }));
13368
- if (observations.length === 0)
13369
- return { availability: "missing", source };
14517
+ if (observations.length === 0) {
14518
+ const toolGate = planToolGateFor(lines2);
14519
+ return {
14520
+ availability: "missing",
14521
+ source,
14522
+ ...toolGate === void 0 ? {} : { toolGate }
14523
+ };
14524
+ }
13370
14525
  return availablePlan(source, derivePlanRevisions(observations));
13371
14526
  }
13372
- function latestClaudePlanPayloadValid(lines) {
14527
+ function modelHasGatedPlanTools(model) {
14528
+ const match = /^claude-(opus|sonnet|haiku|fable|mythos)-(\d+)(?:-(\d+))?/u.exec(model);
14529
+ if (match === null)
14530
+ return false;
14531
+ const family = match[1];
14532
+ const major = Number(match[2]);
14533
+ const minor = Number(match[3] ?? 0);
14534
+ return major >= 5 || family === "opus" && major === 4 && minor >= 8;
14535
+ }
14536
+ function latestClaudePlanPayloadValid(lines2) {
13373
14537
  let latest;
13374
- for (const line of lines) {
14538
+ for (const line of lines2) {
13375
14539
  if (line.kind !== "assistant")
13376
14540
  continue;
13377
14541
  for (const block of line.blocks) {
@@ -13388,9 +14552,9 @@ function latestClaudePlanPayloadValid(lines) {
13388
14552
  }
13389
14553
  return latest;
13390
14554
  }
13391
- function deriveCodexTaskPlan(lines) {
14555
+ function deriveCodexTaskPlan(lines2) {
13392
14556
  const source = "Codex update_plan";
13393
- const calls = lines.filter((line) => line.kind === "tool_call" && line.name === "update_plan");
14557
+ const calls = lines2.filter((line) => line.kind === "tool_call" && line.name === "update_plan");
13394
14558
  if (calls.length === 0)
13395
14559
  return { availability: "missing", source };
13396
14560
  const observations = [];
@@ -13447,6 +14611,345 @@ function isRecord4(value) {
13447
14611
  return typeof value === "object" && value !== null && !Array.isArray(value);
13448
14612
  }
13449
14613
 
14614
+ // apps/cli/dist/project-attribution.js
14615
+ function extractToolObservations(harness, records) {
14616
+ const observations = [];
14617
+ for (const value of records) {
14618
+ if (!isRecord5(value))
14619
+ continue;
14620
+ if (value.hook_event_name === "PostToolUse") {
14621
+ const tool = toolObservation(harness, value.tool_name, value.tool_input, "hook", atOf(value));
14622
+ if (tool !== void 0)
14623
+ observations.push(tool);
14624
+ continue;
14625
+ }
14626
+ if (harness === "codex") {
14627
+ const payload = isRecord5(value.payload) ? value.payload : void 0;
14628
+ if (payload !== void 0 && (payload.type === "function_call" || payload.type === "custom_tool_call")) {
14629
+ const input = payload.input ?? payload.arguments;
14630
+ const tool = toolObservation(harness, payload.name, input, "transcript", atOf(value));
14631
+ if (tool !== void 0)
14632
+ observations.push(tool);
14633
+ }
14634
+ continue;
14635
+ }
14636
+ const message = isRecord5(value.message) ? value.message : void 0;
14637
+ if (message === void 0 || !Array.isArray(message.content))
14638
+ continue;
14639
+ for (const block of message.content) {
14640
+ if (!isRecord5(block) || block.type !== "tool_use")
14641
+ continue;
14642
+ const tool = toolObservation(harness, block.name, block.input, "transcript", atOf(value));
14643
+ if (tool !== void 0)
14644
+ observations.push(tool);
14645
+ }
14646
+ }
14647
+ return observations;
14648
+ }
14649
+ function toolObservation(harness, name, input, source, at) {
14650
+ if (typeof name !== "string")
14651
+ return void 0;
14652
+ const planWrites = fromTool(harness, name, input, source, at);
14653
+ const serializedInput = JSON.stringify(input);
14654
+ return {
14655
+ method: name,
14656
+ source,
14657
+ planWrites,
14658
+ planFileAccess: planWrites.length > 0 || typeof serializedInput === "string" && serializedInput.includes(".wingman") && serializedInput.includes("plan.md"),
14659
+ ...at === void 0 ? {} : { at }
14660
+ };
14661
+ }
14662
+ function attributePlanWrite(before, after, observation) {
14663
+ if (observation.kind === "shell")
14664
+ return { reason: "\u901A\u8FC7 shell \u5199\u5165\u65E0\u6CD5\u786E\u8BA4" };
14665
+ const candidates = /* @__PURE__ */ new Set();
14666
+ const beforeByKey = new Map(flatten(before.nodes).map(({ node }) => [node.key, node]));
14667
+ for (const { node } of flatten(after.nodes)) {
14668
+ const previous = beforeByKey.get(node.key);
14669
+ if (node.status === "in_progress" && previous !== void 0 && previous.status !== "in_progress") {
14670
+ candidates.add(node.key);
14671
+ }
14672
+ }
14673
+ if (!observation.wholeFile) {
14674
+ const beforeByPosition = new Map(flatten(before.nodes).map(({ node, position }) => [position, node]));
14675
+ for (const { node, position } of flatten(after.nodes)) {
14676
+ const previous = beforeByPosition.get(position);
14677
+ if (node.status === "in_progress" && previous?.status === "in_progress" && nodeStructure(previous) !== nodeStructure(node)) {
14678
+ candidates.add(node.key);
14679
+ }
14680
+ }
14681
+ }
14682
+ if (candidates.size === 0)
14683
+ return { reason: "\u672A\u89C2\u5BDF\u5230\u53EF\u5F52\u5C5E\u7684\u8FDB\u884C\u4E2D\u6B65\u9AA4\u53D8\u66F4" };
14684
+ if (candidates.size > 1)
14685
+ return { reason: "\u4E00\u6B21\u5199\u5165\u6D89\u53CA\u591A\u4E2A\u8FDB\u884C\u4E2D\u6B65\u9AA4\uFF0C\u65E0\u6CD5\u552F\u4E00\u5F52\u5C5E" };
14686
+ const attachedTo = [...candidates][0];
14687
+ return {
14688
+ attachedTo,
14689
+ attachedBy: {
14690
+ kind: "plan_file_write",
14691
+ node: attachedTo,
14692
+ method: observation.method,
14693
+ ...observation.at === void 0 ? {} : { at: observation.at }
14694
+ }
14695
+ };
14696
+ }
14697
+ function fromTool(harness, name, input, source, at) {
14698
+ if (typeof name !== "string")
14699
+ return [];
14700
+ if (harness === "codex" && name === "exec") {
14701
+ const inputText = toolText(input, ["input"]);
14702
+ const paths = codexExecPatchPaths(inputText);
14703
+ if (paths.length > 0) {
14704
+ return paths.map((path) => fileObservation(path, "codex apply_patch via exec", source, false, at));
14705
+ }
14706
+ }
14707
+ if (name === "Bash" || name === "exec") {
14708
+ const command = toolText(input, ["command", "input"]);
14709
+ return planPaths(command).map((path) => ({
14710
+ kind: "shell",
14711
+ path,
14712
+ method: name,
14713
+ source,
14714
+ wholeFile: false,
14715
+ ...at === void 0 ? {} : { at },
14716
+ reason: "\u901A\u8FC7 shell \u5199\u5165\u65E0\u6CD5\u786E\u8BA4"
14717
+ }));
14718
+ }
14719
+ if (harness === "cc" && (name === "Write" || name === "Edit" || name === "MultiEdit")) {
14720
+ const path = recordString(input, "file_path");
14721
+ return path === void 0 ? [] : [fileObservation(path, name, source, name === "Write", at)];
14722
+ }
14723
+ if (harness === "dsh" && (name === "write" || name === "edit")) {
14724
+ const path = recordString(input, "path");
14725
+ return path === void 0 ? [] : [fileObservation(path, name, source, name === "write", at)];
14726
+ }
14727
+ if (harness === "codex" && name === "apply_patch") {
14728
+ const patch = toolText(input, ["patch", "input"]);
14729
+ return patchPaths(patch).map((path) => fileObservation(path, name, source, false, at));
14730
+ }
14731
+ return [];
14732
+ }
14733
+ function fileObservation(path, method, source, wholeFile, at) {
14734
+ return {
14735
+ kind: "file_editor",
14736
+ path,
14737
+ method,
14738
+ source,
14739
+ wholeFile,
14740
+ ...at === void 0 ? {} : { at }
14741
+ };
14742
+ }
14743
+ function patchPaths(patch) {
14744
+ if (patch === void 0)
14745
+ return [];
14746
+ const paths = [];
14747
+ for (const line of patch.split("\n")) {
14748
+ const match = /^\*\*\* (?:Update|Add) File: (.+?)\s*$/u.exec(line);
14749
+ if (match?.[1] !== void 0 && isPlanPath(match[1]))
14750
+ paths.push(match[1]);
14751
+ }
14752
+ return paths;
14753
+ }
14754
+ function codexExecPatchPaths(input) {
14755
+ if (input === void 0 || !input.includes("*** Begin Patch"))
14756
+ return [];
14757
+ const literal = /\bconst\s+patch\s*=\s*("(?:\\.|[^"\\])*")/su.exec(input)?.[1];
14758
+ if (literal !== void 0) {
14759
+ try {
14760
+ return patchPaths(JSON.parse(literal));
14761
+ } catch {
14762
+ return [];
14763
+ }
14764
+ }
14765
+ return patchPaths(input);
14766
+ }
14767
+ function planPaths(text) {
14768
+ if (text === void 0)
14769
+ return [];
14770
+ return [...text.matchAll(/[^\s"'`]*\.wingman[\\/]plan\.md/gu)].map((match) => match[0]);
14771
+ }
14772
+ function toolText(input, keys) {
14773
+ if (typeof input === "string")
14774
+ return input;
14775
+ if (!isRecord5(input))
14776
+ return void 0;
14777
+ for (const key of keys) {
14778
+ const value = input[key];
14779
+ if (typeof value === "string")
14780
+ return value;
14781
+ }
14782
+ return void 0;
14783
+ }
14784
+ function recordString(value, key) {
14785
+ if (!isRecord5(value))
14786
+ return void 0;
14787
+ const field = value[key];
14788
+ return typeof field === "string" && isPlanPath(field) ? field : void 0;
14789
+ }
14790
+ function isPlanPath(path) {
14791
+ return /(?:^|[\\/])\.wingman[\\/]plan\.md$/u.test(path);
14792
+ }
14793
+ function atOf(record) {
14794
+ return typeof record.timestamp === "string" ? record.timestamp : typeof record.received_at === "string" ? record.received_at : void 0;
14795
+ }
14796
+ function flatten(nodes, parentPosition = "") {
14797
+ const flattened = [];
14798
+ nodes.forEach((node, index) => {
14799
+ const position = parentPosition === "" ? String(index) : `${parentPosition}.${index}`;
14800
+ flattened.push({ node, position }, ...flatten(node.children, position));
14801
+ });
14802
+ return flattened;
14803
+ }
14804
+ function nodeStructure(node) {
14805
+ return JSON.stringify([node.text, node.children.map(nodeStructure)]);
14806
+ }
14807
+ function isRecord5(value) {
14808
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14809
+ }
14810
+
14811
+ // apps/cli/dist/project-index.js
14812
+ import { lstat, readFile as readFile3, realpath, stat as stat2 } from "node:fs/promises";
14813
+ import { dirname as dirname14, isAbsolute as isAbsolute3, join as join19, relative as relative2, resolve as resolve8 } from "node:path";
14814
+ var PROJECT_ROOT_MAX_DIRECTORIES = 8;
14815
+ var PROJECT_PLAN_POLL_MS = 2e3;
14816
+ async function resolveProjectRoot(cwd, options) {
14817
+ const root = resolve8(cwd);
14818
+ const homeDir = resolve8(options.homeDir);
14819
+ const boundaryDir = options.boundaryDir === void 0 ? void 0 : resolve8(options.boundaryDir);
14820
+ const [rootIdentity, homeIdentity, boundaryIdentity] = await Promise.all([
14821
+ pathIdentity(root),
14822
+ pathIdentity(homeDir),
14823
+ boundaryDir === void 0 ? void 0 : pathIdentity(boundaryDir)
14824
+ ]);
14825
+ const wingmanHomeIdentity = await pathIdentity(join19(homeIdentity, ".wingman"));
14826
+ const planPathIdentity = await pathIdentity(join19(rootIdentity, ".wingman", "plan.md"));
14827
+ if (rootIdentity === homeIdentity || rootIdentity === wingmanHomeIdentity || boundaryIdentity !== void 0 && !isWithin(boundaryIdentity, rootIdentity)) {
14828
+ return { root, rootIdentity, planPathIdentity, lookedUp: [], higherPlanPaths: [] };
14829
+ }
14830
+ const lookedUp = [];
14831
+ const found = [];
14832
+ let current = root;
14833
+ const maxDirectories = options.maxDirectories ?? PROJECT_ROOT_MAX_DIRECTORIES;
14834
+ for (let checked = 0; checked < maxDirectories; checked += 1) {
14835
+ const currentIdentity = await pathIdentity(current);
14836
+ if (currentIdentity === homeIdentity || currentIdentity === wingmanHomeIdentity)
14837
+ break;
14838
+ if (boundaryIdentity !== void 0 && !isWithin(boundaryIdentity, currentIdentity))
14839
+ break;
14840
+ const planPath = join19(current, ".wingman", "plan.md");
14841
+ lookedUp.push(planPath);
14842
+ try {
14843
+ await lstat(planPath);
14844
+ found.push({
14845
+ root: current,
14846
+ rootIdentity: currentIdentity,
14847
+ planPath,
14848
+ planPathIdentity: await pathIdentity(planPath)
14849
+ });
14850
+ } catch {
14851
+ }
14852
+ const parent = dirname14(current);
14853
+ if (parent === current)
14854
+ break;
14855
+ current = parent;
14856
+ }
14857
+ const nearest = found[0];
14858
+ return {
14859
+ root: nearest?.root ?? root,
14860
+ rootIdentity: nearest?.rootIdentity ?? rootIdentity,
14861
+ ...nearest === void 0 ? {} : { planPath: nearest.planPath },
14862
+ planPathIdentity: nearest?.planPathIdentity ?? planPathIdentity,
14863
+ lookedUp,
14864
+ higherPlanPaths: found.slice(1).map((item) => item.planPath)
14865
+ };
14866
+ }
14867
+ function watchProjectPlan(planPath, options) {
14868
+ const intervalMs = options.intervalMs ?? PROJECT_PLAN_POLL_MS;
14869
+ let previousSignature;
14870
+ let polling = false;
14871
+ let closed = false;
14872
+ const poll = async () => {
14873
+ if (closed || polling)
14874
+ return;
14875
+ polling = true;
14876
+ try {
14877
+ const linkInfo = await lstat(planPath);
14878
+ const resolvedPath = await realpath(planPath);
14879
+ const fileInfo = await stat2(planPath);
14880
+ const resolvedInsideRoot = options.projectRoot === void 0 || isWithin(await pathIdentity(options.projectRoot), normalizedPath(resolvedPath));
14881
+ const info = {
14882
+ mtime: fileInfo.mtime.toISOString(),
14883
+ size: fileInfo.size,
14884
+ isRegularFile: fileInfo.isFile(),
14885
+ resolvedPath,
14886
+ resolvedInsideRoot
14887
+ };
14888
+ const signature = [
14889
+ linkInfo.mtimeMs,
14890
+ fileInfo.mtimeMs,
14891
+ fileInfo.size,
14892
+ info.isRegularFile,
14893
+ resolvedPath
14894
+ ].join(":");
14895
+ if (signature === previousSignature)
14896
+ return;
14897
+ previousSignature = signature;
14898
+ if (!info.isRegularFile) {
14899
+ options.onUnavailable?.("\u8BA1\u5212\u6587\u4EF6\u4E0D\u662F\u666E\u901A\u6587\u4EF6", info);
14900
+ return;
14901
+ }
14902
+ if (!info.resolvedInsideRoot) {
14903
+ options.onUnavailable?.("\u8F6F\u94FE\u89E3\u6790\u5230\u9879\u76EE\u6839\u4E4B\u5916", info);
14904
+ return;
14905
+ }
14906
+ if (info.size > PROJECT_PLAN_MAX_BYTES) {
14907
+ options.onUnavailable?.("\u8BA1\u5212\u6587\u4EF6\u8D85\u8FC7 1 MiB", info);
14908
+ return;
14909
+ }
14910
+ options.onRead(await readFile3(planPath), info);
14911
+ } catch {
14912
+ if (previousSignature !== "missing") {
14913
+ previousSignature = "missing";
14914
+ options.onUnavailable?.("\u8BA1\u5212\u6587\u4EF6\u4E0D\u5B58\u5728");
14915
+ }
14916
+ } finally {
14917
+ polling = false;
14918
+ }
14919
+ };
14920
+ const timer = setInterval(() => void poll(), intervalMs);
14921
+ void poll();
14922
+ return {
14923
+ close: () => {
14924
+ closed = true;
14925
+ clearInterval(timer);
14926
+ }
14927
+ };
14928
+ }
14929
+ async function pathIdentity(path) {
14930
+ const resolved = resolve8(path);
14931
+ try {
14932
+ return normalizedPath(await realpath(resolved));
14933
+ } catch {
14934
+ return normalizedPath(resolved);
14935
+ }
14936
+ }
14937
+ async function pathsReferToSameLocation(left, right) {
14938
+ const [leftIdentity, rightIdentity] = await Promise.all([
14939
+ pathIdentity(left),
14940
+ pathIdentity(right)
14941
+ ]);
14942
+ return relative2(leftIdentity, rightIdentity) === "";
14943
+ }
14944
+ function normalizedPath(path) {
14945
+ const resolved = resolve8(path);
14946
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
14947
+ }
14948
+ function isWithin(parent, child) {
14949
+ const pathFromParent = relative2(parent, child);
14950
+ return pathFromParent === "" || !pathFromParent.startsWith("..") && !isAbsolute3(pathFromParent);
14951
+ }
14952
+
13450
14953
  // apps/cli/dist/narrative-service.js
13451
14954
  var MAX_SESSIONS = 8;
13452
14955
  var ENDED_AFTER_STOP_MS = 10 * 6e4;
@@ -13461,6 +14964,8 @@ function createNarrativeService(options = {}) {
13461
14964
  const harnesses = /* @__PURE__ */ new Map();
13462
14965
  const listeners = /* @__PURE__ */ new Set();
13463
14966
  const pendingNotifications = /* @__PURE__ */ new Set();
14967
+ const projectWatchers = /* @__PURE__ */ new Map();
14968
+ const projectFiles = /* @__PURE__ */ new Map();
13464
14969
  let notificationQueued = false;
13465
14970
  let closed = false;
13466
14971
  function notify(harness) {
@@ -13500,6 +15005,9 @@ function createNarrativeService(options = {}) {
13500
15005
  generation: 0,
13501
15006
  lines: [],
13502
15007
  codexLines: [],
15008
+ pendingPlanWrites: [],
15009
+ confirmedPlanWrites: [],
15010
+ nonPlanToolSteps: 0,
13503
15011
  follows: /* @__PURE__ */ new Map(),
13504
15012
  hookIntervals: [],
13505
15013
  waiting: null,
@@ -13516,6 +15024,167 @@ function createNarrativeService(options = {}) {
13516
15024
  clearInterval(session.subagentTimer);
13517
15025
  session.subagentTimer = void 0;
13518
15026
  }
15027
+ function anchorCwd(harness, session, cwd) {
15028
+ if (session.cwd !== void 0)
15029
+ return;
15030
+ session.cwd = cwd;
15031
+ if (options.homeDir === void 0)
15032
+ return;
15033
+ void resolveProjectRoot(cwd, {
15034
+ homeDir: options.homeDir,
15035
+ ...options.projectBoundaryDir === void 0 ? {} : { boundaryDir: options.projectBoundaryDir }
15036
+ }).then((resolution) => {
15037
+ if (closed || session.cwd !== cwd)
15038
+ return;
15039
+ session.projectResolution = resolution;
15040
+ watchResolution(resolution);
15041
+ session.dirty = true;
15042
+ notify(harness);
15043
+ });
15044
+ }
15045
+ function watchResolution(resolution) {
15046
+ if (resolution.lookedUp.length === 0)
15047
+ return;
15048
+ const planPath = resolution.planPath ?? join20(resolution.root, ".wingman", "plan.md");
15049
+ const planPathIdentity = resolution.planPathIdentity;
15050
+ if (projectWatchers.has(planPathIdentity))
15051
+ return;
15052
+ projectWatchers.set(planPathIdentity, watchProjectPlan(planPath, {
15053
+ ...options.projectPollIntervalMs === void 0 ? {} : { intervalMs: options.projectPollIntervalMs },
15054
+ projectRoot: resolution.root,
15055
+ onRead: (content, info) => {
15056
+ const existing = projectFiles.get(planPathIdentity);
15057
+ const parsed = parseProjectPlan(content, {
15058
+ isRegularFile: info.isRegularFile,
15059
+ resolvedInsideRoot: info.resolvedInsideRoot
15060
+ });
15061
+ let revision = existing?.revision ?? 0;
15062
+ let structureKey = existing?.structureKey;
15063
+ let lastGood = existing?.lastGood;
15064
+ if (parsed.availability === "available") {
15065
+ const nextStructureKey = projectPlanStructureKey(parsed.nodes);
15066
+ if (nextStructureKey !== structureKey)
15067
+ revision += 1;
15068
+ structureKey = nextStructureKey;
15069
+ lastGood = {
15070
+ revision,
15071
+ mtime: info.mtime,
15072
+ ...parsed.title === void 0 ? {} : { title: parsed.title },
15073
+ ...parsed.goal === void 0 ? {} : { goal: parsed.goal },
15074
+ ignoredLineCount: parsed.ignoredLineCount,
15075
+ nodes: parsed.nodes
15076
+ };
15077
+ }
15078
+ projectFiles.set(planPathIdentity, {
15079
+ ...existing?.current === void 0 ? {} : { previous: existing.current },
15080
+ current: { content, info },
15081
+ parsed,
15082
+ revision,
15083
+ ...structureKey === void 0 ? {} : { structureKey },
15084
+ ...lastGood === void 0 ? {} : { lastGood },
15085
+ missing: false
15086
+ });
15087
+ projectChanged(planPathIdentity);
15088
+ },
15089
+ onUnavailable: (reason) => {
15090
+ const existing = projectFiles.get(planPathIdentity);
15091
+ const missing = reason === "\u8BA1\u5212\u6587\u4EF6\u4E0D\u5B58\u5728";
15092
+ projectFiles.set(planPathIdentity, {
15093
+ ...existing,
15094
+ ...missing ? {} : { parsed: { availability: "unavailable", reason } },
15095
+ revision: existing?.revision ?? 0,
15096
+ missing
15097
+ });
15098
+ projectChanged(planPathIdentity);
15099
+ }
15100
+ }));
15101
+ }
15102
+ function projectChanged(planPathIdentity) {
15103
+ for (const [harness, sessions] of harnesses) {
15104
+ let changed = false;
15105
+ for (const session of sessions.values()) {
15106
+ const resolution = session.projectResolution;
15107
+ if (resolution?.planPathIdentity === planPathIdentity) {
15108
+ const planPath = resolution.planPath ?? join20(resolution.root, ".wingman", "plan.md");
15109
+ void applyPendingPlanWrites(session, planPath, planPathIdentity).then(() => {
15110
+ session.dirty = true;
15111
+ notify(harness);
15112
+ });
15113
+ session.dirty = true;
15114
+ changed = true;
15115
+ }
15116
+ }
15117
+ if (changed)
15118
+ notify(harness);
15119
+ }
15120
+ }
15121
+ function recordToolObservations(harness, session, tools) {
15122
+ for (const tool of tools) {
15123
+ if (tool.planWrites.length === 0) {
15124
+ if (!tool.planFileAccess && !TODO_TOOLS.has(tool.method))
15125
+ session.nonPlanToolSteps += 1;
15126
+ continue;
15127
+ }
15128
+ recordPlanWrites(harness, session, tool.planWrites, session.nonPlanToolSteps);
15129
+ }
15130
+ }
15131
+ function recordPlanWrites(harness, session, observations, nonPlanStepsBefore) {
15132
+ for (const observation of observations) {
15133
+ if (observation.kind === "shell") {
15134
+ session.planUnattachedReason = observation.reason;
15135
+ continue;
15136
+ }
15137
+ const same = (candidate) => candidate.path === observation.path && candidate.method === observation.method;
15138
+ if (observation.source === "hook" && session.pendingPlanWrites.some((candidate) => candidate.observation.source === "transcript" && same(candidate.observation))) {
15139
+ continue;
15140
+ }
15141
+ if (observation.source === "transcript") {
15142
+ session.pendingPlanWrites = session.pendingPlanWrites.filter((candidate) => candidate.observation.source !== "hook" || !same(candidate.observation));
15143
+ }
15144
+ session.pendingPlanWrites.push({ observation, nonPlanStepsBefore });
15145
+ }
15146
+ const resolution = session.projectResolution;
15147
+ if (resolution !== void 0) {
15148
+ const planPath = resolution.planPath ?? join20(resolution.root, ".wingman", "plan.md");
15149
+ void applyPendingPlanWrites(session, planPath, resolution.planPathIdentity).then(() => {
15150
+ session.dirty = true;
15151
+ notify(harness);
15152
+ });
15153
+ }
15154
+ }
15155
+ async function applyPendingPlanWrites(session, planPath, planPathIdentity) {
15156
+ const versions = projectFiles.get(planPathIdentity);
15157
+ if (versions?.previous === void 0 || versions.current === void 0)
15158
+ return;
15159
+ const before = parseProjectPlan(versions.previous.content);
15160
+ const after = parseProjectPlan(versions.current.content);
15161
+ if (before.availability !== "available" || after.availability !== "available")
15162
+ return;
15163
+ const remaining = [];
15164
+ for (const pending of session.pendingPlanWrites) {
15165
+ const { observation } = pending;
15166
+ const observedPath = resolveObservedPath(observation.path, session.cwd);
15167
+ if (observedPath === void 0 || !await pathsReferToSameLocation(observedPath, planPath)) {
15168
+ remaining.push(pending);
15169
+ continue;
15170
+ }
15171
+ session.confirmedPlanWrites.push({
15172
+ method: observation.method,
15173
+ path: observedPath,
15174
+ source: observation.source,
15175
+ at: observation.at ?? versions.current.info.mtime,
15176
+ nonPlanStepsBefore: pending.nonPlanStepsBefore
15177
+ });
15178
+ const result4 = attributePlanWrite(before, after, observation);
15179
+ if ("attachedTo" in result4) {
15180
+ session.planAttachment = result4;
15181
+ session.planUnattachedReason = void 0;
15182
+ } else {
15183
+ session.planUnattachedReason = result4.reason;
15184
+ }
15185
+ }
15186
+ session.pendingPlanWrites = remaining;
15187
+ }
13519
15188
  function evictOne(sessions, nowIso) {
13520
15189
  let victimId;
13521
15190
  let victimAt = "\uFFFF";
@@ -13569,6 +15238,7 @@ function createNarrativeService(options = {}) {
13569
15238
  session.codexLines.push(parseCodexRolloutLine(line));
13570
15239
  else
13571
15240
  session.lines.push(parseTranscriptLine(line));
15241
+ recordToolObservations(harness, session, extractRawToolObservations(harness, line));
13572
15242
  session.dirty = true;
13573
15243
  notify(harness);
13574
15244
  }, { intervalMs }));
@@ -13577,7 +15247,7 @@ function createNarrativeService(options = {}) {
13577
15247
  const sessionDir = transcriptPath.replace(/\.jsonl$/, "");
13578
15248
  if (sessionDir === transcriptPath)
13579
15249
  return;
13580
- const subagentsDir = join4(sessionDir, "subagents");
15250
+ const subagentsDir = join20(sessionDir, "subagents");
13581
15251
  const generation = session.generation;
13582
15252
  const poll = async () => {
13583
15253
  try {
@@ -13587,7 +15257,7 @@ function createNarrativeService(options = {}) {
13587
15257
  }
13588
15258
  for (const entry of entries) {
13589
15259
  if (/^agent-.*\.jsonl$/.test(entry)) {
13590
- startFollowing(harness, session, join4(subagentsDir, entry), generation);
15260
+ startFollowing(harness, session, join20(subagentsDir, entry), generation);
13591
15261
  }
13592
15262
  }
13593
15263
  } catch {
@@ -13608,10 +15278,12 @@ function createNarrativeService(options = {}) {
13608
15278
  session.lastActivity = event.received_at;
13609
15279
  const cwd = typeof payload.cwd === "string" ? payload.cwd : void 0;
13610
15280
  if (cwd !== void 0) {
15281
+ anchorCwd(event.harness, session, cwd);
13611
15282
  const base = pathBasename(cwd);
13612
15283
  if (base !== "")
13613
15284
  session.label = base;
13614
15285
  }
15286
+ recordToolObservations(event.harness, session, extractToolObservations(event.harness, [{ ...payload, received_at: event.received_at }]));
13615
15287
  if (follow && transcriptPath !== void 0) {
13616
15288
  if (session.transcriptPath !== transcriptPath) {
13617
15289
  resetTranscript(session, transcriptPath);
@@ -13706,9 +15378,15 @@ function createNarrativeService(options = {}) {
13706
15378
  const session2 = sessionOf(harness, id2);
13707
15379
  session2.lines = [];
13708
15380
  session2.codexLines = codexLines;
15381
+ session2.pendingPlanWrites = [];
15382
+ session2.confirmedPlanWrites = [];
15383
+ session2.nonPlanToolSteps = 0;
15384
+ for (const line of sourceLines)
15385
+ recordToolObservations(harness, session2, extractRawToolObservations(harness, line));
13709
15386
  session2.mockNote = mockNote;
13710
15387
  const cwd = meta?.cwd ?? codexLines.find((line) => line.kind === "turn_context")?.cwd;
13711
15388
  if (cwd !== void 0) {
15389
+ anchorCwd(harness, session2, cwd);
13712
15390
  const base = pathBasename(cwd);
13713
15391
  if (base !== "")
13714
15392
  session2.label = base;
@@ -13717,18 +15395,25 @@ function createNarrativeService(options = {}) {
13717
15395
  notify(harness);
13718
15396
  return;
13719
15397
  }
13720
- const lines = sourceLines.map((line) => parseTranscriptLine(line));
13721
- const sessionLine = lines.find((l) => l.kind !== "unknown" && typeof l.sessionId === "string");
15398
+ const lines2 = sourceLines.map((line) => parseTranscriptLine(line));
15399
+ const sessionLine = lines2.find((l) => l.kind !== "unknown" && typeof l.sessionId === "string");
13722
15400
  const id = sessionLine?.sessionId ?? "__fixture__";
13723
15401
  const session = sessionOf(harness, id);
13724
- session.lines = lines;
15402
+ session.lines = lines2;
15403
+ session.pendingPlanWrites = [];
15404
+ session.confirmedPlanWrites = [];
15405
+ session.nonPlanToolSteps = 0;
15406
+ for (const line of sourceLines) {
15407
+ recordToolObservations(harness, session, extractRawToolObservations(harness, line));
15408
+ }
13725
15409
  session.codexLines = [];
13726
15410
  session.mockNote = mockNote;
13727
- for (const line of lines) {
15411
+ for (const line of lines2) {
13728
15412
  if (line.kind === "unknown")
13729
15413
  continue;
13730
15414
  const cwd = line.raw.cwd;
13731
15415
  if (typeof cwd === "string") {
15416
+ anchorCwd(harness, session, cwd);
13732
15417
  const base = pathBasename(cwd);
13733
15418
  if (base !== "")
13734
15419
  session.label = base;
@@ -13757,6 +15442,7 @@ function createNarrativeService(options = {}) {
13757
15442
  const infos = [...sessions.values()].map((session) => ({
13758
15443
  id: session.id,
13759
15444
  label: session.label ?? session.id.slice(0, 8),
15445
+ ...session.cwd === void 0 ? {} : { cwd: session.cwd },
13760
15446
  lastActivity: lastActivityOf(session),
13761
15447
  waiting: session.waiting !== null,
13762
15448
  ended: isEnded(session, nowIso),
@@ -13785,12 +15471,198 @@ function createNarrativeService(options = {}) {
13785
15471
  }
13786
15472
  return emptyState(harness, now().toISOString());
13787
15473
  }
15474
+ function installedProtocolStatus(harness, projectDir) {
15475
+ if (options.protocolStatus !== void 0)
15476
+ return options.protocolStatus(harness, projectDir);
15477
+ const common = options.homeDir === void 0 ? { projectDir } : { homeDir: options.homeDir, projectDir };
15478
+ switch (harness) {
15479
+ case "cc":
15480
+ return protocolStatusCc(common);
15481
+ case "codex":
15482
+ return protocolStatusCodex(common);
15483
+ case "dsh":
15484
+ return protocolStatusDsh(common);
15485
+ }
15486
+ }
15487
+ function projectProtocolStatus(harness, projectDir, planMtime) {
15488
+ const observed = installedProtocolStatus(harness, projectDir);
15489
+ let state;
15490
+ if (observed.state === "installed_not_loaded")
15491
+ state = "installed_not_loaded";
15492
+ else if (observed.state === "installed") {
15493
+ state = planMtime === void 0 ? "installed" : "plan_present";
15494
+ } else
15495
+ state = "not_installed";
15496
+ return {
15497
+ state,
15498
+ targetPath: observed.targetPath,
15499
+ ...observed.version === void 0 ? {} : { version: observed.version },
15500
+ ...state === "plan_present" && planMtime !== void 0 ? { planMtime } : {},
15501
+ ...!("message" in observed) || observed.message === void 0 ? {} : { message: observed.message },
15502
+ ...observed.reason === void 0 ? {} : { reason: observed.reason }
15503
+ };
15504
+ }
15505
+ function complianceFor(session, installed) {
15506
+ const writeCount = session.confirmedPlanWrites.length;
15507
+ const canAttributeProtocol = installed.state === "installed" || installed.state === "plan_present";
15508
+ const level = canAttributeProtocol && writeCount > 0 ? "session_wrote_plan" : installed.state;
15509
+ const latest = session.confirmedPlanWrites.at(-1);
15510
+ const first = session.confirmedPlanWrites[0];
15511
+ let timing;
15512
+ if (first !== void 0) {
15513
+ if (first.nonPlanStepsBefore > 0) {
15514
+ timing = {
15515
+ relation: "after",
15516
+ nonPlanToolStep: first.nonPlanStepsBefore,
15517
+ text: `\u8BA1\u5212\u6587\u4EF6\u9996\u6B21\u5199\u5165\u53D1\u751F\u5728\u7B2C ${first.nonPlanStepsBefore} \u4E2A\u975E\u8BA1\u5212\u5DE5\u5177\u6B65\u9AA4\u4E4B\u540E`
15518
+ };
15519
+ } else if (session.nonPlanToolSteps > 0) {
15520
+ timing = {
15521
+ relation: "before",
15522
+ nonPlanToolStep: 1,
15523
+ text: "\u8BA1\u5212\u6587\u4EF6\u9996\u6B21\u5199\u5165\u53D1\u751F\u5728\u7B2C 1 \u4E2A\u975E\u8BA1\u5212\u5DE5\u5177\u6B65\u9AA4\u4E4B\u524D"
15524
+ };
15525
+ }
15526
+ }
15527
+ return {
15528
+ level,
15529
+ writeCount,
15530
+ ...latest === void 0 ? {} : { latestWriteAt: latest.at },
15531
+ ...latest === void 0 ? {} : { writeSummary: `\u672C\u4F1A\u8BDD\u5199\u5165 ${writeCount} \u6B21 \xB7 \u6700\u8FD1 ${latest.at}` },
15532
+ ...timing === void 0 ? {} : { timing }
15533
+ };
15534
+ }
15535
+ function getProjects() {
15536
+ const grouped = /* @__PURE__ */ new Map();
15537
+ for (const [harness, sessions] of harnesses) {
15538
+ for (const session of sessions.values()) {
15539
+ if (session.cwd === void 0)
15540
+ continue;
15541
+ const rootIdentity = session.projectResolution?.rootIdentity ?? resolve9(session.cwd);
15542
+ const group = grouped.get(rootIdentity);
15543
+ if (group === void 0)
15544
+ grouped.set(rootIdentity, [{ harness, session }]);
15545
+ else
15546
+ group.push({ harness, session });
15547
+ }
15548
+ }
15549
+ const overviews = /* @__PURE__ */ new Map();
15550
+ for (const harness of harnesses.keys())
15551
+ overviews.set(harness, getOverview(harness));
15552
+ const projects = [];
15553
+ for (const members of grouped.values()) {
15554
+ const resolution = members.find((member) => member.session.projectResolution !== void 0)?.session.projectResolution;
15555
+ const root = resolution?.root ?? resolve9(members[0]?.session.cwd ?? "");
15556
+ const planPath = resolution?.planPath ?? join20(root, ".wingman", "plan.md");
15557
+ const runtime = projectFiles.get(resolution?.planPathIdentity ?? planPath);
15558
+ const parsed = runtime?.missing === true ? void 0 : runtime?.parsed;
15559
+ const planMtime = parsed?.availability === "available" ? runtime?.current?.info.mtime : void 0;
15560
+ const protocol = {
15561
+ cc: projectProtocolStatus("cc", root, planMtime),
15562
+ codex: projectProtocolStatus("codex", root, planMtime),
15563
+ dsh: projectProtocolStatus("dsh", root, planMtime)
15564
+ };
15565
+ const sessionViews = members.map(({ harness, session }) => {
15566
+ const overview = overviews.get(harness);
15567
+ const state = overview?.states[session.id];
15568
+ const info = overview?.sessions.find((candidate) => candidate.id === session.id);
15569
+ return {
15570
+ harness,
15571
+ id: session.id,
15572
+ label: session.label ?? session.id.slice(0, 8),
15573
+ ended: info?.ended ?? false,
15574
+ compliance: complianceFor(session, protocol[harness]),
15575
+ ...session.planAttachment === void 0 ? {} : {
15576
+ attachedTo: session.planAttachment.attachedTo,
15577
+ attachedBy: session.planAttachment.attachedBy
15578
+ },
15579
+ ...session.planAttachment !== void 0 ? {} : {
15580
+ unattachedReason: session.planUnattachedReason ?? "\u672A\u89C2\u5BDF\u5230\u8BE5\u4F1A\u8BDD\u901A\u8FC7\u6587\u4EF6\u7F16\u8F91\u5DE5\u5177\u5199\u5165 plan.md"
15581
+ },
15582
+ ...state?.taskWorkflow === void 0 ? {} : {
15583
+ sessionPlan: state.taskWorkflow.plan,
15584
+ feedback: state.taskWorkflow.feedback
15585
+ }
15586
+ };
15587
+ }).sort((left, right) => {
15588
+ const harnessOrder = HARNESS_IDS.indexOf(left.harness) - HARNESS_IDS.indexOf(right.harness);
15589
+ return harnessOrder === 0 ? left.id.localeCompare(right.id) : harnessOrder;
15590
+ });
15591
+ for (const session of sessionViews) {
15592
+ if (session.compliance.level === "session_wrote_plan") {
15593
+ protocol[session.harness] = {
15594
+ ...protocol[session.harness],
15595
+ state: "session_wrote_plan"
15596
+ };
15597
+ }
15598
+ }
15599
+ const summary = parsed?.availability === "available" ? deriveProjectPlanSummary(parsed.nodes) : deriveProjectPlanSummary([]);
15600
+ const reconciliation = deriveProjectReconciliationFacts(parsed?.availability === "available" ? parsed.nodes : [], sessionViews.map((session) => ({
15601
+ id: `${session.harness}:${session.id}`,
15602
+ ...session.attachedTo === void 0 ? {} : { attachedTo: session.attachedTo },
15603
+ ended: session.ended,
15604
+ ...session.sessionPlan?.availability === "available" && session.sessionPlan.steps.length > 0 ? {
15605
+ sessionPlanAllCompleted: session.sessionPlan.steps.every((step) => step.status === "completed")
15606
+ } : {},
15607
+ ...session.attachedBy?.at === void 0 ? {} : { wrotePlanAt: session.attachedBy.at }
15608
+ })), now().toISOString());
15609
+ const lastWriter = [...sessionViews].filter((session) => typeof session.attachedBy?.at === "string").sort((left, right) => right.attachedBy.at.localeCompare(left.attachedBy.at))[0];
15610
+ const titleText = parsed?.availability === "available" && parsed.title !== void 0 ? parsed.title : pathBasename(root) || root;
15611
+ const titleSource = parsed?.availability === "available" && parsed.title !== void 0 ? ".wingman/plan.md H1" : "\u5DE5\u4F5C\u76EE\u5F55";
15612
+ let plan;
15613
+ if (parsed?.availability === "available") {
15614
+ plan = {
15615
+ availability: "available",
15616
+ ignoredLineCount: parsed.ignoredLineCount,
15617
+ nodes: parsed.nodes,
15618
+ higherPlanPaths: resolution?.higherPlanPaths ?? []
15619
+ };
15620
+ } else if (parsed?.availability === "unavailable") {
15621
+ plan = {
15622
+ availability: "unavailable",
15623
+ reason: parsed.reason,
15624
+ ...parsed.line === void 0 ? {} : { line: parsed.line },
15625
+ ...runtime?.lastGood === void 0 ? {} : { lastGood: runtime.lastGood }
15626
+ };
15627
+ } else {
15628
+ plan = {
15629
+ availability: "missing",
15630
+ lookedUp: resolution?.lookedUp ?? [planPath]
15631
+ };
15632
+ }
15633
+ projects.push({
15634
+ root,
15635
+ title: { text: titleText, source: titleSource },
15636
+ ...parsed?.availability === "available" && parsed.goal !== void 0 ? { goal: parsed.goal } : {},
15637
+ plan,
15638
+ ...runtime !== void 0 && runtime.revision > 0 ? { revision: runtime.revision } : {},
15639
+ ...runtime?.current?.info.mtime === void 0 ? {} : { mtime: runtime.current.info.mtime },
15640
+ ...lastWriter?.attachedBy.at === void 0 ? {} : {
15641
+ lastWriter: {
15642
+ harness: lastWriter.harness,
15643
+ id: lastWriter.id,
15644
+ label: lastWriter.label,
15645
+ at: lastWriter.attachedBy.at
15646
+ }
15647
+ },
15648
+ ...summary,
15649
+ facts: reconciliation,
15650
+ protocol,
15651
+ sessions: sessionViews
15652
+ });
15653
+ }
15654
+ projects.sort((left, right) => left.root.localeCompare(right.root));
15655
+ return { projects };
15656
+ }
13788
15657
  function close() {
13789
15658
  closed = true;
13790
15659
  for (const sessions of harnesses.values()) {
13791
15660
  for (const session of sessions.values())
13792
15661
  closeSession(session);
13793
15662
  }
15663
+ for (const watcher of projectWatchers.values())
15664
+ watcher.close();
15665
+ projectWatchers.clear();
13794
15666
  pendingNotifications.clear();
13795
15667
  listeners.clear();
13796
15668
  }
@@ -13801,7 +15673,7 @@ function createNarrativeService(options = {}) {
13801
15673
  listeners.add(listener);
13802
15674
  return () => listeners.delete(listener);
13803
15675
  }
13804
- return { handleEvent, getState, getOverview, seedFixture, subscribe, close };
15676
+ return { handleEvent, getState, getOverview, getProjects, seedFixture, subscribe, close };
13805
15677
  }
13806
15678
  function lastActivityOf(session) {
13807
15679
  if (session.lastActivity !== void 0)
@@ -13853,6 +15725,18 @@ function pathBasename(path) {
13853
15725
  const parts = path.split(/[\\/]/).filter((p) => p !== "");
13854
15726
  return parts[parts.length - 1] ?? "";
13855
15727
  }
15728
+ function extractRawToolObservations(harness, rawLine) {
15729
+ try {
15730
+ return extractToolObservations(harness, [JSON.parse(rawLine)]);
15731
+ } catch {
15732
+ return [];
15733
+ }
15734
+ }
15735
+ function resolveObservedPath(path, cwd) {
15736
+ if (isAbsolute4(path))
15737
+ return resolve9(path);
15738
+ return cwd === void 0 ? void 0 : resolve9(cwd, path);
15739
+ }
13856
15740
  function agentIdOf(payload) {
13857
15741
  const id = payload.agent_id ?? payload.agentId;
13858
15742
  return typeof id === "string" && id !== "" ? id : void 0;
@@ -13905,13 +15789,13 @@ function feedbackView(harness, capability, state) {
13905
15789
  }
13906
15790
  function derive(harness, session, nowIso) {
13907
15791
  const codexLines = sortedCodexByTimestamp(session.codexLines);
13908
- const lines = harness === "codex" ? adaptCodexNarrativeLines(codexLines) : sortedByTimestamp(session.lines);
13909
- const timeline = deriveTodoTimeline(extractTodoSnapshots(lines));
13910
- const allSteps = extractSteps(lines);
15792
+ const lines2 = harness === "codex" ? adaptCodexNarrativeLines(codexLines) : sortedByTimestamp(session.lines);
15793
+ const timeline = deriveTodoTimeline(extractTodoSnapshots(lines2));
15794
+ const allSteps = extractSteps(lines2);
13911
15795
  const steps = allSteps.filter((s) => !TODO_TOOLS.has(s.toolName));
13912
- const intervals = [...deriveSubagentIntervals(lines), ...session.hookIntervals];
15796
+ const intervals = [...deriveSubagentIntervals(lines2), ...session.hookIntervals];
13913
15797
  const attributed = attributeSteps(steps, timeline, intervals);
13914
- const results = collectResults(lines);
15798
+ const results = collectResults(lines2);
13915
15799
  const buckets = /* @__PURE__ */ new Map();
13916
15800
  for (const { step, attribution } of attributed) {
13917
15801
  const key = attribution.kind === "todo" ? attribution.todoId : null;
@@ -13989,9 +15873,9 @@ function derive(harness, session, nowIso) {
13989
15873
  agents: [...agentsByTodo.get(entry.id) ?? []]
13990
15874
  };
13991
15875
  });
13992
- const textTodos = todos.length === 0 ? deriveTextTodos(lines) : void 0;
15876
+ const textTodos = todos.length === 0 ? deriveTextTodos(lines2) : void 0;
13993
15877
  const currentAction = deriveCurrentAction(steps, session.lastEvent, session.pendingHookTool);
13994
- const lastPrompt = deriveLastPrompt(lines, session.hookPrompt, session.hookPromptAt);
15878
+ const lastPrompt = deriveLastPrompt(lines2, session.hookPrompt, session.hookPromptAt);
13995
15879
  const workflowAction = currentAction === null ? void 0 : currentAction.kind === "tool" ? {
13996
15880
  text: [currentAction.toolName, currentAction.detail].filter((part) => part !== "").join(": "),
13997
15881
  source: "transcript / hook tool event",
@@ -13999,7 +15883,7 @@ function derive(harness, session, nowIso) {
13999
15883
  } : { text: currentAction.name, source: "hook event", observedAt: currentAction.at };
14000
15884
  const taskWorkflow = deriveTaskWorkflowView({
14001
15885
  harness,
14002
- lines,
15886
+ lines: lines2,
14003
15887
  codexLines,
14004
15888
  ...lastPrompt === null ? {} : {
14005
15889
  latestInstruction: {
@@ -14018,14 +15902,21 @@ function derive(harness, session, nowIso) {
14018
15902
  harness,
14019
15903
  sessionId: session.id,
14020
15904
  transcriptPath: session.transcriptPath ?? null,
15905
+ ...session.cwd === void 0 ? {} : { cwd: session.cwd },
15906
+ ...session.cwd === void 0 ? {} : {
15907
+ project: {
15908
+ root: session.projectResolution?.root ?? resolve9(session.cwd),
15909
+ ...session.planAttachment === void 0 ? {} : { attachedTo: session.planAttachment.attachedTo }
15910
+ }
15911
+ },
14021
15912
  ...session.mockNote !== void 0 ? { __mock_note: session.mockNote } : {},
14022
15913
  todos,
14023
15914
  ...textTodos !== void 0 && textTodos.length > 0 ? { textTodos } : {},
14024
15915
  traces,
14025
- turnUsages: harness === "codex" ? deriveCodexTurnUsages(codexLines) : deriveTurnUsages(lines),
15916
+ turnUsages: harness === "codex" ? deriveCodexTurnUsages(codexLines) : deriveTurnUsages(lines2),
14026
15917
  currentAction,
14027
15918
  waiting: session.waiting,
14028
- agentQuote: deriveAgentQuote(lines),
15919
+ agentQuote: deriveAgentQuote(lines2),
14029
15920
  // transcript 是会话事实源;hook prompt 只在 tail 尚未读到/格式暂不可读时兜底。
14030
15921
  lastPrompt,
14031
15922
  taskWorkflow,
@@ -14050,9 +15941,9 @@ function insertByStartTs(nodes, node) {
14050
15941
  }
14051
15942
  nodes.push(node);
14052
15943
  }
14053
- function deriveTextTodos(lines) {
14054
- for (let i = lines.length - 1; i >= 0; i--) {
14055
- const line = lines[i];
15944
+ function deriveTextTodos(lines2) {
15945
+ for (let i = lines2.length - 1; i >= 0; i--) {
15946
+ const line = lines2[i];
14056
15947
  if (line === void 0 || line.kind !== "assistant" || line.agentId !== void 0)
14057
15948
  continue;
14058
15949
  for (let j = line.blocks.length - 1; j >= 0; j--) {
@@ -14066,15 +15957,15 @@ function deriveTextTodos(lines) {
14066
15957
  }
14067
15958
  return void 0;
14068
15959
  }
14069
- function sortedByTimestamp(lines) {
14070
- return sortByInheritedTimestamp(lines, (line) => line.kind === "unknown" ? void 0 : line.timestamp, (line) => line.kind === "unknown" ? "" : line.agentId ?? "");
15960
+ function sortedByTimestamp(lines2) {
15961
+ return sortByInheritedTimestamp(lines2, (line) => line.kind === "unknown" ? void 0 : line.timestamp, (line) => line.kind === "unknown" ? "" : line.agentId ?? "");
14071
15962
  }
14072
- function sortedCodexByTimestamp(lines) {
14073
- return sortByInheritedTimestamp(lines, (line) => line.kind === "unknown" ? void 0 : line.timestamp);
15963
+ function sortedCodexByTimestamp(lines2) {
15964
+ return sortByInheritedTimestamp(lines2, (line) => line.kind === "unknown" ? void 0 : line.timestamp);
14074
15965
  }
14075
- function sortByInheritedTimestamp(lines, timestampOf, sourceOf = () => "") {
15966
+ function sortByInheritedTimestamp(lines2, timestampOf, sourceOf = () => "") {
14076
15967
  const previousBySource = /* @__PURE__ */ new Map();
14077
- return lines.map((line, index) => {
15968
+ return lines2.map((line, index) => {
14078
15969
  const source = sourceOf(line);
14079
15970
  const timestamp = timestampOf(line) ?? previousBySource.get(source);
14080
15971
  if (timestamp !== void 0)
@@ -14088,17 +15979,17 @@ function sortByInheritedTimestamp(lines, timestampOf, sourceOf = () => "") {
14088
15979
  return a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : a.index - b.index;
14089
15980
  }).map(({ line }) => line);
14090
15981
  }
14091
- function adaptCodexNarrativeLines(lines) {
14092
- const responseTexts = new Set(lines.flatMap((line) => line.kind === "assistant" && line.source === "response_item" && line.text !== void 0 ? [line.text] : []));
15982
+ function adaptCodexNarrativeLines(lines2) {
15983
+ const responseTexts = new Set(lines2.flatMap((line) => line.kind === "assistant" && line.source === "response_item" && line.text !== void 0 ? [line.text] : []));
14093
15984
  const modelByTurn = /* @__PURE__ */ new Map();
14094
- for (const line of lines) {
15985
+ for (const line of lines2) {
14095
15986
  if (line.kind === "turn_context" && line.turnId !== void 0 && line.model !== void 0) {
14096
15987
  modelByTurn.set(line.turnId, line.model);
14097
15988
  }
14098
15989
  }
14099
15990
  const adapted = [];
14100
15991
  let activeTurnId;
14101
- for (const line of lines) {
15992
+ for (const line of lines2) {
14102
15993
  if (line.kind === "task_started") {
14103
15994
  activeTurnId = line.turnId;
14104
15995
  continue;
@@ -14182,23 +16073,23 @@ function codexToolInput(value) {
14182
16073
  }
14183
16074
  return value === void 0 ? {} : { input: value };
14184
16075
  }
14185
- function collectResults(lines) {
16076
+ function collectResults(lines2) {
14186
16077
  const results = /* @__PURE__ */ new Map();
14187
- for (const line of lines) {
16078
+ for (const line of lines2) {
14188
16079
  if (line.kind !== "user")
14189
16080
  continue;
14190
- for (const result of line.toolResults) {
14191
- if (result.content === void 0)
16081
+ for (const result4 of line.toolResults) {
16082
+ if (result4.content === void 0)
14192
16083
  continue;
14193
16084
  const info = {};
14194
16085
  try {
14195
- info.bytes = Buffer.byteLength(JSON.stringify(result.content), "utf8");
16086
+ info.bytes = Buffer.byteLength(JSON.stringify(result4.content), "utf8");
14196
16087
  } catch {
14197
16088
  }
14198
- const brief = errorBrief(result.content);
16089
+ const brief = errorBrief(result4.content);
14199
16090
  if (brief !== void 0)
14200
16091
  info.brief = brief;
14201
- results.set(result.toolUseId, info);
16092
+ results.set(result4.toolUseId, info);
14202
16093
  }
14203
16094
  }
14204
16095
  return results;
@@ -14273,10 +16164,10 @@ function hookToolDetail(input) {
14273
16164
  return "";
14274
16165
  return inputDetail(input);
14275
16166
  }
14276
- function deriveTurnUsages(lines) {
16167
+ function deriveTurnUsages(lines2) {
14277
16168
  const byMessage = /* @__PURE__ */ new Map();
14278
16169
  let anonymous = 0;
14279
- for (const line of lines) {
16170
+ for (const line of lines2) {
14280
16171
  if (line.kind !== "assistant")
14281
16172
  continue;
14282
16173
  const usage = turnUsage(line);
@@ -14313,9 +16204,9 @@ var ZERO_CODEX_USAGE = {
14313
16204
  reasoningOutputTokens: 0,
14314
16205
  totalTokens: 0
14315
16206
  };
14316
- function deriveCodexTurnUsages(lines) {
16207
+ function deriveCodexTurnUsages(lines2) {
14317
16208
  const modelByTurn = /* @__PURE__ */ new Map();
14318
- for (const line of lines) {
16209
+ for (const line of lines2) {
14319
16210
  if (line.kind === "turn_context" && line.turnId !== void 0 && line.model !== void 0) {
14320
16211
  modelByTurn.set(line.turnId, line.model);
14321
16212
  }
@@ -14356,7 +16247,7 @@ function deriveCodexTurnUsages(lines) {
14356
16247
  view.totalTokens = delta.totalTokens;
14357
16248
  turns.push(view);
14358
16249
  }
14359
- for (const line of lines) {
16250
+ for (const line of lines2) {
14360
16251
  if (line.kind === "task_started") {
14361
16252
  if (active !== void 0 && active.terminal === void 0)
14362
16253
  active.terminal = "incomplete";
@@ -14450,9 +16341,9 @@ function deriveCurrentAction(steps, lastEvent, pendingHookTool) {
14450
16341
  return { kind: "event", name: lastEvent.name, at: lastEvent.at };
14451
16342
  return null;
14452
16343
  }
14453
- function deriveAgentQuote(lines) {
14454
- for (let i = lines.length - 1; i >= 0; i--) {
14455
- const line = lines[i];
16344
+ function deriveAgentQuote(lines2) {
16345
+ for (let i = lines2.length - 1; i >= 0; i--) {
16346
+ const line = lines2[i];
14456
16347
  if (line === void 0 || line.kind !== "assistant" || line.agentId !== void 0)
14457
16348
  continue;
14458
16349
  for (let j = line.blocks.length - 1; j >= 0; j--) {
@@ -14464,9 +16355,9 @@ function deriveAgentQuote(lines) {
14464
16355
  }
14465
16356
  return null;
14466
16357
  }
14467
- function deriveLastPrompt(lines, hookPrompt, hookPromptAt) {
14468
- for (let i = lines.length - 1; i >= 0; i--) {
14469
- const line = lines[i];
16358
+ function deriveLastPrompt(lines2, hookPrompt, hookPromptAt) {
16359
+ for (let i = lines2.length - 1; i >= 0; i--) {
16360
+ const line = lines2[i];
14470
16361
  if (line === void 0 || line.kind !== "user" || line.agentId !== void 0)
14471
16362
  continue;
14472
16363
  if (line.toolResults.length > 0)
@@ -14498,9 +16389,146 @@ function errorBrief(content) {
14498
16389
  return void 0;
14499
16390
  }
14500
16391
 
16392
+ // apps/cli/dist/protocol-cli.js
16393
+ init_dist2();
16394
+ init_dist4();
16395
+ init_dist5();
16396
+ import { createInterface } from "node:readline/promises";
16397
+ var ProtocolUsageError = class extends Error {
16398
+ };
16399
+ async function runProtocolCli(args) {
16400
+ const parsed = parseProtocolArgs(args);
16401
+ if (parsed.action === "status") {
16402
+ printStatus(getStatus(parsed.harness, parsed.projectDir));
16403
+ return 0;
16404
+ }
16405
+ const preview = mutate(parsed.action, parsed.harness, parsed.projectDir, true);
16406
+ printPreview(preview);
16407
+ if (!preview.changed)
16408
+ return 0;
16409
+ if (parsed.dryRun)
16410
+ return 0;
16411
+ if (!parsed.yes && !await confirmWrite()) {
16412
+ process.stdout.write("wingman:\u5DF2\u53D6\u6D88\uFF0C\u672A\u5199\u5165\u4EFB\u4F55\u6587\u4EF6\u3002\n");
16413
+ return 0;
16414
+ }
16415
+ const applied = mutate(parsed.action, parsed.harness, parsed.projectDir, false);
16416
+ process.stdout.write(`wingman:${parsed.harness} protocol ${parsed.action} \u5B8C\u6210
16417
+ state: ${applied.state}
16418
+ backupDir: ${applied.backupDir ?? "(no backup)"}
16419
+ effectiveAt: ${applied.effectiveAt}
16420
+ totalBytes: ${applied.totalBytes}
16421
+ ${applied.message ?? ""}${applied.message === void 0 ? "" : "\n"}`);
16422
+ return 0;
16423
+ }
16424
+ function parseProtocolArgs(args) {
16425
+ const [actionText, harnessText, ...tail] = args;
16426
+ if (actionText !== "install" && actionText !== "uninstall" && actionText !== "status") {
16427
+ throw new ProtocolUsageError("protocol \u7528\u6CD5:wingman protocol install|uninstall|status <cc|codex|dsh> [--project <dir>] [--dry-run] [--yes]");
16428
+ }
16429
+ if (harnessText !== "cc" && harnessText !== "codex" && harnessText !== "dsh") {
16430
+ throw new ProtocolUsageError(`protocol \u9700\u8981 harness \u2208 cc|codex|dsh,\u5F97\u5230 ${harnessText ?? "(\u7F3A\u53C2)"}`);
16431
+ }
16432
+ let projectDir = process.cwd();
16433
+ let dryRun = false;
16434
+ let yes = false;
16435
+ const rest = [...tail];
16436
+ while (rest.length > 0) {
16437
+ const arg = rest.shift();
16438
+ if (arg === "--project") {
16439
+ const value = rest.shift();
16440
+ if (value === void 0)
16441
+ throw new ProtocolUsageError("--project \u9700\u8981\u76EE\u5F55\u8DEF\u5F84,\u5F97\u5230(\u7F3A\u53C2)");
16442
+ projectDir = value;
16443
+ } else if (arg === "--dry-run") {
16444
+ dryRun = true;
16445
+ } else if (arg === "--yes") {
16446
+ yes = true;
16447
+ } else {
16448
+ throw new ProtocolUsageError(`protocol \u4E0D\u8BA4\u8BC6\u53C2\u6570:${arg}`);
16449
+ }
16450
+ }
16451
+ return { action: actionText, harness: harnessText, projectDir, dryRun, yes };
16452
+ }
16453
+ function getStatus(harness, projectDir) {
16454
+ switch (harness) {
16455
+ case "cc":
16456
+ return protocolStatusCc({ projectDir });
16457
+ case "codex":
16458
+ return protocolStatusCodex({ projectDir });
16459
+ case "dsh":
16460
+ return protocolStatusDsh({ projectDir });
16461
+ }
16462
+ }
16463
+ function mutate(action, harness, projectDir, dryRun) {
16464
+ switch (harness) {
16465
+ case "cc":
16466
+ return action === "install" ? installProtocolCc({ projectDir, dryRun }) : uninstallProtocolCc({ projectDir, dryRun });
16467
+ case "codex":
16468
+ return action === "install" ? installProtocolCodex({ projectDir, dryRun }) : uninstallProtocolCodex({ projectDir, dryRun });
16469
+ case "dsh":
16470
+ return action === "install" ? installProtocolDsh({ projectDir, dryRun }) : uninstallProtocolDsh({ projectDir, dryRun });
16471
+ }
16472
+ }
16473
+ function printStatus(value) {
16474
+ process.stdout.write(`harness: ${value.harness}
16475
+ target: ${value.targetPath}
16476
+ state: ${value.state}
16477
+ ${value.version === void 0 ? "" : `version: ${value.version}
16478
+ `}${value.message === void 0 ? "" : `${value.message}
16479
+ `}${value.reason === void 0 ? "" : `reason: ${value.reason}
16480
+ `}`);
16481
+ }
16482
+ function printPreview(value) {
16483
+ process.stdout.write(`${unifiedDiff(value.targetPath, value.before, value.after)}
16484
+ target: ${value.targetPath}
16485
+ state: ${value.state}
16486
+ backupDir: ${value.backupDir ?? "(no backup)"}
16487
+ effectiveAt: ${value.effectiveAt}
16488
+ totalBytes: ${value.totalBytes}
16489
+ ${value.message === void 0 ? "" : `${value.message}
16490
+ `}`);
16491
+ if (value.targetPath.endsWith("AGENTS.md") && value.afterExists && value.totalBytes >= 31 * 1024) {
16492
+ process.stdout.write("\u8B66\u544A:Codex \u53EF\u80FD\u622A\u65AD\u672C\u5757\uFF1BAGENTS.md \u88C5\u540E\u5DF2\u63A5\u8FD1\u6216\u8D85\u8FC7\u9ED8\u8BA4 32 KiB project_doc_max_bytes\u3002\n");
16493
+ }
16494
+ }
16495
+ function unifiedDiff(path, before, after) {
16496
+ if (before === after)
16497
+ return `--- ${path}
16498
+ +++ ${path}
16499
+ (no changes)`;
16500
+ const beforeLines = lines(before);
16501
+ const afterLines = lines(after);
16502
+ return [
16503
+ `--- ${path}`,
16504
+ `+++ ${path}`,
16505
+ `@@ -1,${beforeLines.length} +1,${afterLines.length} @@`,
16506
+ ...beforeLines.map((line) => `-${line}`),
16507
+ ...afterLines.map((line) => `+${line}`)
16508
+ ].join("\n");
16509
+ }
16510
+ function lines(text) {
16511
+ if (text === "")
16512
+ return [];
16513
+ const normalized = text.replaceAll("\r\n", "\n");
16514
+ const values = normalized.split("\n");
16515
+ if (values.at(-1) === "")
16516
+ values.pop();
16517
+ return values;
16518
+ }
16519
+ async function confirmWrite() {
16520
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
16521
+ try {
16522
+ const answer = await prompt.question("\u786E\u8BA4\u5199\u5165? [y/N] ");
16523
+ return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
16524
+ } finally {
16525
+ prompt.close();
16526
+ }
16527
+ }
16528
+
14501
16529
  // apps/cli/dist/registry.js
14502
- import { existsSync as existsSync10 } from "node:fs";
14503
- import { homedir as homedir9 } from "node:os";
16530
+ import { existsSync as existsSync13 } from "node:fs";
16531
+ import { homedir as homedir11 } from "node:os";
14504
16532
  async function importHarnessModule(harness) {
14505
16533
  const mod = await importAdapterModule(harness);
14506
16534
  if (typeof mod.createAdapter !== "function") {
@@ -14522,7 +16550,7 @@ function importAdapterModule(harness) {
14522
16550
  }
14523
16551
  }
14524
16552
  function fixtureScanOptions(harness) {
14525
- if (!existsSync10(fixturePath())) {
16553
+ if (!existsSync13(fixturePath())) {
14526
16554
  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");
14527
16555
  }
14528
16556
  switch (harness) {
@@ -14540,16 +16568,16 @@ async function loadHarness(harness, mode) {
14540
16568
  return {
14541
16569
  harness,
14542
16570
  adapter,
14543
- scanOptions: mode.fixtures ? fixtureScanOptions(harness) : { homeDir: homedir9() },
16571
+ scanOptions: mode.fixtures ? fixtureScanOptions(harness) : { homeDir: homedir11() },
14544
16572
  feedbackSignals
14545
16573
  };
14546
16574
  }
14547
16575
 
14548
16576
  // apps/cli/dist/runtime-state.js
14549
16577
  import { randomBytes as randomBytes2 } from "node:crypto";
14550
- import { closeSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync11, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
14551
- import { homedir as homedir10 } from "node:os";
14552
- import { dirname as dirname11, join as join17 } from "node:path";
16578
+ import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
16579
+ import { homedir as homedir12 } from "node:os";
16580
+ import { dirname as dirname15, join as join21 } from "node:path";
14553
16581
  function createHookToken() {
14554
16582
  return randomBytes2(32).toString("base64url");
14555
16583
  }
@@ -14560,15 +16588,15 @@ function writeServeRuntimeState(port, token, options = {}) {
14560
16588
  const pid = options.pid ?? process.pid;
14561
16589
  const suffix = randomBytes2(8).toString("base64url");
14562
16590
  const owner = `serve-${startedAtMs}-${pid}-${suffix}`;
14563
- const path = join17(options.homeDir ?? homedir10(), ".wingman", "state", "serve", `${owner}.json`);
14564
- const tempPath = join17(dirname11(path), `.${owner}.tmp`);
16591
+ const path = join21(options.homeDir ?? homedir12(), ".wingman", "state", "serve", `${owner}.json`);
16592
+ const tempPath = join21(dirname15(path), `.${owner}.tmp`);
14565
16593
  const contents = Buffer.from(`${JSON.stringify({ version: 2, owner, pid, port, startedAtMs, token })}
14566
16594
  `, "utf8");
14567
16595
  let fd;
14568
16596
  try {
14569
- mkdirSync6(dirname11(path), { recursive: true, mode: 448 });
16597
+ mkdirSync9(dirname15(path), { recursive: true, mode: 448 });
14570
16598
  fd = openSync(tempPath, "wx", 384);
14571
- writeFileSync7(fd, contents);
16599
+ writeFileSync10(fd, contents);
14572
16600
  closeSync(fd);
14573
16601
  fd = void 0;
14574
16602
  renameSync3(tempPath, path);
@@ -14581,7 +16609,7 @@ function writeServeRuntimeState(port, token, options = {}) {
14581
16609
  }
14582
16610
  }
14583
16611
  try {
14584
- rmSync4(tempPath, { force: true });
16612
+ rmSync7(tempPath, { force: true });
14585
16613
  } catch {
14586
16614
  }
14587
16615
  warn(`Codex hook \u8FD0\u884C\u6001\u5199\u5165\u5931\u8D25,\u9762\u677F\u7EE7\u7EED\u8FD0\u884C:${errorMessage(error)}`);
@@ -14592,9 +16620,9 @@ function removeServeRuntimeState(registration) {
14592
16620
  if (registration === void 0)
14593
16621
  return;
14594
16622
  try {
14595
- const current = readFileSync11(registration.path);
16623
+ const current = readFileSync14(registration.path);
14596
16624
  if (current.equals(registration.contents))
14597
- rmSync4(registration.path, { force: true });
16625
+ rmSync7(registration.path, { force: true });
14598
16626
  } catch {
14599
16627
  }
14600
16628
  }
@@ -14617,9 +16645,9 @@ async function scanValidated(adapter, options) {
14617
16645
  // apps/cli/dist/server.js
14618
16646
  init_dist();
14619
16647
  import { createHmac, timingSafeEqual } from "node:crypto";
14620
- import { readFile as readFile3 } from "node:fs/promises";
16648
+ import { readFile as readFile4 } from "node:fs/promises";
14621
16649
  import { createServer } from "node:http";
14622
- import { extname, resolve as resolve5, sep as sep2 } from "node:path";
16650
+ import { extname, resolve as resolve10, sep as sep2 } from "node:path";
14623
16651
 
14624
16652
  // apps/cli/dist/suggest.js
14625
16653
  init_dist();
@@ -14887,6 +16915,14 @@ async function handle(req, res, options, warn, runtime) {
14887
16915
  sendJson2(res, 200, publicLicenseStatus(licenseStatus(options.licenseOptions)));
14888
16916
  return;
14889
16917
  }
16918
+ if (url.pathname === "/api/projects") {
16919
+ if (!options.narrative) {
16920
+ sendText2(res, 404, "not found");
16921
+ return;
16922
+ }
16923
+ sendJson2(res, 200, options.narrative.getProjects());
16924
+ return;
16925
+ }
14890
16926
  if (url.pathname === "/api/narrative") {
14891
16927
  if (!options.narrative) {
14892
16928
  sendText2(res, 404, "not found");
@@ -15073,11 +17109,11 @@ async function handleActionApply(req, res, options, runtime) {
15073
17109
  return;
15074
17110
  }
15075
17111
  try {
15076
- const result = await loaded.adapter.applyAction(plan, loaded.scanOptions);
15077
- if (result.ok) {
17112
+ const result4 = await loaded.adapter.applyAction(plan, loaded.scanOptions);
17113
+ if (result4.ok) {
15078
17114
  runtime.anatomy = null;
15079
17115
  }
15080
- sendJson2(res, 200, result);
17116
+ sendJson2(res, 200, result4);
15081
17117
  } catch (error) {
15082
17118
  sendJson2(res, 400, { error: errorMessage2(error) });
15083
17119
  }
@@ -15089,12 +17125,12 @@ async function handleLicenseInstall(req, res, options) {
15089
17125
  sendJson2(res, 400, { error: "license \u6587\u4EF6\u8D85\u8FC7 64KB \u4E0A\u9650\u6216\u8BFB\u53D6\u5931\u8D25" });
15090
17126
  return;
15091
17127
  }
15092
- const result = installLicense(raw, options.licenseOptions);
15093
- if (!result.ok) {
15094
- sendJson2(res, 400, { error: result.error });
17128
+ const result4 = installLicense(raw, options.licenseOptions);
17129
+ if (!result4.ok) {
17130
+ sendJson2(res, 400, { error: result4.error });
15095
17131
  return;
15096
17132
  }
15097
- sendJson2(res, 200, publicLicenseStatus(result.status));
17133
+ sendJson2(res, 200, publicLicenseStatus(result4.status));
15098
17134
  }
15099
17135
  async function handleLicenseRefresh(res, options) {
15100
17136
  const homeDir = options.licenseRefreshOptions?.homeDir ?? options.licenseOptions?.homeDir;
@@ -15106,11 +17142,11 @@ async function handleLicenseRefresh(res, options) {
15106
17142
  ...publicKeyPem === void 0 ? {} : { publicKeyPem },
15107
17143
  ...now === void 0 ? {} : { now }
15108
17144
  };
15109
- const result = await refreshLicense(refreshOptions);
15110
- if (!result.ok) {
17145
+ const result4 = await refreshLicense(refreshOptions);
17146
+ if (!result4.ok) {
15111
17147
  sendJson2(res, 400, {
15112
- error: result.message,
15113
- canSubscribe: result.reason === "inactive"
17148
+ error: result4.message,
17149
+ canSubscribe: result4.reason === "inactive"
15114
17150
  });
15115
17151
  return;
15116
17152
  }
@@ -15122,14 +17158,14 @@ async function handleLicenseRefresh(res, options) {
15122
17158
  };
15123
17159
  sendJson2(res, 200, {
15124
17160
  ...publicLicenseStatus(licenseStatus(statusOptions)),
15125
- message: result.message
17161
+ message: result4.message
15126
17162
  });
15127
17163
  }
15128
- function publicLicenseStatus(status) {
15129
- const body = { state: status.state, detail: status.detail };
15130
- if (status.claims) {
15131
- body.plan = status.claims.plan;
15132
- body.exp = status.claims.exp;
17164
+ function publicLicenseStatus(status3) {
17165
+ const body = { state: status3.state, detail: status3.detail };
17166
+ if (status3.claims) {
17167
+ body.plan = status3.claims.plan;
17168
+ body.exp = status3.claims.exp;
15133
17169
  }
15134
17170
  return body;
15135
17171
  }
@@ -15185,8 +17221,8 @@ function readJsonBody(req) {
15185
17221
  req.on("error", () => resolveBody(void 0));
15186
17222
  });
15187
17223
  }
15188
- function sendJson2(res, status, body) {
15189
- res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
17224
+ function sendJson2(res, status3, body) {
17225
+ res.writeHead(status3, { "content-type": "application/json; charset=utf-8" });
15190
17226
  res.end(JSON.stringify(body));
15191
17227
  }
15192
17228
  async function serveStatic(pathname, res, panelDistDir) {
@@ -15198,14 +17234,14 @@ async function serveStatic(pathname, res, panelDistDir) {
15198
17234
  return;
15199
17235
  }
15200
17236
  const rel = decoded === "/" ? "/index.html" : decoded;
15201
- const distDir = resolve5(panelDistDir);
15202
- const filePath = resolve5(distDir, `.${rel}`);
17237
+ const distDir = resolve10(panelDistDir);
17238
+ const filePath = resolve10(distDir, `.${rel}`);
15203
17239
  if (filePath !== distDir && !filePath.startsWith(distDir + sep2)) {
15204
17240
  sendText2(res, 403, "forbidden");
15205
17241
  return;
15206
17242
  }
15207
17243
  try {
15208
- const content = await readFile3(filePath);
17244
+ const content = await readFile4(filePath);
15209
17245
  res.writeHead(200, {
15210
17246
  "content-type": CONTENT_TYPES[extname(filePath)] ?? "application/octet-stream"
15211
17247
  });
@@ -15218,8 +17254,8 @@ async function serveStatic(pathname, res, panelDistDir) {
15218
17254
  }
15219
17255
  }
15220
17256
  }
15221
- function sendText2(res, status, body) {
15222
- res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
17257
+ function sendText2(res, status3, body) {
17258
+ res.writeHead(status3, { "content-type": "text/plain; charset=utf-8" });
15223
17259
  res.end(body);
15224
17260
  }
15225
17261
  function errorMessage2(error) {
@@ -15235,6 +17271,8 @@ var HELP = `wingman \u2014 Not your copilot. Your wingman.
15235
17271
  [--panel-dist <dir>] \u9762\u677F\u9759\u6001\u4EA7\u7269\u76EE\u5F55(\u9ED8\u8BA4\u4ED3\u5E93\u5E03\u5C40 apps/panel/dist)
15236
17272
  wingman hook install <harness> [--port 7333] \u5B9E\u51B5\u63A5\u7EBF(\u5199\u524D\u5907\u4EFD,\u5E42\u7B49)
15237
17273
  wingman hook uninstall <harness> \u89E3\u7EBF(\u914D\u7F6E\u6062\u590D,\u5E42\u7B49)
17274
+ wingman protocol install|uninstall|status <harness> \u6309\u9879\u76EE\u7BA1\u7406 Plan Protocol
17275
+ [--project <dir>] [--dry-run] [--yes]
15238
17276
  wingman license status \u67E5\u770B\u8BA2\u9605\u72B6\u6001(\u5199\u52A8\u4F5C\u7684\u95F8)
15239
17277
  wingman license refresh \u8054\u7F51\u5411\u7B7E\u53D1\u7AEF\u6362\u65B0 license(\u7EED\u671F)
15240
17278
  wingman help \u663E\u793A\u672C\u5E2E\u52A9
@@ -15256,21 +17294,23 @@ async function main(argv) {
15256
17294
  return runServe(rest);
15257
17295
  case "hook":
15258
17296
  return runHook(rest);
17297
+ case "protocol":
17298
+ return runProtocolCli(rest);
15259
17299
  case "license": {
15260
17300
  if (rest[0] === "status") {
15261
- const status = licenseStatus();
15262
- process.stdout.write(`${status.state}:${status.detail}
17301
+ const status3 = licenseStatus();
17302
+ process.stdout.write(`${status3.state}:${status3.detail}
15263
17303
  `);
15264
17304
  return 0;
15265
17305
  }
15266
17306
  if (rest[0] === "refresh") {
15267
- const result = await refreshLicense();
15268
- process.stdout.write(`${result.message}
17307
+ const result4 = await refreshLicense();
17308
+ process.stdout.write(`${result4.message}
15269
17309
  `);
15270
- if (!result.ok)
17310
+ if (!result4.ok)
15271
17311
  return 1;
15272
- const status = licenseStatus();
15273
- process.stdout.write(`${status.state}:${status.detail}
17312
+ const status3 = licenseStatus();
17313
+ process.stdout.write(`${status3.state}:${status3.detail}
15274
17314
  `);
15275
17315
  return 0;
15276
17316
  }
@@ -15317,9 +17357,11 @@ async function runServe(args) {
15317
17357
  `);
15318
17358
  }
15319
17359
  }
15320
- const panelDistDir = panelDist === void 0 ? resolve6(dirname12(fileURLToPath2(import.meta.url)), "../../panel/dist") : resolve6(panelDist);
17360
+ const panelDistDir = panelDist === void 0 ? resolve11(dirname16(fileURLToPath2(import.meta.url)), "../../panel/dist") : resolve11(panelDist);
15321
17361
  const narrative = createNarrativeService({
15322
17362
  followTranscripts: !fixtures,
17363
+ homeDir: fixtures ? fixturesRoot : homedir13(),
17364
+ ...fixtures ? { projectBoundaryDir: fixturesRoot } : {},
15323
17365
  feedbackSignals: (harness) => {
15324
17366
  const loaded = harnesses.find((candidate) => candidate.harness === harness);
15325
17367
  return loaded?.feedbackSignals(loaded.scanOptions).feedback;
@@ -15327,7 +17369,7 @@ async function runServe(args) {
15327
17369
  });
15328
17370
  if (fixtures) {
15329
17371
  try {
15330
- const raw = readFileSync12(fixturePath("cc", "sessions", "session-todos.jsonl"), "utf8");
17372
+ const raw = readFileSync15(fixturePath("cc", "sessions", "session-todos.jsonl"), "utf8");
15331
17373
  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");
15332
17374
  } catch (error) {
15333
17375
  const message = error instanceof Error ? error.message : String(error);
@@ -15460,9 +17502,10 @@ main(process.argv.slice(2)).then((code) => {
15460
17502
  const message = error instanceof Error ? error.message : String(error);
15461
17503
  process.stderr.write(`wingman:${message}
15462
17504
  `);
15463
- if (error instanceof UsageError)
17505
+ if (error instanceof UsageError || error instanceof ProtocolUsageError) {
15464
17506
  process.stderr.write(`
15465
17507
  ${HELP}`);
17508
+ }
15466
17509
  process.exitCode = 1;
15467
17510
  });
15468
17511
  /*! Bundled license information: