nolo-cli 0.7.0 → 0.8.0-alpha.5

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.
Files changed (2) hide show
  1. package/index.js +1134 -436
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1209,8 +1209,29 @@ var init_levelLockError = __esm({
1209
1209
  }
1210
1210
  });
1211
1211
 
1212
+ // packages/cli/levelLazyShim.mjs
1213
+ import { createRequire } from "node:module";
1214
+ function ensureLevel() {
1215
+ if (!_RealLevel) {
1216
+ _RealLevel = _require("level").Level;
1217
+ }
1218
+ return _RealLevel;
1219
+ }
1220
+ var _require, _RealLevel, Level;
1221
+ var init_levelLazyShim = __esm({
1222
+ "packages/cli/levelLazyShim.mjs"() {
1223
+ "use strict";
1224
+ _require = createRequire(import.meta.url);
1225
+ Level = class {
1226
+ constructor(...args2) {
1227
+ const RealLevel = ensureLevel();
1228
+ return new RealLevel(...args2);
1229
+ }
1230
+ };
1231
+ }
1232
+ });
1233
+
1212
1234
  // packages/database-engine/levelAuthorityStore.ts
1213
- import { Level } from "level";
1214
1235
  function createLevelAuthorityStore(source) {
1215
1236
  const levelDb = typeof source === "string" ? new Level(source, { valueEncoding: safeJsonEncoding }) : source;
1216
1237
  const writeBatchOps = async (ops) => {
@@ -1281,6 +1302,7 @@ var safeJsonEncoding;
1281
1302
  var init_levelAuthorityStore = __esm({
1282
1303
  "packages/database-engine/levelAuthorityStore.ts"() {
1283
1304
  "use strict";
1305
+ init_levelLazyShim();
1284
1306
  safeJsonEncoding = {
1285
1307
  name: "safe-json",
1286
1308
  format: "utf8",
@@ -16077,12 +16099,14 @@ function buildLocalToolExecutors(args2) {
16077
16099
  })();
16078
16100
  const question = String(parsedArgs.question ?? "").trim();
16079
16101
  const choices = Array.isArray(parsedArgs.choices) ? parsedArgs.choices : [];
16102
+ const questions = Array.isArray(parsedArgs.questions) ? parsedArgs.questions : void 0;
16080
16103
  const blocking = parsedArgs.blocking !== false;
16081
- if (!question || choices.length === 0) {
16104
+ const hasQuestions = questions && questions.length > 0;
16105
+ if (!hasQuestions && (!question || choices.length === 0)) {
16082
16106
  return {
16083
16107
  content: JSON.stringify({
16084
16108
  error: "ui_ask_choice",
16085
- detail: "ui_ask_choice \u9700\u8981 question \u548C\u81F3\u5C11\u4E00\u4E2A choice\u3002"
16109
+ detail: "ui_ask_choice \u9700\u8981 question+choices \u6216 questions\u3002"
16086
16110
  }),
16087
16111
  metadata: { uiAskChoice: true, error: true }
16088
16112
  };
@@ -16090,10 +16114,28 @@ function buildLocalToolExecutors(args2) {
16090
16114
  if (args2.requestUserChoice) {
16091
16115
  try {
16092
16116
  const result = await args2.requestUserChoice({
16093
- question,
16094
- choices,
16095
- blocking
16117
+ question: hasQuestions ? questions[0].question : question,
16118
+ choices: hasQuestions ? questions[0].choices : choices,
16119
+ blocking,
16120
+ ...hasQuestions ? { questions } : {}
16096
16121
  });
16122
+ if (result.kind === "multi-submitted") {
16123
+ return {
16124
+ content: JSON.stringify({
16125
+ type: "ui_ask_choice",
16126
+ question,
16127
+ choices,
16128
+ blocking,
16129
+ ...hasQuestions ? { questions } : {},
16130
+ answers: result.answers,
16131
+ selected: {
16132
+ label: result.answers.map((a) => a.userMessage).join(", "),
16133
+ userMessage: result.userMessage
16134
+ }
16135
+ }),
16136
+ metadata: { uiAskChoice: true, resolved: true }
16137
+ };
16138
+ }
16097
16139
  if (result.kind === "selected") {
16098
16140
  return {
16099
16141
  content: JSON.stringify({
@@ -16101,6 +16143,7 @@ function buildLocalToolExecutors(args2) {
16101
16143
  question,
16102
16144
  choices,
16103
16145
  blocking,
16146
+ ...hasQuestions ? { questions } : {},
16104
16147
  selected: {
16105
16148
  label: result.label,
16106
16149
  userMessage: result.userMessage
@@ -16115,6 +16158,7 @@ function buildLocalToolExecutors(args2) {
16115
16158
  question,
16116
16159
  choices,
16117
16160
  blocking,
16161
+ ...hasQuestions ? { questions } : {},
16118
16162
  selected: { label: "", userMessage: "" },
16119
16163
  cancelled: true
16120
16164
  }),
@@ -16128,7 +16172,8 @@ function buildLocalToolExecutors(args2) {
16128
16172
  type: "ui_ask_choice",
16129
16173
  question,
16130
16174
  choices,
16131
- blocking
16175
+ blocking,
16176
+ ...hasQuestions ? { questions } : {}
16132
16177
  }),
16133
16178
  metadata: { uiAskChoice: true }
16134
16179
  };
@@ -37355,11 +37400,24 @@ var init_dialogUrl = __esm({
37355
37400
 
37356
37401
  // packages/ai/tools/uiAskChoiceTool.ts
37357
37402
  async function uiAskChoiceFunc(args2, _thunkApi) {
37403
+ const blocking = args2?.blocking !== false;
37404
+ if (Array.isArray(args2?.questions) && args2.questions.length > 0) {
37405
+ const firstQ = args2.questions[0];
37406
+ return {
37407
+ rawData: {
37408
+ type: "ui_ask_choice",
37409
+ question: firstQ?.question ?? "",
37410
+ choices: firstQ?.choices ?? [],
37411
+ blocking,
37412
+ questions: args2.questions
37413
+ },
37414
+ displayData: args2.questions.map((q) => q.question).join(" / ")
37415
+ };
37416
+ }
37358
37417
  const question = String(args2?.question ?? "").trim();
37359
37418
  const choices = Array.isArray(args2?.choices) ? args2.choices : [];
37360
- const blocking = args2?.blocking !== false;
37361
37419
  if (!question || choices.length === 0) {
37362
- throw new Error("ui_ask_choice \u9700\u8981 question \u548C\u81F3\u5C11\u4E00\u4E2A choice\u3002");
37420
+ throw new Error("ui_ask_choice \u9700\u8981 question+choices \u6216 questions\u3002");
37363
37421
  }
37364
37422
  return {
37365
37423
  rawData: {
@@ -37408,14 +37466,20 @@ var init_uiAskChoiceTool = __esm({
37408
37466
  "\u5728\u4EE3\u7801\u534F\u4F5C\u573A\u666F\u4E0B\u7684\u63A8\u8350\u7528\u6CD5\uFF1A",
37409
37467
  "- \u5982\u679C\u4F60\u5DF2\u7ECF\u5728\u672C\u8F6E assistant \u7684 content \u4E2D\u7ED9\u51FA\u4E86\u5206\u6790\u3001\u8BF4\u660E\u6216\u591A\u4E2A\u5907\u9009\u65B9\u6848\uFF0C\u53EF\u4EE5\u5728\u540C\u4E00\u8F6E\u6D88\u606F\u7684\u7ED3\u5C3E\u8C03\u7528\u672C\u5DE5\u5177\uFF0C\u8BF7\u7528\u6237\u9009\u62E9\u201C\u662F\u5426\u6839\u636E\u4E0A\u8FF0\u65B9\u6848\u5F00\u59CB\u5B9E\u9645\u4FEE\u6539\u4EE3\u7801\u201D\u6216\u201C\u4F18\u5148\u6267\u884C\u54EA\u4E00\u4E2A\u65B9\u6848/\u6B65\u9AA4\u201D\u3002",
37410
37468
  "- \u5728\u8FD9\u79CD\u60C5\u51B5\u4E0B\uFF0Cquestion \u5B57\u6BB5\u901A\u5E38\u662F\u4E00\u4E2A\u7B80\u77ED\u7684\u95EE\u9898\uFF08\u4F8B\u5982\uFF1A\u201C\u63A5\u4E0B\u6765\u4F60\u5E0C\u671B\u6211\u6309\u54EA\u4E2A\u65B9\u6848\u6765\u5177\u4F53\u4FEE\u6539\u4EE3\u7801\uFF1F\u201D\uFF09\uFF0C\u800C\u8BE6\u7EC6\u7684\u89E3\u91CA\u548C\u65B9\u6848\u63CF\u8FF0\u653E\u5728 content \u4E2D\u3002",
37411
- "- \u5982\u679C\u5F53\u524D\u5BF9\u8BDD\u53EA\u9700\u8981\u4E00\u4E2A\u7B80\u5355\u7684\u9009\u62E9\uFF0C\u800C\u4E0D\u9700\u8981\u989D\u5916\u89E3\u91CA\uFF0C\u4F60\u4E5F\u53EF\u4EE5\u4E0D\u8F93\u51FA\u989D\u5916\u7684 content\uFF0C\u800C\u53EA\u8C03\u7528\u672C\u5DE5\u5177\uFF0C\u7531 question \u5B57\u6BB5\u76F4\u63A5\u5411\u7528\u6237\u63D0\u95EE\u3002"
37469
+ "- \u5982\u679C\u5F53\u524D\u5BF9\u8BDD\u53EA\u9700\u8981\u4E00\u4E2A\u7B80\u5355\u7684\u9009\u62E9\uFF0C\u800C\u4E0D\u9700\u8981\u989D\u5916\u89E3\u91CA\uFF0C\u4F60\u4E5F\u53EF\u4EE5\u4E0D\u8F93\u51FA\u989D\u5916\u7684 content\uFF0C\u800C\u53EA\u8C03\u7528\u672C\u5DE5\u5177\uFF0C\u7531 question \u5B57\u6BB5\u76F4\u63A5\u5411\u7528\u6237\u63D0\u95EE\u3002",
37470
+ "",
37471
+ "\u591A\u95EE\u9898 & \u591A\u9009\u652F\u6301\uFF1A",
37472
+ "- \u5F53\u4F60\u9700\u8981\u4E00\u6B21\u95EE\u591A\u4E2A\u95EE\u9898\u65F6\uFF0C\u4F7F\u7528 questions \u6570\u7EC4\u4EE3\u66FF question+choices\u3002",
37473
+ "- \u6BCF\u4E2A question \u53EF\u4EE5\u8BBE\u7F6E multiSelect: true \u5141\u8BB8\u591A\u9009\u3002",
37474
+ "- \u6BCF\u4E2A question \u53EF\u4EE5\u8BBE\u7F6E allowOther: false \u9690\u85CF\u201C\u5176\u4ED6\u201D\u8F93\u5165\u6846\u3002",
37475
+ "- \u6BCF\u4E2A choice \u53EF\u4EE5\u52A0 detail \u5B57\u6BB5\u63D0\u4F9B\u66F4\u957F\u7684\u63CF\u8FF0\u3002"
37412
37476
  ].join("\n"),
37413
37477
  parameters: {
37414
37478
  type: "object",
37415
37479
  properties: {
37416
37480
  question: {
37417
37481
  type: "string",
37418
- description: "\u5C55\u793A\u7ED9\u7528\u6237\u7684\u95EE\u9898\u6587\u6848\uFF0C\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u8981\u505A\u4EC0\u4E48\u9009\u62E9\u3002\u4F8B\u5982\uFF1A\u201C\u63A5\u4E0B\u6765\u4F60\u66F4\u5E0C\u671B\u6211\u5E2E\u4F60\u505A\u54EA\u4EF6\u4E8B\uFF1F\u201D\u3002\u4E0D\u8981\u5728\u540C\u4E00\u8F6E assistant \u666E\u901A\u6587\u672C\u91CC\u91CD\u590D\u8FD9\u53E5\u8BDD\u3002"
37482
+ description: "\u5C55\u793A\u7ED9\u7528\u6237\u7684\u95EE\u9898\u6587\u6848\uFF08\u5355\u95EE\u9898\u6A21\u5F0F\uFF09\u3002\u4E0E questions \u4E8C\u9009\u4E00\u3002"
37419
37483
  },
37420
37484
  choices: {
37421
37485
  type: "array",
@@ -37431,6 +37495,10 @@ var init_uiAskChoiceTool = __esm({
37431
37495
  type: "string",
37432
37496
  description: "\u663E\u793A\u7ED9\u7528\u6237\u770B\u7684\u6309\u94AE\u6587\u5B57\u3002\u4F8B\u5982\uFF1A\u201C\u751F\u6210\u672C\u5468\u5468\u62A5\u201D\u3002"
37433
37497
  },
37498
+ detail: {
37499
+ type: "string",
37500
+ description: "\u9009\u9879\u7684\u8865\u5145\u63CF\u8FF0\uFF0C\u663E\u793A\u5728 label \u4E0B\u65B9\u3002\u53EF\u9009\u3002"
37501
+ },
37434
37502
  userMessage: {
37435
37503
  type: "string",
37436
37504
  description: [
@@ -37443,6 +37511,56 @@ var init_uiAskChoiceTool = __esm({
37443
37511
  required: ["id", "label"]
37444
37512
  }
37445
37513
  },
37514
+ questions: {
37515
+ type: "array",
37516
+ description: "\u591A\u95EE\u9898\u6A21\u5F0F\uFF1A\u4E00\u6B21\u95EE\u591A\u4E2A\u95EE\u9898\uFF0C\u6BCF\u4E2A\u95EE\u9898\u72EC\u7ACB\u6E32\u67D3\u4E3A\u4E00\u4E2A tab\u3002\u4E0E question+choices \u4E8C\u9009\u4E00\u3002",
37517
+ items: {
37518
+ type: "object",
37519
+ properties: {
37520
+ id: {
37521
+ type: "string",
37522
+ description: "\u95EE\u9898\u6807\u8BC6\uFF0C\u7528\u4E8E\u7ED3\u679C\u5BF9\u5E94\u3002"
37523
+ },
37524
+ question: {
37525
+ type: "string",
37526
+ description: "\u95EE\u9898\u6587\u6848\u3002"
37527
+ },
37528
+ choices: {
37529
+ type: "array",
37530
+ description: "\u8BE5\u95EE\u9898\u7684\u5907\u9009\u9879\u3002",
37531
+ items: {
37532
+ type: "object",
37533
+ properties: {
37534
+ id: { type: "string" },
37535
+ label: { type: "string" },
37536
+ detail: {
37537
+ type: "string",
37538
+ description: "\u8865\u5145\u63CF\u8FF0\u3002"
37539
+ },
37540
+ userMessage: { type: "string" }
37541
+ },
37542
+ required: ["id", "label"]
37543
+ }
37544
+ },
37545
+ multiSelect: {
37546
+ type: "boolean",
37547
+ description: "\u5141\u8BB8\u591A\u9009\u3002\u9ED8\u8BA4 false\u3002",
37548
+ default: false
37549
+ },
37550
+ allowOther: {
37551
+ type: "boolean",
37552
+ description: "\u663E\u793A\u201C\u5176\u4ED6\u201D\u81EA\u7531\u8F93\u5165\u884C\u3002\u9ED8\u8BA4 true\u3002",
37553
+ default: true
37554
+ },
37555
+ required: {
37556
+ type: "boolean",
37557
+ description: "\u662F\u5426\u5FC5\u987B\u56DE\u7B54\u624D\u80FD\u63D0\u4EA4\u3002\u9ED8\u8BA4 true\u3002",
37558
+ default: true
37559
+ }
37560
+ },
37561
+ required: ["id", "question", "choices"]
37562
+ }
37563
+ },
37446
37564
  blocking: {
37447
37565
  type: "boolean",
37448
37566
  description: [
@@ -37452,7 +37570,7 @@ var init_uiAskChoiceTool = __esm({
37452
37570
  default: true
37453
37571
  }
37454
37572
  },
37455
- required: ["question", "choices"]
37573
+ required: []
37456
37574
  }
37457
37575
  };
37458
37576
  }
@@ -93696,7 +93814,7 @@ function formatCompactToolLine(event, pending, colorEnabled) {
93696
93814
  const label = rawArgs ? `${rawLabel} ${rawArgs}` : rawLabel;
93697
93815
  if (event.type === "tool-error") {
93698
93816
  const message = clip5(event.message ?? t("toolFailed"), 96);
93699
- return formatToolTraceLine(` \u25B8 ${label} \u2717 ${message}`, colorEnabled, "error");
93817
+ return formatToolTraceLine(`\u25B8 ${label} \u2717 ${message}`, colorEnabled, "error");
93700
93818
  }
93701
93819
  if (event.type === "tool-result" && (event.toolName === "ui_ask_choice" || event.metadata?.uiAskChoice)) {
93702
93820
  const block = formatUiAskChoiceBlock(event, colorEnabled);
@@ -93708,7 +93826,7 @@ function formatCompactToolLine(event, pending, colorEnabled) {
93708
93826
  const marker = failed ? "\u2717" : isNeedsActionToolResult(event) ? "!" : "\u2713";
93709
93827
  let mainLine;
93710
93828
  if (!colorEnabled) {
93711
- mainLine = ` \u25B8 ${label} ${marker}${suffix}
93829
+ mainLine = `\u25B8 ${label} ${marker}${suffix}
93712
93830
  `;
93713
93831
  } else {
93714
93832
  const chromePointer = themeText("\u25B8", "chrome", true);
@@ -93716,7 +93834,7 @@ function formatCompactToolLine(event, pending, colorEnabled) {
93716
93834
  const argsPart = rawArgs ? ` ${dimCliText(rawArgs, true)}` : "";
93717
93835
  const statusToken = failed ? themeText("\u2717", "danger", true) : isNeedsActionToolResult(event) ? themeText("!", "warning", true) : themeText("\u2713", "success", true);
93718
93836
  const suffixPart = hint.inline ? ` ${dimCliText(hint.inline, true)}` : "";
93719
- mainLine = ` ${chromePointer} ${mutedLabel}${argsPart} ${statusToken}${suffixPart}
93837
+ mainLine = `${chromePointer} ${mutedLabel}${argsPart} ${statusToken}${suffixPart}
93720
93838
  `;
93721
93839
  }
93722
93840
  if (!hint.detail) return mainLine;
@@ -93726,28 +93844,28 @@ function formatCompactToolLine(event, pending, colorEnabled) {
93726
93844
  function formatEditDetailLine(line, colorEnabled) {
93727
93845
  const marker = line.slice(0, 2);
93728
93846
  const rest = line.slice(2);
93729
- if (!colorEnabled) return ` ${line}
93847
+ if (!colorEnabled) return ` ${line}
93730
93848
  `;
93731
93849
  const diff = diffLineSequences();
93732
93850
  if (!diff) {
93733
93851
  const color = marker === "- " ? "red" : marker === "+ " ? "green" : void 0;
93734
93852
  if (color) {
93735
- return ` ${styleCliText(marker, color, true)}${dimCliText(rest, true)}
93853
+ return ` ${styleCliText(marker, color, true)}${dimCliText(rest, true)}
93736
93854
  `;
93737
93855
  }
93738
- return ` ${dimCliText(line, true)}
93856
+ return ` ${dimCliText(line, true)}
93739
93857
  `;
93740
93858
  }
93741
93859
  const RESET2 = "\x1B[0m";
93742
93860
  if (marker === "- ") {
93743
- return ` ${diff.removed.bg}${diff.removed.fg}- ${rest}${RESET2}
93861
+ return ` ${diff.removed.bg}${diff.removed.fg}- ${rest}${RESET2}
93744
93862
  `;
93745
93863
  }
93746
93864
  if (marker === "+ ") {
93747
- return ` ${diff.added.bg}${diff.added.fg}+ ${rest}${RESET2}
93865
+ return ` ${diff.added.bg}${diff.added.fg}+ ${rest}${RESET2}
93748
93866
  `;
93749
93867
  }
93750
- return ` ${dimCliText(line, true)}
93868
+ return ` ${dimCliText(line, true)}
93751
93869
  `;
93752
93870
  }
93753
93871
  function createToolEventFormatter(mode, colorEnabled = resolveCliColorEnabled()) {
@@ -97769,7 +97887,6 @@ var init_offlineMarxistsAgentCommand = __esm({
97769
97887
  import fs3 from "node:fs";
97770
97888
  import path6 from "node:path";
97771
97889
  import os from "node:os";
97772
- import { Level as Level2 } from "level";
97773
97890
  async function clearAgentKeysFromLocalLevelDbs(args2) {
97774
97891
  const home = os.homedir();
97775
97892
  const noloHome = process.env.NOLO_HOME?.trim() || path6.join(home, ".nolo");
@@ -97783,7 +97900,7 @@ async function clearAgentKeysFromLocalLevelDbs(args2) {
97783
97900
  const cleaned = [];
97784
97901
  for (const dbPath of dbPaths) {
97785
97902
  try {
97786
- const db = new Level2(dbPath, { valueEncoding: "json" });
97903
+ const db = new Level(dbPath, { valueEncoding: "json" });
97787
97904
  await db.open();
97788
97905
  try {
97789
97906
  for (const key of args2.keys) {
@@ -97804,6 +97921,7 @@ async function clearAgentKeysFromLocalLevelDbs(args2) {
97804
97921
  var init_localLevelDbCleanup = __esm({
97805
97922
  "packages/cli/localLevelDbCleanup.ts"() {
97806
97923
  "use strict";
97924
+ init_levelLazyShim();
97807
97925
  }
97808
97926
  });
97809
97927
 
@@ -99870,7 +99988,6 @@ var init_memoryAuthorityStore = __esm({
99870
99988
  // packages/database-engine/serverStoreFactory.ts
99871
99989
  import fs4 from "fs";
99872
99990
  import path7 from "path";
99873
- import { Level as Level3 } from "level";
99874
99991
  function ensureServerDbDirectory(dbPath) {
99875
99992
  const dir = path7.dirname(dbPath);
99876
99993
  if (!fs4.existsSync(dir)) {
@@ -99878,7 +99995,7 @@ function ensureServerDbDirectory(dbPath) {
99878
99995
  }
99879
99996
  }
99880
99997
  function createLevelBackedAuthorityStore(dbPath) {
99881
- const levelDb = new Level3(dbPath, {
99998
+ const levelDb = new Level(dbPath, {
99882
99999
  valueEncoding: safeJsonEncoding2
99883
100000
  });
99884
100001
  return createLevelAuthorityStore(levelDb);
@@ -99930,6 +100047,7 @@ var safeJsonEncoding2;
99930
100047
  var init_serverStoreFactory = __esm({
99931
100048
  "packages/database-engine/serverStoreFactory.ts"() {
99932
100049
  "use strict";
100050
+ init_levelLazyShim();
99933
100051
  init_trimmedLowercaseString();
99934
100052
  init_legacyServerDb();
99935
100053
  init_levelAuthorityStore();
@@ -106909,13 +107027,73 @@ function renderDialogOverflow(text, colorEnabled = resolveCliColorEnabled()) {
106909
107027
  function renderDialogCommand(text, colorEnabled = resolveCliColorEnabled()) {
106910
107028
  return themeText(` ${text}`, "danger", colorEnabled);
106911
107029
  }
106912
- var DIALOG_CURSOR;
107030
+ var DIALOG_CURSOR, DIALOG_CHECKED, DIALOG_UNCHECKED;
106913
107031
  var init_dialogFrame = __esm({
106914
107032
  "packages/cli/tui/dialogFrame.ts"() {
106915
107033
  "use strict";
106916
107034
  init_terminalStyles();
106917
107035
  init_theme();
106918
107036
  DIALOG_CURSOR = "\u276F";
107037
+ DIALOG_CHECKED = "\u25C9";
107038
+ DIALOG_UNCHECKED = "\u25CB";
107039
+ }
107040
+ });
107041
+
107042
+ // packages/cli/tui/tuiScrollbar.ts
107043
+ function parseScrollAction(sequence) {
107044
+ const mouse = SGR_MOUSE_REGEX.exec(sequence);
107045
+ if (mouse) {
107046
+ const button = Number(mouse[1]);
107047
+ if ((button & 64) === 0) return null;
107048
+ if ((button & 2) !== 0) return null;
107049
+ return (button & 1) !== 0 ? "wheel-down" : "wheel-up";
107050
+ }
107051
+ switch (sequence) {
107052
+ case "\x1B[5~":
107053
+ return "page-up";
107054
+ case "\x1B[6~":
107055
+ return "page-down";
107056
+ case "\x1B[5;2~":
107057
+ case "\x1B[5;5~":
107058
+ return "half-page-up";
107059
+ case "\x1B[6;2~":
107060
+ case "\x1B[6;5~":
107061
+ return "half-page-down";
107062
+ case "\x1B[H":
107063
+ case "\x1B[1~":
107064
+ case "\x1B[7~":
107065
+ return "top";
107066
+ case "\x1B[F":
107067
+ case "\x1B[4~":
107068
+ case "\x1B[8~":
107069
+ return "bottom";
107070
+ default:
107071
+ return null;
107072
+ }
107073
+ }
107074
+ function renderScrollbarRow(rowIndex, visibleHeight, totalLines, scrollTop) {
107075
+ if (totalLines <= visibleHeight) return " ";
107076
+ const trackHeight = visibleHeight;
107077
+ const thumbSize = Math.max(
107078
+ 1,
107079
+ Math.floor(visibleHeight * visibleHeight / totalLines)
107080
+ );
107081
+ const maxScrollTop = totalLines - visibleHeight;
107082
+ const thumbTop = Math.floor(
107083
+ scrollTop / maxScrollTop * (trackHeight - thumbSize)
107084
+ );
107085
+ const thumbBottom = thumbTop + thumbSize;
107086
+ if (rowIndex >= thumbTop && rowIndex < thumbBottom) {
107087
+ return "\u2588";
107088
+ }
107089
+ return "\u2502";
107090
+ }
107091
+ var SGR_MOUSE_REGEX, WHEEL_SCROLL_LINES;
107092
+ var init_tuiScrollbar = __esm({
107093
+ "packages/cli/tui/tuiScrollbar.ts"() {
107094
+ "use strict";
107095
+ SGR_MOUSE_REGEX = /^\x1b\[<(\d+);\d+;\d+[Mm]$/;
107096
+ WHEEL_SCROLL_LINES = 3;
106919
107097
  }
106920
107098
  });
106921
107099
 
@@ -107064,6 +107242,7 @@ async function runSelectDialog(args2) {
107064
107242
  const output2 = args2.output ?? process.stdout;
107065
107243
  const input2 = args2.input ?? process.stdin;
107066
107244
  const readKey = args2.readKey ?? createRawKeyReader(input2);
107245
+ output2.write("\x1B[?1006h\x1B[?1000h");
107067
107246
  const wasRaw = Boolean(input2.isTTY && input2.isRaw);
107068
107247
  let renderedLineCount = 0;
107069
107248
  const bottomAnchored = Boolean(args2.bottomAnchored && args2.bottomRow);
@@ -107127,6 +107306,18 @@ async function runSelectDialog(args2) {
107127
107306
  if (sequence == null) {
107128
107307
  return { kind: "cancelled" };
107129
107308
  }
107309
+ const scrollAction = parseScrollAction(sequence);
107310
+ if (scrollAction === "wheel-up" || scrollAction === "wheel-down") {
107311
+ selectedIndex = Math.min(
107312
+ Math.max(
107313
+ selectedIndex + (scrollAction === "wheel-up" ? -1 : 1),
107314
+ 0
107315
+ ),
107316
+ items.length - 1
107317
+ );
107318
+ paint();
107319
+ continue;
107320
+ }
107130
107321
  if (isCancel(sequence)) {
107131
107322
  return { kind: "cancelled" };
107132
107323
  }
@@ -107145,6 +107336,7 @@ async function runSelectDialog(args2) {
107145
107336
  }
107146
107337
  }
107147
107338
  } finally {
107339
+ output2.write("\x1B[?1000l\x1B[?1006l");
107148
107340
  resizeTarget.off?.("resize", onOutputResize);
107149
107341
  readKey.dispose?.();
107150
107342
  if (input2.isTTY) {
@@ -107169,6 +107361,7 @@ var init_selectDialog = __esm({
107169
107361
  "use strict";
107170
107362
  init_dialogFrame();
107171
107363
  init_i18n2();
107364
+ init_tuiScrollbar();
107172
107365
  CSI_ARROW_UP = "\x1B[A";
107173
107366
  CSI_ARROW_DOWN = "\x1B[B";
107174
107367
  CSI_ARROW_UP_APP = "\x1BOA";
@@ -107257,6 +107450,509 @@ var init_confirmDialog = __esm({
107257
107450
  }
107258
107451
  });
107259
107452
 
107453
+ // packages/ai/tools/askChoiceState.ts
107454
+ function normalizeAskChoiceArgs(args2) {
107455
+ const blocking = args2.blocking !== false;
107456
+ if (Array.isArray(args2.questions) && args2.questions.length > 0) {
107457
+ return {
107458
+ questions: args2.questions.map(
107459
+ (q, i) => normalizeQuestion(q, i)
107460
+ ),
107461
+ blocking
107462
+ };
107463
+ }
107464
+ const question = String(args2.question ?? "").trim();
107465
+ const choices = Array.isArray(args2.choices) ? args2.choices : [];
107466
+ if (!question || choices.length === 0) {
107467
+ return { questions: [], blocking };
107468
+ }
107469
+ return {
107470
+ questions: [
107471
+ normalizeQuestion(
107472
+ { question, choices, multiSelect: false, allowOther: true, required: true },
107473
+ 0
107474
+ )
107475
+ ],
107476
+ blocking
107477
+ };
107478
+ }
107479
+ function normalizeQuestion(raw, index) {
107480
+ return {
107481
+ id: String(raw?.id ?? `q${index}`),
107482
+ question: String(raw?.question ?? "").trim(),
107483
+ choices: (Array.isArray(raw?.choices) ? raw.choices : []).map(
107484
+ (c, ci) => ({
107485
+ id: String(c?.id ?? `c${ci}`),
107486
+ label: String(c?.label ?? "").trim(),
107487
+ ...typeof c?.detail === "string" && c.detail.trim() ? { detail: c.detail.trim() } : {},
107488
+ ...typeof c?.userMessage === "string" && c.userMessage.trim() ? { userMessage: c.userMessage.trim() } : {}
107489
+ })
107490
+ ),
107491
+ multiSelect: raw?.multiSelect === true,
107492
+ allowOther: raw?.allowOther !== false,
107493
+ required: raw?.required !== false
107494
+ };
107495
+ }
107496
+ function createInitialAskChoiceState(questions) {
107497
+ return {
107498
+ questions,
107499
+ activeIndex: 0,
107500
+ questionStates: questions.map(() => ({
107501
+ cursorIndex: 0,
107502
+ selectedIds: [],
107503
+ pickedId: null,
107504
+ otherText: "",
107505
+ otherFocused: false
107506
+ })),
107507
+ phase: "active"
107508
+ };
107509
+ }
107510
+ function askChoiceReducer(state4, action3) {
107511
+ if (state4.phase !== "active") return state4;
107512
+ switch (action3.type) {
107513
+ case "CANCEL":
107514
+ return { ...state4, phase: "cancelled" };
107515
+ case "SUBMIT": {
107516
+ if (!canSubmit(state4)) return state4;
107517
+ return { ...state4, phase: "submitted" };
107518
+ }
107519
+ case "SWITCH_TAB": {
107520
+ const idx = clamp3(action3.index, 0, state4.questions.length - 1);
107521
+ return { ...state4, activeIndex: idx };
107522
+ }
107523
+ case "NEXT_TAB": {
107524
+ const idx = Math.min(state4.activeIndex + 1, state4.questions.length - 1);
107525
+ return { ...state4, activeIndex: idx };
107526
+ }
107527
+ case "PREV_TAB": {
107528
+ const idx = Math.max(state4.activeIndex - 1, 0);
107529
+ return { ...state4, activeIndex: idx };
107530
+ }
107531
+ case "MOVE_CURSOR": {
107532
+ const qs = state4.questionStates[state4.activeIndex];
107533
+ const q = state4.questions[state4.activeIndex];
107534
+ const maxIndex = q.allowOther ? q.choices.length : q.choices.length - 1;
107535
+ const next = clamp3(qs.cursorIndex + action3.delta, 0, maxIndex);
107536
+ const newQs = [...state4.questionStates];
107537
+ newQs[state4.activeIndex] = {
107538
+ ...qs,
107539
+ cursorIndex: next,
107540
+ otherFocused: false
107541
+ };
107542
+ return { ...state4, questionStates: newQs };
107543
+ }
107544
+ case "TOGGLE_AT_CURSOR": {
107545
+ const qs = state4.questionStates[state4.activeIndex];
107546
+ const q = state4.questions[state4.activeIndex];
107547
+ if (!q.multiSelect) return state4;
107548
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
107549
+ if (isOtherRow) {
107550
+ const newQs2 = [...state4.questionStates];
107551
+ newQs2[state4.activeIndex] = { ...qs, otherFocused: !qs.otherFocused };
107552
+ return { ...state4, questionStates: newQs2 };
107553
+ }
107554
+ const choiceId = q.choices[qs.cursorIndex]?.id;
107555
+ if (!choiceId) return state4;
107556
+ const has = qs.selectedIds.includes(choiceId);
107557
+ const newSelected = has ? qs.selectedIds.filter((id) => id !== choiceId) : [...qs.selectedIds, choiceId];
107558
+ const newQs = [...state4.questionStates];
107559
+ newQs[state4.activeIndex] = { ...qs, selectedIds: newSelected };
107560
+ return { ...state4, questionStates: newQs };
107561
+ }
107562
+ case "SELECT_AT_CURSOR": {
107563
+ const qs = state4.questionStates[state4.activeIndex];
107564
+ const q = state4.questions[state4.activeIndex];
107565
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
107566
+ if (isOtherRow) {
107567
+ const newQs2 = [...state4.questionStates];
107568
+ newQs2[state4.activeIndex] = { ...qs, otherFocused: true };
107569
+ return { ...state4, questionStates: newQs2 };
107570
+ }
107571
+ const choiceId = q.choices[qs.cursorIndex]?.id;
107572
+ if (!choiceId) return state4;
107573
+ if (q.multiSelect) {
107574
+ const has = qs.selectedIds.includes(choiceId);
107575
+ const newSelected = has ? qs.selectedIds.filter((id) => id !== choiceId) : [...qs.selectedIds, choiceId];
107576
+ const newQs2 = [...state4.questionStates];
107577
+ newQs2[state4.activeIndex] = { ...qs, selectedIds: newSelected };
107578
+ return { ...state4, questionStates: newQs2 };
107579
+ }
107580
+ const newQs = [...state4.questionStates];
107581
+ newQs[state4.activeIndex] = { ...qs, pickedId: choiceId, otherFocused: false };
107582
+ if (state4.questions.length === 1) {
107583
+ return {
107584
+ ...state4,
107585
+ questionStates: newQs,
107586
+ phase: "submitted"
107587
+ };
107588
+ }
107589
+ const nextState = { ...state4, questionStates: newQs };
107590
+ const isLastTab = state4.activeIndex >= state4.questions.length - 1;
107591
+ if (isLastTab && canSubmit(nextState)) {
107592
+ return { ...nextState, phase: "submitted" };
107593
+ }
107594
+ const nextTab = Math.min(
107595
+ state4.activeIndex + 1,
107596
+ state4.questions.length - 1
107597
+ );
107598
+ return { ...nextState, activeIndex: nextTab };
107599
+ }
107600
+ case "FOCUS_OTHER": {
107601
+ const qs = state4.questionStates[state4.activeIndex];
107602
+ const newQs = [...state4.questionStates];
107603
+ newQs[state4.activeIndex] = { ...qs, otherFocused: true };
107604
+ return { ...state4, questionStates: newQs };
107605
+ }
107606
+ case "BLUR_OTHER": {
107607
+ const qs = state4.questionStates[state4.activeIndex];
107608
+ const newQs = [...state4.questionStates];
107609
+ newQs[state4.activeIndex] = { ...qs, otherFocused: false };
107610
+ return { ...state4, questionStates: newQs };
107611
+ }
107612
+ case "SET_OTHER_TEXT": {
107613
+ const qs = state4.questionStates[state4.activeIndex];
107614
+ const newQs = [...state4.questionStates];
107615
+ newQs[state4.activeIndex] = { ...qs, otherText: action3.text };
107616
+ return { ...state4, questionStates: newQs };
107617
+ }
107618
+ default:
107619
+ return state4;
107620
+ }
107621
+ }
107622
+ function isQuestionAnswered(q, qs) {
107623
+ if (!q.required) return true;
107624
+ const hasSelection = q.multiSelect ? qs.selectedIds.length > 0 : qs.pickedId !== null;
107625
+ const hasOther = q.allowOther && qs.otherText.trim().length > 0;
107626
+ return hasSelection || hasOther;
107627
+ }
107628
+ function canSubmit(state4) {
107629
+ return state4.questions.every(
107630
+ (q, i) => isQuestionAnswered(q, state4.questionStates[i])
107631
+ );
107632
+ }
107633
+ function buildAskChoiceResult(state4) {
107634
+ if (state4.phase === "cancelled") return { kind: "cancelled" };
107635
+ if (state4.phase !== "submitted") return { kind: "cancelled" };
107636
+ const answers = state4.questions.map((q, i) => {
107637
+ const qs = state4.questionStates[i];
107638
+ const selectedIds = q.multiSelect ? qs.selectedIds : qs.pickedId ? [qs.pickedId] : [];
107639
+ const otherText = qs.otherText.trim();
107640
+ const parts = [];
107641
+ for (const id of selectedIds) {
107642
+ const choice = q.choices.find((c) => c.id === id);
107643
+ if (choice) {
107644
+ parts.push(choice.userMessage || choice.label);
107645
+ }
107646
+ }
107647
+ if (otherText) {
107648
+ parts.push(otherText);
107649
+ }
107650
+ return {
107651
+ questionId: q.id,
107652
+ selectedIds,
107653
+ otherText,
107654
+ userMessage: parts.join("\n")
107655
+ };
107656
+ });
107657
+ return { kind: "submitted", answers };
107658
+ }
107659
+ function clamp3(value, min, max) {
107660
+ return Math.max(min, Math.min(max, value));
107661
+ }
107662
+ var init_askChoiceState = __esm({
107663
+ "packages/ai/tools/askChoiceState.ts"() {
107664
+ "use strict";
107665
+ }
107666
+ });
107667
+
107668
+ // packages/cli/tui/askChoiceDialog.ts
107669
+ function renderTabBar(questions, activeIndex, colorEnabled) {
107670
+ const tabs = questions.map((q, i) => {
107671
+ const label = `Q${i + 1}`;
107672
+ if (i === activeIndex) {
107673
+ return colorEnabled ? `${themeColorSequence("accent")}\x1B[1m ${label} \x1B[0m` : `[${label}]`;
107674
+ }
107675
+ return colorEnabled ? themeText(` ${label} `, "muted", colorEnabled) : ` ${label} `;
107676
+ });
107677
+ const submitLabel = "Submit";
107678
+ return tabs.join(" ") + " " + (colorEnabled ? themeText(submitLabel, "chrome", colorEnabled) : submitLabel);
107679
+ }
107680
+ function renderFooter(colorEnabled) {
107681
+ const hints = "\u21B5 pick/submit \xB7 space toggle \xB7 tab switch \xB7 esc cancel";
107682
+ return colorEnabled ? themeText(` ${hints}`, "chrome", colorEnabled) : ` ${hints}`;
107683
+ }
107684
+ function renderAskChoiceFrame(state4) {
107685
+ const colorEnabled = resolveCliColorEnabled();
107686
+ const q = state4.questions[state4.activeIndex];
107687
+ const qs = state4.questionStates[state4.activeIndex];
107688
+ const lines = [];
107689
+ lines.push(renderDialogTitle("question"));
107690
+ lines.push("");
107691
+ if (state4.questions.length > 1) {
107692
+ lines.push(renderTabBar(state4.questions, state4.activeIndex, colorEnabled));
107693
+ lines.push("");
107694
+ }
107695
+ lines.push(
107696
+ colorEnabled ? `${themeColorSequence("accent")}? ${q.question}\x1B[0m` : `? ${q.question}`
107697
+ );
107698
+ if (q.multiSelect) {
107699
+ lines.push(
107700
+ colorEnabled ? themeText(" Space to toggle, Enter to confirm selection.", "muted", colorEnabled) : " Space to toggle, Enter to confirm selection."
107701
+ );
107702
+ } else {
107703
+ lines.push(
107704
+ colorEnabled ? themeText(" Type your answer, then press Enter to save.", "muted", colorEnabled) : " Type your answer, then press Enter to save."
107705
+ );
107706
+ }
107707
+ lines.push("");
107708
+ const totalRows = q.choices.length + (q.allowOther ? 1 : 0);
107709
+ const window2 = computeVisibleWindow({
107710
+ selectedIndex: qs.cursorIndex,
107711
+ total: totalRows
107712
+ });
107713
+ if (window2.start > 0) {
107714
+ lines.push(
107715
+ colorEnabled ? themeText(` \u2191 ${window2.start} more`, "chrome", colorEnabled) : ` \u2191 ${window2.start} more`
107716
+ );
107717
+ }
107718
+ for (let i = window2.start; i < window2.end; i++) {
107719
+ if (i < q.choices.length) {
107720
+ const choice = q.choices[i];
107721
+ const focused = qs.cursorIndex === i;
107722
+ const checkbox = q.multiSelect ? qs.selectedIds.includes(choice.id) ? DIALOG_CHECKED : DIALOG_UNCHECKED : qs.pickedId === choice.id ? DIALOG_CHECKED : void 0;
107723
+ lines.push(
107724
+ renderDialogRow({
107725
+ label: `[${i + 1}] ${choice.label}`,
107726
+ ...choice.detail ? { detail: choice.detail } : {},
107727
+ focused,
107728
+ ...checkbox ? { checkbox } : {}
107729
+ })
107730
+ );
107731
+ } else {
107732
+ const focused = qs.cursorIndex === i;
107733
+ const marker = focused ? DIALOG_CURSOR : " ";
107734
+ const otherContent = qs.otherFocused ? `${qs.otherText}\u2588` : qs.otherText || "";
107735
+ const row = `${marker} [${i + 1}] Other: ${otherContent}`;
107736
+ if (focused && colorEnabled) {
107737
+ lines.push(
107738
+ `\x1B[1m${themeColorSequence("accent")}${row}\x1B[0m`
107739
+ );
107740
+ } else {
107741
+ lines.push(row);
107742
+ }
107743
+ }
107744
+ }
107745
+ if (window2.end < totalRows) {
107746
+ lines.push(
107747
+ colorEnabled ? themeText(` \u2193 ${totalRows - window2.end} more`, "chrome", colorEnabled) : ` \u2193 ${totalRows - window2.end} more`
107748
+ );
107749
+ }
107750
+ lines.push("");
107751
+ lines.push(renderFooter(colorEnabled));
107752
+ return lines.join("\n");
107753
+ }
107754
+ async function runAskChoiceDialog(args2) {
107755
+ const { request } = args2;
107756
+ const normalized = normalizeAskChoiceArgs({
107757
+ question: request.question,
107758
+ choices: request.choices,
107759
+ questions: request.questions,
107760
+ blocking: request.blocking
107761
+ });
107762
+ if (normalized.questions.length === 0) {
107763
+ return { kind: "cancelled" };
107764
+ }
107765
+ const q0 = normalized.questions[0];
107766
+ if (normalized.questions.length === 1 && !q0.multiSelect && !q0.allowOther) {
107767
+ }
107768
+ let state4 = createInitialAskChoiceState(normalized.questions);
107769
+ const output2 = args2.output ?? process.stdout;
107770
+ const input2 = args2.input ?? process.stdin;
107771
+ const readKey = args2.readKey ?? createRawKeyReader(input2);
107772
+ output2.write("\x1B[?1006h\x1B[?1000h");
107773
+ const wasRaw = Boolean(input2.isTTY && input2.isRaw);
107774
+ let renderedLineCount = 0;
107775
+ const bottomAnchored = Boolean(args2.bottomAnchored && args2.bottomRow);
107776
+ const resolveBottomRow = () => Math.max(
107777
+ 1,
107778
+ typeof args2.bottomRow === "function" ? args2.bottomRow() : args2.bottomRow ?? 0
107779
+ );
107780
+ let lastBottomRow = 0;
107781
+ const paint = () => {
107782
+ const frame = renderAskChoiceFrame(state4);
107783
+ const lines = frame.split("\n");
107784
+ const lineCount = lines.length;
107785
+ const canPosition = outputIsTty(output2) && typeof output2.write === "function";
107786
+ if (bottomAnchored && canPosition) {
107787
+ const anchorRow = resolveBottomRow();
107788
+ clearAnchoredLines(
107789
+ output2,
107790
+ lastBottomRow > 0 ? lastBottomRow : anchorRow,
107791
+ renderedLineCount
107792
+ );
107793
+ for (let i = 0; i < lines.length; i++) {
107794
+ const row = anchorRow - (lines.length - 1 - i);
107795
+ if (row < 1) break;
107796
+ output2.write(`\x1B[${row};1H\x1B[2K${lines[i]}`);
107797
+ }
107798
+ lastBottomRow = anchorRow;
107799
+ renderedLineCount = lineCount;
107800
+ return;
107801
+ }
107802
+ if (canPosition) {
107803
+ for (let i = 0; i < renderedLineCount; i++) {
107804
+ output2.write("\x1B[1A\x1B[2K");
107805
+ }
107806
+ output2.write(`${frame}
107807
+ `);
107808
+ renderedLineCount = lineCount;
107809
+ return;
107810
+ }
107811
+ if (typeof output2.write === "function") {
107812
+ output2.write(`${frame}
107813
+ `);
107814
+ }
107815
+ renderedLineCount = lineCount;
107816
+ };
107817
+ const resizeTarget = output2;
107818
+ const onOutputResize = () => paint();
107819
+ if (input2.isTTY && !wasRaw) {
107820
+ input2.setRawMode?.(true);
107821
+ }
107822
+ if (bottomAnchored && outputIsTty(output2)) {
107823
+ resizeTarget.on?.("resize", onOutputResize);
107824
+ }
107825
+ paint();
107826
+ try {
107827
+ while (state4.phase === "active") {
107828
+ const sequence = await readKey();
107829
+ if (sequence == null) {
107830
+ state4 = askChoiceReducer(state4, { type: "CANCEL" });
107831
+ break;
107832
+ }
107833
+ const scrollAction = parseScrollAction(sequence);
107834
+ if (scrollAction === "wheel-up" || scrollAction === "wheel-down") {
107835
+ state4 = askChoiceReducer(state4, {
107836
+ type: "MOVE_CURSOR",
107837
+ delta: scrollAction === "wheel-up" ? -1 : 1
107838
+ });
107839
+ paint();
107840
+ continue;
107841
+ }
107842
+ let action3 = null;
107843
+ if (isCancel(sequence)) {
107844
+ action3 = { type: "CANCEL" };
107845
+ } else if (sequence === CSI_TAB) {
107846
+ action3 = { type: "NEXT_TAB" };
107847
+ } else if (sequence === CSI_SHIFT_TAB) {
107848
+ action3 = { type: "PREV_TAB" };
107849
+ } else if (isArrowUp(sequence)) {
107850
+ action3 = { type: "MOVE_CURSOR", delta: -1 };
107851
+ } else if (isArrowDown(sequence)) {
107852
+ action3 = { type: "MOVE_CURSOR", delta: 1 };
107853
+ } else if (sequence === CSI_SPACE) {
107854
+ action3 = { type: "TOGGLE_AT_CURSOR" };
107855
+ } else if (isSubmit(sequence)) {
107856
+ const qs = state4.questionStates[state4.activeIndex];
107857
+ const q = state4.questions[state4.activeIndex];
107858
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
107859
+ if (qs.otherFocused) {
107860
+ action3 = { type: "BLUR_OTHER" };
107861
+ } else if (isOtherRow && !qs.otherFocused) {
107862
+ action3 = { type: "FOCUS_OTHER" };
107863
+ } else if (q.multiSelect) {
107864
+ action3 = { type: "SUBMIT" };
107865
+ } else {
107866
+ action3 = { type: "SELECT_AT_CURSOR" };
107867
+ }
107868
+ } else if (sequence === CSI_BACKSPACE || sequence === CSI_BACKSPACE_ALT) {
107869
+ const qs = state4.questionStates[state4.activeIndex];
107870
+ if (qs.otherFocused && qs.otherText.length > 0) {
107871
+ action3 = {
107872
+ type: "SET_OTHER_TEXT",
107873
+ text: qs.otherText.slice(0, -1)
107874
+ };
107875
+ }
107876
+ } else if (sequence.length === 1 && sequence.charCodeAt(0) >= 32) {
107877
+ const qs = state4.questionStates[state4.activeIndex];
107878
+ if (qs.otherFocused) {
107879
+ action3 = {
107880
+ type: "SET_OTHER_TEXT",
107881
+ text: qs.otherText + sequence
107882
+ };
107883
+ }
107884
+ }
107885
+ if (action3) {
107886
+ const prev = state4;
107887
+ state4 = askChoiceReducer(state4, action3);
107888
+ if (state4.phase !== "active") break;
107889
+ if (action3.type === "BLUR_OTHER" && state4.activeIndex >= state4.questions.length - 1 && canSubmit(state4)) {
107890
+ state4 = askChoiceReducer(state4, { type: "SUBMIT" });
107891
+ break;
107892
+ }
107893
+ paint();
107894
+ }
107895
+ }
107896
+ } finally {
107897
+ output2.write("\x1B[?1000l\x1B[?1006l");
107898
+ resizeTarget.off?.("resize", onOutputResize);
107899
+ readKey.dispose?.();
107900
+ if (input2.isTTY) {
107901
+ drainInputBuffer(input2);
107902
+ if (!wasRaw) input2.setRawMode?.(false);
107903
+ if (bottomAnchored) {
107904
+ clearAnchoredLines(
107905
+ output2,
107906
+ lastBottomRow > 0 ? lastBottomRow : resolveBottomRow(),
107907
+ renderedLineCount
107908
+ );
107909
+ } else {
107910
+ for (let i = 0; i < renderedLineCount; i++) {
107911
+ output2.write("\x1B[1A\x1B[2K");
107912
+ }
107913
+ }
107914
+ renderedLineCount = 0;
107915
+ }
107916
+ }
107917
+ const result = buildAskChoiceResult(state4);
107918
+ if (result.kind === "cancelled") {
107919
+ return { kind: "cancelled" };
107920
+ }
107921
+ if (result.answers.length === 1) {
107922
+ const a = result.answers[0];
107923
+ return {
107924
+ kind: "selected",
107925
+ userMessage: a.userMessage,
107926
+ label: a.selectedIds.map((id) => {
107927
+ const q = normalized.questions[0];
107928
+ return q.choices.find((c) => c.id === id)?.label ?? "";
107929
+ }).join(", ") || a.otherText || ""
107930
+ };
107931
+ }
107932
+ return {
107933
+ kind: "multi-submitted",
107934
+ answers: result.answers,
107935
+ userMessage: result.answers.map((a) => a.userMessage).filter(Boolean).join("\n\n")
107936
+ };
107937
+ }
107938
+ var CSI_TAB, CSI_SHIFT_TAB, CSI_BACKSPACE, CSI_BACKSPACE_ALT, CSI_SPACE;
107939
+ var init_askChoiceDialog = __esm({
107940
+ "packages/cli/tui/askChoiceDialog.ts"() {
107941
+ "use strict";
107942
+ init_askChoiceState();
107943
+ init_dialogFrame();
107944
+ init_selectDialog();
107945
+ init_terminalStyles();
107946
+ init_theme();
107947
+ init_tuiScrollbar();
107948
+ CSI_TAB = " ";
107949
+ CSI_SHIFT_TAB = "\x1B[Z";
107950
+ CSI_BACKSPACE = "\x7F";
107951
+ CSI_BACKSPACE_ALT = "\b";
107952
+ CSI_SPACE = " ";
107953
+ }
107954
+ });
107955
+
107260
107956
  // packages/cli/tui/dialogHost.ts
107261
107957
  function resolveTtyRows(output2) {
107262
107958
  if (typeof output2 === "object" && output2 !== null && "rows" in output2 && typeof output2.rows === "number") {
@@ -107294,6 +107990,92 @@ var init_dialogHost = __esm({
107294
107990
  }
107295
107991
  });
107296
107992
 
107993
+ // packages/cli/tui/activityIndicator.ts
107994
+ function createActivityIndicator(deps) {
107995
+ const now = deps.now ?? (() => Date.now());
107996
+ const frameIntervalMs = deps.frameIntervalMs ?? ACTIVITY_FRAME_INTERVAL_MS;
107997
+ const fallbackDelayMs = deps.fallbackDelayMs ?? ACTIVITY_FALLBACK_DELAY_MS;
107998
+ const setIntervalFn = deps.setIntervalFn ?? ((cb, ms) => setInterval(cb, ms));
107999
+ const clearIntervalFn = deps.clearIntervalFn ?? ((handle) => clearInterval(handle));
108000
+ let explicitLabel = null;
108001
+ let explicitStartedAt = 0;
108002
+ let fallbackActive = false;
108003
+ let fallbackStartedAt = 0;
108004
+ let lastActivityAt = 0;
108005
+ let frameIndex = 0;
108006
+ let timer = null;
108007
+ const elapsedSecFrom = (startedAt) => startedAt > 0 ? Math.max(0, Math.floor((now() - startedAt) / 1e3)) : 0;
108008
+ const tick = () => {
108009
+ frameIndex += 1;
108010
+ if (explicitLabel === null && !fallbackActive && deps.isTurnActive() && lastActivityAt > 0 && now() - lastActivityAt >= fallbackDelayMs) {
108011
+ fallbackActive = true;
108012
+ fallbackStartedAt = now();
108013
+ }
108014
+ deps.onRepaint();
108015
+ };
108016
+ const ensureTimer = () => {
108017
+ if (timer === null) {
108018
+ timer = setIntervalFn(tick, frameIntervalMs);
108019
+ }
108020
+ };
108021
+ const report = (label) => {
108022
+ lastActivityAt = now();
108023
+ if (label !== null) {
108024
+ explicitLabel = label;
108025
+ if (explicitStartedAt === 0) explicitStartedAt = lastActivityAt;
108026
+ fallbackActive = false;
108027
+ fallbackStartedAt = 0;
108028
+ } else {
108029
+ explicitLabel = null;
108030
+ explicitStartedAt = 0;
108031
+ fallbackActive = false;
108032
+ fallbackStartedAt = 0;
108033
+ }
108034
+ ensureTimer();
108035
+ deps.onRepaint();
108036
+ };
108037
+ const getView = () => {
108038
+ if (!deps.isTurnActive()) return null;
108039
+ const frame = ACTIVITY_FRAMES[frameIndex % ACTIVITY_FRAMES.length];
108040
+ if (explicitLabel !== null) {
108041
+ return {
108042
+ frame,
108043
+ label: explicitLabel,
108044
+ elapsedSec: elapsedSecFrom(explicitStartedAt)
108045
+ };
108046
+ }
108047
+ if (fallbackActive) {
108048
+ return {
108049
+ frame,
108050
+ label: deps.fallbackLabel(),
108051
+ elapsedSec: elapsedSecFrom(fallbackStartedAt)
108052
+ };
108053
+ }
108054
+ return null;
108055
+ };
108056
+ const stop = () => {
108057
+ if (timer !== null) {
108058
+ clearIntervalFn(timer);
108059
+ timer = null;
108060
+ }
108061
+ explicitLabel = null;
108062
+ explicitStartedAt = 0;
108063
+ fallbackActive = false;
108064
+ fallbackStartedAt = 0;
108065
+ lastActivityAt = 0;
108066
+ };
108067
+ return { report, getView, stop };
108068
+ }
108069
+ var ACTIVITY_FRAMES, ACTIVITY_FRAME_INTERVAL_MS, ACTIVITY_FALLBACK_DELAY_MS;
108070
+ var init_activityIndicator = __esm({
108071
+ "packages/cli/tui/activityIndicator.ts"() {
108072
+ "use strict";
108073
+ ACTIVITY_FRAMES = ["\xB7", "~", "\u2248", "\u223F", "\u2248", "~"];
108074
+ ACTIVITY_FRAME_INTERVAL_MS = 150;
108075
+ ACTIVITY_FALLBACK_DELAY_MS = 1200;
108076
+ }
108077
+ });
108078
+
107297
108079
  // packages/ai/agent/utils/sortUtils.ts
107298
108080
  function sortAgentsFavoriteOwnedPublic(items) {
107299
108081
  const map = /* @__PURE__ */ new Map();
@@ -108051,6 +108833,278 @@ var init_gitStatus = __esm({
108051
108833
  }
108052
108834
  });
108053
108835
 
108836
+ // packages/cli/tui/tuiAnsi.ts
108837
+ function stripAnsi(text) {
108838
+ return text.replace(OSC_HYPERLINK_REGEX, "").replace(ANSI_ESCAPE_REGEX, "");
108839
+ }
108840
+ function applyTerminalOutputToText(existing, chunk) {
108841
+ if (!chunk) return existing;
108842
+ let text = existing;
108843
+ let index = 0;
108844
+ while (index < chunk.length) {
108845
+ if (chunk[index] === "\x1B") {
108846
+ const sgr = SGR_SEQUENCE_REGEX.exec(chunk.slice(index));
108847
+ if (sgr) {
108848
+ text += sgr[0];
108849
+ index += sgr[0].length;
108850
+ continue;
108851
+ }
108852
+ const osc = chunk.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
108853
+ if (osc) {
108854
+ index += osc[0].length;
108855
+ continue;
108856
+ }
108857
+ const csi = chunk.slice(index).match(/^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/);
108858
+ if (csi) {
108859
+ index += csi[0].length;
108860
+ continue;
108861
+ }
108862
+ index += 1;
108863
+ continue;
108864
+ }
108865
+ const ch = chunk[index];
108866
+ if (ch === "\r") {
108867
+ const lastNl = text.lastIndexOf("\n");
108868
+ text = lastNl === -1 ? "" : text.slice(0, lastNl + 1);
108869
+ index += 1;
108870
+ continue;
108871
+ }
108872
+ if (ch === "\n") {
108873
+ text += "\n";
108874
+ index += 1;
108875
+ continue;
108876
+ }
108877
+ if (ch === "\b") {
108878
+ const trailing = TRAILING_SGR_REGEX.exec(text);
108879
+ const sgrTail = trailing ? trailing[0] : "";
108880
+ const head = sgrTail ? text.slice(0, -sgrTail.length) : text;
108881
+ if (head.length > 0 && head[head.length - 1] !== "\n") {
108882
+ text = head.slice(0, -1) + sgrTail;
108883
+ }
108884
+ index += 1;
108885
+ continue;
108886
+ }
108887
+ const code = ch.charCodeAt(0);
108888
+ if (code < 32 && ch !== " " || code === 127) {
108889
+ index += 1;
108890
+ continue;
108891
+ }
108892
+ text += ch;
108893
+ index += 1;
108894
+ }
108895
+ return text;
108896
+ }
108897
+ function displayWidth2(str) {
108898
+ let width = 0;
108899
+ for (const char of str) {
108900
+ const code = char.codePointAt(0) ?? 0;
108901
+ if (code < 32 || code === 127) continue;
108902
+ if (code >= 4352 && code <= 4447 || // 0x2768-0x2775 (ornamental brackets, incl. the ❯ prompt at U+276F)
108903
+ // render narrow in terminals; counting them wide drifts the cursor.
108904
+ code >= 9728 && code <= 10175 && !(code >= 10088 && code <= 10101) || // 0x2B00-0x2BFF (Misc Symbols and Arrows, incl. ⬢ U+2B22 used as the
108905
+ // status-line agent icon) render double-wide in common monospace fonts;
108906
+ // counting ⬢ as width 1 drifts the whole status line right of the icon.
108907
+ code >= 11008 && code <= 11263 || code >= 11904 && code <= 12350 || code >= 12352 && code <= 13247 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 127744 && code <= 129791 || code >= 131072 && code <= 196605 || code >= 196608 && code <= 262141 || // East Asian Ambiguous CJK quotation marks (U+201C/D “ ”, U+2018/9 ‘ ’).
108908
+ // In CJK locale + CJK fonts these render double-wide like 全角 punctuation;
108909
+ // in English fonts they stay width 1. Follow the active locale so Chinese
108910
+ // replies align correctly without breaking English terminal output.
108911
+ getCliLocale() === "zh" && (code === 8220 || code === 8221 || code === 8216 || code === 8217)) {
108912
+ width += 2;
108913
+ } else {
108914
+ width += 1;
108915
+ }
108916
+ }
108917
+ return width;
108918
+ }
108919
+ function visibleWidth(str) {
108920
+ return displayWidth2(stripAnsi(str));
108921
+ }
108922
+ function truncateAnsi(text, maxWidth) {
108923
+ if (maxWidth <= 0) return "";
108924
+ if (visibleWidth(text) <= maxWidth) return text;
108925
+ let width = 0;
108926
+ let out = "";
108927
+ let i = 0;
108928
+ let sawAnsi = false;
108929
+ while (i < text.length) {
108930
+ if (text[i] === "\x1B" && text[i + 1] === "[") {
108931
+ sawAnsi = true;
108932
+ let j = i + 2;
108933
+ while (j < text.length) {
108934
+ const code = text.charCodeAt(j);
108935
+ j += 1;
108936
+ if (code >= 64 && code <= 126) break;
108937
+ }
108938
+ out += text.slice(i, j);
108939
+ i = j;
108940
+ continue;
108941
+ }
108942
+ const codePoint = text.codePointAt(i) ?? 0;
108943
+ const char = String.fromCodePoint(codePoint);
108944
+ const charWidth = displayWidth2(char);
108945
+ if (width + charWidth > maxWidth) break;
108946
+ out += char;
108947
+ width += charWidth;
108948
+ i += char.length;
108949
+ }
108950
+ return sawAnsi ? `${out}\x1B[0m` : out;
108951
+ }
108952
+ function fitAnsiLine(text, width, ellipsis = "\u2026") {
108953
+ if (width <= 0) return "";
108954
+ if (visibleWidth(text) <= width) return text;
108955
+ const ellipsisWidth = displayWidth2(ellipsis);
108956
+ if (width < ellipsisWidth) return truncateAnsi(text, width);
108957
+ if (width === ellipsisWidth) return truncateAnsi(ellipsis, width) || truncateAnsi(text, width);
108958
+ return `${truncateAnsi(text, width - ellipsisWidth)}${ellipsis}`;
108959
+ }
108960
+ function countPhysicalLines(text, columns) {
108961
+ const lines = text.split("\n");
108962
+ let total = 0;
108963
+ for (const line of lines) {
108964
+ const width = displayWidth2(line);
108965
+ total += Math.max(1, Math.ceil(width / columns));
108966
+ }
108967
+ return Math.max(total, 1);
108968
+ }
108969
+ function takeDisplayWidth(text, width) {
108970
+ let used = 0;
108971
+ let index = 0;
108972
+ for (const char of text) {
108973
+ const charWidth = displayWidth2(char);
108974
+ if (used + charWidth > width && used > 0) break;
108975
+ used += charWidth;
108976
+ index += char.length;
108977
+ }
108978
+ return { prefix: text.slice(0, index), rest: text.slice(index) };
108979
+ }
108980
+ function padOrTruncateToWidth(text, width) {
108981
+ const textWidth = visibleWidth(text);
108982
+ if (textWidth > width) {
108983
+ return truncateAnsi(text, width);
108984
+ }
108985
+ return `${text}${" ".repeat(width - textWidth)}`;
108986
+ }
108987
+ function tokenizeAnsiLine(line) {
108988
+ const tokens = [];
108989
+ let index = 0;
108990
+ while (index < line.length) {
108991
+ if (line[index] === "\x1B") {
108992
+ const osc = line.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
108993
+ if (osc) {
108994
+ tokens.push({ kind: "sgr", value: osc[0], width: 0 });
108995
+ index += osc[0].length;
108996
+ continue;
108997
+ }
108998
+ const sgr = SGR_SEQUENCE_REGEX.exec(line.slice(index));
108999
+ if (sgr) {
109000
+ tokens.push({ kind: "sgr", value: sgr[0], width: 0 });
109001
+ index += sgr[0].length;
109002
+ continue;
109003
+ }
109004
+ }
109005
+ const codePoint = line.codePointAt(index) ?? 0;
109006
+ const value = String.fromCodePoint(codePoint);
109007
+ tokens.push({ kind: "char", value, width: displayWidth2(value) });
109008
+ index += value.length;
109009
+ }
109010
+ return tokens;
109011
+ }
109012
+ function wrapTranscriptLine(line, columns) {
109013
+ if (line === "") return [""];
109014
+ const tokens = tokenizeAnsiLine(line);
109015
+ const result = [];
109016
+ let activeStyles = [];
109017
+ const applyStyleToken = (value) => {
109018
+ if (SGR_RESET_REGEX.test(value)) {
109019
+ activeStyles = [];
109020
+ } else {
109021
+ activeStyles.push(value);
109022
+ }
109023
+ };
109024
+ let start = 0;
109025
+ while (start < tokens.length) {
109026
+ if (tokens.slice(start).every((token) => token.kind === "sgr")) {
109027
+ if (result.length > 0) break;
109028
+ }
109029
+ const openingStyles = [...activeStyles];
109030
+ let width = 0;
109031
+ let end = start;
109032
+ let lastBreak = -1;
109033
+ while (end < tokens.length) {
109034
+ const token = tokens[end];
109035
+ if (token.kind === "sgr") {
109036
+ end += 1;
109037
+ continue;
109038
+ }
109039
+ if (width + token.width > columns && width > 0) break;
109040
+ width += token.width;
109041
+ end += 1;
109042
+ if (token.value === " " || token.value === " ") {
109043
+ lastBreak = end;
109044
+ }
109045
+ }
109046
+ let segmentEnd = end;
109047
+ if (end < tokens.length && lastBreak > start) {
109048
+ const overflowToken = tokens[end];
109049
+ if (overflowToken.kind === "char" && overflowToken.value !== " " && overflowToken.width === 1) {
109050
+ segmentEnd = lastBreak;
109051
+ }
109052
+ }
109053
+ if (segmentEnd === start) segmentEnd = start + 1;
109054
+ let segment = "";
109055
+ let sawStyle = openingStyles.length > 0;
109056
+ for (let i = start; i < segmentEnd; i += 1) {
109057
+ const token = tokens[i];
109058
+ segment += token.value;
109059
+ if (token.kind === "sgr") {
109060
+ sawStyle = true;
109061
+ applyStyleToken(token.value);
109062
+ }
109063
+ }
109064
+ const prefix = openingStyles.join("");
109065
+ const needsReset = (sawStyle || activeStyles.length > 0) && !segment.endsWith("\x1B[0m");
109066
+ result.push(`${prefix}${segment}${needsReset ? "\x1B[0m" : ""}`);
109067
+ start = segmentEnd;
109068
+ while (start < tokens.length) {
109069
+ const token = tokens[start];
109070
+ if (token.kind === "char" && token.value === " ") {
109071
+ start += 1;
109072
+ continue;
109073
+ }
109074
+ break;
109075
+ }
109076
+ }
109077
+ return result.length > 0 ? result : [""];
109078
+ }
109079
+ function wrapTextToLines(text, columns) {
109080
+ const result = [];
109081
+ for (const logicalLine of text.split("\n")) {
109082
+ if (logicalLine === "") {
109083
+ result.push("");
109084
+ continue;
109085
+ }
109086
+ let remaining = logicalLine;
109087
+ while (remaining.length > 0) {
109088
+ const { prefix, rest } = takeDisplayWidth(remaining, columns);
109089
+ result.push(prefix);
109090
+ remaining = rest;
109091
+ }
109092
+ }
109093
+ return result;
109094
+ }
109095
+ var ANSI_ESCAPE_REGEX, OSC_HYPERLINK_REGEX, SGR_SEQUENCE_REGEX, TRAILING_SGR_REGEX, SGR_RESET_REGEX;
109096
+ var init_tuiAnsi = __esm({
109097
+ "packages/cli/tui/tuiAnsi.ts"() {
109098
+ "use strict";
109099
+ init_i18n2();
109100
+ ANSI_ESCAPE_REGEX = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
109101
+ OSC_HYPERLINK_REGEX = /\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)\x1b\]8;;(?:\x07|\x1b\\)|\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/g;
109102
+ SGR_SEQUENCE_REGEX = /^\x1b\[[0-9;]*m/;
109103
+ TRAILING_SGR_REGEX = /(?:\x1b\[[0-9;]*m)+$/;
109104
+ SGR_RESET_REGEX = /^\x1b\[0?m$/;
109105
+ }
109106
+ });
109107
+
108054
109108
  // packages/cli/tui/sessionRender.ts
108055
109109
  function formatCwd(cwd) {
108056
109110
  const parts = cwd.split(/[/\\]/);
@@ -108080,7 +109134,7 @@ function renderStatusLine(state4) {
108080
109134
  const sep3 = themeText(" \xB7 ", "chrome", colorEnabled);
108081
109135
  const autoRouteActive = state4.agentKey === DEFAULT_TUI_AGENT_KEY && (typeof process === "undefined" || process.env?.NOLO_AUTO_ROUTE !== "0");
108082
109136
  const agentDisplayName = autoRouteActive ? "auto" : state4.agentName;
108083
- const modeSuffix = state4.modeLabel && !(autoRouteActive && state4.modeLabel === "auto") ? ` \xB7 ${state4.modeLabel}` : "";
109137
+ const modeSuffix = state4.modeLabel && state4.modeLabel !== "auto" ? ` \xB7 ${state4.modeLabel}` : "";
108084
109138
  const agentLabel = `\u{1F3D4} ${agentDisplayName}${modeSuffix}`;
108085
109139
  const agentSegment = themeText(agentLabel, "accent", colorEnabled);
108086
109140
  const cwdSegment = themeText(`\u{1F4C1} ${formatCwd(state4.cwd)}`, "info", colorEnabled);
@@ -108168,18 +109222,18 @@ function buildColoredScene(isDark, frame = 0, maxFrames = 0) {
108168
109222
  ` ${pk}\u2581\u2581\u2581\u2581\u2581\u2581\u2571${r} ${tr}\u2660${r} ${tr}\u2660${r} ${pk}\u2572\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581${r}`
108169
109223
  ].join("\n");
108170
109224
  }
108171
- function renderWelcome(state4, frame = 0, maxFrames = 0) {
109225
+ function renderWelcome(state4, frame = 0, maxFrames = 0, columns) {
108172
109226
  const colorEnabled = resolveCliColorEnabled();
108173
109227
  const brightness = resolveTuiBrightness();
108174
109228
  const isDark = brightness === "dark";
108175
- const sceneArt = colorEnabled ? buildColoredScene(isDark, frame, maxFrames) : buildPlainScene(isDark, frame, maxFrames);
109229
+ let sceneArt = colorEnabled ? buildColoredScene(isDark, frame, maxFrames) : buildPlainScene(isDark, frame, maxFrames);
109230
+ if (typeof columns === "number" && columns > 0) {
109231
+ const widestSceneCol = sceneArt.split("\n").reduce((max, line) => Math.max(max, displayWidth2(stripAnsi(line))), 0);
109232
+ if (columns < widestSceneCol) sceneArt = "";
109233
+ }
108176
109234
  const versionLine = colorEnabled ? `\x1B[1m${themeColorSequence("accent")}nolo\x1B[0m ${state4.cliVersion ?? ""} | server ${state4.serverUrl}`.replace(" |", " |") : `nolo ${state4.cliVersion ?? ""} | server ${state4.serverUrl}`.replace(" |", " |");
108177
- return [
108178
- sceneArt,
108179
- versionLine,
108180
- t("welcomeHint"),
108181
- ""
108182
- ].join("\n");
109235
+ const body = sceneArt ? [sceneArt, versionLine, t("welcomeHint"), ""] : [versionLine, t("welcomeHint"), ""];
109236
+ return body.join("\n");
108183
109237
  }
108184
109238
  function renderPrompt(_state) {
108185
109239
  return t("promptLabel");
@@ -108287,6 +109341,7 @@ var init_sessionRender = __esm({
108287
109341
  init_dialogFrame();
108288
109342
  init_i18n2();
108289
109343
  init_readlineWorkspace();
109344
+ init_tuiAnsi();
108290
109345
  init_theme();
108291
109346
  init_processRegistry();
108292
109347
  }
@@ -109422,336 +110477,6 @@ var init_chatQueueTuiBinding = __esm({
109422
110477
  }
109423
110478
  });
109424
110479
 
109425
- // packages/cli/tui/tuiAnsi.ts
109426
- function stripAnsi(text) {
109427
- return text.replace(OSC_HYPERLINK_REGEX, "").replace(ANSI_ESCAPE_REGEX, "");
109428
- }
109429
- function applyTerminalOutputToText(existing, chunk) {
109430
- if (!chunk) return existing;
109431
- let text = existing;
109432
- let index = 0;
109433
- while (index < chunk.length) {
109434
- if (chunk[index] === "\x1B") {
109435
- const sgr = SGR_SEQUENCE_REGEX.exec(chunk.slice(index));
109436
- if (sgr) {
109437
- text += sgr[0];
109438
- index += sgr[0].length;
109439
- continue;
109440
- }
109441
- const osc = chunk.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
109442
- if (osc) {
109443
- index += osc[0].length;
109444
- continue;
109445
- }
109446
- const csi = chunk.slice(index).match(/^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/);
109447
- if (csi) {
109448
- index += csi[0].length;
109449
- continue;
109450
- }
109451
- index += 1;
109452
- continue;
109453
- }
109454
- const ch = chunk[index];
109455
- if (ch === "\r") {
109456
- const lastNl = text.lastIndexOf("\n");
109457
- text = lastNl === -1 ? "" : text.slice(0, lastNl + 1);
109458
- index += 1;
109459
- continue;
109460
- }
109461
- if (ch === "\n") {
109462
- text += "\n";
109463
- index += 1;
109464
- continue;
109465
- }
109466
- if (ch === "\b") {
109467
- const trailing = TRAILING_SGR_REGEX.exec(text);
109468
- const sgrTail = trailing ? trailing[0] : "";
109469
- const head = sgrTail ? text.slice(0, -sgrTail.length) : text;
109470
- if (head.length > 0 && head[head.length - 1] !== "\n") {
109471
- text = head.slice(0, -1) + sgrTail;
109472
- }
109473
- index += 1;
109474
- continue;
109475
- }
109476
- const code = ch.charCodeAt(0);
109477
- if (code < 32 && ch !== " " || code === 127) {
109478
- index += 1;
109479
- continue;
109480
- }
109481
- text += ch;
109482
- index += 1;
109483
- }
109484
- return text;
109485
- }
109486
- function displayWidth2(str) {
109487
- let width = 0;
109488
- for (const char of str) {
109489
- const code = char.codePointAt(0) ?? 0;
109490
- if (code < 32 || code === 127) continue;
109491
- if (code >= 4352 && code <= 4447 || // 0x2768-0x2775 (ornamental brackets, incl. the ❯ prompt at U+276F)
109492
- // render narrow in terminals; counting them wide drifts the cursor.
109493
- code >= 9728 && code <= 10175 && !(code >= 10088 && code <= 10101) || // 0x2B00-0x2BFF (Misc Symbols and Arrows, incl. ⬢ U+2B22 used as the
109494
- // status-line agent icon) render double-wide in common monospace fonts;
109495
- // counting ⬢ as width 1 drifts the whole status line right of the icon.
109496
- code >= 11008 && code <= 11263 || code >= 11904 && code <= 12350 || code >= 12352 && code <= 13247 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 127744 && code <= 129791 || code >= 131072 && code <= 196605 || code >= 196608 && code <= 262141 || // East Asian Ambiguous CJK quotation marks (U+201C/D “ ”, U+2018/9 ‘ ’).
109497
- // In CJK locale + CJK fonts these render double-wide like 全角 punctuation;
109498
- // in English fonts they stay width 1. Follow the active locale so Chinese
109499
- // replies align correctly without breaking English terminal output.
109500
- getCliLocale() === "zh" && (code === 8220 || code === 8221 || code === 8216 || code === 8217)) {
109501
- width += 2;
109502
- } else {
109503
- width += 1;
109504
- }
109505
- }
109506
- return width;
109507
- }
109508
- function visibleWidth(str) {
109509
- return displayWidth2(stripAnsi(str));
109510
- }
109511
- function truncateAnsi(text, maxWidth) {
109512
- if (maxWidth <= 0) return "";
109513
- if (visibleWidth(text) <= maxWidth) return text;
109514
- let width = 0;
109515
- let out = "";
109516
- let i = 0;
109517
- let sawAnsi = false;
109518
- while (i < text.length) {
109519
- if (text[i] === "\x1B" && text[i + 1] === "[") {
109520
- sawAnsi = true;
109521
- let j = i + 2;
109522
- while (j < text.length) {
109523
- const code = text.charCodeAt(j);
109524
- j += 1;
109525
- if (code >= 64 && code <= 126) break;
109526
- }
109527
- out += text.slice(i, j);
109528
- i = j;
109529
- continue;
109530
- }
109531
- const codePoint = text.codePointAt(i) ?? 0;
109532
- const char = String.fromCodePoint(codePoint);
109533
- const charWidth = displayWidth2(char);
109534
- if (width + charWidth > maxWidth) break;
109535
- out += char;
109536
- width += charWidth;
109537
- i += char.length;
109538
- }
109539
- return sawAnsi ? `${out}\x1B[0m` : out;
109540
- }
109541
- function fitAnsiLine(text, width, ellipsis = "\u2026") {
109542
- if (width <= 0) return "";
109543
- if (visibleWidth(text) <= width) return text;
109544
- const ellipsisWidth = displayWidth2(ellipsis);
109545
- if (width < ellipsisWidth) return truncateAnsi(text, width);
109546
- if (width === ellipsisWidth) return truncateAnsi(ellipsis, width) || truncateAnsi(text, width);
109547
- return `${truncateAnsi(text, width - ellipsisWidth)}${ellipsis}`;
109548
- }
109549
- function countPhysicalLines(text, columns) {
109550
- const lines = text.split("\n");
109551
- let total = 0;
109552
- for (const line of lines) {
109553
- const width = displayWidth2(line);
109554
- total += Math.max(1, Math.ceil(width / columns));
109555
- }
109556
- return Math.max(total, 1);
109557
- }
109558
- function takeDisplayWidth(text, width) {
109559
- let used = 0;
109560
- let index = 0;
109561
- for (const char of text) {
109562
- const charWidth = displayWidth2(char);
109563
- if (used + charWidth > width && used > 0) break;
109564
- used += charWidth;
109565
- index += char.length;
109566
- }
109567
- return { prefix: text.slice(0, index), rest: text.slice(index) };
109568
- }
109569
- function padOrTruncateToWidth(text, width) {
109570
- const textWidth = visibleWidth(text);
109571
- if (textWidth > width) {
109572
- return truncateAnsi(text, width);
109573
- }
109574
- return `${text}${" ".repeat(width - textWidth)}`;
109575
- }
109576
- function tokenizeAnsiLine(line) {
109577
- const tokens = [];
109578
- let index = 0;
109579
- while (index < line.length) {
109580
- if (line[index] === "\x1B") {
109581
- const osc = line.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
109582
- if (osc) {
109583
- tokens.push({ kind: "sgr", value: osc[0], width: 0 });
109584
- index += osc[0].length;
109585
- continue;
109586
- }
109587
- const sgr = SGR_SEQUENCE_REGEX.exec(line.slice(index));
109588
- if (sgr) {
109589
- tokens.push({ kind: "sgr", value: sgr[0], width: 0 });
109590
- index += sgr[0].length;
109591
- continue;
109592
- }
109593
- }
109594
- const codePoint = line.codePointAt(index) ?? 0;
109595
- const value = String.fromCodePoint(codePoint);
109596
- tokens.push({ kind: "char", value, width: displayWidth2(value) });
109597
- index += value.length;
109598
- }
109599
- return tokens;
109600
- }
109601
- function wrapTranscriptLine(line, columns) {
109602
- if (line === "") return [""];
109603
- const tokens = tokenizeAnsiLine(line);
109604
- const result = [];
109605
- let activeStyles = [];
109606
- const applyStyleToken = (value) => {
109607
- if (SGR_RESET_REGEX.test(value)) {
109608
- activeStyles = [];
109609
- } else {
109610
- activeStyles.push(value);
109611
- }
109612
- };
109613
- let start = 0;
109614
- while (start < tokens.length) {
109615
- if (tokens.slice(start).every((token) => token.kind === "sgr")) {
109616
- if (result.length > 0) break;
109617
- }
109618
- const openingStyles = [...activeStyles];
109619
- let width = 0;
109620
- let end = start;
109621
- let lastBreak = -1;
109622
- while (end < tokens.length) {
109623
- const token = tokens[end];
109624
- if (token.kind === "sgr") {
109625
- end += 1;
109626
- continue;
109627
- }
109628
- if (width + token.width > columns && width > 0) break;
109629
- width += token.width;
109630
- end += 1;
109631
- if (token.value === " " || token.value === " ") {
109632
- lastBreak = end;
109633
- }
109634
- }
109635
- let segmentEnd = end;
109636
- if (end < tokens.length && lastBreak > start) {
109637
- const overflowToken = tokens[end];
109638
- if (overflowToken.kind === "char" && overflowToken.value !== " " && overflowToken.width === 1) {
109639
- segmentEnd = lastBreak;
109640
- }
109641
- }
109642
- if (segmentEnd === start) segmentEnd = start + 1;
109643
- let segment = "";
109644
- let sawStyle = openingStyles.length > 0;
109645
- for (let i = start; i < segmentEnd; i += 1) {
109646
- const token = tokens[i];
109647
- segment += token.value;
109648
- if (token.kind === "sgr") {
109649
- sawStyle = true;
109650
- applyStyleToken(token.value);
109651
- }
109652
- }
109653
- const prefix = openingStyles.join("");
109654
- const needsReset = (sawStyle || activeStyles.length > 0) && !segment.endsWith("\x1B[0m");
109655
- result.push(`${prefix}${segment}${needsReset ? "\x1B[0m" : ""}`);
109656
- start = segmentEnd;
109657
- while (start < tokens.length) {
109658
- const token = tokens[start];
109659
- if (token.kind === "char" && token.value === " ") {
109660
- start += 1;
109661
- continue;
109662
- }
109663
- break;
109664
- }
109665
- }
109666
- return result.length > 0 ? result : [""];
109667
- }
109668
- function wrapTextToLines(text, columns) {
109669
- const result = [];
109670
- for (const logicalLine of text.split("\n")) {
109671
- if (logicalLine === "") {
109672
- result.push("");
109673
- continue;
109674
- }
109675
- let remaining = logicalLine;
109676
- while (remaining.length > 0) {
109677
- const { prefix, rest } = takeDisplayWidth(remaining, columns);
109678
- result.push(prefix);
109679
- remaining = rest;
109680
- }
109681
- }
109682
- return result;
109683
- }
109684
- var ANSI_ESCAPE_REGEX, OSC_HYPERLINK_REGEX, SGR_SEQUENCE_REGEX, TRAILING_SGR_REGEX, SGR_RESET_REGEX;
109685
- var init_tuiAnsi = __esm({
109686
- "packages/cli/tui/tuiAnsi.ts"() {
109687
- "use strict";
109688
- init_i18n2();
109689
- ANSI_ESCAPE_REGEX = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
109690
- OSC_HYPERLINK_REGEX = /\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)\x1b\]8;;(?:\x07|\x1b\\)|\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/g;
109691
- SGR_SEQUENCE_REGEX = /^\x1b\[[0-9;]*m/;
109692
- TRAILING_SGR_REGEX = /(?:\x1b\[[0-9;]*m)+$/;
109693
- SGR_RESET_REGEX = /^\x1b\[0?m$/;
109694
- }
109695
- });
109696
-
109697
- // packages/cli/tui/tuiScrollbar.ts
109698
- function parseScrollAction(sequence) {
109699
- const mouse = SGR_MOUSE_REGEX.exec(sequence);
109700
- if (mouse) {
109701
- const button = Number(mouse[1]);
109702
- if ((button & 64) === 0) return null;
109703
- if ((button & 2) !== 0) return null;
109704
- return (button & 1) !== 0 ? "wheel-down" : "wheel-up";
109705
- }
109706
- switch (sequence) {
109707
- case "\x1B[5~":
109708
- return "page-up";
109709
- case "\x1B[6~":
109710
- return "page-down";
109711
- case "\x1B[5;2~":
109712
- case "\x1B[5;5~":
109713
- return "half-page-up";
109714
- case "\x1B[6;2~":
109715
- case "\x1B[6;5~":
109716
- return "half-page-down";
109717
- case "\x1B[H":
109718
- case "\x1B[1~":
109719
- case "\x1B[7~":
109720
- return "top";
109721
- case "\x1B[F":
109722
- case "\x1B[4~":
109723
- case "\x1B[8~":
109724
- return "bottom";
109725
- default:
109726
- return null;
109727
- }
109728
- }
109729
- function renderScrollbarRow(rowIndex, visibleHeight, totalLines, scrollTop) {
109730
- if (totalLines <= visibleHeight) return " ";
109731
- const trackHeight = visibleHeight;
109732
- const thumbSize = Math.max(
109733
- 1,
109734
- Math.floor(visibleHeight * visibleHeight / totalLines)
109735
- );
109736
- const maxScrollTop = totalLines - visibleHeight;
109737
- const thumbTop = Math.floor(
109738
- scrollTop / maxScrollTop * (trackHeight - thumbSize)
109739
- );
109740
- const thumbBottom = thumbTop + thumbSize;
109741
- if (rowIndex >= thumbTop && rowIndex < thumbBottom) {
109742
- return "\u2588";
109743
- }
109744
- return "\u2502";
109745
- }
109746
- var SGR_MOUSE_REGEX, WHEEL_SCROLL_LINES;
109747
- var init_tuiScrollbar = __esm({
109748
- "packages/cli/tui/tuiScrollbar.ts"() {
109749
- "use strict";
109750
- SGR_MOUSE_REGEX = /^\x1b\[<(\d+);\d+;\d+[Mm]$/;
109751
- WHEEL_SCROLL_LINES = 3;
109752
- }
109753
- });
109754
-
109755
110480
  // packages/cli/tui/tuiHistory.ts
109756
110481
  function createTurnHistory() {
109757
110482
  return {
@@ -110664,58 +111389,27 @@ async function startTuiWorkspace(options) {
110664
111389
  stdout: output2
110665
111390
  });
110666
111391
  if (detected) setActiveBrightness(detected);
110667
- const frames = 15;
110668
- output2.write("\x1B[?25l");
110669
- for (let i = 0; i < frames; i++) {
110670
- output2.write(renderWelcome(state4, i, frames));
110671
- if (i < frames - 1) {
110672
- await new Promise((resolve9) => setTimeout(resolve9, 100));
110673
- output2.write("\r\x1B[8A\x1B[0J");
110674
- }
110675
- }
110676
- output2.write("\x1B[?25h");
111392
+ const bannerColumns = output2.columns;
111393
+ output2.write(renderWelcome(state4, 0, 0, bannerColumns));
110677
111394
  let fixedInput = createNoopFixedInput();
110678
111395
  let buffer = "";
110679
111396
  let cursorPos = 0;
110680
111397
  let activeTurnAbort = null;
110681
- let activityLabel = null;
110682
- let activityStartedAt = 0;
110683
- let activityFrameIndex = 0;
110684
- let activityTimer = null;
110685
- const stopActivity = () => {
110686
- if (activityTimer !== null) {
110687
- clearInterval(activityTimer);
110688
- activityTimer = null;
110689
- }
110690
- activityLabel = null;
110691
- activityStartedAt = 0;
110692
- };
110693
- const activityReporter = (label) => {
110694
- if (label !== null) {
110695
- activityLabel = label;
110696
- if (activityStartedAt === 0) {
110697
- activityStartedAt = Date.now();
110698
- }
110699
- if (activityTimer === null) {
110700
- activityTimer = setInterval(() => {
110701
- activityFrameIndex += 1;
110702
- if (fixedInput.active && !fixedInput.isPaused()) {
110703
- output2.write("\x1B[?2026h\x1B[?25l");
110704
- try {
110705
- fixedInput.repaint(buffer);
110706
- } finally {
110707
- output2.write("\x1B[?25h\x1B[?2026l");
110708
- }
110709
- }
110710
- }, 150);
110711
- }
110712
- } else {
110713
- stopActivity();
111398
+ const activityIndicator = createActivityIndicator({
111399
+ isTurnActive: () => activeTurnAbort !== null,
111400
+ fallbackLabel: () => `${state4.agentName} -> working`,
111401
+ onRepaint: () => {
110714
111402
  if (fixedInput.active && !fixedInput.isPaused()) {
110715
- fixedInput.repaint(buffer);
111403
+ output2.write("\x1B[?2026h\x1B[?25l");
111404
+ try {
111405
+ fixedInput.repaint(buffer);
111406
+ } finally {
111407
+ output2.write("\x1B[?25h\x1B[?2026l");
111408
+ }
110716
111409
  }
110717
111410
  }
110718
- };
111411
+ });
111412
+ const activityReporter = (label) => activityIndicator.report(label);
110719
111413
  let explicitAgentSwitch = false;
110720
111414
  let copyViewExitResolver = null;
110721
111415
  const history = createTurnHistory();
@@ -110814,25 +111508,15 @@ ${t("copyViewHint")}
110814
111508
  scheduleRender();
110815
111509
  }) : output2;
110816
111510
  const requestUserChoice = isInteractiveInput(input2) && dialogHost ? async (req) => {
110817
- const items = req.choices.map((c) => ({
110818
- label: c.label
110819
- }));
110820
111511
  try {
110821
- const pickResult = await dialogHost.run(
110822
- (anchor) => runSelectDialog({
110823
- items,
110824
- title: req.question,
111512
+ return await dialogHost.run(
111513
+ (anchor) => runAskChoiceDialog({
111514
+ request: req,
110825
111515
  input: input2,
110826
111516
  output: output2,
110827
111517
  ...anchor
110828
111518
  })
110829
111519
  );
110830
- if (pickResult.kind === "selected") {
110831
- const choice = req.choices[pickResult.index];
110832
- const userMessage = choice?.userMessage?.trim() || choice?.label || "";
110833
- return { kind: "selected", userMessage, label: choice?.label ?? "" };
110834
- }
110835
- return { kind: "cancelled" };
110836
111520
  } catch {
110837
111521
  return { kind: "cancelled" };
110838
111522
  }
@@ -110885,7 +111569,7 @@ ${t("copyViewHint")}
110885
111569
  }
110886
111570
  return { ok: !wasAborted, aborted: wasAborted };
110887
111571
  } finally {
110888
- stopActivity();
111572
+ activityIndicator.stop();
110889
111573
  activeTurnAbort = null;
110890
111574
  explicitAgentSwitch = false;
110891
111575
  }
@@ -111244,17 +111928,15 @@ ${err.message}` : ""}`
111244
111928
  return base;
111245
111929
  },
111246
111930
  getActivityLine: () => {
111247
- if (activityLabel === null) return null;
111931
+ const view = activityIndicator.getView();
111932
+ if (view === null) return null;
111248
111933
  const colorEnabled = resolveCliColorEnabled();
111249
- const FRAMES = ["\xB7", "~", "\u2248", "\u223F", "\u2248", "~"];
111250
- const frame = FRAMES[activityFrameIndex % FRAMES.length];
111251
- const elapsedSec = activityStartedAt > 0 ? Math.max(0, Math.floor((Date.now() - activityStartedAt) / 1e3)) : 0;
111252
- const elapsed = formatElapsedSeconds(elapsedSec);
111934
+ const elapsed = formatElapsedSeconds(view.elapsedSec);
111253
111935
  const stopHint = t("stopHint");
111254
111936
  if (!colorEnabled) {
111255
- return `${frame} ${activityLabel} (${elapsed}) \xB7 ${stopHint}`;
111937
+ return `${view.frame} ${view.label} (${elapsed}) \xB7 ${stopHint}`;
111256
111938
  }
111257
- return themeText(frame, "accent") + " " + themeText(activityLabel, "muted") + themeText(` (${elapsed})`, "chrome") + themeText(` \xB7 ${stopHint}`, "chrome");
111939
+ return themeText(view.frame, "accent") + " " + themeText(view.label, "muted") + themeText(` (${elapsed})`, "chrome") + themeText(` \xB7 ${stopHint}`, "chrome");
111258
111940
  },
111259
111941
  getQueueLines: () => {
111260
111942
  if (!chatQueueBinding || chatQueueBinding.queueLength() === 0) return [];
@@ -111343,12 +112025,27 @@ ${err.message}` : ""}`
111343
112025
  if (busyLock) {
111344
112026
  const submittedText2 = result.submit;
111345
112027
  const trimmedText = submittedText2.trim();
111346
- if (trimmedText === "/context" || trimmedText === "/ctx") {
112028
+ const busySlashCommand = trimmedText.split(/\s+/)[0]?.toLowerCase();
112029
+ const isBusyLocalSlash = busySlashCommand === "/context" || busySlashCommand === "/ctx" || busySlashCommand === "/switch";
112030
+ if (isBusyLocalSlash) {
112031
+ const beforeAgentKey = state4.agentKey;
111347
112032
  const res = handleTuiInput(submittedText2, state4);
111348
- state4 = res.nextState;
111349
- if (res.output) {
111350
- output2.write(`${res.output}
112033
+ if (res.action) {
112034
+ output2.write(
112035
+ "Model picker isn't available while a reply is running. Use `/switch <name>` to switch now (takes effect on the next turn), or wait for the reply to finish.\n"
112036
+ );
112037
+ } else {
112038
+ state4 = res.nextState;
112039
+ let msg = res.output;
112040
+ if (busySlashCommand === "/switch" && res.nextState.agentKey !== beforeAgentKey) {
112041
+ const hint = "Note: the new model takes effect on the next turn. Switching models may consume more tokens because the conversation context is re-sent to the new model.";
112042
+ msg = msg ? `${msg}
112043
+ ${hint}` : hint;
112044
+ }
112045
+ if (msg) {
112046
+ output2.write(`${msg}
111351
112047
  `);
112048
+ }
111352
112049
  }
111353
112050
  buffer = "";
111354
112051
  cursorPos2 = 0;
@@ -111571,8 +112268,9 @@ var init_readlineWorkspace = __esm({
111571
112268
  init_updateCommands();
111572
112269
  init_processSpawn();
111573
112270
  init_confirmDialog();
111574
- init_selectDialog();
112271
+ init_askChoiceDialog();
111575
112272
  init_dialogHost();
112273
+ init_activityIndicator();
111576
112274
  init_agentPicker();
111577
112275
  init_agentCatalog();
111578
112276
  init_dialogPicker();