harness-wingman 0.5.0 → 0.6.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.
@@ -3239,7 +3239,7 @@ var init_actions2 = __esm({
3239
3239
 
3240
3240
  // packages/adapter-codex/dist/hooks.js
3241
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";
3242
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, realpathSync, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
3243
3243
  import { basename as basename5, dirname as dirname8, join as join12 } from "node:path";
3244
3244
  async function installHooks(port, options) {
3245
3245
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
@@ -3395,25 +3395,51 @@ function restoreTarget(remainder, state) {
3395
3395
  }
3396
3396
  function resolveNodeExecutable(probe = {}) {
3397
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;
3398
+ const exists = probe.exists ?? existsSync8;
3399
+ let executable = isNodeExecutable(execPath) ? execPath : void 0;
3400
+ if (executable === void 0) {
3401
+ const loginShellWhich = probe.loginShellWhich ?? defaultLoginShellWhich;
3402
+ try {
3403
+ const shellNode = loginShellWhich(probe.shell ?? process.env.SHELL ?? "/bin/sh")?.trim();
3404
+ if (shellNode !== void 0 && shellNode !== "node" && isNodeExecutable(shellNode)) {
3405
+ executable = shellNode;
3406
+ }
3407
+ } catch {
3405
3408
  }
3406
- } catch {
3407
3409
  }
3408
- const exists = probe.exists ?? existsSync8;
3409
- for (const candidate of ["/opt/homebrew/bin/node", "/usr/local/bin/node"]) {
3410
+ if (executable === void 0) {
3411
+ for (const candidate of ["/opt/homebrew/bin/node", "/usr/local/bin/node"]) {
3412
+ try {
3413
+ if (exists(candidate) && isNodeExecutable(candidate)) {
3414
+ executable = candidate;
3415
+ break;
3416
+ }
3417
+ } catch {
3418
+ }
3419
+ }
3420
+ }
3421
+ if (executable === void 0)
3422
+ return "node";
3423
+ const realpath2 = probe.realpath ?? defaultRealpath;
3424
+ for (const alias of ["/opt/homebrew/bin/node", "/usr/local/bin/node"]) {
3410
3425
  try {
3411
- if (exists(candidate) && isNodeExecutable(candidate))
3412
- return candidate;
3426
+ if (!exists(alias))
3427
+ continue;
3428
+ const executableRealpath = realpath2(executable);
3429
+ if (executableRealpath !== void 0 && realpath2(alias) === executableRealpath) {
3430
+ return alias;
3431
+ }
3413
3432
  } catch {
3414
3433
  }
3415
3434
  }
3416
- return "node";
3435
+ return executable;
3436
+ }
3437
+ function defaultRealpath(path) {
3438
+ try {
3439
+ return realpathSync(path);
3440
+ } catch {
3441
+ return void 0;
3442
+ }
3417
3443
  }
3418
3444
  function defaultLoginShellWhich(shell) {
3419
3445
  const result4 = spawnSync(shell, ["-lc", "command -v node"], {
@@ -12905,9 +12931,9 @@ var init_dist5 = __esm({
12905
12931
 
12906
12932
  // apps/cli/dist/main.js
12907
12933
  init_dist();
12908
- import { readFileSync as readFileSync15 } from "node:fs";
12934
+ import { readFileSync as readFileSync17 } from "node:fs";
12909
12935
  import { homedir as homedir13 } from "node:os";
12910
- import { dirname as dirname16, resolve as resolve11 } from "node:path";
12936
+ import { dirname as dirname17, resolve as resolve11 } from "node:path";
12911
12937
  import { fileURLToPath as fileURLToPath2 } from "node:url";
12912
12938
 
12913
12939
  // packages/fixtures/dist/index.js
@@ -13308,7 +13334,7 @@ init_dist4();
13308
13334
  init_dist5();
13309
13335
  init_dist();
13310
13336
  import { readdir } from "node:fs/promises";
13311
- import { isAbsolute as isAbsolute4, join as join20, resolve as resolve9 } from "node:path";
13337
+ import { isAbsolute as isAbsolute4, join as join21, resolve as resolve9 } from "node:path";
13312
13338
 
13313
13339
  // packages/narrative/dist/attribute.js
13314
13340
  function extractSteps(lines2) {
@@ -13882,7 +13908,7 @@ function parseProjectPlan(input, facts = {}) {
13882
13908
  }
13883
13909
  let title;
13884
13910
  let goal;
13885
- let ignoredLineCount = 0;
13911
+ const ignoredLines = [];
13886
13912
  let fence;
13887
13913
  let stepCount = 0;
13888
13914
  let lastStep;
@@ -13892,7 +13918,7 @@ function parseProjectPlan(input, facts = {}) {
13892
13918
  const lineNumber = index + 1;
13893
13919
  const fenceMatch = FENCE_LINE.exec(line);
13894
13920
  if (fence !== void 0) {
13895
- ignoredLineCount += 1;
13921
+ ignoredLines.push(ignoredLine(lineNumber, line));
13896
13922
  if (fenceMatch?.[1] === fence)
13897
13923
  fence = void 0;
13898
13924
  lastStep = void 0;
@@ -13900,7 +13926,7 @@ function parseProjectPlan(input, facts = {}) {
13900
13926
  }
13901
13927
  if (fenceMatch !== null) {
13902
13928
  fence = fenceMatch[1];
13903
- ignoredLineCount += 1;
13929
+ ignoredLines.push(ignoredLine(lineNumber, line));
13904
13930
  lastStep = void 0;
13905
13931
  continue;
13906
13932
  }
@@ -13910,8 +13936,9 @@ function parseProjectPlan(input, facts = {}) {
13910
13936
  if (headingMatch !== null) {
13911
13937
  if (title === void 0)
13912
13938
  title = headingMatch[1];
13913
- else
13914
- ignoredLineCount += 1;
13939
+ else {
13940
+ ignoredLines.push(ignoredLine(lineNumber, line));
13941
+ }
13915
13942
  lastStep = void 0;
13916
13943
  continue;
13917
13944
  }
@@ -13951,7 +13978,7 @@ function parseProjectPlan(input, facts = {}) {
13951
13978
  lastStep = { indent, line: lineNumber, node };
13952
13979
  continue;
13953
13980
  }
13954
- ignoredLineCount += 1;
13981
+ ignoredLines.push(ignoredLine(lineNumber, line));
13955
13982
  lastStep = void 0;
13956
13983
  }
13957
13984
  assignNodeKeys(nodes);
@@ -13960,7 +13987,8 @@ function parseProjectPlan(input, facts = {}) {
13960
13987
  version: 1,
13961
13988
  ...title === void 0 ? {} : { title },
13962
13989
  ...goal === void 0 ? {} : { goal },
13963
- ignoredLineCount,
13990
+ ignoredLineCount: ignoredLines.length,
13991
+ ignoredLines,
13964
13992
  nodes
13965
13993
  };
13966
13994
  }
@@ -13996,7 +14024,8 @@ function deriveProjectPlanSummary(nodes) {
13996
14024
  };
13997
14025
  const allLeavesDone = leaves.length > 0 && leaves.every((node) => node.status === "completed" || node.status === "cancelled");
13998
14026
  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`,
14027
+ currentPointLabel: currentPoints.length === 0 ? "\u5F53\u524D\u70B9" : currentPoints.length === 1 ? "\u5F53\u524D\u70B9" : `\u5E76\u884C ${currentPoints.length} \u9879`,
14028
+ ...currentPoints.length === 0 ? { currentPointEmptyText: "\u8BA1\u5212\u672A\u6807\u8BB0\u8FDB\u884C\u4E2D\u6B65\u9AA4" } : {},
14000
14029
  currentPoints,
14001
14030
  checkpoints,
14002
14031
  counts,
@@ -14040,6 +14069,9 @@ function validateFileFacts(facts) {
14040
14069
  function unavailable(reason, line) {
14041
14070
  return { availability: "unavailable", reason, ...line === void 0 ? {} : { line } };
14042
14071
  }
14072
+ function ignoredLine(line, text) {
14073
+ return { line, preview: Array.from(text).slice(0, 40).join("") };
14074
+ }
14043
14075
  function indentColumns(indent) {
14044
14076
  let columns = 0;
14045
14077
  for (const character of indent)
@@ -14406,6 +14438,12 @@ function costUsd(usage, model) {
14406
14438
  }
14407
14439
 
14408
14440
  // packages/narrative/dist/workflow.js
14441
+ var PLAN_MANAGEMENT_TOOL_NAMES = /* @__PURE__ */ new Set([
14442
+ "TodoWrite",
14443
+ "TaskCreate",
14444
+ "TaskUpdate",
14445
+ "update_plan"
14446
+ ]);
14409
14447
  function deriveTaskWorkflowView(input) {
14410
14448
  return {
14411
14449
  ...input.goal === void 0 ? {} : { goal: input.goal },
@@ -14611,6 +14649,51 @@ function isRecord4(value) {
14611
14649
  return typeof value === "object" && value !== null && !Array.isArray(value);
14612
14650
  }
14613
14651
 
14652
+ // packages/narrative/dist/verification.js
14653
+ var CHECK_STATS_SOURCE = "Harness tool_result(is_error / exit_code)";
14654
+ function deriveCheckStats(steps, briefs) {
14655
+ let runs = 0;
14656
+ let failures = 0;
14657
+ let latestFailureStep;
14658
+ for (const step of steps) {
14659
+ if (step.endTs === void 0 || PLAN_MANAGEMENT_TOOL_NAMES.has(step.toolName))
14660
+ continue;
14661
+ runs += 1;
14662
+ if (!step.isError)
14663
+ continue;
14664
+ failures += 1;
14665
+ if (latestFailureStep?.endTs === void 0 || latestFailureStep.endTs < step.endTs) {
14666
+ latestFailureStep = step;
14667
+ }
14668
+ }
14669
+ if (latestFailureStep?.endTs === void 0)
14670
+ return { runs, failures };
14671
+ const brief = briefs.get(latestFailureStep.toolUseId);
14672
+ return {
14673
+ runs,
14674
+ failures,
14675
+ latestFailure: {
14676
+ toolName: latestFailureStep.toolName,
14677
+ ...brief === void 0 ? {} : { brief },
14678
+ at: latestFailureStep.endTs
14679
+ }
14680
+ };
14681
+ }
14682
+ function deriveTodoCheckStats(attributed) {
14683
+ const stats = /* @__PURE__ */ new Map();
14684
+ for (const { step, attribution } of attributed) {
14685
+ if (attribution.kind !== "todo" || step.endTs === void 0 || PLAN_MANAGEMENT_TOOL_NAMES.has(step.toolName)) {
14686
+ continue;
14687
+ }
14688
+ const current = stats.get(attribution.todoId) ?? { runs: 0, failures: 0 };
14689
+ current.runs += 1;
14690
+ if (step.isError)
14691
+ current.failures += 1;
14692
+ stats.set(attribution.todoId, current);
14693
+ }
14694
+ return stats;
14695
+ }
14696
+
14614
14697
  // apps/cli/dist/project-attribution.js
14615
14698
  function extractToolObservations(harness, records) {
14616
14699
  const observations = [];
@@ -14950,18 +15033,171 @@ function isWithin(parent, child) {
14950
15033
  return pathFromParent === "" || !pathFromParent.startsWith("..") && !isAbsolute3(pathFromParent);
14951
15034
  }
14952
15035
 
15036
+ // apps/cli/dist/task-overlay.js
15037
+ import { randomBytes as randomBytes2 } from "node:crypto";
15038
+ import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
15039
+ import { dirname as dirname15, join as join20 } from "node:path";
15040
+ function taskOverlayPath(homeDir) {
15041
+ return join20(homeDir, ".wingman", "state", "task-overlay.json");
15042
+ }
15043
+ function readTaskOverlay(homeDir) {
15044
+ const path = taskOverlayPath(homeDir);
15045
+ let raw;
15046
+ try {
15047
+ raw = readFileSync14(path, "utf8");
15048
+ } catch (error) {
15049
+ if (isErrno(error, "ENOENT"))
15050
+ return { version: 1, entries: {} };
15051
+ return { unavailable: `overlay \u6587\u4EF6\u65E0\u6CD5\u8BFB\u53D6;\u8BF7\u4EBA\u5DE5\u68C0\u67E5 ${path}:${errorMessage(error)}` };
15052
+ }
15053
+ try {
15054
+ const parsed = JSON.parse(raw);
15055
+ if (!isTaskOverlay(parsed))
15056
+ throw new Error("\u4E0D\u662F\u5408\u6CD5 v1 \u7ED3\u6784");
15057
+ return parsed;
15058
+ } catch (error) {
15059
+ return { unavailable: `overlay \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790;\u8BF7\u4EBA\u5DE5\u68C0\u67E5 ${path}:${errorMessage(error)}` };
15060
+ }
15061
+ }
15062
+ function patchTaskOverlay(homeDir, key, patch, now) {
15063
+ const current = readTaskOverlay(homeDir);
15064
+ if ("unavailable" in current) {
15065
+ return {
15066
+ unavailable: `overlay \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790,\u62D2\u7EDD\u8986\u76D6;\u8BF7\u4EBA\u5DE5\u68C0\u67E5 ${taskOverlayPath(homeDir)}`
15067
+ };
15068
+ }
15069
+ const entry = {
15070
+ ...current.entries[key],
15071
+ updatedAt: now.toISOString()
15072
+ };
15073
+ if ("title" in patch) {
15074
+ const title = patch.title?.trim() ?? "";
15075
+ if (title === "")
15076
+ delete entry.title;
15077
+ else
15078
+ entry.title = title;
15079
+ }
15080
+ if (patch.pinned !== void 0)
15081
+ entry.pinned = patch.pinned;
15082
+ if (patch.hidden !== void 0)
15083
+ entry.hidden = patch.hidden;
15084
+ if (patch.accepted === true)
15085
+ entry.accepted = { at: now.toISOString() };
15086
+ else if (patch.accepted === false)
15087
+ delete entry.accepted;
15088
+ const empty = entry.title === void 0 && entry.pinned !== true && entry.hidden !== true && entry.accepted === void 0;
15089
+ const entries = { ...current.entries };
15090
+ if (empty)
15091
+ delete entries[key];
15092
+ else
15093
+ entries[key] = entry;
15094
+ writeTaskOverlay(homeDir, { version: 1, entries });
15095
+ return { entry: empty ? null : entry };
15096
+ }
15097
+ function writeTaskOverlay(homeDir, overlay) {
15098
+ const path = taskOverlayPath(homeDir);
15099
+ const tempPath = join20(dirname15(path), `.task-overlay-${randomBytes2(8).toString("hex")}.tmp`);
15100
+ let fd;
15101
+ try {
15102
+ mkdirSync9(dirname15(path), { recursive: true, mode: 448 });
15103
+ fd = openSync(tempPath, "wx", 384);
15104
+ writeFileSync10(fd, `${JSON.stringify(overlay)}
15105
+ `, "utf8");
15106
+ closeSync(fd);
15107
+ fd = void 0;
15108
+ renameSync3(tempPath, path);
15109
+ } catch (error) {
15110
+ if (fd !== void 0) {
15111
+ try {
15112
+ closeSync(fd);
15113
+ } catch {
15114
+ }
15115
+ }
15116
+ try {
15117
+ rmSync7(tempPath, { force: true });
15118
+ } catch {
15119
+ }
15120
+ throw error;
15121
+ }
15122
+ }
15123
+ function isTaskOverlay(value) {
15124
+ if (!isRecord6(value) || value.version !== 1 || !isRecord6(value.entries))
15125
+ return false;
15126
+ return Object.values(value.entries).every(isTaskOverlayEntry);
15127
+ }
15128
+ function isTaskOverlayEntry(value) {
15129
+ if (!isRecord6(value))
15130
+ return false;
15131
+ if (Object.keys(value).some((key) => !["title", "pinned", "hidden", "accepted", "updatedAt"].includes(key))) {
15132
+ return false;
15133
+ }
15134
+ return (value.title === void 0 || typeof value.title === "string") && (value.pinned === void 0 || typeof value.pinned === "boolean") && (value.hidden === void 0 || typeof value.hidden === "boolean") && (value.accepted === void 0 || isTaskAcceptance(value.accepted)) && typeof value.updatedAt === "string";
15135
+ }
15136
+ function isTaskAcceptance(value) {
15137
+ return isRecord6(value) && Object.keys(value).every((key) => key === "at") && typeof value.at === "string";
15138
+ }
15139
+ function isRecord6(value) {
15140
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15141
+ }
15142
+ function isErrno(error, code) {
15143
+ return isRecord6(error) && error.code === code;
15144
+ }
15145
+ function errorMessage(error) {
15146
+ return error instanceof Error ? error.message : String(error);
15147
+ }
15148
+
15149
+ // apps/cli/dist/version.js
15150
+ import { readFileSync as readFileSync15 } from "node:fs";
15151
+ function resolveWingmanVersion({ defined, moduleUrl = import.meta.url, readPackage }) {
15152
+ if (typeof defined === "string" && defined.trim() !== "")
15153
+ return defined;
15154
+ for (const packageUrl of [
15155
+ new URL("../package.json", moduleUrl),
15156
+ new URL("../../../package.json", moduleUrl)
15157
+ ]) {
15158
+ try {
15159
+ const pkg = readPackage(packageUrl);
15160
+ if (typeof pkg === "object" && pkg !== null && "name" in pkg && pkg.name === "harness-wingman" && "version" in pkg && typeof pkg.version === "string") {
15161
+ return pkg.version;
15162
+ }
15163
+ } catch {
15164
+ }
15165
+ }
15166
+ return void 0;
15167
+ }
15168
+ function wingmanVersion() {
15169
+ const defined = false ? void 0 : "0.6.0";
15170
+ return resolveWingmanVersion({
15171
+ defined,
15172
+ readPackage: (url) => JSON.parse(readFileSync15(url, "utf8"))
15173
+ });
15174
+ }
15175
+
14953
15176
  // apps/cli/dist/narrative-service.js
14954
15177
  var MAX_SESSIONS = 8;
14955
15178
  var ENDED_AFTER_STOP_MS = 10 * 6e4;
14956
- var TODO_TOOLS = /* @__PURE__ */ new Set(["TaskCreate", "TaskUpdate", "TodoWrite", "update_plan"]);
14957
15179
  var DETAIL_LIMIT = 80;
14958
15180
  var QUOTE_LIMIT = 160;
14959
15181
  var ERROR_BRIEF_LIMIT = 60;
15182
+ function acceptanceView(homeDir, overlay, key) {
15183
+ if (homeDir === void 0)
15184
+ return { state: "unavailable", reason: "\u672A\u914D\u7F6E\u672C\u673A\u72B6\u6001\u76EE\u5F55" };
15185
+ if (overlay === void 0 || "unavailable" in overlay) {
15186
+ return {
15187
+ state: "unavailable",
15188
+ reason: overlay?.unavailable ?? "\u672C\u673A\u9A8C\u6536\u72B6\u6001\u4E0D\u53EF\u8BFB"
15189
+ };
15190
+ }
15191
+ const accepted = overlay.entries[key]?.accepted;
15192
+ return accepted === void 0 ? { state: "none" } : { state: "accepted", at: accepted.at };
15193
+ }
14960
15194
  function createNarrativeService(options = {}) {
14961
15195
  const follow = options.followTranscripts ?? true;
14962
15196
  const intervalMs = options.intervalMs ?? 500;
14963
15197
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
15198
+ const startedAt = now().toISOString();
14964
15199
  const harnesses = /* @__PURE__ */ new Map();
15200
+ const notices = /* @__PURE__ */ new Map();
14965
15201
  const listeners = /* @__PURE__ */ new Set();
14966
15202
  const pendingNotifications = /* @__PURE__ */ new Set();
14967
15203
  const projectWatchers = /* @__PURE__ */ new Map();
@@ -15045,7 +15281,7 @@ function createNarrativeService(options = {}) {
15045
15281
  function watchResolution(resolution) {
15046
15282
  if (resolution.lookedUp.length === 0)
15047
15283
  return;
15048
- const planPath = resolution.planPath ?? join20(resolution.root, ".wingman", "plan.md");
15284
+ const planPath = resolution.planPath ?? join21(resolution.root, ".wingman", "plan.md");
15049
15285
  const planPathIdentity = resolution.planPathIdentity;
15050
15286
  if (projectWatchers.has(planPathIdentity))
15051
15287
  return;
@@ -15105,7 +15341,7 @@ function createNarrativeService(options = {}) {
15105
15341
  for (const session of sessions.values()) {
15106
15342
  const resolution = session.projectResolution;
15107
15343
  if (resolution?.planPathIdentity === planPathIdentity) {
15108
- const planPath = resolution.planPath ?? join20(resolution.root, ".wingman", "plan.md");
15344
+ const planPath = resolution.planPath ?? join21(resolution.root, ".wingman", "plan.md");
15109
15345
  void applyPendingPlanWrites(session, planPath, planPathIdentity).then(() => {
15110
15346
  session.dirty = true;
15111
15347
  notify(harness);
@@ -15121,8 +15357,9 @@ function createNarrativeService(options = {}) {
15121
15357
  function recordToolObservations(harness, session, tools) {
15122
15358
  for (const tool of tools) {
15123
15359
  if (tool.planWrites.length === 0) {
15124
- if (!tool.planFileAccess && !TODO_TOOLS.has(tool.method))
15360
+ if (!tool.planFileAccess && !PLAN_MANAGEMENT_TOOL_NAMES.has(tool.method)) {
15125
15361
  session.nonPlanToolSteps += 1;
15362
+ }
15126
15363
  continue;
15127
15364
  }
15128
15365
  recordPlanWrites(harness, session, tool.planWrites, session.nonPlanToolSteps);
@@ -15145,7 +15382,7 @@ function createNarrativeService(options = {}) {
15145
15382
  }
15146
15383
  const resolution = session.projectResolution;
15147
15384
  if (resolution !== void 0) {
15148
- const planPath = resolution.planPath ?? join20(resolution.root, ".wingman", "plan.md");
15385
+ const planPath = resolution.planPath ?? join21(resolution.root, ".wingman", "plan.md");
15149
15386
  void applyPendingPlanWrites(session, planPath, resolution.planPathIdentity).then(() => {
15150
15387
  session.dirty = true;
15151
15388
  notify(harness);
@@ -15185,7 +15422,7 @@ function createNarrativeService(options = {}) {
15185
15422
  }
15186
15423
  session.pendingPlanWrites = remaining;
15187
15424
  }
15188
- function evictOne(sessions, nowIso) {
15425
+ function evictOne(harness, sessions, nowIso) {
15189
15426
  let victimId;
15190
15427
  let victimAt = "\uFFFF";
15191
15428
  let victimEnded = false;
@@ -15201,8 +15438,20 @@ function createNarrativeService(options = {}) {
15201
15438
  }
15202
15439
  if (victimId !== void 0) {
15203
15440
  const victim = sessions.get(victimId);
15204
- if (victim)
15441
+ if (victim) {
15205
15442
  closeSession(victim);
15443
+ const current = notices.get(harness) ?? [];
15444
+ current.push({
15445
+ kind: "evicted",
15446
+ sessionId: victim.id,
15447
+ label: victim.label ?? victim.id.slice(0, 8),
15448
+ at: nowIso,
15449
+ reason: "\u5DF2\u8FBE 8 \u4E2A\u4F1A\u8BDD\u4E0A\u9650"
15450
+ });
15451
+ if (current.length > 5)
15452
+ current.splice(0, current.length - 5);
15453
+ notices.set(harness, current);
15454
+ }
15206
15455
  sessions.delete(victimId);
15207
15456
  }
15208
15457
  }
@@ -15211,7 +15460,7 @@ function createNarrativeService(options = {}) {
15211
15460
  let session = sessions.get(id);
15212
15461
  if (session === void 0) {
15213
15462
  if (sessions.size >= MAX_SESSIONS)
15214
- evictOne(sessions, now().toISOString());
15463
+ evictOne(harness, sessions, now().toISOString());
15215
15464
  session = newSession(id);
15216
15465
  sessions.set(id, session);
15217
15466
  }
@@ -15247,7 +15496,7 @@ function createNarrativeService(options = {}) {
15247
15496
  const sessionDir = transcriptPath.replace(/\.jsonl$/, "");
15248
15497
  if (sessionDir === transcriptPath)
15249
15498
  return;
15250
- const subagentsDir = join20(sessionDir, "subagents");
15499
+ const subagentsDir = join21(sessionDir, "subagents");
15251
15500
  const generation = session.generation;
15252
15501
  const poll = async () => {
15253
15502
  try {
@@ -15257,7 +15506,7 @@ function createNarrativeService(options = {}) {
15257
15506
  }
15258
15507
  for (const entry of entries) {
15259
15508
  if (/^agent-.*\.jsonl$/.test(entry)) {
15260
- startFollowing(harness, session, join20(subagentsDir, entry), generation);
15509
+ startFollowing(harness, session, join21(subagentsDir, entry), generation);
15261
15510
  }
15262
15511
  }
15263
15512
  } catch {
@@ -15433,6 +15682,7 @@ function createNarrativeService(options = {}) {
15433
15682
  function getOverview(harness) {
15434
15683
  const nowIso = now().toISOString();
15435
15684
  const sessions = sessionsOf(harness);
15685
+ const overlay = options.homeDir === void 0 ? void 0 : readTaskOverlay(options.homeDir);
15436
15686
  let feedback;
15437
15687
  try {
15438
15688
  feedback = options.feedbackSignals?.(harness);
@@ -15451,15 +15701,28 @@ function createNarrativeService(options = {}) {
15451
15701
  const states = {};
15452
15702
  for (const [id, session] of sessions) {
15453
15703
  const state = deriveCached(harness, session);
15704
+ const verification = state.taskWorkflow?.verification;
15454
15705
  states[id] = state.taskWorkflow === void 0 ? state : {
15455
15706
  ...state,
15456
15707
  taskWorkflow: {
15457
15708
  ...state.taskWorkflow,
15458
- feedback: feedbackView(harness, feedback, session.feedbackState)
15709
+ feedback: feedbackView(harness, feedback, session.feedbackState),
15710
+ ...verification === void 0 ? {} : {
15711
+ verification: {
15712
+ ...verification,
15713
+ acceptance: acceptanceView(options.homeDir, overlay, `${harness}:${id}`)
15714
+ }
15715
+ }
15459
15716
  }
15460
15717
  };
15461
15718
  }
15462
- return { harness, sessions: infos, states };
15719
+ return {
15720
+ harness,
15721
+ startedAt,
15722
+ notices: [...notices.get(harness) ?? []],
15723
+ sessions: infos,
15724
+ states
15725
+ };
15463
15726
  }
15464
15727
  function getState(harness) {
15465
15728
  const overview = getOverview(harness);
@@ -15533,6 +15796,8 @@ function createNarrativeService(options = {}) {
15533
15796
  };
15534
15797
  }
15535
15798
  function getProjects() {
15799
+ const overlay = options.homeDir === void 0 ? void 0 : readTaskOverlay(options.homeDir);
15800
+ const overlayEntries = overlay === void 0 || "unavailable" in overlay ? {} : overlay.entries;
15536
15801
  const grouped = /* @__PURE__ */ new Map();
15537
15802
  for (const [harness, sessions] of harnesses) {
15538
15803
  for (const session of sessions.values()) {
@@ -15553,7 +15818,7 @@ function createNarrativeService(options = {}) {
15553
15818
  for (const members of grouped.values()) {
15554
15819
  const resolution = members.find((member) => member.session.projectResolution !== void 0)?.session.projectResolution;
15555
15820
  const root = resolution?.root ?? resolve9(members[0]?.session.cwd ?? "");
15556
- const planPath = resolution?.planPath ?? join20(root, ".wingman", "plan.md");
15821
+ const planPath = resolution?.planPath ?? join21(root, ".wingman", "plan.md");
15557
15822
  const runtime = projectFiles.get(resolution?.planPathIdentity ?? planPath);
15558
15823
  const parsed = runtime?.missing === true ? void 0 : runtime?.parsed;
15559
15824
  const planMtime = parsed?.availability === "available" ? runtime?.current?.info.mtime : void 0;
@@ -15566,11 +15831,31 @@ function createNarrativeService(options = {}) {
15566
15831
  const overview = overviews.get(harness);
15567
15832
  const state = overview?.states[session.id];
15568
15833
  const info = overview?.sessions.find((candidate) => candidate.id === session.id);
15834
+ const label = session.label ?? session.id.slice(0, 8);
15835
+ const userEntry = overlayEntries[`${harness}:${session.id}`];
15836
+ const verification = state?.taskWorkflow?.verification;
15837
+ const attachedTitle = session.planAttachment?.attachedTo.split(" > ").at(-1);
15838
+ const instructionTitle = state?.taskWorkflow?.latestInstruction?.text;
15569
15839
  return {
15570
15840
  harness,
15571
15841
  id: session.id,
15572
- label: session.label ?? session.id.slice(0, 8),
15842
+ label,
15573
15843
  ended: info?.ended ?? false,
15844
+ title: attachedTitle !== void 0 ? { text: attachedTitle, source: "plan.md \u6B65\u9AA4" } : instructionTitle !== void 0 ? { text: truncate(instructionTitle, DETAIL_LIMIT), source: "\u6700\u65B0\u6307\u4EE4" } : { text: label, source: "\u5DE5\u4F5C\u76EE\u5F55" },
15845
+ user: {
15846
+ ...userEntry?.title === void 0 ? {} : { title: userEntry.title },
15847
+ pinned: userEntry?.pinned ?? false,
15848
+ hidden: userEntry?.hidden ?? false
15849
+ },
15850
+ ...session.cwd === void 0 ? {} : { cwd: session.cwd },
15851
+ ...info?.lastActivity === void 0 ? {} : { lastActivity: info.lastActivity },
15852
+ waiting: info?.waiting ?? false,
15853
+ checks: verification?.checks ?? {
15854
+ runs: 0,
15855
+ failures: 0,
15856
+ source: CHECK_STATS_SOURCE
15857
+ },
15858
+ acceptance: acceptanceView(options.homeDir, overlay, `${harness}:${session.id}`),
15574
15859
  compliance: complianceFor(session, protocol[harness]),
15575
15860
  ...session.planAttachment === void 0 ? {} : {
15576
15861
  attachedTo: session.planAttachment.attachedTo,
@@ -15614,6 +15899,7 @@ function createNarrativeService(options = {}) {
15614
15899
  plan = {
15615
15900
  availability: "available",
15616
15901
  ignoredLineCount: parsed.ignoredLineCount,
15902
+ ignoredLines: parsed.ignoredLines,
15617
15903
  nodes: parsed.nodes,
15618
15904
  higherPlanPaths: resolution?.higherPlanPaths ?? []
15619
15905
  };
@@ -15774,9 +16060,11 @@ function waitingSignal(payload) {
15774
16060
  }
15775
16061
  function feedbackView(harness, capability, state) {
15776
16062
  if (capability === "not_wired") {
16063
+ const version = wingmanVersion();
16064
+ const command = `npx harness-wingman${version === void 0 ? "" : `@${version}`} hook install ${harness}`;
15777
16065
  return {
15778
16066
  state: "unavailable",
15779
- reason: `\u53CD\u9988\u4FE1\u53F7\u672A\u63A5\u7EBF\uFF1B\u8BF7\u8FD0\u884C npx harness-wingman hook install ${harness}`
16067
+ reason: `\u53CD\u9988\u4FE1\u53F7\u672A\u63A5\u7EBF\uFF1B\u8BF7\u8FD0\u884C ${command}${version === void 0 ? "\uFF08\u7248\u672C\u672A\u77E5,\u53EF\u80FD\u88C5\u5230\u522B\u7684\u7248\u672C\uFF09" : ""}`
15780
16068
  };
15781
16069
  }
15782
16070
  if (capability === "unavailable") {
@@ -15792,10 +16080,17 @@ function derive(harness, session, nowIso) {
15792
16080
  const lines2 = harness === "codex" ? adaptCodexNarrativeLines(codexLines) : sortedByTimestamp(session.lines);
15793
16081
  const timeline = deriveTodoTimeline(extractTodoSnapshots(lines2));
15794
16082
  const allSteps = extractSteps(lines2);
15795
- const steps = allSteps.filter((s) => !TODO_TOOLS.has(s.toolName));
16083
+ const steps = allSteps.filter((s) => !PLAN_MANAGEMENT_TOOL_NAMES.has(s.toolName));
15796
16084
  const intervals = [...deriveSubagentIntervals(lines2), ...session.hookIntervals];
15797
16085
  const attributed = attributeSteps(steps, timeline, intervals);
15798
16086
  const results = collectResults(lines2);
16087
+ const resultBriefs = /* @__PURE__ */ new Map();
16088
+ for (const [toolUseId, result4] of results) {
16089
+ if (result4.brief !== void 0)
16090
+ resultBriefs.set(toolUseId, result4.brief);
16091
+ }
16092
+ const checks = deriveCheckStats(allSteps, resultBriefs);
16093
+ const todoChecks = deriveTodoCheckStats(attributed);
15799
16094
  const buckets = /* @__PURE__ */ new Map();
15800
16095
  for (const { step, attribution } of attributed) {
15801
16096
  const key = attribution.kind === "todo" ? attribution.todoId : null;
@@ -15881,7 +16176,7 @@ function derive(harness, session, nowIso) {
15881
16176
  source: "transcript / hook tool event",
15882
16177
  ...currentAction.since === void 0 ? {} : { observedAt: currentAction.since }
15883
16178
  } : { text: currentAction.name, source: "hook event", observedAt: currentAction.at };
15884
- const taskWorkflow = deriveTaskWorkflowView({
16179
+ const workflowBase = deriveTaskWorkflowView({
15885
16180
  harness,
15886
16181
  lines: lines2,
15887
16182
  codexLines,
@@ -15898,6 +16193,26 @@ function derive(harness, session, nowIso) {
15898
16193
  },
15899
16194
  feedback: { state: "unavailable", reason: "\u53CD\u9988\u80FD\u529B\u5C1A\u672A\u6620\u5C04" }
15900
16195
  });
16196
+ const activeTodos = todos.filter((todo) => todo.status === "in_progress");
16197
+ const activeTodo = activeTodos.length === 1 ? activeTodos[0] : void 0;
16198
+ const activeChecks = activeTodo === void 0 ? void 0 : todoChecks.get(activeTodo.id);
16199
+ const plan = workflowBase.plan;
16200
+ const taskWorkflow = {
16201
+ ...workflowBase,
16202
+ verification: {
16203
+ agentMarkedComplete: plan.availability === "available" && plan.steps.length > 0 && plan.steps.every((step) => step.status === "completed"),
16204
+ checks: { ...checks, source: CHECK_STATS_SOURCE },
16205
+ ...activeTodo === void 0 ? {} : {
16206
+ currentStep: {
16207
+ text: activeTodo.content,
16208
+ runs: activeChecks?.runs ?? 0,
16209
+ failures: activeChecks?.failures ?? 0
16210
+ }
16211
+ },
16212
+ // 响应组装时从 overlay 重读并覆盖;检查与计划派生仍可安全缓存。
16213
+ acceptance: { state: "unavailable", reason: "\u9A8C\u6536\u72B6\u6001\u5728\u54CD\u5E94\u65F6\u8BFB\u53D6" }
16214
+ }
16215
+ };
15901
16216
  return {
15902
16217
  harness,
15903
16218
  sessionId: session.id,
@@ -16574,45 +16889,45 @@ async function loadHarness(harness, mode) {
16574
16889
  }
16575
16890
 
16576
16891
  // apps/cli/dist/runtime-state.js
16577
- import { randomBytes as randomBytes2 } from "node:crypto";
16578
- import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
16892
+ import { randomBytes as randomBytes3 } from "node:crypto";
16893
+ import { closeSync as closeSync2, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as readFileSync16, renameSync as renameSync4, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
16579
16894
  import { homedir as homedir12 } from "node:os";
16580
- import { dirname as dirname15, join as join21 } from "node:path";
16895
+ import { dirname as dirname16, join as join22 } from "node:path";
16581
16896
  function createHookToken() {
16582
- return randomBytes2(32).toString("base64url");
16897
+ return randomBytes3(32).toString("base64url");
16583
16898
  }
16584
16899
  function writeServeRuntimeState(port, token, options = {}) {
16585
16900
  const warn = options.warn ?? (() => {
16586
16901
  });
16587
16902
  const startedAtMs = (options.now ?? Date.now)();
16588
16903
  const pid = options.pid ?? process.pid;
16589
- const suffix = randomBytes2(8).toString("base64url");
16904
+ const suffix = randomBytes3(8).toString("base64url");
16590
16905
  const owner = `serve-${startedAtMs}-${pid}-${suffix}`;
16591
- const path = join21(options.homeDir ?? homedir12(), ".wingman", "state", "serve", `${owner}.json`);
16592
- const tempPath = join21(dirname15(path), `.${owner}.tmp`);
16906
+ const path = join22(options.homeDir ?? homedir12(), ".wingman", "state", "serve", `${owner}.json`);
16907
+ const tempPath = join22(dirname16(path), `.${owner}.tmp`);
16593
16908
  const contents = Buffer.from(`${JSON.stringify({ version: 2, owner, pid, port, startedAtMs, token })}
16594
16909
  `, "utf8");
16595
16910
  let fd;
16596
16911
  try {
16597
- mkdirSync9(dirname15(path), { recursive: true, mode: 448 });
16598
- fd = openSync(tempPath, "wx", 384);
16599
- writeFileSync10(fd, contents);
16600
- closeSync(fd);
16912
+ mkdirSync10(dirname16(path), { recursive: true, mode: 448 });
16913
+ fd = openSync2(tempPath, "wx", 384);
16914
+ writeFileSync11(fd, contents);
16915
+ closeSync2(fd);
16601
16916
  fd = void 0;
16602
- renameSync3(tempPath, path);
16917
+ renameSync4(tempPath, path);
16603
16918
  return { path, contents };
16604
16919
  } catch (error) {
16605
16920
  if (fd !== void 0) {
16606
16921
  try {
16607
- closeSync(fd);
16922
+ closeSync2(fd);
16608
16923
  } catch {
16609
16924
  }
16610
16925
  }
16611
16926
  try {
16612
- rmSync7(tempPath, { force: true });
16927
+ rmSync8(tempPath, { force: true });
16613
16928
  } catch {
16614
16929
  }
16615
- warn(`Codex hook \u8FD0\u884C\u6001\u5199\u5165\u5931\u8D25,\u9762\u677F\u7EE7\u7EED\u8FD0\u884C:${errorMessage(error)}`);
16930
+ warn(`Codex hook \u8FD0\u884C\u6001\u5199\u5165\u5931\u8D25,\u9762\u677F\u7EE7\u7EED\u8FD0\u884C:${errorMessage2(error)}`);
16616
16931
  return void 0;
16617
16932
  }
16618
16933
  }
@@ -16620,13 +16935,13 @@ function removeServeRuntimeState(registration) {
16620
16935
  if (registration === void 0)
16621
16936
  return;
16622
16937
  try {
16623
- const current = readFileSync14(registration.path);
16938
+ const current = readFileSync16(registration.path);
16624
16939
  if (current.equals(registration.contents))
16625
- rmSync7(registration.path, { force: true });
16940
+ rmSync8(registration.path, { force: true });
16626
16941
  } catch {
16627
16942
  }
16628
16943
  }
16629
- function errorMessage(error) {
16944
+ function errorMessage2(error) {
16630
16945
  return error instanceof Error ? error.message : String(error);
16631
16946
  }
16632
16947
 
@@ -16816,7 +17131,7 @@ function createWingmanServer(options) {
16816
17131
  const runtime = { anatomy: null };
16817
17132
  return createServer({ requireHostHeader: false }, (req, res) => {
16818
17133
  handle(req, res, options, warn, runtime).catch((error) => {
16819
- warn(`[wingman] \u8BF7\u6C42\u5904\u7406\u5931\u8D25:${errorMessage2(error)}`);
17134
+ warn(`[wingman] \u8BF7\u6C42\u5904\u7406\u5931\u8D25:${errorMessage3(error)}`);
16820
17135
  if (!res.headersSent)
16821
17136
  sendText2(res, 500, "internal error");
16822
17137
  else
@@ -16891,6 +17206,10 @@ async function handle(req, res, options, warn, runtime) {
16891
17206
  await handleActionApply(req, res, options, runtime);
16892
17207
  return;
16893
17208
  }
17209
+ if (url.pathname === "/api/task-overlay" && req.method === "POST") {
17210
+ await handleTaskOverlay(req, res, options);
17211
+ return;
17212
+ }
16894
17213
  if (url.pathname === "/api/license/install" && req.method === "POST") {
16895
17214
  await handleLicenseInstall(req, res, options);
16896
17215
  return;
@@ -16955,6 +17274,10 @@ async function handle(req, res, options, warn, runtime) {
16955
17274
  res.end(JSON.stringify(configs));
16956
17275
  return;
16957
17276
  }
17277
+ if (url.pathname === "/api/version") {
17278
+ sendJson2(res, 200, { version: wingmanVersion() ?? null });
17279
+ return;
17280
+ }
16958
17281
  if (url.pathname.startsWith("/api/")) {
16959
17282
  sendText2(res, 404, "not found");
16960
17283
  return;
@@ -17012,7 +17335,7 @@ async function scanAll(harnesses, warn) {
17012
17335
  const detected = await adapter.detect(scanOptions);
17013
17336
  installed = detected.installed;
17014
17337
  } catch (error) {
17015
- warn(`[wingman] ${harness} detect \u5931\u8D25,\u5DF2\u8DF3\u8FC7:${errorMessage2(error)}`);
17338
+ warn(`[wingman] ${harness} detect \u5931\u8D25,\u5DF2\u8DF3\u8FC7:${errorMessage3(error)}`);
17016
17339
  continue;
17017
17340
  }
17018
17341
  if (!installed)
@@ -17020,7 +17343,7 @@ async function scanAll(harnesses, warn) {
17020
17343
  try {
17021
17344
  configs.push(await scanValidated(adapter, scanOptions));
17022
17345
  } catch (error) {
17023
- warn(`[wingman] ${harness} \u626B\u63CF\u5931\u8D25,\u964D\u7EA7\u663E\u793A:${errorMessage2(error)}`);
17346
+ warn(`[wingman] ${harness} \u626B\u63CF\u5931\u8D25,\u964D\u7EA7\u663E\u793A:${errorMessage3(error)}`);
17024
17347
  configs.push(degradedScanConfig(harness, error));
17025
17348
  }
17026
17349
  }
@@ -17057,7 +17380,7 @@ function degradedScanConfig(harness, error) {
17057
17380
  };
17058
17381
  }
17059
17382
  function errorSummary(error) {
17060
- const first = errorMessage2(error).split("\n", 1)[0] ?? "";
17383
+ const first = errorMessage3(error).split("\n", 1)[0] ?? "";
17061
17384
  const scrubbed = first.replace(/(?:[A-Za-z]:)?[\\/][^\s'"()|,;]+/g, "\u2026").trim();
17062
17385
  return scrubbed.length <= 120 ? scrubbed : `${scrubbed.slice(0, 119)}\u2026`;
17063
17386
  }
@@ -17081,7 +17404,7 @@ async function handleActionPlan(req, res, options) {
17081
17404
  const plan = await loaded.adapter.planAction(action, loaded.scanOptions);
17082
17405
  sendJson2(res, 200, plan);
17083
17406
  } catch (error) {
17084
- sendJson2(res, 400, { error: errorMessage2(error) });
17407
+ sendJson2(res, 400, { error: errorMessage3(error) });
17085
17408
  }
17086
17409
  }
17087
17410
  async function handleActionApply(req, res, options, runtime) {
@@ -17115,8 +17438,32 @@ async function handleActionApply(req, res, options, runtime) {
17115
17438
  }
17116
17439
  sendJson2(res, 200, result4);
17117
17440
  } catch (error) {
17118
- sendJson2(res, 400, { error: errorMessage2(error) });
17441
+ sendJson2(res, 400, { error: errorMessage3(error) });
17442
+ }
17443
+ }
17444
+ async function handleTaskOverlay(req, res, options) {
17445
+ if (options.readonlyActions) {
17446
+ sendJson2(res, 403, { error: "fixtures \u6A21\u5F0F\u53EA\u8BFB:\u4E0D\u5199\u4EFB\u52A1\u5361 overlay" });
17447
+ return;
17448
+ }
17449
+ const body = await readJsonBody(req);
17450
+ if (!isTaskOverlayRequest(body)) {
17451
+ sendJson2(res, 400, {
17452
+ error: "\u8BF7\u6C42\u9700\u8981\u5408\u6CD5 harness\u3001\u975E\u7A7A sessionId \u4E0E\u81F3\u5C11\u4E00\u4E2A title/pinned/hidden/accepted \u8865\u4E01\u5B57\u6BB5"
17453
+ });
17454
+ return;
17455
+ }
17456
+ if (options.homeDir === void 0) {
17457
+ sendJson2(res, 500, { error: "\u4EFB\u52A1\u5361 overlay \u7F3A\u5C11 homeDir" });
17458
+ return;
17459
+ }
17460
+ const key = `${body.harness}:${body.sessionId}`;
17461
+ const result4 = patchTaskOverlay(options.homeDir, key, body.patch, /* @__PURE__ */ new Date());
17462
+ if ("unavailable" in result4) {
17463
+ sendJson2(res, 409, { error: result4.unavailable });
17464
+ return;
17119
17465
  }
17466
+ sendJson2(res, 200, { key, entry: result4.entry });
17120
17467
  }
17121
17468
  var LICENSE_BODY_LIMIT = 64 * 1024;
17122
17469
  async function handleLicenseInstall(req, res, options) {
@@ -17198,6 +17545,25 @@ function isToggleRequest(value) {
17198
17545
  const o = value;
17199
17546
  return o.kind === "toggle" && typeof o.itemId === "string" && typeof o.enable === "boolean";
17200
17547
  }
17548
+ function isTaskOverlayRequest(value) {
17549
+ if (!isRecord7(value) || !isHarnessId3(value.harness))
17550
+ return false;
17551
+ if (typeof value.sessionId !== "string" || value.sessionId.trim() === "")
17552
+ return false;
17553
+ if (!isRecord7(value.patch))
17554
+ return false;
17555
+ const keys = Object.keys(value.patch);
17556
+ if (keys.length === 0 || keys.some((key) => !["title", "pinned", "hidden", "accepted"].includes(key))) {
17557
+ return false;
17558
+ }
17559
+ return (value.patch.title === void 0 || value.patch.title === null || typeof value.patch.title === "string") && (value.patch.pinned === void 0 || typeof value.patch.pinned === "boolean") && (value.patch.hidden === void 0 || typeof value.patch.hidden === "boolean") && (value.patch.accepted === void 0 || typeof value.patch.accepted === "boolean");
17560
+ }
17561
+ function isHarnessId3(value) {
17562
+ return typeof value === "string" && HARNESS_IDS.includes(value);
17563
+ }
17564
+ function isRecord7(value) {
17565
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17566
+ }
17201
17567
  function readJsonBody(req) {
17202
17568
  return new Promise((resolveBody) => {
17203
17569
  const chunks = [];
@@ -17258,7 +17624,7 @@ function sendText2(res, status3, body) {
17258
17624
  res.writeHead(status3, { "content-type": "text/plain; charset=utf-8" });
17259
17625
  res.end(body);
17260
17626
  }
17261
- function errorMessage2(error) {
17627
+ function errorMessage3(error) {
17262
17628
  return error instanceof Error ? error.message : String(error);
17263
17629
  }
17264
17630
 
@@ -17275,12 +17641,18 @@ var HELP = `wingman \u2014 Not your copilot. Your wingman.
17275
17641
  [--project <dir>] [--dry-run] [--yes]
17276
17642
  wingman license status \u67E5\u770B\u8BA2\u9605\u72B6\u6001(\u5199\u52A8\u4F5C\u7684\u95F8)
17277
17643
  wingman license refresh \u8054\u7F51\u5411\u7B7E\u53D1\u7AEF\u6362\u65B0 license(\u7EED\u671F)
17644
+ wingman --version | -v | version \u663E\u793A\u7248\u672C
17278
17645
  wingman help \u663E\u793A\u672C\u5E2E\u52A9
17279
17646
  `;
17280
17647
  var UsageError = class extends Error {
17281
17648
  };
17282
17649
  async function main(argv) {
17283
17650
  const [command, ...rest] = argv;
17651
+ if (command === "--version" || command === "-v" || command === "version") {
17652
+ process.stdout.write(`${wingmanVersion() ?? "unknown"}
17653
+ `);
17654
+ return 0;
17655
+ }
17284
17656
  switch (command) {
17285
17657
  case void 0:
17286
17658
  case "help":
@@ -17346,6 +17718,7 @@ ${counts}
17346
17718
  }
17347
17719
  async function runServe(args) {
17348
17720
  const { port, fixtures, panelDist } = parseServeArgs(args);
17721
+ const homeDir = fixtures ? fixturesRoot : homedir13();
17349
17722
  const hookToken = createHookToken();
17350
17723
  const harnesses = [];
17351
17724
  for (const id of HARNESS_IDS) {
@@ -17357,10 +17730,10 @@ async function runServe(args) {
17357
17730
  `);
17358
17731
  }
17359
17732
  }
17360
- const panelDistDir = panelDist === void 0 ? resolve11(dirname16(fileURLToPath2(import.meta.url)), "../../panel/dist") : resolve11(panelDist);
17733
+ const panelDistDir = panelDist === void 0 ? resolve11(dirname17(fileURLToPath2(import.meta.url)), "../../panel/dist") : resolve11(panelDist);
17361
17734
  const narrative = createNarrativeService({
17362
17735
  followTranscripts: !fixtures,
17363
- homeDir: fixtures ? fixturesRoot : homedir13(),
17736
+ homeDir,
17364
17737
  ...fixtures ? { projectBoundaryDir: fixturesRoot } : {},
17365
17738
  feedbackSignals: (harness) => {
17366
17739
  const loaded = harnesses.find((candidate) => candidate.harness === harness);
@@ -17369,7 +17742,7 @@ async function runServe(args) {
17369
17742
  });
17370
17743
  if (fixtures) {
17371
17744
  try {
17372
- const raw = readFileSync15(fixturePath("cc", "sessions", "session-todos.jsonl"), "utf8");
17745
+ const raw = readFileSync17(fixturePath("cc", "sessions", "session-todos.jsonl"), "utf8");
17373
17746
  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");
17374
17747
  } catch (error) {
17375
17748
  const message = error instanceof Error ? error.message : String(error);
@@ -17382,6 +17755,7 @@ async function runServe(args) {
17382
17755
  panelDistDir,
17383
17756
  receiver: createReceiver({ onEvent: narrative.handleEvent }),
17384
17757
  readonlyActions: fixtures,
17758
+ homeDir,
17385
17759
  narrative,
17386
17760
  hookToken
17387
17761
  });
@@ -17412,7 +17786,7 @@ async function runHook(args) {
17412
17786
  if (action !== "install" && action !== "uninstall" || harness === void 0) {
17413
17787
  throw new UsageError("hook \u7528\u6CD5:wingman hook install|uninstall <cc|codex|dsh> [--port 7333]");
17414
17788
  }
17415
- if (!isHarnessId3(harness)) {
17789
+ if (!isHarnessId4(harness)) {
17416
17790
  throw new UsageError(`hook \u9700\u8981 harness \u2208 ${HARNESS_IDS.join("|")},\u5F97\u5230 ${harness}`);
17417
17791
  }
17418
17792
  let port = 7333;
@@ -17455,7 +17829,7 @@ function parseScanArgs(args) {
17455
17829
  json = true;
17456
17830
  } else if (arg === "--harness") {
17457
17831
  const value = rest.shift();
17458
- if (value === void 0 || !isHarnessId3(value)) {
17832
+ if (value === void 0 || !isHarnessId4(value)) {
17459
17833
  throw new UsageError(`--harness \u9700\u8981 ${HARNESS_IDS.join("|")},\u5F97\u5230 ${value ?? "(\u7F3A\u53C2)"}`);
17460
17834
  }
17461
17835
  harness = value;
@@ -17493,7 +17867,7 @@ function parseServeArgs(args) {
17493
17867
  }
17494
17868
  return { port, fixtures, panelDist };
17495
17869
  }
17496
- function isHarnessId3(value) {
17870
+ function isHarnessId4(value) {
17497
17871
  return HARNESS_IDS.includes(value);
17498
17872
  }
17499
17873
  main(process.argv.slice(2)).then((code) => {