flavor-code 1.3.11 → 1.3.13

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.
@@ -12,7 +12,7 @@ import {
12
12
  redactConfig,
13
13
  setProjectMcpServerDisabled,
14
14
  updateProtectedFile
15
- } from "./chunk-IG4Z23CL.js";
15
+ } from "./chunk-JFKRX2KG.js";
16
16
  import {
17
17
  AuditLogger,
18
18
  appendUsageLog,
@@ -66,12 +66,12 @@ function normalizeProviderError(error) {
66
66
 
67
67
  // src/utils/async.ts
68
68
  function withTimeout(promise, ms) {
69
- return new Promise((resolve28, reject) => {
69
+ return new Promise((resolve29, reject) => {
70
70
  let settled = false;
71
71
  const timer = setTimeout(() => {
72
72
  if (settled) return;
73
73
  settled = true;
74
- resolve28({ timedOut: true, value: void 0 });
74
+ resolve29({ timedOut: true, value: void 0 });
75
75
  }, ms);
76
76
  timer.unref?.();
77
77
  promise.then(
@@ -79,7 +79,7 @@ function withTimeout(promise, ms) {
79
79
  if (!settled) {
80
80
  settled = true;
81
81
  clearTimeout(timer);
82
- resolve28({ timedOut: false, value });
82
+ resolve29({ timedOut: false, value });
83
83
  }
84
84
  },
85
85
  (error) => {
@@ -94,13 +94,13 @@ function withTimeout(promise, ms) {
94
94
  }
95
95
  function awaitWithSignal(promise, signal) {
96
96
  signal.throwIfAborted();
97
- return new Promise((resolve28, reject) => {
97
+ return new Promise((resolve29, reject) => {
98
98
  const onAbort = () => reject(signal.reason);
99
99
  signal.addEventListener("abort", onAbort, { once: true });
100
100
  promise.then(
101
101
  (value) => {
102
102
  signal.removeEventListener("abort", onAbort);
103
- resolve28(value);
103
+ resolve29(value);
104
104
  },
105
105
  (error) => {
106
106
  signal.removeEventListener("abort", onAbort);
@@ -1082,6 +1082,231 @@ function packageVersion() {
1082
1082
  throw new Error("package.json with a version field was not found");
1083
1083
  }
1084
1084
 
1085
+ // src/ui/char-width.ts
1086
+ function charWidth(codePoint) {
1087
+ if (codePoint === 8203 || // ZERO WIDTH SPACE
1088
+ codePoint === 8204 || // ZERO WIDTH NON-JOINER
1089
+ codePoint === 8205 || // ZERO WIDTH JOINER
1090
+ codePoint === 65279 || // BOM / ZERO WIDTH NO-BREAK SPACE
1091
+ codePoint >= 768 && codePoint <= 879 || // Combining Diacritical Marks
1092
+ codePoint >= 1155 && codePoint <= 1161 || // Cyrillic combining marks
1093
+ codePoint >= 1425 && codePoint <= 1469 || // Hebrew combining marks
1094
+ codePoint >= 1552 && codePoint <= 1562 || // Arabic combining marks
1095
+ codePoint >= 1611 && codePoint <= 1631 || // Arabic combining marks
1096
+ codePoint >= 1648 && codePoint <= 1648 || // Arabic combining marks
1097
+ codePoint >= 1750 && codePoint <= 1756 || // Arabic combining marks
1098
+ codePoint >= 1759 && codePoint <= 1764 || // Arabic combining marks
1099
+ codePoint >= 1767 && codePoint <= 1768 || // Arabic combining marks
1100
+ codePoint >= 1770 && codePoint <= 1773 || // Arabic combining marks
1101
+ codePoint >= 1809 && codePoint <= 1809 || // Syriac combining marks
1102
+ codePoint >= 1840 && codePoint <= 1866 || // Syriac combining marks
1103
+ codePoint >= 1958 && codePoint <= 1968 || // Thaana combining marks
1104
+ codePoint >= 2304 && codePoint <= 2306 || // Devanagari combining marks
1105
+ codePoint >= 2362 && codePoint <= 2362 || // Devanagari combining marks
1106
+ codePoint >= 2364 && codePoint <= 2364 || // Devanagari combining marks
1107
+ codePoint >= 2369 && codePoint <= 2376 || // Devanagari combining marks
1108
+ codePoint >= 2381 && codePoint <= 2381 || // Devanagari combining marks
1109
+ codePoint >= 2385 && codePoint <= 2391 || // Devanagari combining marks
1110
+ codePoint >= 2402 && codePoint <= 2403 || // Devanagari combining marks
1111
+ codePoint >= 2433 && codePoint <= 2433 || // Bengali combining marks
1112
+ codePoint >= 2492 && codePoint <= 2492 || // Bengali combining marks
1113
+ codePoint >= 2497 && codePoint <= 2500 || // Bengali combining marks
1114
+ codePoint >= 2509 && codePoint <= 2509 || // Bengali combining marks
1115
+ codePoint >= 2530 && codePoint <= 2531 || // Bengali combining marks
1116
+ codePoint >= 2561 && codePoint <= 2562 || // Gurmukhi combining marks
1117
+ codePoint >= 2620 && codePoint <= 2620 || // Gurmukhi combining marks
1118
+ codePoint >= 2625 && codePoint <= 2626 || // Gurmukhi combining marks
1119
+ codePoint >= 2631 && codePoint <= 2632 || // Gurmukhi combining marks
1120
+ codePoint >= 2635 && codePoint <= 2637 || // Gurmukhi combining marks
1121
+ codePoint >= 2672 && codePoint <= 2673 || // Gurmukhi combining marks
1122
+ codePoint >= 2689 && codePoint <= 2690 || // Gujarati combining marks
1123
+ codePoint >= 2748 && codePoint <= 2748 || // Gujarati combining marks
1124
+ codePoint >= 2753 && codePoint <= 2757 || // Gujarati combining marks
1125
+ codePoint >= 2759 && codePoint <= 2760 || // Gujarati combining marks
1126
+ codePoint >= 2765 && codePoint <= 2765 || // Gujarati combining marks
1127
+ codePoint >= 2786 && codePoint <= 2787 || // Gujarati combining marks
1128
+ codePoint >= 2817 && codePoint <= 2817 || // Oriya combining marks
1129
+ codePoint >= 2876 && codePoint <= 2876 || // Oriya combining marks
1130
+ codePoint >= 2879 && codePoint <= 2879 || // Oriya combining marks
1131
+ codePoint >= 2881 && codePoint <= 2883 || // Oriya combining marks
1132
+ codePoint >= 2893 && codePoint <= 2893 || // Oriya combining marks
1133
+ codePoint >= 2902 && codePoint <= 2902 || // Oriya combining marks
1134
+ codePoint >= 2946 && codePoint <= 2946 || // Tamil combining marks
1135
+ codePoint >= 3008 && codePoint <= 3008 || // Tamil combining marks
1136
+ codePoint >= 3021 && codePoint <= 3021 || // Tamil combining marks
1137
+ codePoint >= 3134 && codePoint <= 3136 || // Telugu combining marks
1138
+ codePoint >= 3142 && codePoint <= 3144 || // Telugu combining marks
1139
+ codePoint >= 3146 && codePoint <= 3149 || // Telugu combining marks
1140
+ codePoint >= 3157 && codePoint <= 3158 || // Telugu combining marks
1141
+ codePoint >= 3260 && codePoint <= 3260 || // Kannada combining marks
1142
+ codePoint >= 3263 && codePoint <= 3263 || // Kannada combining marks
1143
+ codePoint >= 3270 && codePoint <= 3270 || // Kannada combining marks
1144
+ codePoint >= 3276 && codePoint <= 3277 || // Kannada combining marks
1145
+ codePoint >= 3298 && codePoint <= 3299 || // Kannada combining marks
1146
+ codePoint >= 3393 && codePoint <= 3395 || // Malayalam combining marks
1147
+ codePoint >= 3405 && codePoint <= 3405 || // Malayalam combining marks
1148
+ codePoint >= 3530 && codePoint <= 3530 || // Sinhala combining marks
1149
+ codePoint >= 3538 && codePoint <= 3540 || // Sinhala combining marks
1150
+ codePoint >= 3542 && codePoint <= 3542 || // Sinhala combining marks
1151
+ codePoint >= 3633 && codePoint <= 3633 || // Thai combining marks
1152
+ codePoint >= 3636 && codePoint <= 3642 || // Thai combining marks
1153
+ codePoint >= 3655 && codePoint <= 3662 || // Thai combining marks
1154
+ codePoint >= 3761 && codePoint <= 3761 || // Lao combining marks
1155
+ codePoint >= 3764 && codePoint <= 3769 || // Lao combining marks
1156
+ codePoint >= 3771 && codePoint <= 3772 || // Lao combining marks
1157
+ codePoint >= 3784 && codePoint <= 3789 || // Lao combining marks
1158
+ codePoint >= 3864 && codePoint <= 3865 || // Tibetan combining marks
1159
+ codePoint >= 3893 && codePoint <= 3893 || // Tibetan combining marks
1160
+ codePoint >= 3895 && codePoint <= 3895 || // Tibetan combining marks
1161
+ codePoint >= 3897 && codePoint <= 3897 || // Tibetan combining marks
1162
+ codePoint >= 3953 && codePoint <= 3966 || // Tibetan combining marks
1163
+ codePoint >= 3968 && codePoint <= 3972 || // Tibetan combining marks
1164
+ codePoint >= 3974 && codePoint <= 3975 || // Tibetan combining marks
1165
+ codePoint >= 3984 && codePoint <= 3991 || // Tibetan combining marks
1166
+ codePoint >= 3993 && codePoint <= 4028 || // Tibetan combining marks
1167
+ codePoint >= 4038 && codePoint <= 4038 || // Tibetan combining marks
1168
+ codePoint >= 4146 && codePoint <= 4151 || // Myanmar combining marks
1169
+ codePoint >= 4153 && codePoint <= 4154 || // Myanmar combining marks
1170
+ codePoint >= 4157 && codePoint <= 4158 || // Myanmar combining marks
1171
+ codePoint >= 4184 && codePoint <= 4185 || // Myanmar combining marks
1172
+ codePoint >= 4190 && codePoint <= 4192 || // Myanmar combining marks
1173
+ codePoint >= 4209 && codePoint <= 4212 || // Myanmar combining marks
1174
+ codePoint >= 4226 && codePoint <= 4226 || // Myanmar combining marks
1175
+ codePoint >= 4229 && codePoint <= 4230 || // Myanmar combining marks
1176
+ codePoint >= 4237 && codePoint <= 4237 || // Myanmar combining marks
1177
+ codePoint >= 4957 && codePoint <= 4959 || // Ethiopic combining marks
1178
+ codePoint >= 5906 && codePoint <= 5908 || // Tagalog combining marks
1179
+ codePoint >= 5938 && codePoint <= 5940 || // Hanunoo combining marks
1180
+ codePoint >= 5970 && codePoint <= 5971 || // Buhid combining marks
1181
+ codePoint >= 6002 && codePoint <= 6003 || // Tagbanwa combining marks
1182
+ codePoint >= 6068 && codePoint <= 6069 || // Khmer combining marks
1183
+ codePoint >= 6071 && codePoint <= 6077 || // Khmer combining marks
1184
+ codePoint >= 6086 && codePoint <= 6086 || // Khmer combining marks
1185
+ codePoint >= 6089 && codePoint <= 6099 || // Khmer combining marks
1186
+ codePoint >= 6109 && codePoint <= 6109 || // Khmer combining marks
1187
+ codePoint >= 6155 && codePoint <= 6157 || // Mongolian combining marks
1188
+ codePoint >= 6313 && codePoint <= 6313 || // Mongolian combining marks
1189
+ codePoint >= 6432 && codePoint <= 6434 || // Limbu combining marks
1190
+ codePoint >= 6439 && codePoint <= 6440 || // Limbu combining marks
1191
+ codePoint >= 6450 && codePoint <= 6450 || // Limbu combining marks
1192
+ codePoint >= 6457 && codePoint <= 6459 || // Limbu combining marks
1193
+ codePoint >= 6679 && codePoint <= 6680 || // Buginese combining marks
1194
+ codePoint >= 6912 && codePoint <= 6915 || // Balinese combining marks
1195
+ codePoint >= 6964 && codePoint <= 6964 || // Balinese combining marks
1196
+ codePoint >= 6966 && codePoint <= 6970 || // Balinese combining marks
1197
+ codePoint >= 6972 && codePoint <= 6972 || // Balinese combining marks
1198
+ codePoint >= 6978 && codePoint <= 6978 || // Balinese combining marks
1199
+ codePoint >= 7019 && codePoint <= 7027 || // Balinese combining marks
1200
+ codePoint >= 7616 && codePoint <= 7679 || // Combining Diacritical Marks Supplement
1201
+ codePoint >= 8400 && codePoint <= 8432 || // Combining Diacritical Marks for Symbols
1202
+ codePoint >= 11503 && codePoint <= 11505 || // Coptic combining marks
1203
+ codePoint >= 11647 && codePoint <= 11647 || // Tifinagh combining marks
1204
+ codePoint >= 11744 && codePoint <= 11775 || // Cyrillic Extended-A combining marks
1205
+ codePoint >= 42607 && codePoint <= 42610 || // Cyrillic Extended-B combining marks
1206
+ codePoint >= 42612 && codePoint <= 42621 || // Cyrillic Extended-B combining marks
1207
+ codePoint >= 42654 && codePoint <= 42655 || // Cyrillic Extended-B combining marks
1208
+ codePoint >= 42736 && codePoint <= 42737 || // Bamum combining marks
1209
+ codePoint >= 43010 && codePoint <= 43010 || // Syloti Nagri combining marks
1210
+ codePoint >= 43014 && codePoint <= 43014 || // Syloti Nagri combining marks
1211
+ codePoint >= 43019 && codePoint <= 43019 || // Syloti Nagri combining marks
1212
+ codePoint >= 43045 && codePoint <= 43046 || // Syloti Nagri combining marks
1213
+ codePoint >= 43204 && codePoint <= 43204 || // Saurashtra combining marks
1214
+ codePoint >= 43232 && codePoint <= 43249 || // Devanagari Extended combining marks
1215
+ codePoint >= 43302 && codePoint <= 43309 || // Kayah Li combining marks
1216
+ codePoint >= 43335 && codePoint <= 43345 || // Rejang combining marks
1217
+ codePoint >= 43392 && codePoint <= 43394 || // Javanese combining marks
1218
+ codePoint >= 43443 && codePoint <= 43443 || // Javanese combining marks
1219
+ codePoint >= 43446 && codePoint <= 43449 || // Javanese combining marks
1220
+ codePoint >= 43452 && codePoint <= 43452 || // Javanese combining marks
1221
+ codePoint >= 43561 && codePoint <= 43566 || // Cham combining marks
1222
+ codePoint >= 43569 && codePoint <= 43570 || // Cham combining marks
1223
+ codePoint >= 43573 && codePoint <= 43574 || // Cham combining marks
1224
+ codePoint >= 43587 && codePoint <= 43587 || // Cham combining marks
1225
+ codePoint >= 43596 && codePoint <= 43596 || // Cham combining marks
1226
+ codePoint >= 43696 && codePoint <= 43696 || // Tai Viet combining marks
1227
+ codePoint >= 43698 && codePoint <= 43700 || // Tai Viet combining marks
1228
+ codePoint >= 43703 && codePoint <= 43704 || // Tai Viet combining marks
1229
+ codePoint >= 43710 && codePoint <= 43711 || // Tai Viet combining marks
1230
+ codePoint >= 43713 && codePoint <= 43713 || // Tai Viet combining marks
1231
+ codePoint >= 44005 && codePoint <= 44005 || // Meetei Mayek combining marks
1232
+ codePoint >= 44008 && codePoint <= 44008 || // Meetei Mayek combining marks
1233
+ codePoint >= 44013 && codePoint <= 44013 || // Meetei Mayek combining marks
1234
+ codePoint >= 64286 && codePoint <= 64286 || // Hebrew combining marks
1235
+ codePoint >= 65024 && codePoint <= 65039 || // Variation Selectors
1236
+ codePoint >= 65056 && codePoint <= 65071 || // Combining Half Marks
1237
+ codePoint >= 917760 && codePoint <= 917999) {
1238
+ return 0;
1239
+ }
1240
+ if (codePoint >= 4352 && codePoint <= 4447 || // Hangul Jamo
1241
+ codePoint >= 9001 && codePoint <= 9002 || // Left/Right-pointing angle bracket
1242
+ codePoint >= 11904 && codePoint <= 12350 || // CJK Radicals Supplement, Kangxi Radicals, CJK Symbols
1243
+ codePoint >= 12352 && codePoint <= 13247 || // Hiragana, Katakana, Bopomofo, Hangul Compatibility, Kanbun, CJK Strokes, Enclosed CJK
1244
+ codePoint >= 13312 && codePoint <= 19903 || // CJK Unified Ideographs Extension A
1245
+ codePoint >= 19968 && codePoint <= 42191 || // CJK Unified Ideographs, Yi Syllables, Yi Radicals
1246
+ codePoint >= 43360 && codePoint <= 43388 || // Hangul Jamo Extended-A
1247
+ codePoint >= 44032 && codePoint <= 55203 || // Hangul Syllables
1248
+ codePoint >= 63744 && codePoint <= 64255 || // CJK Compatibility Ideographs
1249
+ codePoint >= 65040 && codePoint <= 65049 || // Vertical forms
1250
+ codePoint >= 65072 && codePoint <= 65135 || // CJK Compatibility Forms, Small Form Variants
1251
+ codePoint >= 65281 && codePoint <= 65376 || // Fullwidth Forms
1252
+ codePoint >= 65504 && codePoint <= 65510 || // Fullwidth Signs
1253
+ codePoint >= 110592 && codePoint <= 111359 || // Kana Supplement, Kana Extended-A, Small Kana Extension
1254
+ codePoint >= 126980 && codePoint <= 126980 || // Mahjong tile
1255
+ codePoint >= 127183 && codePoint <= 127183 || // Playing card
1256
+ codePoint >= 127232 && codePoint <= 129535 || // Enclosed Alphanumeric Supplement, Emoticons, Transport, etc.
1257
+ codePoint >= 129536 && codePoint <= 129647 || // Chess Symbols
1258
+ codePoint >= 129648 && codePoint <= 129791 || // Symbols and Pictographs Extended-A
1259
+ codePoint >= 131072 && codePoint <= 196605 || // CJK Unified Ideographs Extension B–G, CJK Compatibility Supplement
1260
+ codePoint >= 196608 && codePoint <= 262141) {
1261
+ return 2;
1262
+ }
1263
+ return 1;
1264
+ }
1265
+
1266
+ // src/ui/thinking-line.ts
1267
+ var THINKING_SCROLL_CELLS_PER_SEC = 16;
1268
+ var THINKING_EASE = 0.18;
1269
+ var THINKING_MAX_STEP_MS = 250;
1270
+ var THINKING_MAX_STORED_CHARS = 4e3;
1271
+ function normalizeThinkingText(text) {
1272
+ return text.replace(/\s+/gu, " ").trim();
1273
+ }
1274
+ function thinkingDisplayWidth(text) {
1275
+ let width = 0;
1276
+ for (const character of text) width += charWidth(character.codePointAt(0) ?? 32);
1277
+ return width;
1278
+ }
1279
+ function sliceThinkingByWidth(text, startCell, cellCount) {
1280
+ let out = "";
1281
+ let cell = 0;
1282
+ for (const character of text) {
1283
+ const w = charWidth(character.codePointAt(0) ?? 32);
1284
+ if (cell + w > startCell && cell < startCell + cellCount) out += character;
1285
+ cell += w;
1286
+ if (cell >= startCell + cellCount) break;
1287
+ }
1288
+ return out;
1289
+ }
1290
+ function thinkingWindow(text, previousStart, options) {
1291
+ const normalized = normalizeThinkingText(text);
1292
+ const width = Math.max(1, Math.floor(options.width));
1293
+ const total = thinkingDisplayWidth(normalized);
1294
+ if (total <= width) return { text: normalized, start: 0 };
1295
+ const maxStart = total - width;
1296
+ const from = Math.min(Math.max(0, previousStart), maxStart);
1297
+ const cellsPerSecond = options.cellsPerSecond ?? THINKING_SCROLL_CELLS_PER_SEC;
1298
+ const ease = options.ease ?? THINKING_EASE;
1299
+ const dt = Math.max(0, Math.min(options.dtMs, THINKING_MAX_STEP_MS));
1300
+ const lag = maxStart - from;
1301
+ const step = Math.max(cellsPerSecond * dt / 1e3, lag * ease);
1302
+ const start = Math.min(maxStart, Math.floor(from + step));
1303
+ return { text: sliceThinkingByWidth(normalized, start, width), start };
1304
+ }
1305
+ function appendThinkingText(previous, delta) {
1306
+ const next = (previous ?? "") + delta;
1307
+ return next.length > THINKING_MAX_STORED_CHARS ? next.slice(-THINKING_MAX_STORED_CHARS) : next;
1308
+ }
1309
+
1085
1310
  // src/ui/transcript.ts
1086
1311
  function createTranscriptState() {
1087
1312
  return { completed: [], nextId: 1 };
@@ -1178,6 +1403,9 @@ function transcriptReducer(state, action) {
1178
1403
  if (event.type === "text") {
1179
1404
  return { ...state, active: addText(withoutModelActivity(state.active), event.text) };
1180
1405
  }
1406
+ if (event.type === "thinking") {
1407
+ return { ...state, active: attachThinking(state.active, event.text) };
1408
+ }
1181
1409
  if (event.type === "tool-start") return upsertStatus({ ...state, active: withoutModelActivity(state.active) }, {
1182
1410
  kind: "status",
1183
1411
  id: `tool:${event.id}`,
@@ -1478,6 +1706,18 @@ function finishActive(state) {
1478
1706
  ...state.taskSnapshot === void 0 ? {} : { taskSnapshot: state.taskSnapshot }
1479
1707
  };
1480
1708
  }
1709
+ function attachThinking(turn, text) {
1710
+ const blocks = [...turn.blocks];
1711
+ const index = blocks.findIndex((block2) => block2.kind === "status" && block2.activity === "model" && block2.state === "running");
1712
+ if (index < 0) return turn;
1713
+ const block = blocks[index];
1714
+ if (block === void 0 || block.kind !== "status") return turn;
1715
+ blocks[index] = {
1716
+ ...block,
1717
+ thinkingText: appendThinkingText(block.thinkingText, text)
1718
+ };
1719
+ return { ...turn, blocks };
1720
+ }
1481
1721
  function withoutModelActivity(turn, id) {
1482
1722
  const blocks = turn.blocks.filter((block) => block.kind !== "status" || block.activity !== "model" || id !== void 0 && block.id !== id);
1483
1723
  if (blocks.length === turn.blocks.length) return turn;
@@ -2984,6 +3224,7 @@ var MVP_COMMANDS = [
2984
3224
  "mcp",
2985
3225
  "commit",
2986
3226
  "review",
3227
+ "explain",
2987
3228
  "ide",
2988
3229
  "memory",
2989
3230
  "remember",
@@ -3022,6 +3263,7 @@ var COMMAND_DESCRIPTIONS = {
3022
3263
  evolve: "Self-improvement loop: capture failures, suggest fixes, verify with tests",
3023
3264
  commit: "Generate a commit message for staged changes and commit after confirmation",
3024
3265
  review: "Review uncommitted changes for bugs and risks before committing",
3266
+ explain: "Explain a symbol for newcomers (AST graph + source + git history)",
3025
3267
  mcp: "Manage MCP servers",
3026
3268
  ide: "Show the connected IDE and editor context",
3027
3269
  memory: "Show long-term project memory",
@@ -3098,6 +3340,12 @@ function parseSlashCommand(input, dynamicCommands = [], skillCommands = [], mana
3098
3340
  const focus = args.join(" ").trim();
3099
3341
  return focus.length === 0 ? { name } : { name, focus };
3100
3342
  }
3343
+ if (name === "explain") {
3344
+ const [query, ...focusParts] = args;
3345
+ if (query === void 0) return { name };
3346
+ const focus = focusParts.join(" ").trim();
3347
+ return focus.length === 0 ? { name, query } : { name, query, focus };
3348
+ }
3101
3349
  if (name === "loop") {
3102
3350
  const goal = args.join(" ").trim();
3103
3351
  if (!goal) return { name: "invalid", command: name, message: "Use /loop <goal>." };
@@ -3701,6 +3949,14 @@ var FlavorSession = class {
3701
3949
  this.#notice(await required(this.#services.gitCommit, "commit")(command.hint, signal));
3702
3950
  } else if (command.name === "review") {
3703
3951
  this.#notice(await required(this.#services.gitReview, "review")(command.focus, signal));
3952
+ } else if (command.name === "explain") {
3953
+ const result = await required(this.#services.explain, "explain")(
3954
+ command.query,
3955
+ command.focus,
3956
+ signal,
3957
+ (chunk) => this.#services.output({ type: "text", text: chunk })
3958
+ );
3959
+ if (result !== "") this.#notice(result);
3704
3960
  } else if (command.name === "mcp") {
3705
3961
  this.#notice(await this.#services.mcp(command, signal));
3706
3962
  } else if (command.name === "pals") {
@@ -3867,9 +4123,9 @@ var FlavorSession = class {
3867
4123
  }
3868
4124
  #waitForPlanningIdle(key) {
3869
4125
  if (!this.#activePlanning.has(key)) return Promise.resolve();
3870
- return new Promise((resolve28) => {
4126
+ return new Promise((resolve29) => {
3871
4127
  const check = () => {
3872
- if (!this.#activePlanning.has(key)) resolve28();
4128
+ if (!this.#activePlanning.has(key)) resolve29();
3873
4129
  else this.#planningIdleWaiters.add(check);
3874
4130
  };
3875
4131
  this.#planningIdleWaiters.add(check);
@@ -4217,12 +4473,12 @@ var BrokerStateError = class extends Error {
4217
4473
  };
4218
4474
  var WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\";
4219
4475
  function canConnect(address) {
4220
- return new Promise((resolve28) => {
4476
+ return new Promise((resolve29) => {
4221
4477
  const socket = new Socket();
4222
4478
  const finish = (connected) => {
4223
4479
  socket.removeAllListeners();
4224
4480
  socket.destroy();
4225
- resolve28(connected);
4481
+ resolve29(connected);
4226
4482
  };
4227
4483
  socket.once("connect", () => finish(true));
4228
4484
  socket.once("error", () => finish(false));
@@ -4271,7 +4527,7 @@ var PalBrokerServer = class {
4271
4527
  this.authToken ??= await loadOrCreatePalAuthToken(this.authHome === void 0 ? {} : { home: this.authHome });
4272
4528
  try {
4273
4529
  await this.prepareAddress();
4274
- await new Promise((resolve28, reject) => {
4530
+ await new Promise((resolve29, reject) => {
4275
4531
  const onError = (error) => {
4276
4532
  this.server.off("listening", onListening);
4277
4533
  reject(error);
@@ -4280,7 +4536,7 @@ var PalBrokerServer = class {
4280
4536
  this.server.off("error", onError);
4281
4537
  this.started = true;
4282
4538
  this.ownsAddress = true;
4283
- resolve28();
4539
+ resolve29();
4284
4540
  };
4285
4541
  this.server.once("error", onError);
4286
4542
  this.server.once("listening", onListening);
@@ -4308,7 +4564,7 @@ var PalBrokerServer = class {
4308
4564
  const ownsAddress = this.ownsAddress;
4309
4565
  for (const connection of this.connections) connection.socket.destroy();
4310
4566
  if (this.started) {
4311
- await new Promise((resolve28) => this.server.close(() => resolve28()));
4567
+ await new Promise((resolve29) => this.server.close(() => resolve29()));
4312
4568
  this.started = false;
4313
4569
  }
4314
4570
  this.ownsAddress = false;
@@ -4327,8 +4583,8 @@ var PalBrokerServer = class {
4327
4583
  });
4328
4584
  if (this.endpointLock === void 0) throw new Error(`Pals endpoint '${this.address}' is active or owned`);
4329
4585
  try {
4330
- const stat9 = await lstat3(this.address);
4331
- if (!stat9.isSocket()) throw new Error(`Refusing to unlink '${this.address}' because it is not a socket`);
4586
+ const stat10 = await lstat3(this.address);
4587
+ if (!stat10.isSocket()) throw new Error(`Refusing to unlink '${this.address}' because it is not a socket`);
4332
4588
  await unlink3(this.address);
4333
4589
  } catch (error) {
4334
4590
  if (error.code !== "ENOENT") throw error;
@@ -4341,8 +4597,8 @@ var PalBrokerServer = class {
4341
4597
  }
4342
4598
  async unlinkKnownSocket() {
4343
4599
  try {
4344
- const stat9 = await lstat3(this.address);
4345
- if (stat9.isSocket()) await unlink3(this.address);
4600
+ const stat10 = await lstat3(this.address);
4601
+ if (stat10.isSocket()) await unlink3(this.address);
4346
4602
  } catch (error) {
4347
4603
  if (error.code !== "ENOENT") throw error;
4348
4604
  }
@@ -4977,15 +5233,15 @@ var PalBrokerState = class {
4977
5233
  // src/pals/broker-cli.ts
4978
5234
  var startupAttempts = /* @__PURE__ */ new Map();
4979
5235
  function delay(milliseconds) {
4980
- return new Promise((resolve28) => setTimeout(resolve28, milliseconds));
5236
+ return new Promise((resolve29) => setTimeout(resolve29, milliseconds));
4981
5237
  }
4982
5238
  function canConnect2(address) {
4983
- return new Promise((resolve28) => {
5239
+ return new Promise((resolve29) => {
4984
5240
  const socket = new Socket2();
4985
5241
  const finish = (connected) => {
4986
5242
  socket.removeAllListeners();
4987
5243
  socket.destroy();
4988
- resolve28(connected);
5244
+ resolve29(connected);
4989
5245
  };
4990
5246
  socket.once("connect", () => finish(true));
4991
5247
  socket.once("error", () => finish(false));
@@ -5028,8 +5284,8 @@ async function runPalBroker(options) {
5028
5284
  const sweepIntervalMs = options.sweepIntervalMs ?? 5e3;
5029
5285
  const idleShutdownMs = options.idleShutdownMs ?? 3e4;
5030
5286
  let resolveClosed;
5031
- const closed = new Promise((resolve28) => {
5032
- resolveClosed = resolve28;
5287
+ const closed = new Promise((resolve29) => {
5288
+ resolveClosed = resolve29;
5033
5289
  });
5034
5290
  let closing;
5035
5291
  const timer = setInterval(() => {
@@ -5072,7 +5328,7 @@ async function ensureOnce(options) {
5072
5328
  import { readFile as readFile22 } from "fs/promises";
5073
5329
  import { createHash as createHash18, randomUUID as randomUUID20 } from "crypto";
5074
5330
  import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
5075
- import { basename as basename6, join as join25, relative as relative20, resolve as resolve27, sep as sep20 } from "path";
5331
+ import { basename as basename6, join as join26, relative as relative20, resolve as resolve28, sep as sep20 } from "path";
5076
5332
 
5077
5333
  // src/agent/subagents.ts
5078
5334
  import { z as z5 } from "zod";
@@ -6341,7 +6597,12 @@ function providerValidMessages(input) {
6341
6597
  const calls = original.toolCalls.filter((call) => call.id && call.name && availableResults.has(call.id));
6342
6598
  calls.forEach((call) => announced.add(call.id));
6343
6599
  if (!original.content && calls.length === 0) continue;
6344
- output.push({ role: "assistant", content: original.content, ...calls.length === 0 ? {} : { toolCalls: calls.map((call) => ({ ...call })) } });
6600
+ output.push({
6601
+ role: "assistant",
6602
+ content: original.content,
6603
+ ...calls.length === 0 ? {} : { toolCalls: calls.map((call) => ({ ...call })) },
6604
+ ...original.thinkingBlocks === void 0 ? {} : { thinkingBlocks: original.thinkingBlocks.map((block) => ({ ...block })) }
6605
+ });
6345
6606
  continue;
6346
6607
  }
6347
6608
  if (original.role === "tool") {
@@ -6919,10 +7180,10 @@ function sanitize(message2, secrets) {
6919
7180
  }
6920
7181
  function waitForRetry(delayMs, signal) {
6921
7182
  if (signal?.aborted) return Promise.reject(signal.reason);
6922
- return new Promise((resolve28, reject) => {
7183
+ return new Promise((resolve29, reject) => {
6923
7184
  const timer = setTimeout(() => {
6924
7185
  signal?.removeEventListener("abort", onAbort);
6925
- resolve28();
7186
+ resolve29();
6926
7187
  }, delayMs);
6927
7188
  const onAbort = () => {
6928
7189
  clearTimeout(timer);
@@ -7064,6 +7325,7 @@ var AgentLoop = class {
7064
7325
  }
7065
7326
  let assistantText = "";
7066
7327
  const collectedToolCalls = [];
7328
+ const collectedThinking = [];
7067
7329
  let completed = false;
7068
7330
  let reactiveRetried = false;
7069
7331
  let attempt = 1;
@@ -7101,6 +7363,7 @@ var AgentLoop = class {
7101
7363
  }
7102
7364
  assistantText = "";
7103
7365
  collectedToolCalls.length = 0;
7366
+ collectedThinking.length = 0;
7104
7367
  completed = false;
7105
7368
  let terminalError;
7106
7369
  let providerError = false;
@@ -7121,6 +7384,10 @@ var AgentLoop = class {
7121
7384
  assistantText += event.text;
7122
7385
  accumulatedText += event.text;
7123
7386
  yield event;
7387
+ } else if (event.type === "thinking") {
7388
+ yield event;
7389
+ } else if (event.type === "thinking-block") {
7390
+ collectedThinking.push({ text: event.text, ...event.signature === void 0 ? {} : { signature: event.signature } });
7124
7391
  } else if (event.type === "tool-call") {
7125
7392
  collectedToolCalls.push({ kind: "valid", id: event.id, name: event.name, input: event.input });
7126
7393
  } else if (event.type === "invalid-tool-call") {
@@ -7468,7 +7735,8 @@ var AgentLoop = class {
7468
7735
  const assistantMessage = {
7469
7736
  role: "assistant",
7470
7737
  content: assistantText,
7471
- toolCalls: toolCalls.map(({ id, name, input }) => ({ id, name, input }))
7738
+ toolCalls: toolCalls.map(({ id, name, input }) => ({ id, name, input })),
7739
+ ...collectedThinking.length === 0 ? {} : { thinkingBlocks: collectedThinking.map((block) => ({ ...block })) }
7472
7740
  };
7473
7741
  this.#options.context.appendMany([assistantMessage, ...stagedMessages]);
7474
7742
  for (const { call, result } of stagedResults) {
@@ -7526,10 +7794,10 @@ function serializeToolInput(input) {
7526
7794
  }
7527
7795
  function waitForRetry2(delayMs, signal) {
7528
7796
  if (signal?.aborted) return Promise.reject(signal.reason);
7529
- return new Promise((resolve28, reject) => {
7797
+ return new Promise((resolve29, reject) => {
7530
7798
  const timer = setTimeout(() => {
7531
7799
  signal?.removeEventListener("abort", onAbort);
7532
- resolve28();
7800
+ resolve29();
7533
7801
  }, delayMs);
7534
7802
  const onAbort = () => {
7535
7803
  clearTimeout(timer);
@@ -8735,7 +9003,7 @@ function decoratePayload(payload, envelope) {
8735
9003
  return envelope === void 0 ? payload : { ...payload, ...envelope };
8736
9004
  }
8737
9005
  function runShellHandler(descriptor, event, signal) {
8738
- return new Promise((resolve28, reject) => {
9006
+ return new Promise((resolve29, reject) => {
8739
9007
  const child = spawn4(descriptor.command, [...descriptor.args ?? []], {
8740
9008
  env: { ...process.env, ...descriptor.env },
8741
9009
  shell: false,
@@ -8778,7 +9046,7 @@ function runShellHandler(descriptor, event, signal) {
8778
9046
  return;
8779
9047
  }
8780
9048
  try {
8781
- resolve28(HookDecisionSchema.parse(JSON.parse(stdout)));
9049
+ resolve29(HookDecisionSchema.parse(JSON.parse(stdout)));
8782
9050
  } catch (error) {
8783
9051
  reject(error);
8784
9052
  }
@@ -8975,14 +9243,14 @@ async function createIslandControlServer(options) {
8975
9243
  void dispatch(line, socket, options, token);
8976
9244
  });
8977
9245
  });
8978
- await new Promise((resolve28, reject) => {
9246
+ await new Promise((resolve29, reject) => {
8979
9247
  const onError = (error) => {
8980
9248
  server.removeListener("listening", onListening);
8981
9249
  reject(error);
8982
9250
  };
8983
9251
  const onListening = () => {
8984
9252
  server.removeListener("error", onError);
8985
- resolve28();
9253
+ resolve29();
8986
9254
  };
8987
9255
  server.once("error", onError);
8988
9256
  server.once("listening", onListening);
@@ -9053,11 +9321,11 @@ function write(socket, payload) {
9053
9321
  }
9054
9322
  }
9055
9323
  function closeServer(server) {
9056
- return new Promise((resolve28) => {
9324
+ return new Promise((resolve29) => {
9057
9325
  try {
9058
- server.close(() => resolve28());
9326
+ server.close(() => resolve29());
9059
9327
  } catch {
9060
- resolve28();
9328
+ resolve29();
9061
9329
  }
9062
9330
  });
9063
9331
  }
@@ -10242,7 +10510,7 @@ var USAGE = [
10242
10510
  " clear reset signals, done markers, and verified markers"
10243
10511
  ].join("\n");
10244
10512
  function runCommand(command, cwd, timeoutMs) {
10245
- return new Promise((resolve28) => {
10513
+ return new Promise((resolve29) => {
10246
10514
  const isWindows = process.platform === "win32";
10247
10515
  const child = spawn5(isWindows ? "cmd.exe" : "/bin/sh", isWindows ? ["/d", "/s", "/c", command] : ["-c", command], {
10248
10516
  cwd,
@@ -10252,7 +10520,7 @@ function runCommand(command, cwd, timeoutMs) {
10252
10520
  let stderr = "";
10253
10521
  const timer = setTimeout(() => {
10254
10522
  child.kill();
10255
- resolve28({ ok: false, stdout, stderr: `${stderr}
10523
+ resolve29({ ok: false, stdout, stderr: `${stderr}
10256
10524
  [killed: exceeded ${timeoutMs}ms]`, code: "timeout" });
10257
10525
  }, timeoutMs);
10258
10526
  child.stdout.on("data", (chunk) => {
@@ -10263,12 +10531,12 @@ function runCommand(command, cwd, timeoutMs) {
10263
10531
  });
10264
10532
  child.on("error", (error) => {
10265
10533
  clearTimeout(timer);
10266
- resolve28({ ok: false, stdout, stderr: `${stderr}
10534
+ resolve29({ ok: false, stdout, stderr: `${stderr}
10267
10535
  ${error.message}`, code: "spawn" });
10268
10536
  });
10269
10537
  child.on("close", (code) => {
10270
10538
  clearTimeout(timer);
10271
- resolve28({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10539
+ resolve29({ ok: code === 0, stdout, stderr, code: code ?? "unknown" });
10272
10540
  });
10273
10541
  });
10274
10542
  }
@@ -12725,6 +12993,7 @@ var CLAUDE_CLIENT_HEADERS = {
12725
12993
  "x-app": "cli"
12726
12994
  };
12727
12995
  var DEFAULT_MAX_OUTPUT_TOKENS = 32768;
12996
+ var DEFAULT_THINKING_BUDGET = 8192;
12728
12997
  var MAX_CACHE_MARKERS = 4;
12729
12998
  function collectCacheMarkers(system, messages) {
12730
12999
  const markers = [];
@@ -12783,9 +13052,26 @@ function formatCacheUsage(model, snapshot4, shape) {
12783
13052
  ...shape === void 0 ? {} : { requestMessages: shape.messages, requestMarkers: shape.markers }
12784
13053
  });
12785
13054
  }
13055
+ function stripThinkingBlocks(messages) {
13056
+ for (const message2 of messages) {
13057
+ if (message2.role !== "assistant" || !Array.isArray(message2.content)) continue;
13058
+ const blocks = message2.content;
13059
+ if (!blocks.some((block) => block?.type === "thinking")) continue;
13060
+ message2.content = blocks.filter((block) => block?.type !== "thinking");
13061
+ }
13062
+ }
13063
+ function isThinkingParamRejected(error) {
13064
+ const status = error?.status;
13065
+ if (status !== void 0 && status !== 400) return false;
13066
+ const message2 = error instanceof Error ? error.message : String(error ?? "");
13067
+ return /\bthinking\b/iu.test(message2) && /(budget_tokens|expected|not supported|unsupported|unknown|invalid|extra inputs|unrecognized)/iu.test(message2);
13068
+ }
12786
13069
  var AnthropicModelAdapter = class {
12787
13070
  client;
12788
13071
  maxOutputTokens;
13072
+ thinkingBudget;
13073
+ /** Set once an endpoint rejects the `thinking` parameter; later requests stop sending it. */
13074
+ #thinkingRejected = false;
12789
13075
  debugUsage;
12790
13076
  constructor(options) {
12791
13077
  const maxOutputTokens = options.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
@@ -12793,6 +13079,7 @@ var AnthropicModelAdapter = class {
12793
13079
  throw new Error("maxOutputTokens must be a positive integer");
12794
13080
  }
12795
13081
  this.maxOutputTokens = maxOutputTokens;
13082
+ this.thinkingBudget = options.thinkingBudget ?? DEFAULT_THINKING_BUDGET;
12796
13083
  this.debugUsage = options.debugUsage ?? isEnvTruthy(process.env.FLAVOR_DEBUG_USAGE);
12797
13084
  this.client = options.client ?? new Anthropic({
12798
13085
  ...options.apiKey === void 0 ? {} : { apiKey: options.apiKey },
@@ -12800,6 +13087,16 @@ var AnthropicModelAdapter = class {
12800
13087
  ...options.headers === void 0 ? {} : { defaultHeaders: options.headers }
12801
13088
  });
12802
13089
  }
13090
+ /**
13091
+ * Extended-thinking request parameter when enabled, otherwise undefined.
13092
+ * Anthropic requires the budget to fit inside max_tokens with a 1024 floor.
13093
+ */
13094
+ #thinkingParam() {
13095
+ if (this.#thinkingRejected || this.thinkingBudget <= 0) return void 0;
13096
+ const budget = Math.min(this.thinkingBudget, this.maxOutputTokens - 1);
13097
+ if (budget < 1024) return void 0;
13098
+ return { type: "enabled", budget_tokens: budget };
13099
+ }
12803
13100
  #logUsage(request, snapshot4, shape) {
12804
13101
  const line = formatCacheUsage(request.model, snapshot4, shape);
12805
13102
  if (this.debugUsage) {
@@ -12855,7 +13152,13 @@ var AnthropicModelAdapter = class {
12855
13152
  }
12856
13153
  messages.push({ role: "user", content: results });
12857
13154
  } else if (message2.role === "assistant" && message2.toolCalls?.length) {
13155
+ const echoThinking = this.#thinkingRejected ? [] : (message2.thinkingBlocks ?? []).filter((block) => block.signature !== void 0);
12858
13156
  const content = [
13157
+ ...echoThinking.map((block) => ({
13158
+ type: "thinking",
13159
+ thinking: block.text,
13160
+ signature: block.signature
13161
+ })),
12859
13162
  ...modelContentText(message2.content) ? [{ type: "text", text: modelContentText(message2.content) }] : [],
12860
13163
  ...message2.toolCalls.map((call) => ({
12861
13164
  type: "tool_use",
@@ -12917,18 +13220,56 @@ var AnthropicModelAdapter = class {
12917
13220
  stream: true,
12918
13221
  messages,
12919
13222
  ...system ? { system } : {},
13223
+ ...(() => {
13224
+ const thinking = this.#thinkingParam();
13225
+ return thinking === void 0 ? {} : { thinking };
13226
+ })(),
12920
13227
  tools: [...request.tools].sort((a, b) => a.name.localeCompare(b.name)).map((tool) => ({
12921
13228
  name: tool.name,
12922
13229
  description: tool.description,
12923
13230
  input_schema: { ...tool.inputSchema, type: "object" }
12924
13231
  }))
12925
13232
  };
12926
- const stream = await this.client.messages.create(body, { signal: request.signal });
13233
+ let stream;
13234
+ try {
13235
+ stream = await this.client.messages.create(body, { signal: request.signal });
13236
+ } catch (error) {
13237
+ if (this.thinkingBudget > 0 && !this.#thinkingRejected && isThinkingParamRejected(error)) {
13238
+ this.#thinkingRejected = true;
13239
+ delete body.thinking;
13240
+ stripThinkingBlocks(body.messages);
13241
+ stream = await this.client.messages.create(body, { signal: request.signal });
13242
+ } else {
13243
+ throw error;
13244
+ }
13245
+ }
13246
+ const thinkingBuffers = /* @__PURE__ */ new Map();
12927
13247
  for await (const event of stream) {
12928
13248
  if (event.type === "message_start") {
12929
13249
  hasUsage = event.message?.usage !== void 0;
12930
13250
  inputTokens = updateInputUsage(inputUsage, event.message?.usage);
12931
13251
  outputTokens = event.message?.usage?.output_tokens ?? outputTokens;
13252
+ } else if (event.type === "content_block_start" && event.content_block?.type === "thinking" && event.index !== void 0) {
13253
+ thinkingBuffers.set(event.index, { text: "" });
13254
+ } else if (event.type === "content_block_delta" && event.delta?.type === "thinking_delta") {
13255
+ if (event.index !== void 0 && event.delta.thinking) {
13256
+ const pending = thinkingBuffers.get(event.index);
13257
+ if (pending) pending.text += event.delta.thinking;
13258
+ }
13259
+ if (event.delta.thinking) yield { type: "thinking", text: event.delta.thinking };
13260
+ } else if (event.type === "content_block_delta" && event.delta?.type === "signature_delta" && event.index !== void 0) {
13261
+ const pending = thinkingBuffers.get(event.index);
13262
+ if (pending) pending.signature = (pending.signature ?? "") + (event.delta.signature ?? "");
13263
+ } else if (event.type === "content_block_stop" && event.index !== void 0 && thinkingBuffers.has(event.index)) {
13264
+ const pending = thinkingBuffers.get(event.index);
13265
+ if (pending.text.length > 0 || pending.signature !== void 0) {
13266
+ yield {
13267
+ type: "thinking-block",
13268
+ text: pending.text,
13269
+ ...pending.signature === void 0 ? {} : { signature: pending.signature }
13270
+ };
13271
+ }
13272
+ thinkingBuffers.delete(event.index);
12932
13273
  } else if (event.type === "content_block_start" && event.content_block?.type === "tool_use" && event.index !== void 0 && event.content_block.id && event.content_block.name) {
12933
13274
  pendingTools.set(event.index, {
12934
13275
  id: event.content_block.id,
@@ -13131,12 +13472,14 @@ function formatOpenAIUsage(model, breakdown) {
13131
13472
  var OpenAIModelAdapter = class {
13132
13473
  client;
13133
13474
  debugUsage;
13475
+ thinkingEffort;
13134
13476
  constructor(options) {
13135
13477
  this.client = options.client ?? new OpenAI({
13136
13478
  ...options.apiKey === void 0 ? {} : { apiKey: options.apiKey },
13137
13479
  ...options.baseURL === void 0 ? {} : { baseURL: options.baseURL }
13138
13480
  });
13139
13481
  this.debugUsage = options.debugUsage ?? isEnvTruthy(process.env.FLAVOR_DEBUG_USAGE);
13482
+ this.thinkingEffort = options.thinkingEffort;
13140
13483
  }
13141
13484
  #logUsage(model, breakdown) {
13142
13485
  if (breakdown === void 0) return;
@@ -13158,6 +13501,7 @@ var OpenAIModelAdapter = class {
13158
13501
  const body = {
13159
13502
  model: request.model,
13160
13503
  input: (await Promise.all(request.messages.map(toInput))).flat(),
13504
+ ...this.thinkingEffort === void 0 ? {} : { reasoning: { effort: this.thinkingEffort } },
13161
13505
  tools: [...request.tools].sort((a, b) => a.name.localeCompare(b.name)).map((tool) => ({
13162
13506
  type: "function",
13163
13507
  name: tool.name,
@@ -13190,6 +13534,8 @@ var OpenAIModelAdapter = class {
13190
13534
  }
13191
13535
  } else if (event.type === "response.output_text.delta" && event.delta) {
13192
13536
  yield { type: "text", text: event.delta };
13537
+ } else if ((event.type === "response.reasoning_summary_text.delta" || event.type === "response.reasoning_text.delta") && event.delta) {
13538
+ yield { type: "thinking", text: event.delta };
13193
13539
  } else if (event.type === "response.function_call_arguments.done" && event.output_index !== void 0 && event.name) {
13194
13540
  const callId = callIds.get(event.output_index);
13195
13541
  if (callId && !emittedCalls.has(event.output_index)) {
@@ -13706,8 +14052,8 @@ var OAuthCallbackAuthProvider = class {
13706
14052
  async #startCallbackServer(expectedState, signal) {
13707
14053
  let serverResolve;
13708
14054
  let serverReject;
13709
- const codePromise = new Promise((resolve28, reject) => {
13710
- serverResolve = resolve28;
14055
+ const codePromise = new Promise((resolve29, reject) => {
14056
+ serverResolve = resolve29;
13711
14057
  serverReject = reject;
13712
14058
  });
13713
14059
  const server = createServer3((req, res) => {
@@ -13741,7 +14087,7 @@ var OAuthCallbackAuthProvider = class {
13741
14087
  );
13742
14088
  serverResolve(code);
13743
14089
  });
13744
- return new Promise((resolve28, reject) => {
14090
+ return new Promise((resolve29, reject) => {
13745
14091
  server.once("error", (error) => {
13746
14092
  reject(new Error(`OAuth callback server failed: ${error.message}`));
13747
14093
  });
@@ -13758,7 +14104,7 @@ var OAuthCallbackAuthProvider = class {
13758
14104
  signal.addEventListener("abort", () => {
13759
14105
  serverReject(new Error("OAuth flow aborted"));
13760
14106
  }, { once: true });
13761
- resolve28({ redirectUri, promise: codePromise });
14107
+ resolve29({ redirectUri, promise: codePromise });
13762
14108
  });
13763
14109
  });
13764
14110
  }
@@ -16897,8 +17243,8 @@ var LspConnection = class {
16897
17243
  const id = this.#nextId++;
16898
17244
  const request = { jsonrpc: "2.0", id, method, params };
16899
17245
  const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
16900
- const promise = new Promise((resolve28, reject) => {
16901
- this.#pending.set(id, { resolve: resolve28, reject });
17246
+ const promise = new Promise((resolve29, reject) => {
17247
+ this.#pending.set(id, { resolve: resolve29, reject });
16902
17248
  timeout.addEventListener("abort", () => {
16903
17249
  if (this.#pending.has(id)) {
16904
17250
  this.#pending.delete(id);
@@ -17302,19 +17648,19 @@ async function changeSummary(workspace) {
17302
17648
  };
17303
17649
  }
17304
17650
  async function stagedDiff(workspace, maxChars = 96e3) {
17305
- const stat9 = await git2(workspace, ["diff", "--staged", "--stat"]);
17651
+ const stat10 = await git2(workspace, ["diff", "--staged", "--stat"]);
17306
17652
  const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
17307
17653
  if (!diff.ok) throw gitFailure(diff, "diff --staged");
17308
17654
  const text = diff.stdout;
17309
17655
  return {
17310
17656
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
17311
17657
  \u2026[diff truncated]` : text,
17312
- stat: stat9.stdout.trim(),
17658
+ stat: stat10.stdout.trim(),
17313
17659
  truncated: text.length > maxChars
17314
17660
  };
17315
17661
  }
17316
17662
  async function uncommittedDiff(workspace, maxChars = 96e3) {
17317
- const stat9 = await git2(workspace, ["diff", "HEAD", "--stat"]);
17663
+ const stat10 = await git2(workspace, ["diff", "HEAD", "--stat"]);
17318
17664
  const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
17319
17665
  if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
17320
17666
  const summary2 = await changeSummary(workspace);
@@ -17322,7 +17668,7 @@ async function uncommittedDiff(workspace, maxChars = 96e3) {
17322
17668
  return {
17323
17669
  diff: text.length > maxChars ? `${text.slice(0, maxChars)}
17324
17670
  \u2026[diff truncated]` : text,
17325
- stat: stat9.stdout.trim(),
17671
+ stat: stat10.stdout.trim(),
17326
17672
  untracked: summary2.untrackedFiles
17327
17673
  };
17328
17674
  }
@@ -17463,6 +17809,232 @@ function formatReviewReport(report) {
17463
17809
  return lines.join("\n");
17464
17810
  }
17465
17811
 
17812
+ // src/explain/service.ts
17813
+ async function resolveExplainTarget(graph, query) {
17814
+ const trimmed = query.trim();
17815
+ if (trimmed.includes("#")) {
17816
+ const hit = await graph.relations(trimmed).catch(() => void 0);
17817
+ if (hit?.origin !== void 0) return { kind: "resolved", node: hit.origin };
17818
+ }
17819
+ const hits = await graph.search(trimmed, 10);
17820
+ if (hits.length === 0) return { kind: "not-found", query: trimmed };
17821
+ if (hits.length === 1) return { kind: "resolved", node: hits[0] };
17822
+ return { kind: "ambiguous", candidates: hits };
17823
+ }
17824
+ function explainCandidateLabel(candidate) {
17825
+ return candidate.id;
17826
+ }
17827
+ async function selectExplainCandidate(candidates, questions, signal) {
17828
+ const shown = candidates.slice(0, 3);
17829
+ const answers = await questions.ask([{
17830
+ header: "Symbol",
17831
+ question: "Multiple symbols match. Which one should /explain cover? (Pick one, or type a more exact name.)",
17832
+ options: [
17833
+ ...shown.map((candidate) => ({
17834
+ label: explainCandidateLabel(candidate),
17835
+ description: `${candidate.kind} \xB7 ${candidate.filePath}:${candidate.startLine}-${candidate.endLine}`
17836
+ })),
17837
+ { label: "Cancel", description: "Abort /explain without choosing." }
17838
+ ]
17839
+ }], signal);
17840
+ const answer = (answers[0] ?? "").trim();
17841
+ if (answer === "Cancel" || answer === "") return { kind: "cancelled" };
17842
+ const picked = shown.find((candidate) => explainCandidateLabel(candidate) === answer);
17843
+ if (picked !== void 0) return { kind: "picked", node: picked };
17844
+ return { kind: "typed", query: answer };
17845
+ }
17846
+ var MAX_SOURCE_CHARS = 2e4;
17847
+ function buildExplainPrompt(input) {
17848
+ const source = input.anchorSource.length > MAX_SOURCE_CHARS ? `${input.anchorSource.slice(0, MAX_SOURCE_CHARS)}
17849
+ \u2026 (truncated)` : input.anchorSource;
17850
+ const line = (n) => `- ${n.name} \u2014 ${n.filePath}:${n.startLine}`;
17851
+ return [
17852
+ "You are a senior engineer onboarding a new team member. Explain the following code symbol clearly and concretely; never invent behavior that is not in the shown evidence.",
17853
+ `Answer in ${input.language}.`,
17854
+ "Structure the answer with short markdown sections:",
17855
+ "1. \u5B83\u662F\u505A\u4EC0\u4E48\u7684 \u2014 one paragraph, plain language, beginner first.",
17856
+ "2. \u5173\u952E\u5B9E\u73B0\u70B9 \u2014 2-5 bullets tied to concrete lines/branches in the source.",
17857
+ "3. \u8C01\u8C03\u7528\u5B83 / \u5B83\u8C03\u7528\u8C01 \u2014 use the relation lists; explain the data flow in one or two sentences.",
17858
+ "4. \u4E3A\u4EC0\u4E48\u8FD9\u6837\u5199 \u2014 infer from the recent commit subjects when possible; say '\u4E0D\u786E\u5B9A' instead of guessing.",
17859
+ "5. \u65B0\u4EBA\u6CE8\u610F\u4E8B\u9879 \u2014 pitfalls, guards or invariants worth knowing.",
17860
+ ...input.focus === void 0 || input.focus.trim() === "" ? [] : [`The user specifically asks about: ${input.focus.trim()}`],
17861
+ "",
17862
+ `Symbol: ${input.anchor.id} (${input.anchor.kind}${input.anchor.signature ? `, signature: ${input.anchor.signature}` : ""})`,
17863
+ "",
17864
+ `Source (${input.anchor.filePath}:${input.anchor.startLine}-${input.anchor.endLine}):`,
17865
+ source,
17866
+ "",
17867
+ `Callers:
17868
+ ${input.callers.map(line).join("\n") || "- (none)"}`,
17869
+ `Callees:
17870
+ ${input.callees.map(line).join("\n") || "- (none)"}`,
17871
+ "",
17872
+ `Recent commits touching the file:
17873
+ ${input.history.map((h) => `- ${h.date} ${h.author}: ${h.subject}`).join("\n") || "- (no history)"}`
17874
+ ].join("\n");
17875
+ }
17876
+ async function explainWithModel(options, prompt, signal, onText) {
17877
+ const { adapter, model } = options.registry.get(options.modelId());
17878
+ let text = "";
17879
+ for await (const event of adapter.stream({
17880
+ model,
17881
+ messages: [{ role: "user", content: prompt }],
17882
+ tools: [],
17883
+ signal
17884
+ })) {
17885
+ if (event.type === "text") {
17886
+ text += event.text;
17887
+ onText?.(event.text);
17888
+ } else if (event.type === "error") throw new Error(`explain generation failed: ${event.error.message}`);
17889
+ else if (event.type === "done") break;
17890
+ }
17891
+ const cleaned = text.trim();
17892
+ if (cleaned === "") throw new Error("explain generation returned no text");
17893
+ return cleaned;
17894
+ }
17895
+ var HISTORY_LIMIT = 8;
17896
+ var MAX_RESOLVE_ROUNDS = 2;
17897
+ async function runExplain(deps, query, focus, signal) {
17898
+ if (query === void 0) {
17899
+ return "Usage: /explain <symbol | file.ts#symbol> [what to focus on]. Example: /explain src/order.ts#cancelOrder error handling";
17900
+ }
17901
+ if (!(await deps.graph.status()).available) {
17902
+ return "The code graph is not built yet \u2014 run /ast init, then try /explain again.";
17903
+ }
17904
+ let target = await resolveExplainTarget(deps.graph, query);
17905
+ for (let round = 1; round <= MAX_RESOLVE_ROUNDS; round += 1) {
17906
+ if (target.kind !== "ambiguous") break;
17907
+ if (deps.questions === void 0) {
17908
+ return `"${query}" is ambiguous. Use the exact node id, e.g. /explain ${target.candidates[0]?.id ?? "path/file.ts#name"}.`;
17909
+ }
17910
+ const selection = await selectExplainCandidate(target.candidates, deps.questions, signal);
17911
+ if (selection.kind === "cancelled") return "/explain cancelled \u2014 no symbol was selected.";
17912
+ if (selection.kind === "picked") {
17913
+ target = { kind: "resolved", node: selection.node };
17914
+ break;
17915
+ }
17916
+ target = await resolveExplainTarget(deps.graph, selection.query);
17917
+ if (round === MAX_RESOLVE_ROUNDS && target.kind === "ambiguous") {
17918
+ return `"${selection.query}" is still ambiguous. Pick one node id explicitly: /explain ${target.candidates[0]?.id ?? "path/file.ts#name"}`;
17919
+ }
17920
+ }
17921
+ if (target.kind === "not-found") {
17922
+ return `No matching symbol for "${target.query}" \u2014 check the name, or refresh the graph with /ast sync.`;
17923
+ }
17924
+ if (target.kind !== "resolved") {
17925
+ return `"${query}" is ambiguous. Pick one node id explicitly: /explain ${target.candidates[0]?.id ?? "path/file.ts#name"}`;
17926
+ }
17927
+ const anchor = target.node;
17928
+ signal.throwIfAborted();
17929
+ deps.notify?.(`Explaining ${anchor.id} \u2026`);
17930
+ const { callers, callees } = await deps.graph.relations(anchor.id, 1);
17931
+ const raw = await deps.readFile(anchor.filePath).catch(() => "");
17932
+ const lines = raw.split("\n");
17933
+ const anchorSource = lines.slice(Math.max(0, anchor.startLine - 1), anchor.endLine).join("\n") || raw;
17934
+ const history = deps.history === void 0 ? [] : await deps.history(anchor.filePath, HISTORY_LIMIT).catch(() => []);
17935
+ try {
17936
+ if (deps.onText !== void 0) deps.onText(`Explain ${anchor.id}
17937
+
17938
+ `);
17939
+ const explanation = await explainWithModel(
17940
+ { registry: deps.registry, modelId: deps.modelId },
17941
+ buildExplainPrompt({
17942
+ anchor,
17943
+ anchorSource,
17944
+ callers: callers.map((n) => ({ name: n.name, filePath: n.filePath, startLine: n.startLine })),
17945
+ callees: callees.map((n) => ({ name: n.name, filePath: n.filePath, startLine: n.startLine })),
17946
+ history,
17947
+ ...focus === void 0 ? {} : { focus },
17948
+ language: deps.language
17949
+ }),
17950
+ signal,
17951
+ deps.onText
17952
+ );
17953
+ return deps.onText === void 0 ? `Explain ${anchor.id}
17954
+
17955
+ ${explanation}` : "";
17956
+ } catch (error) {
17957
+ if (signal.aborted) throw error;
17958
+ return `/explain failed: ${error instanceof Error ? error.message : String(error)}`;
17959
+ }
17960
+ }
17961
+
17962
+ // src/desktop/astgraph-service.ts
17963
+ import { stat as stat8 } from "fs/promises";
17964
+ import { createRequire as nodeCreateRequire } from "module";
17965
+ import { join as join22, resolve as resolve22 } from "path";
17966
+ var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
17967
+ var DesktopAstGraphService = class {
17968
+ #path;
17969
+ constructor(workspace) {
17970
+ this.#path = join22(resolve22(workspace), ".flavor", "astgraph", "index.db");
17971
+ }
17972
+ async status() {
17973
+ if ((await stat8(this.#path).catch(() => void 0))?.isFile() !== true) {
17974
+ return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
17975
+ }
17976
+ return this.#read((db) => {
17977
+ const count = (table) => Number(db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count);
17978
+ const metadata = db.prepare("SELECT value FROM project_metadata WHERE key = 'last_index'").get();
17979
+ let indexedAt;
17980
+ try {
17981
+ const at = Number(JSON.parse(metadata?.value ?? "{}").at);
17982
+ if (Number.isFinite(at)) indexedAt = new Date(at).toISOString();
17983
+ } catch {
17984
+ }
17985
+ return { available: true, path: this.#path, files: count("files"), nodes: count("nodes"), edges: count("edges"), ...indexedAt === void 0 ? {} : { indexedAt } };
17986
+ });
17987
+ }
17988
+ async search(query, limit = 40) {
17989
+ const value = query.trim().slice(0, 200);
17990
+ if (value.length === 0 || !(await this.status()).available) return [];
17991
+ return this.#read((db) => db.prepare(`SELECT ${FIELDS} FROM nodes WHERE lower(name) LIKE ? OR lower(qualified_name) LIKE ? ORDER BY CASE WHEN lower(name) = ? THEN 0 ELSE 1 END, name LIMIT ?`).all(`%${value.toLowerCase()}%`, `%${value.toLowerCase()}%`, value.toLowerCase(), Math.min(Math.max(limit, 1), 100)).map(mapNode));
17992
+ }
17993
+ async relations(id, hops = 2) {
17994
+ if (!/^[A-Za-z0-9._:/#@+~-]{1,512}$/.test(id)) throw new Error("Invalid AST node id");
17995
+ if (!(await this.status()).available) return { callers: [], callees: [], impact: [] };
17996
+ return this.#read((db) => {
17997
+ const originRow = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(id);
17998
+ const linked = (direction) => {
17999
+ const source = direction === "callers" ? "e.source" : "e.target";
18000
+ const predicate = direction === "callers" ? "e.target = ?" : "e.source = ?";
18001
+ return db.prepare(`SELECT DISTINCT n.${FIELDS.split(", ").join(", n.")} FROM edges e JOIN nodes n ON n.id = ${source} WHERE ${predicate} AND e.kind IN ('calls','imports') LIMIT 100`).all(id).map(mapNode);
18002
+ };
18003
+ const seen = /* @__PURE__ */ new Map();
18004
+ let frontier = [id];
18005
+ for (let hop = 1; hop <= Math.min(Math.max(hops, 1), 4) && frontier.length > 0 && seen.size < 200; hop += 1) {
18006
+ const next = [];
18007
+ for (const current of frontier) {
18008
+ const rows = db.prepare("SELECT source AS other FROM edges WHERE target = ? UNION SELECT target AS other FROM edges WHERE source = ? LIMIT 200").all(current, current);
18009
+ for (const row of rows) if (row.other !== id && !seen.has(row.other)) {
18010
+ seen.set(row.other, hop);
18011
+ next.push(row.other);
18012
+ }
18013
+ }
18014
+ frontier = next;
18015
+ }
18016
+ const impact = [...seen].flatMap(([nodeId, hop]) => {
18017
+ const row = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(nodeId);
18018
+ return row === void 0 ? [] : [{ ...mapNode(row), hop }];
18019
+ });
18020
+ return { ...originRow === void 0 ? {} : { origin: mapNode(originRow) }, callers: linked("callers"), callees: linked("callees"), impact };
18021
+ });
18022
+ }
18023
+ #read(run) {
18024
+ const sqlite = nodeCreateRequire(import.meta.url)("node:sqlite");
18025
+ const db = new sqlite.DatabaseSync(this.#path, { readOnly: true });
18026
+ try {
18027
+ return run(db);
18028
+ } finally {
18029
+ db.close();
18030
+ }
18031
+ }
18032
+ };
18033
+ function mapNode(value) {
18034
+ const row = value;
18035
+ return { id: String(row.id), kind: String(row.kind), name: String(row.name), qualifiedName: String(row.qualified_name), filePath: String(row.file_path), language: String(row.language), startLine: Number(row.start_line), endLine: Number(row.end_line), ...row.signature == null ? {} : { signature: String(row.signature) } };
18036
+ }
18037
+
17466
18038
  // src/git/tools.ts
17467
18039
  import { z as z28 } from "zod";
17468
18040
  var GitHistoryInput = z28.object({
@@ -17557,7 +18129,7 @@ var QuestionBridge = class {
17557
18129
  }
17558
18130
  }
17559
18131
  #askLocally(questions, signal) {
17560
- return new Promise((resolve28, reject) => {
18132
+ return new Promise((resolve29, reject) => {
17561
18133
  const onAbort = () => {
17562
18134
  this.#pending = void 0;
17563
18135
  this.#active = false;
@@ -17571,7 +18143,7 @@ var QuestionBridge = class {
17571
18143
  this.#pending = void 0;
17572
18144
  this.#active = false;
17573
18145
  this.#onChange?.();
17574
- resolve28(answers);
18146
+ resolve29(answers);
17575
18147
  },
17576
18148
  reject: (error) => {
17577
18149
  signal.removeEventListener("abort", onAbort);
@@ -17664,7 +18236,7 @@ function createTodoWriteTool() {
17664
18236
  // src/tools/managed.ts
17665
18237
  import { randomUUID as randomUUID16 } from "crypto";
17666
18238
  import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as readFile19, readdir as readdir8, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
17667
- import { dirname as dirname18, join as join22, resolve as resolve22 } from "path";
18239
+ import { dirname as dirname18, join as join23, resolve as resolve23 } from "path";
17668
18240
  import { z as z31 } from "zod";
17669
18241
  var MANAGED_TOOL_VERSION = 1;
17670
18242
  var MAX_RECORD_BYTES = 256 * 1024;
@@ -17706,9 +18278,9 @@ var ManagedToolStore = class {
17706
18278
  #entries = /* @__PURE__ */ new Map();
17707
18279
  #diagnostics = [];
17708
18280
  constructor(options) {
17709
- this.#workspace = resolve22(options.workspace);
17710
- this.#projectRoot = join22(this.#workspace, ".flavor", "tools");
17711
- this.#globalRoot = join22(resolve22(options.home), ".flavor-code", "tools");
18281
+ this.#workspace = resolve23(options.workspace);
18282
+ this.#projectRoot = join23(this.#workspace, ".flavor", "tools");
18283
+ this.#globalRoot = join23(resolve23(options.home), ".flavor-code", "tools");
17712
18284
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
17713
18285
  }
17714
18286
  get diagnostics() {
@@ -17755,7 +18327,7 @@ var ManagedToolStore = class {
17755
18327
  version: MANAGED_TOOL_VERSION,
17756
18328
  createdAt: this.#now()
17757
18329
  });
17758
- const path = join22(this.directory(input.scope), filename(input.name));
18330
+ const path = join23(this.directory(input.scope), filename(input.name));
17759
18331
  const definition = compileDefinition(record, input.scope, this.#workspace);
17760
18332
  await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
17761
18333
  `);
@@ -17780,7 +18352,7 @@ var ManagedToolStore = class {
17780
18352
  if (!parsed.success) return [];
17781
18353
  const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
17782
18354
  if (matches.length > 0) return matches.map((entry2) => entry2.path);
17783
- return [join22(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
18355
+ return [join23(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
17784
18356
  }
17785
18357
  async #loadDirectory(scope2) {
17786
18358
  const root = this.directory(scope2);
@@ -17793,7 +18365,7 @@ var ManagedToolStore = class {
17793
18365
  return;
17794
18366
  }
17795
18367
  for (const name of names) {
17796
- const path = join22(root, name);
18368
+ const path = join23(root, name);
17797
18369
  try {
17798
18370
  const info = await lstat11(path);
17799
18371
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
@@ -18712,7 +19284,7 @@ ${total}`;
18712
19284
  // src/memory/store.ts
18713
19285
  import { createHash as createHash16 } from "crypto";
18714
19286
  import { readFile as readFile20, rm as rm9 } from "fs/promises";
18715
- import { resolve as resolve23, join as join23, relative as relative17, sep as sep17 } from "path";
19287
+ import { resolve as resolve24, join as join24, relative as relative17, sep as sep17 } from "path";
18716
19288
 
18717
19289
  // src/memory/similarity.ts
18718
19290
  function normalizeForSimilarity(value) {
@@ -18814,10 +19386,10 @@ var MemoryStore = class {
18814
19386
  constructor(options) {
18815
19387
  if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
18816
19388
  if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
18817
- this.workspace = resolve23(options.workspace);
18818
- this.#memoryRoot = join23(this.workspace, ".flavor", "memory");
18819
- this.path = join23(this.#memoryRoot, "MEMORY.md");
18820
- this.#behaviorPath = join23(this.#memoryRoot, "behavior.json");
19389
+ this.workspace = resolve24(options.workspace);
19390
+ this.#memoryRoot = join24(this.workspace, ".flavor", "memory");
19391
+ this.path = join24(this.#memoryRoot, "MEMORY.md");
19392
+ this.#behaviorPath = join24(this.#memoryRoot, "behavior.json");
18821
19393
  this.#maxEntries = options.maxEntries;
18822
19394
  this.#maxEntryChars = options.maxEntryChars;
18823
19395
  }
@@ -19040,7 +19612,7 @@ var MemoryStore = class {
19040
19612
  }
19041
19613
  #resolveContentPath(contentPath) {
19042
19614
  if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
19043
- const path = resolve23(this.#memoryRoot, ...contentPath.split("/"));
19615
+ const path = resolve24(this.#memoryRoot, ...contentPath.split("/"));
19044
19616
  const rel = relative17(this.#memoryRoot, path);
19045
19617
  if (rel === ".." || rel.startsWith(`..${sep17}`)) throw new Error("Memory content path escapes the memory directory");
19046
19618
  return path;
@@ -19511,8 +20083,8 @@ function createExecutionEnvironment(workspace, config) {
19511
20083
  }
19512
20084
 
19513
20085
  // src/ide/client.ts
19514
- import { readFile as readFile21, readdir as readdir9, stat as stat8 } from "fs/promises";
19515
- import { basename as basename5, join as join24, relative as relative18, resolve as resolve24, sep as sep18 } from "path";
20086
+ import { readFile as readFile21, readdir as readdir9, stat as stat9 } from "fs/promises";
20087
+ import { basename as basename5, join as join25, relative as relative18, resolve as resolve25, sep as sep18 } from "path";
19516
20088
  var FlavorIdeClient = class {
19517
20089
  #workspace;
19518
20090
  #home;
@@ -19523,8 +20095,8 @@ var FlavorIdeClient = class {
19523
20095
  #sessionId;
19524
20096
  #eventTail = Promise.resolve();
19525
20097
  constructor(options) {
19526
- this.#workspace = resolve24(options.workspace);
19527
- this.#home = resolve24(options.home);
20098
+ this.#workspace = resolve25(options.workspace);
20099
+ this.#home = resolve25(options.home);
19528
20100
  this.#environment = options.environment ?? process.env;
19529
20101
  this.#fetch = options.fetch ?? fetch;
19530
20102
  }
@@ -19608,7 +20180,7 @@ var FlavorIdeClient = class {
19608
20180
  };
19609
20181
  if (await this.#healthy(configured)) return configured;
19610
20182
  }
19611
- const directory = join24(this.#home, ".flavor-code", "ide");
20183
+ const directory = join25(this.#home, ".flavor-code", "ide");
19612
20184
  let entries;
19613
20185
  try {
19614
20186
  entries = (await readdir9(directory)).filter((name) => name.endsWith(".lock"));
@@ -19616,9 +20188,9 @@ var FlavorIdeClient = class {
19616
20188
  return void 0;
19617
20189
  }
19618
20190
  const candidates = (await Promise.all(entries.map(async (name) => {
19619
- const path = join24(directory, name);
20191
+ const path = join25(directory, name);
19620
20192
  try {
19621
- const [contents, info] = await Promise.all([readFile21(path, "utf8"), stat8(path)]);
20193
+ const [contents, info] = await Promise.all([readFile21(path, "utf8"), stat9(path)]);
19622
20194
  const lock = parseLockfile(contents);
19623
20195
  return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
19624
20196
  } catch {
@@ -19722,8 +20294,8 @@ function parsePort(value) {
19722
20294
  return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
19723
20295
  }
19724
20296
  function isWithin8(parent, child) {
19725
- const base = normalize(resolve24(parent));
19726
- const target = normalize(resolve24(child));
20297
+ const base = normalize(resolve25(parent));
20298
+ const target = normalize(resolve25(child));
19727
20299
  return target === base || target.startsWith(`${base}${sep18}`);
19728
20300
  }
19729
20301
  function normalize(path) {
@@ -19753,8 +20325,8 @@ var JobRegistry = class {
19753
20325
  const id = `job-${randomUUID17().slice(0, 12)}`;
19754
20326
  const now = (/* @__PURE__ */ new Date()).toISOString();
19755
20327
  let settle;
19756
- const done = new Promise((resolve28) => {
19757
- settle = resolve28;
20328
+ const done = new Promise((resolve29) => {
20329
+ settle = resolve29;
19758
20330
  });
19759
20331
  const record = {
19760
20332
  id,
@@ -19877,8 +20449,8 @@ function snapshot2(job) {
19877
20449
 
19878
20450
  // src/terminal/service.ts
19879
20451
  import { randomUUID as randomUUID18 } from "crypto";
19880
- import { createRequire as nodeCreateRequire } from "module";
19881
- import { isAbsolute as isAbsolute11, relative as relative19, resolve as resolve25, sep as sep19 } from "path";
20452
+ import { createRequire as nodeCreateRequire2 } from "module";
20453
+ import { isAbsolute as isAbsolute11, relative as relative19, resolve as resolve26, sep as sep19 } from "path";
19882
20454
  var TerminalService = class {
19883
20455
  #root;
19884
20456
  #factory;
@@ -19886,7 +20458,7 @@ var TerminalService = class {
19886
20458
  #maxOutputChars;
19887
20459
  #sessions = /* @__PURE__ */ new Map();
19888
20460
  constructor(workspace, options = {}) {
19889
- this.#root = resolve25(workspace);
20461
+ this.#root = resolve26(workspace);
19890
20462
  this.#factory = options.factory ?? defaultFactory;
19891
20463
  this.#jobs = options.jobs;
19892
20464
  this.#maxOutputChars = options.maxOutputChars ?? 2e5;
@@ -19991,7 +20563,7 @@ function snapshot3(record) {
19991
20563
  }
19992
20564
  function defaultFactory(shell, args, options) {
19993
20565
  try {
19994
- const pty = nodeCreateRequire(import.meta.url)("node-pty");
20566
+ const pty = nodeCreateRequire2(import.meta.url)("node-pty");
19995
20567
  return pty.spawn(shell, args, { ...options, name: "xterm-256color" });
19996
20568
  } catch (error) {
19997
20569
  throw new Error(`Pseudo-terminal backend is unavailable: ${error instanceof Error ? error.message : String(error)}`);
@@ -20004,7 +20576,7 @@ function cleanEnvironment(environment) {
20004
20576
  return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
20005
20577
  }
20006
20578
  function workspacePath(root, input) {
20007
- const path = resolve25(root, input);
20579
+ const path = resolve26(root, input);
20008
20580
  const delta = relative19(root, path);
20009
20581
  if (delta === ".." || delta.startsWith(`..${sep19}`) || isAbsolute11(delta)) throw new Error("Terminal cwd is outside the workspace");
20010
20582
  return path;
@@ -20012,7 +20584,7 @@ function workspacePath(root, input) {
20012
20584
 
20013
20585
  // src/tools/terminal.ts
20014
20586
  import { z as z33 } from "zod";
20015
- import { resolve as resolve26 } from "path";
20587
+ import { resolve as resolve27 } from "path";
20016
20588
  var Id = z33.string().regex(/^term-[a-f0-9-]+$/);
20017
20589
  var OpenInput = z33.object({ cwd: z33.string().optional(), shell: z33.string().optional(), args: z33.array(z33.string()).optional(), columns: z33.coerce.number().int().min(10).max(500).optional(), rows: z33.coerce.number().int().min(2).max(300).optional() });
20018
20590
  var OptionalBoolean = z33.union([z33.boolean(), z33.string().refine((value) => value === "true" || value === "false")]);
@@ -20026,8 +20598,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
20026
20598
  name: "TerminalOpen",
20027
20599
  description: "Open a persistent interactive pseudo-terminal in the workspace",
20028
20600
  inputSchema: OpenInput,
20029
- paths: (input) => [resolve26(workspace, input.cwd ?? ".")],
20030
- permissions: (input) => ({ paths: [resolve26(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve26(workspace, input.cwd ?? ".") }),
20601
+ paths: (input) => [resolve27(workspace, input.cwd ?? ".")],
20602
+ permissions: (input) => ({ paths: [resolve27(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve27(workspace, input.cwd ?? ".") }),
20031
20603
  execute: async (input, _signal, context) => terminals.open({
20032
20604
  owner: owner(context),
20033
20605
  ...input.cwd === void 0 ? {} : { cwd: input.cwd },
@@ -20253,8 +20825,8 @@ var PalClient = class {
20253
20825
  let disconnectTimer;
20254
20826
  await Promise.race([
20255
20827
  this.request({ version: 1, type: "disconnect", requestId: randomUUID19() }).catch(() => void 0),
20256
- new Promise((resolve28) => {
20257
- disconnectTimer = setTimeout(resolve28, Math.min(this.requestTimeoutMs, 250));
20828
+ new Promise((resolve29) => {
20829
+ disconnectTimer = setTimeout(resolve29, Math.min(this.requestTimeoutMs, 250));
20258
20830
  })
20259
20831
  ]);
20260
20832
  if (disconnectTimer !== void 0) clearTimeout(disconnectTimer);
@@ -20300,7 +20872,7 @@ var PalClient = class {
20300
20872
  return presence;
20301
20873
  }
20302
20874
  openSocket() {
20303
- return new Promise((resolve28, reject) => {
20875
+ return new Promise((resolve29, reject) => {
20304
20876
  let settled = false;
20305
20877
  const rejectConnection = (error) => {
20306
20878
  if (settled) return;
@@ -20323,7 +20895,7 @@ var PalClient = class {
20323
20895
  settled = true;
20324
20896
  if (this.connectingReject === rejectConnection) this.connectingReject = void 0;
20325
20897
  socket.off("error", onInitialError);
20326
- resolve28();
20898
+ resolve29();
20327
20899
  };
20328
20900
  const onInitialError = (error) => {
20329
20901
  socket.off("connect", onConnect);
@@ -20354,12 +20926,12 @@ var PalClient = class {
20354
20926
  if (this.closed || socket === void 0 || socket.destroyed || !socket.writable) {
20355
20927
  return Promise.reject(new Error(this.closed ? "Pal client is closed" : "Pal client is not connected"));
20356
20928
  }
20357
- return new Promise((resolve28, reject) => {
20929
+ return new Promise((resolve29, reject) => {
20358
20930
  const timer = setTimeout(() => {
20359
20931
  this.pending.delete(request.requestId);
20360
20932
  reject(new Error(`Pal request '${request.type}' timed out`));
20361
20933
  }, this.requestTimeoutMs);
20362
- this.pending.set(request.requestId, { resolve: resolve28, reject, timer });
20934
+ this.pending.set(request.requestId, { resolve: resolve29, reject, timer });
20363
20935
  let frame;
20364
20936
  try {
20365
20937
  frame = encodeControlFrame(BrokerRequestSchema.parse(request));
@@ -20763,8 +21335,8 @@ var ApprovalBridge = class {
20763
21335
  }
20764
21336
  };
20765
21337
  async function createProductionRuntime(options) {
20766
- const workspace = resolve27(options.workspace ?? process.cwd());
20767
- const home = resolve27(options.home ?? homedir2());
21338
+ const workspace = resolve28(options.workspace ?? process.cwd());
21339
+ const home = resolve28(options.home ?? homedir2());
20768
21340
  const environment = options.environment ?? process.env;
20769
21341
  const ide = new FlavorIdeClient({ workspace, home, environment });
20770
21342
  const ideReady = ide.initialize();
@@ -20988,8 +21560,8 @@ async function createProductionRuntime(options) {
20988
21560
  const registeredProviders = registration.registered;
20989
21561
  let effectiveLlm = registration.effectiveLlm;
20990
21562
  const pluginHost = new PluginHost({
20991
- globalPluginDirs: [join25(home, ".flavor-code", "plugins")],
20992
- projectPluginDirs: [join25(workspace, ".flavor", "plugins")],
21563
+ globalPluginDirs: [join26(home, ".flavor-code", "plugins")],
21564
+ projectPluginDirs: [join26(workspace, ".flavor", "plugins")],
20993
21565
  config,
20994
21566
  // Existing project plugins (including astgraph and superharness) use Node.js
20995
21567
  // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
@@ -21097,7 +21669,7 @@ async function createProductionRuntime(options) {
21097
21669
  const address = palSocketAddress({
21098
21670
  platform: process.platform,
21099
21671
  userScope: home,
21100
- runtimeDir: join25(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
21672
+ runtimeDir: join26(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
21101
21673
  });
21102
21674
  const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
21103
21675
  instanceId: collaboration.instanceId,
@@ -21208,8 +21780,8 @@ async function createProductionRuntime(options) {
21208
21780
  diagnostics.push(`MCP servers could not start: ${message(error)}`);
21209
21781
  });
21210
21782
  const skills = new SkillRegistry({
21211
- globalRoots: [join25(home, ".flavor-code", "skills")],
21212
- projectRoots: [join25(workspace, ".flavor", "skills"), ...pluginSkillRoots],
21783
+ globalRoots: [join26(home, ".flavor-code", "skills")],
21784
+ projectRoots: [join26(workspace, ".flavor", "skills"), ...pluginSkillRoots],
21213
21785
  authorizeResource: async () => true,
21214
21786
  disabledNames: config.skills.disabled
21215
21787
  });
@@ -21217,7 +21789,7 @@ async function createProductionRuntime(options) {
21217
21789
  skillsReady.catch(() => void 0);
21218
21790
  tools.push(createSkillTool(skills), createSkillResourceTool(skills));
21219
21791
  if (options.extraTools !== void 0) tools.push(...options.extraTools);
21220
- const flavor = await optionalText2(join25(workspace, "FLAVOR.md"));
21792
+ const flavor = await optionalText2(join26(workspace, "FLAVOR.md"));
21221
21793
  const instructionBaseline = await workspaceInstructions.baseline();
21222
21794
  let memoryContext;
21223
21795
  if (memoryStore !== void 0) {
@@ -21795,7 +22367,7 @@ async function createProductionRuntime(options) {
21795
22367
  evolveService.toolDefinition(),
21796
22368
  ...mcpTools
21797
22369
  ];
21798
- const loopFlavor = await optionalText2(join25(input.workspace, "FLAVOR.md"));
22370
+ const loopFlavor = await optionalText2(join26(input.workspace, "FLAVOR.md"));
21799
22371
  const loopInstructionBaseline = await loopInstructions.baseline();
21800
22372
  const loopEnvironment = createPromptEnvironment({
21801
22373
  now: /* @__PURE__ */ new Date(),
@@ -22155,6 +22727,22 @@ async function createProductionRuntime(options) {
22155
22727
  modelId: () => harness.subagentModelId,
22156
22728
  notify: (message2) => emitOutput({ type: "notice", message: message2 })
22157
22729
  }, focus, signal),
22730
+ explain: (query, focus, signal, onText) => runExplain({
22731
+ graph: new DesktopAstGraphService(workspace),
22732
+ readFile: async (path) => {
22733
+ const root = resolve28(workspace);
22734
+ const abs = resolve28(root, path);
22735
+ if (abs !== root && !abs.startsWith(root + sep20)) throw new Error(`path outside workspace: ${path}`);
22736
+ return readFile22(abs, "utf8");
22737
+ },
22738
+ history: async (path, limit) => fileHistory(workspace, path, limit),
22739
+ questions,
22740
+ registry,
22741
+ modelId: () => harness.subagentModelId,
22742
+ language,
22743
+ notify: (message2) => emitOutput({ type: "notice", message: message2 }),
22744
+ ...onText === void 0 ? {} : { onText }
22745
+ }, query, focus, signal),
22158
22746
  audit: async (toolFilter) => {
22159
22747
  try {
22160
22748
  const raw = await readFile22(auditLogger.path, "utf8");
@@ -22344,7 +22932,7 @@ ${renderMemoryDocument(entries)}`;
22344
22932
  return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
22345
22933
  }
22346
22934
  try {
22347
- const tokenStore = createFileTokenStore(join25(home, ".flavor-code", "auth.json"));
22935
+ const tokenStore = createFileTokenStore(join26(home, ".flavor-code", "auth.json"));
22348
22936
  const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
22349
22937
  await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
22350
22938
  const oauth = new OAuthCallbackAuthProvider({
@@ -22367,8 +22955,12 @@ ${renderMemoryDocument(entries)}`;
22367
22955
  };
22368
22956
  registry.register(next.providerId, next.apiType === "anthropic" ? new AnthropicModelAdapter({
22369
22957
  ...adapterOptions,
22958
+ ...providerConfig?.thinkingBudget === void 0 ? {} : { thinkingBudget: providerConfig.thinkingBudget },
22370
22959
  ...providerConfig?.claudeClient === true ? { headers: CLAUDE_CLIENT_HEADERS } : {}
22371
- }) : new OpenAIModelAdapter(adapterOptions));
22960
+ }) : new OpenAIModelAdapter({
22961
+ ...adapterOptions,
22962
+ ...providerConfig?.thinkingEffort === void 0 ? {} : { thinkingEffort: providerConfig.thinkingEffort }
22963
+ }));
22372
22964
  effectiveLlm = next;
22373
22965
  mainModel = `${next.providerId}:${next.defaultModel}`;
22374
22966
  childModel = `${next.providerId}:${next.cheapModel}`;
@@ -22387,7 +22979,7 @@ ${renderMemoryDocument(entries)}`;
22387
22979
  }
22388
22980
  },
22389
22981
  async logout() {
22390
- const tokenStore = createFileTokenStore(join25(home, ".flavor-code", "auth.json"));
22982
+ const tokenStore = createFileTokenStore(join26(home, ".flavor-code", "auth.json"));
22391
22983
  const tokens = await tokenStore.load();
22392
22984
  const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
22393
22985
  if (!hadCredentials) {
@@ -22663,8 +23255,8 @@ async function workspaceFingerprint(workspace) {
22663
23255
  let remaining = 5 * 1024 * 1024;
22664
23256
  for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
22665
23257
  if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
22666
- const path = resolve27(workspace, name);
22667
- const relativePath = relative20(resolve27(workspace), path);
23258
+ const path = resolve28(workspace, name);
23259
+ const relativePath = relative20(resolve28(workspace), path);
22668
23260
  if (relativePath === ".." || relativePath.startsWith(`..${sep20}`)) continue;
22669
23261
  hash.update(name);
22670
23262
  if (remaining <= 0) continue;
@@ -22882,7 +23474,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
22882
23474
  const configured = { ...providers };
22883
23475
  if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
22884
23476
  if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
22885
- const oauthTokenStore = createFileTokenStore(join25(home, ".flavor-code", "auth.json"));
23477
+ const oauthTokenStore = createFileTokenStore(join26(home, ".flavor-code", "auth.json"));
22886
23478
  const registered = [];
22887
23479
  let effectiveLlm;
22888
23480
  for (const [name, provider] of Object.entries(configured)) {
@@ -22954,8 +23546,12 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
22954
23546
  };
22955
23547
  const adapter = apiProtocol === "anthropic" ? new AnthropicModelAdapter({
22956
23548
  ...adapterOptions,
23549
+ ...runtimeProvider.thinkingBudget === void 0 ? {} : { thinkingBudget: runtimeProvider.thinkingBudget },
22957
23550
  ...runtimeProvider.claudeClient === true ? { headers: CLAUDE_CLIENT_HEADERS } : {}
22958
- }) : new OpenAIModelAdapter(adapterOptions);
23551
+ }) : new OpenAIModelAdapter({
23552
+ ...adapterOptions,
23553
+ ...runtimeProvider.thinkingEffort === void 0 ? {} : { thinkingEffort: runtimeProvider.thinkingEffort }
23554
+ });
22959
23555
  const cacheProfile = resolveCacheProfile({ apiType: apiProtocol, baseURL: runtimeProvider.baseURL });
22960
23556
  if (apiProtocol === "openai" && isDashScopeBaseURL(runtimeProvider.baseURL)) {
22961
23557
  diagnostics.push(
@@ -23320,6 +23916,8 @@ export {
23320
23916
  executeShell,
23321
23917
  packageVersion,
23322
23918
  SkillRegistry,
23919
+ charWidth,
23920
+ thinkingWindow,
23323
23921
  createTranscriptState,
23324
23922
  transcriptReducer,
23325
23923
  WorkspaceCheckpointStore,