nolo-cli 0.7.0 → 0.8.0-alpha.2

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 +1038 -368
  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
  }
@@ -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,15 @@ 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";
106919
107039
  }
106920
107040
  });
106921
107041
 
@@ -107257,6 +107377,496 @@ var init_confirmDialog = __esm({
107257
107377
  }
107258
107378
  });
107259
107379
 
107380
+ // packages/ai/tools/askChoiceState.ts
107381
+ function normalizeAskChoiceArgs(args2) {
107382
+ const blocking = args2.blocking !== false;
107383
+ if (Array.isArray(args2.questions) && args2.questions.length > 0) {
107384
+ return {
107385
+ questions: args2.questions.map(
107386
+ (q, i) => normalizeQuestion(q, i)
107387
+ ),
107388
+ blocking
107389
+ };
107390
+ }
107391
+ const question = String(args2.question ?? "").trim();
107392
+ const choices = Array.isArray(args2.choices) ? args2.choices : [];
107393
+ if (!question || choices.length === 0) {
107394
+ return { questions: [], blocking };
107395
+ }
107396
+ return {
107397
+ questions: [
107398
+ normalizeQuestion(
107399
+ { question, choices, multiSelect: false, allowOther: true, required: true },
107400
+ 0
107401
+ )
107402
+ ],
107403
+ blocking
107404
+ };
107405
+ }
107406
+ function normalizeQuestion(raw, index) {
107407
+ return {
107408
+ id: String(raw?.id ?? `q${index}`),
107409
+ question: String(raw?.question ?? "").trim(),
107410
+ choices: (Array.isArray(raw?.choices) ? raw.choices : []).map(
107411
+ (c, ci) => ({
107412
+ id: String(c?.id ?? `c${ci}`),
107413
+ label: String(c?.label ?? "").trim(),
107414
+ ...typeof c?.detail === "string" && c.detail.trim() ? { detail: c.detail.trim() } : {},
107415
+ ...typeof c?.userMessage === "string" && c.userMessage.trim() ? { userMessage: c.userMessage.trim() } : {}
107416
+ })
107417
+ ),
107418
+ multiSelect: raw?.multiSelect === true,
107419
+ allowOther: raw?.allowOther !== false,
107420
+ required: raw?.required !== false
107421
+ };
107422
+ }
107423
+ function createInitialAskChoiceState(questions) {
107424
+ return {
107425
+ questions,
107426
+ activeIndex: 0,
107427
+ questionStates: questions.map(() => ({
107428
+ cursorIndex: 0,
107429
+ selectedIds: [],
107430
+ pickedId: null,
107431
+ otherText: "",
107432
+ otherFocused: false
107433
+ })),
107434
+ phase: "active"
107435
+ };
107436
+ }
107437
+ function askChoiceReducer(state4, action3) {
107438
+ if (state4.phase !== "active") return state4;
107439
+ switch (action3.type) {
107440
+ case "CANCEL":
107441
+ return { ...state4, phase: "cancelled" };
107442
+ case "SUBMIT": {
107443
+ if (!canSubmit(state4)) return state4;
107444
+ return { ...state4, phase: "submitted" };
107445
+ }
107446
+ case "SWITCH_TAB": {
107447
+ const idx = clamp3(action3.index, 0, state4.questions.length - 1);
107448
+ return { ...state4, activeIndex: idx };
107449
+ }
107450
+ case "NEXT_TAB": {
107451
+ const idx = Math.min(state4.activeIndex + 1, state4.questions.length - 1);
107452
+ return { ...state4, activeIndex: idx };
107453
+ }
107454
+ case "PREV_TAB": {
107455
+ const idx = Math.max(state4.activeIndex - 1, 0);
107456
+ return { ...state4, activeIndex: idx };
107457
+ }
107458
+ case "MOVE_CURSOR": {
107459
+ const qs = state4.questionStates[state4.activeIndex];
107460
+ const q = state4.questions[state4.activeIndex];
107461
+ const maxIndex = q.allowOther ? q.choices.length : q.choices.length - 1;
107462
+ const next = clamp3(qs.cursorIndex + action3.delta, 0, maxIndex);
107463
+ const newQs = [...state4.questionStates];
107464
+ newQs[state4.activeIndex] = {
107465
+ ...qs,
107466
+ cursorIndex: next,
107467
+ otherFocused: false
107468
+ };
107469
+ return { ...state4, questionStates: newQs };
107470
+ }
107471
+ case "TOGGLE_AT_CURSOR": {
107472
+ const qs = state4.questionStates[state4.activeIndex];
107473
+ const q = state4.questions[state4.activeIndex];
107474
+ if (!q.multiSelect) return state4;
107475
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
107476
+ if (isOtherRow) {
107477
+ const newQs2 = [...state4.questionStates];
107478
+ newQs2[state4.activeIndex] = { ...qs, otherFocused: !qs.otherFocused };
107479
+ return { ...state4, questionStates: newQs2 };
107480
+ }
107481
+ const choiceId = q.choices[qs.cursorIndex]?.id;
107482
+ if (!choiceId) return state4;
107483
+ const has = qs.selectedIds.includes(choiceId);
107484
+ const newSelected = has ? qs.selectedIds.filter((id) => id !== choiceId) : [...qs.selectedIds, choiceId];
107485
+ const newQs = [...state4.questionStates];
107486
+ newQs[state4.activeIndex] = { ...qs, selectedIds: newSelected };
107487
+ return { ...state4, questionStates: newQs };
107488
+ }
107489
+ case "SELECT_AT_CURSOR": {
107490
+ const qs = state4.questionStates[state4.activeIndex];
107491
+ const q = state4.questions[state4.activeIndex];
107492
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
107493
+ if (isOtherRow) {
107494
+ const newQs2 = [...state4.questionStates];
107495
+ newQs2[state4.activeIndex] = { ...qs, otherFocused: true };
107496
+ return { ...state4, questionStates: newQs2 };
107497
+ }
107498
+ const choiceId = q.choices[qs.cursorIndex]?.id;
107499
+ if (!choiceId) return state4;
107500
+ if (q.multiSelect) {
107501
+ const has = qs.selectedIds.includes(choiceId);
107502
+ const newSelected = has ? qs.selectedIds.filter((id) => id !== choiceId) : [...qs.selectedIds, choiceId];
107503
+ const newQs2 = [...state4.questionStates];
107504
+ newQs2[state4.activeIndex] = { ...qs, selectedIds: newSelected };
107505
+ return { ...state4, questionStates: newQs2 };
107506
+ }
107507
+ const newQs = [...state4.questionStates];
107508
+ newQs[state4.activeIndex] = { ...qs, pickedId: choiceId, otherFocused: false };
107509
+ if (state4.questions.length === 1) {
107510
+ return {
107511
+ ...state4,
107512
+ questionStates: newQs,
107513
+ phase: "submitted"
107514
+ };
107515
+ }
107516
+ const nextTab = Math.min(
107517
+ state4.activeIndex + 1,
107518
+ state4.questions.length - 1
107519
+ );
107520
+ return {
107521
+ ...state4,
107522
+ questionStates: newQs,
107523
+ activeIndex: nextTab
107524
+ };
107525
+ }
107526
+ case "FOCUS_OTHER": {
107527
+ const qs = state4.questionStates[state4.activeIndex];
107528
+ const newQs = [...state4.questionStates];
107529
+ newQs[state4.activeIndex] = { ...qs, otherFocused: true };
107530
+ return { ...state4, questionStates: newQs };
107531
+ }
107532
+ case "BLUR_OTHER": {
107533
+ const qs = state4.questionStates[state4.activeIndex];
107534
+ const newQs = [...state4.questionStates];
107535
+ newQs[state4.activeIndex] = { ...qs, otherFocused: false };
107536
+ return { ...state4, questionStates: newQs };
107537
+ }
107538
+ case "SET_OTHER_TEXT": {
107539
+ const qs = state4.questionStates[state4.activeIndex];
107540
+ const newQs = [...state4.questionStates];
107541
+ newQs[state4.activeIndex] = { ...qs, otherText: action3.text };
107542
+ return { ...state4, questionStates: newQs };
107543
+ }
107544
+ default:
107545
+ return state4;
107546
+ }
107547
+ }
107548
+ function isQuestionAnswered(q, qs) {
107549
+ if (!q.required) return true;
107550
+ const hasSelection = q.multiSelect ? qs.selectedIds.length > 0 : qs.pickedId !== null;
107551
+ const hasOther = q.allowOther && qs.otherText.trim().length > 0;
107552
+ return hasSelection || hasOther;
107553
+ }
107554
+ function canSubmit(state4) {
107555
+ return state4.questions.every(
107556
+ (q, i) => isQuestionAnswered(q, state4.questionStates[i])
107557
+ );
107558
+ }
107559
+ function buildAskChoiceResult(state4) {
107560
+ if (state4.phase === "cancelled") return { kind: "cancelled" };
107561
+ if (state4.phase !== "submitted") return { kind: "cancelled" };
107562
+ const answers = state4.questions.map((q, i) => {
107563
+ const qs = state4.questionStates[i];
107564
+ const selectedIds = q.multiSelect ? qs.selectedIds : qs.pickedId ? [qs.pickedId] : [];
107565
+ const otherText = qs.otherText.trim();
107566
+ const parts = [];
107567
+ for (const id of selectedIds) {
107568
+ const choice = q.choices.find((c) => c.id === id);
107569
+ if (choice) {
107570
+ parts.push(choice.userMessage || choice.label);
107571
+ }
107572
+ }
107573
+ if (otherText) {
107574
+ parts.push(otherText);
107575
+ }
107576
+ return {
107577
+ questionId: q.id,
107578
+ selectedIds,
107579
+ otherText,
107580
+ userMessage: parts.join("\n")
107581
+ };
107582
+ });
107583
+ return { kind: "submitted", answers };
107584
+ }
107585
+ function clamp3(value, min, max) {
107586
+ return Math.max(min, Math.min(max, value));
107587
+ }
107588
+ var init_askChoiceState = __esm({
107589
+ "packages/ai/tools/askChoiceState.ts"() {
107590
+ "use strict";
107591
+ }
107592
+ });
107593
+
107594
+ // packages/cli/tui/askChoiceDialog.ts
107595
+ function renderTabBar(questions, activeIndex, colorEnabled) {
107596
+ const tabs = questions.map((q, i) => {
107597
+ const label = `Q${i + 1}`;
107598
+ if (i === activeIndex) {
107599
+ return colorEnabled ? `${themeColorSequence("accent")}\x1B[1m ${label} \x1B[0m` : `[${label}]`;
107600
+ }
107601
+ return colorEnabled ? themeText(` ${label} `, "muted", colorEnabled) : ` ${label} `;
107602
+ });
107603
+ const submitLabel = "Submit";
107604
+ return tabs.join(" ") + " " + (colorEnabled ? themeText(submitLabel, "chrome", colorEnabled) : submitLabel);
107605
+ }
107606
+ function renderFooter(colorEnabled) {
107607
+ const hints = "type answer \u21B5save tab switch s submit esc cancel";
107608
+ return colorEnabled ? themeText(` ${hints}`, "chrome", colorEnabled) : ` ${hints}`;
107609
+ }
107610
+ function renderAskChoiceFrame(state4) {
107611
+ const colorEnabled = resolveCliColorEnabled();
107612
+ const q = state4.questions[state4.activeIndex];
107613
+ const qs = state4.questionStates[state4.activeIndex];
107614
+ const lines = [];
107615
+ lines.push(renderDialogTitle("question"));
107616
+ lines.push("");
107617
+ if (state4.questions.length > 1) {
107618
+ lines.push(renderTabBar(state4.questions, state4.activeIndex, colorEnabled));
107619
+ lines.push("");
107620
+ }
107621
+ lines.push(
107622
+ colorEnabled ? `${themeColorSequence("accent")}? ${q.question}\x1B[0m` : `? ${q.question}`
107623
+ );
107624
+ if (q.multiSelect) {
107625
+ lines.push(
107626
+ colorEnabled ? themeText(" Space to toggle, Enter to confirm selection.", "muted", colorEnabled) : " Space to toggle, Enter to confirm selection."
107627
+ );
107628
+ } else {
107629
+ lines.push(
107630
+ colorEnabled ? themeText(" Type your answer, then press Enter to save.", "muted", colorEnabled) : " Type your answer, then press Enter to save."
107631
+ );
107632
+ }
107633
+ lines.push("");
107634
+ const totalRows = q.choices.length + (q.allowOther ? 1 : 0);
107635
+ const window2 = computeVisibleWindow({
107636
+ selectedIndex: qs.cursorIndex,
107637
+ total: totalRows
107638
+ });
107639
+ if (window2.start > 0) {
107640
+ lines.push(
107641
+ colorEnabled ? themeText(` \u2191 ${window2.start} more`, "chrome", colorEnabled) : ` \u2191 ${window2.start} more`
107642
+ );
107643
+ }
107644
+ for (let i = window2.start; i < window2.end; i++) {
107645
+ if (i < q.choices.length) {
107646
+ const choice = q.choices[i];
107647
+ const focused = qs.cursorIndex === i;
107648
+ const checkbox = q.multiSelect ? qs.selectedIds.includes(choice.id) ? DIALOG_CHECKED : DIALOG_UNCHECKED : qs.pickedId === choice.id ? DIALOG_CHECKED : void 0;
107649
+ lines.push(
107650
+ renderDialogRow({
107651
+ label: `[${i + 1}] ${choice.label}`,
107652
+ ...choice.detail ? { detail: choice.detail } : {},
107653
+ focused,
107654
+ ...checkbox ? { checkbox } : {}
107655
+ })
107656
+ );
107657
+ } else {
107658
+ const focused = qs.cursorIndex === i;
107659
+ const marker = focused ? DIALOG_CURSOR : " ";
107660
+ const otherContent = qs.otherFocused ? `${qs.otherText}\u2588` : qs.otherText || "";
107661
+ const row = `${marker} [${i + 1}] Other: ${otherContent}`;
107662
+ if (focused && colorEnabled) {
107663
+ lines.push(
107664
+ `\x1B[1m${themeColorSequence("accent")}${row}\x1B[0m`
107665
+ );
107666
+ } else {
107667
+ lines.push(row);
107668
+ }
107669
+ }
107670
+ }
107671
+ if (window2.end < totalRows) {
107672
+ lines.push(
107673
+ colorEnabled ? themeText(` \u2193 ${totalRows - window2.end} more`, "chrome", colorEnabled) : ` \u2193 ${totalRows - window2.end} more`
107674
+ );
107675
+ }
107676
+ lines.push("");
107677
+ lines.push(renderFooter(colorEnabled));
107678
+ return lines.join("\n");
107679
+ }
107680
+ async function runAskChoiceDialog(args2) {
107681
+ const { request } = args2;
107682
+ const normalized = normalizeAskChoiceArgs({
107683
+ question: request.question,
107684
+ choices: request.choices,
107685
+ questions: request.questions,
107686
+ blocking: request.blocking
107687
+ });
107688
+ if (normalized.questions.length === 0) {
107689
+ return { kind: "cancelled" };
107690
+ }
107691
+ const q0 = normalized.questions[0];
107692
+ if (normalized.questions.length === 1 && !q0.multiSelect && !q0.allowOther) {
107693
+ }
107694
+ let state4 = createInitialAskChoiceState(normalized.questions);
107695
+ const output2 = args2.output ?? process.stdout;
107696
+ const input2 = args2.input ?? process.stdin;
107697
+ const readKey = args2.readKey ?? createRawKeyReader(input2);
107698
+ const wasRaw = Boolean(input2.isTTY && input2.isRaw);
107699
+ let renderedLineCount = 0;
107700
+ const bottomAnchored = Boolean(args2.bottomAnchored && args2.bottomRow);
107701
+ const resolveBottomRow = () => Math.max(
107702
+ 1,
107703
+ typeof args2.bottomRow === "function" ? args2.bottomRow() : args2.bottomRow ?? 0
107704
+ );
107705
+ let lastBottomRow = 0;
107706
+ const paint = () => {
107707
+ const frame = renderAskChoiceFrame(state4);
107708
+ const lines = frame.split("\n");
107709
+ const lineCount = lines.length;
107710
+ const canPosition = outputIsTty(output2) && typeof output2.write === "function";
107711
+ if (bottomAnchored && canPosition) {
107712
+ const anchorRow = resolveBottomRow();
107713
+ clearAnchoredLines(
107714
+ output2,
107715
+ lastBottomRow > 0 ? lastBottomRow : anchorRow,
107716
+ renderedLineCount
107717
+ );
107718
+ for (let i = 0; i < lines.length; i++) {
107719
+ const row = anchorRow - (lines.length - 1 - i);
107720
+ if (row < 1) break;
107721
+ output2.write(`\x1B[${row};1H\x1B[2K${lines[i]}`);
107722
+ }
107723
+ lastBottomRow = anchorRow;
107724
+ renderedLineCount = lineCount;
107725
+ return;
107726
+ }
107727
+ if (canPosition) {
107728
+ for (let i = 0; i < renderedLineCount; i++) {
107729
+ output2.write("\x1B[1A\x1B[2K");
107730
+ }
107731
+ output2.write(`${frame}
107732
+ `);
107733
+ renderedLineCount = lineCount;
107734
+ return;
107735
+ }
107736
+ if (typeof output2.write === "function") {
107737
+ output2.write(`${frame}
107738
+ `);
107739
+ }
107740
+ renderedLineCount = lineCount;
107741
+ };
107742
+ const resizeTarget = output2;
107743
+ const onOutputResize = () => paint();
107744
+ if (input2.isTTY && !wasRaw) {
107745
+ input2.setRawMode?.(true);
107746
+ }
107747
+ if (bottomAnchored && outputIsTty(output2)) {
107748
+ resizeTarget.on?.("resize", onOutputResize);
107749
+ }
107750
+ paint();
107751
+ try {
107752
+ while (state4.phase === "active") {
107753
+ const sequence = await readKey();
107754
+ if (sequence == null) {
107755
+ state4 = askChoiceReducer(state4, { type: "CANCEL" });
107756
+ break;
107757
+ }
107758
+ let action3 = null;
107759
+ if (isCancel(sequence)) {
107760
+ action3 = { type: "CANCEL" };
107761
+ } else if (sequence === CSI_TAB) {
107762
+ action3 = { type: "NEXT_TAB" };
107763
+ } else if (sequence === CSI_SHIFT_TAB) {
107764
+ action3 = { type: "PREV_TAB" };
107765
+ } else if (isArrowUp(sequence)) {
107766
+ action3 = { type: "MOVE_CURSOR", delta: -1 };
107767
+ } else if (isArrowDown(sequence)) {
107768
+ action3 = { type: "MOVE_CURSOR", delta: 1 };
107769
+ } else if (sequence === CSI_SPACE) {
107770
+ action3 = { type: "TOGGLE_AT_CURSOR" };
107771
+ } else if (sequence === "s" && !state4.questionStates[state4.activeIndex].otherFocused && canSubmit(state4)) {
107772
+ action3 = { type: "SUBMIT" };
107773
+ } else if (isSubmit(sequence)) {
107774
+ const qs = state4.questionStates[state4.activeIndex];
107775
+ const q = state4.questions[state4.activeIndex];
107776
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
107777
+ if (qs.otherFocused) {
107778
+ action3 = { type: "BLUR_OTHER" };
107779
+ } else if (isOtherRow && !qs.otherFocused) {
107780
+ action3 = { type: "FOCUS_OTHER" };
107781
+ } else {
107782
+ action3 = { type: "SELECT_AT_CURSOR" };
107783
+ }
107784
+ } else if (sequence === CSI_BACKSPACE || sequence === CSI_BACKSPACE_ALT) {
107785
+ const qs = state4.questionStates[state4.activeIndex];
107786
+ if (qs.otherFocused && qs.otherText.length > 0) {
107787
+ action3 = {
107788
+ type: "SET_OTHER_TEXT",
107789
+ text: qs.otherText.slice(0, -1)
107790
+ };
107791
+ }
107792
+ } else if (sequence.length === 1 && sequence.charCodeAt(0) >= 32) {
107793
+ const qs = state4.questionStates[state4.activeIndex];
107794
+ if (qs.otherFocused) {
107795
+ action3 = {
107796
+ type: "SET_OTHER_TEXT",
107797
+ text: qs.otherText + sequence
107798
+ };
107799
+ }
107800
+ }
107801
+ if (action3) {
107802
+ const prev = state4;
107803
+ state4 = askChoiceReducer(state4, action3);
107804
+ if (state4.phase !== "active") break;
107805
+ if (action3.type === "BLUR_OTHER" && state4.questions.length === 1 && canSubmit(state4)) {
107806
+ state4 = askChoiceReducer(state4, { type: "SUBMIT" });
107807
+ break;
107808
+ }
107809
+ paint();
107810
+ }
107811
+ }
107812
+ } finally {
107813
+ resizeTarget.off?.("resize", onOutputResize);
107814
+ readKey.dispose?.();
107815
+ if (input2.isTTY) {
107816
+ drainInputBuffer(input2);
107817
+ if (!wasRaw) input2.setRawMode?.(false);
107818
+ if (bottomAnchored) {
107819
+ clearAnchoredLines(
107820
+ output2,
107821
+ lastBottomRow > 0 ? lastBottomRow : resolveBottomRow(),
107822
+ renderedLineCount
107823
+ );
107824
+ } else {
107825
+ for (let i = 0; i < renderedLineCount; i++) {
107826
+ output2.write("\x1B[1A\x1B[2K");
107827
+ }
107828
+ }
107829
+ renderedLineCount = 0;
107830
+ }
107831
+ }
107832
+ const result = buildAskChoiceResult(state4);
107833
+ if (result.kind === "cancelled") {
107834
+ return { kind: "cancelled" };
107835
+ }
107836
+ if (result.answers.length === 1) {
107837
+ const a = result.answers[0];
107838
+ return {
107839
+ kind: "selected",
107840
+ userMessage: a.userMessage,
107841
+ label: a.selectedIds.map((id) => {
107842
+ const q = normalized.questions[0];
107843
+ return q.choices.find((c) => c.id === id)?.label ?? "";
107844
+ }).join(", ") || a.otherText || ""
107845
+ };
107846
+ }
107847
+ return {
107848
+ kind: "multi-submitted",
107849
+ answers: result.answers,
107850
+ userMessage: result.answers.map((a) => a.userMessage).filter(Boolean).join("\n\n")
107851
+ };
107852
+ }
107853
+ var CSI_TAB, CSI_SHIFT_TAB, CSI_BACKSPACE, CSI_BACKSPACE_ALT, CSI_SPACE;
107854
+ var init_askChoiceDialog = __esm({
107855
+ "packages/cli/tui/askChoiceDialog.ts"() {
107856
+ "use strict";
107857
+ init_askChoiceState();
107858
+ init_dialogFrame();
107859
+ init_selectDialog();
107860
+ init_terminalStyles();
107861
+ init_theme();
107862
+ CSI_TAB = " ";
107863
+ CSI_SHIFT_TAB = "\x1B[Z";
107864
+ CSI_BACKSPACE = "\x7F";
107865
+ CSI_BACKSPACE_ALT = "\b";
107866
+ CSI_SPACE = " ";
107867
+ }
107868
+ });
107869
+
107260
107870
  // packages/cli/tui/dialogHost.ts
107261
107871
  function resolveTtyRows(output2) {
107262
107872
  if (typeof output2 === "object" && output2 !== null && "rows" in output2 && typeof output2.rows === "number") {
@@ -107294,6 +107904,92 @@ var init_dialogHost = __esm({
107294
107904
  }
107295
107905
  });
107296
107906
 
107907
+ // packages/cli/tui/activityIndicator.ts
107908
+ function createActivityIndicator(deps) {
107909
+ const now = deps.now ?? (() => Date.now());
107910
+ const frameIntervalMs = deps.frameIntervalMs ?? ACTIVITY_FRAME_INTERVAL_MS;
107911
+ const fallbackDelayMs = deps.fallbackDelayMs ?? ACTIVITY_FALLBACK_DELAY_MS;
107912
+ const setIntervalFn = deps.setIntervalFn ?? ((cb, ms) => setInterval(cb, ms));
107913
+ const clearIntervalFn = deps.clearIntervalFn ?? ((handle) => clearInterval(handle));
107914
+ let explicitLabel = null;
107915
+ let explicitStartedAt = 0;
107916
+ let fallbackActive = false;
107917
+ let fallbackStartedAt = 0;
107918
+ let lastActivityAt = 0;
107919
+ let frameIndex = 0;
107920
+ let timer = null;
107921
+ const elapsedSecFrom = (startedAt) => startedAt > 0 ? Math.max(0, Math.floor((now() - startedAt) / 1e3)) : 0;
107922
+ const tick = () => {
107923
+ frameIndex += 1;
107924
+ if (explicitLabel === null && !fallbackActive && deps.isTurnActive() && lastActivityAt > 0 && now() - lastActivityAt >= fallbackDelayMs) {
107925
+ fallbackActive = true;
107926
+ fallbackStartedAt = now();
107927
+ }
107928
+ deps.onRepaint();
107929
+ };
107930
+ const ensureTimer = () => {
107931
+ if (timer === null) {
107932
+ timer = setIntervalFn(tick, frameIntervalMs);
107933
+ }
107934
+ };
107935
+ const report = (label) => {
107936
+ lastActivityAt = now();
107937
+ if (label !== null) {
107938
+ explicitLabel = label;
107939
+ if (explicitStartedAt === 0) explicitStartedAt = lastActivityAt;
107940
+ fallbackActive = false;
107941
+ fallbackStartedAt = 0;
107942
+ } else {
107943
+ explicitLabel = null;
107944
+ explicitStartedAt = 0;
107945
+ fallbackActive = false;
107946
+ fallbackStartedAt = 0;
107947
+ }
107948
+ ensureTimer();
107949
+ deps.onRepaint();
107950
+ };
107951
+ const getView = () => {
107952
+ if (!deps.isTurnActive()) return null;
107953
+ const frame = ACTIVITY_FRAMES[frameIndex % ACTIVITY_FRAMES.length];
107954
+ if (explicitLabel !== null) {
107955
+ return {
107956
+ frame,
107957
+ label: explicitLabel,
107958
+ elapsedSec: elapsedSecFrom(explicitStartedAt)
107959
+ };
107960
+ }
107961
+ if (fallbackActive) {
107962
+ return {
107963
+ frame,
107964
+ label: deps.fallbackLabel(),
107965
+ elapsedSec: elapsedSecFrom(fallbackStartedAt)
107966
+ };
107967
+ }
107968
+ return null;
107969
+ };
107970
+ const stop = () => {
107971
+ if (timer !== null) {
107972
+ clearIntervalFn(timer);
107973
+ timer = null;
107974
+ }
107975
+ explicitLabel = null;
107976
+ explicitStartedAt = 0;
107977
+ fallbackActive = false;
107978
+ fallbackStartedAt = 0;
107979
+ lastActivityAt = 0;
107980
+ };
107981
+ return { report, getView, stop };
107982
+ }
107983
+ var ACTIVITY_FRAMES, ACTIVITY_FRAME_INTERVAL_MS, ACTIVITY_FALLBACK_DELAY_MS;
107984
+ var init_activityIndicator = __esm({
107985
+ "packages/cli/tui/activityIndicator.ts"() {
107986
+ "use strict";
107987
+ ACTIVITY_FRAMES = ["\xB7", "~", "\u2248", "\u223F", "\u2248", "~"];
107988
+ ACTIVITY_FRAME_INTERVAL_MS = 150;
107989
+ ACTIVITY_FALLBACK_DELAY_MS = 1200;
107990
+ }
107991
+ });
107992
+
107297
107993
  // packages/ai/agent/utils/sortUtils.ts
107298
107994
  function sortAgentsFavoriteOwnedPublic(items) {
107299
107995
  const map = /* @__PURE__ */ new Map();
@@ -108051,6 +108747,278 @@ var init_gitStatus = __esm({
108051
108747
  }
108052
108748
  });
108053
108749
 
108750
+ // packages/cli/tui/tuiAnsi.ts
108751
+ function stripAnsi(text) {
108752
+ return text.replace(OSC_HYPERLINK_REGEX, "").replace(ANSI_ESCAPE_REGEX, "");
108753
+ }
108754
+ function applyTerminalOutputToText(existing, chunk) {
108755
+ if (!chunk) return existing;
108756
+ let text = existing;
108757
+ let index = 0;
108758
+ while (index < chunk.length) {
108759
+ if (chunk[index] === "\x1B") {
108760
+ const sgr = SGR_SEQUENCE_REGEX.exec(chunk.slice(index));
108761
+ if (sgr) {
108762
+ text += sgr[0];
108763
+ index += sgr[0].length;
108764
+ continue;
108765
+ }
108766
+ const osc = chunk.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
108767
+ if (osc) {
108768
+ index += osc[0].length;
108769
+ continue;
108770
+ }
108771
+ const csi = chunk.slice(index).match(/^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/);
108772
+ if (csi) {
108773
+ index += csi[0].length;
108774
+ continue;
108775
+ }
108776
+ index += 1;
108777
+ continue;
108778
+ }
108779
+ const ch = chunk[index];
108780
+ if (ch === "\r") {
108781
+ const lastNl = text.lastIndexOf("\n");
108782
+ text = lastNl === -1 ? "" : text.slice(0, lastNl + 1);
108783
+ index += 1;
108784
+ continue;
108785
+ }
108786
+ if (ch === "\n") {
108787
+ text += "\n";
108788
+ index += 1;
108789
+ continue;
108790
+ }
108791
+ if (ch === "\b") {
108792
+ const trailing = TRAILING_SGR_REGEX.exec(text);
108793
+ const sgrTail = trailing ? trailing[0] : "";
108794
+ const head = sgrTail ? text.slice(0, -sgrTail.length) : text;
108795
+ if (head.length > 0 && head[head.length - 1] !== "\n") {
108796
+ text = head.slice(0, -1) + sgrTail;
108797
+ }
108798
+ index += 1;
108799
+ continue;
108800
+ }
108801
+ const code = ch.charCodeAt(0);
108802
+ if (code < 32 && ch !== " " || code === 127) {
108803
+ index += 1;
108804
+ continue;
108805
+ }
108806
+ text += ch;
108807
+ index += 1;
108808
+ }
108809
+ return text;
108810
+ }
108811
+ function displayWidth2(str) {
108812
+ let width = 0;
108813
+ for (const char of str) {
108814
+ const code = char.codePointAt(0) ?? 0;
108815
+ if (code < 32 || code === 127) continue;
108816
+ if (code >= 4352 && code <= 4447 || // 0x2768-0x2775 (ornamental brackets, incl. the ❯ prompt at U+276F)
108817
+ // render narrow in terminals; counting them wide drifts the cursor.
108818
+ code >= 9728 && code <= 10175 && !(code >= 10088 && code <= 10101) || // 0x2B00-0x2BFF (Misc Symbols and Arrows, incl. ⬢ U+2B22 used as the
108819
+ // status-line agent icon) render double-wide in common monospace fonts;
108820
+ // counting ⬢ as width 1 drifts the whole status line right of the icon.
108821
+ 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 ‘ ’).
108822
+ // In CJK locale + CJK fonts these render double-wide like 全角 punctuation;
108823
+ // in English fonts they stay width 1. Follow the active locale so Chinese
108824
+ // replies align correctly without breaking English terminal output.
108825
+ getCliLocale() === "zh" && (code === 8220 || code === 8221 || code === 8216 || code === 8217)) {
108826
+ width += 2;
108827
+ } else {
108828
+ width += 1;
108829
+ }
108830
+ }
108831
+ return width;
108832
+ }
108833
+ function visibleWidth(str) {
108834
+ return displayWidth2(stripAnsi(str));
108835
+ }
108836
+ function truncateAnsi(text, maxWidth) {
108837
+ if (maxWidth <= 0) return "";
108838
+ if (visibleWidth(text) <= maxWidth) return text;
108839
+ let width = 0;
108840
+ let out = "";
108841
+ let i = 0;
108842
+ let sawAnsi = false;
108843
+ while (i < text.length) {
108844
+ if (text[i] === "\x1B" && text[i + 1] === "[") {
108845
+ sawAnsi = true;
108846
+ let j = i + 2;
108847
+ while (j < text.length) {
108848
+ const code = text.charCodeAt(j);
108849
+ j += 1;
108850
+ if (code >= 64 && code <= 126) break;
108851
+ }
108852
+ out += text.slice(i, j);
108853
+ i = j;
108854
+ continue;
108855
+ }
108856
+ const codePoint = text.codePointAt(i) ?? 0;
108857
+ const char = String.fromCodePoint(codePoint);
108858
+ const charWidth = displayWidth2(char);
108859
+ if (width + charWidth > maxWidth) break;
108860
+ out += char;
108861
+ width += charWidth;
108862
+ i += char.length;
108863
+ }
108864
+ return sawAnsi ? `${out}\x1B[0m` : out;
108865
+ }
108866
+ function fitAnsiLine(text, width, ellipsis = "\u2026") {
108867
+ if (width <= 0) return "";
108868
+ if (visibleWidth(text) <= width) return text;
108869
+ const ellipsisWidth = displayWidth2(ellipsis);
108870
+ if (width < ellipsisWidth) return truncateAnsi(text, width);
108871
+ if (width === ellipsisWidth) return truncateAnsi(ellipsis, width) || truncateAnsi(text, width);
108872
+ return `${truncateAnsi(text, width - ellipsisWidth)}${ellipsis}`;
108873
+ }
108874
+ function countPhysicalLines(text, columns) {
108875
+ const lines = text.split("\n");
108876
+ let total = 0;
108877
+ for (const line of lines) {
108878
+ const width = displayWidth2(line);
108879
+ total += Math.max(1, Math.ceil(width / columns));
108880
+ }
108881
+ return Math.max(total, 1);
108882
+ }
108883
+ function takeDisplayWidth(text, width) {
108884
+ let used = 0;
108885
+ let index = 0;
108886
+ for (const char of text) {
108887
+ const charWidth = displayWidth2(char);
108888
+ if (used + charWidth > width && used > 0) break;
108889
+ used += charWidth;
108890
+ index += char.length;
108891
+ }
108892
+ return { prefix: text.slice(0, index), rest: text.slice(index) };
108893
+ }
108894
+ function padOrTruncateToWidth(text, width) {
108895
+ const textWidth = visibleWidth(text);
108896
+ if (textWidth > width) {
108897
+ return truncateAnsi(text, width);
108898
+ }
108899
+ return `${text}${" ".repeat(width - textWidth)}`;
108900
+ }
108901
+ function tokenizeAnsiLine(line) {
108902
+ const tokens = [];
108903
+ let index = 0;
108904
+ while (index < line.length) {
108905
+ if (line[index] === "\x1B") {
108906
+ const osc = line.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
108907
+ if (osc) {
108908
+ tokens.push({ kind: "sgr", value: osc[0], width: 0 });
108909
+ index += osc[0].length;
108910
+ continue;
108911
+ }
108912
+ const sgr = SGR_SEQUENCE_REGEX.exec(line.slice(index));
108913
+ if (sgr) {
108914
+ tokens.push({ kind: "sgr", value: sgr[0], width: 0 });
108915
+ index += sgr[0].length;
108916
+ continue;
108917
+ }
108918
+ }
108919
+ const codePoint = line.codePointAt(index) ?? 0;
108920
+ const value = String.fromCodePoint(codePoint);
108921
+ tokens.push({ kind: "char", value, width: displayWidth2(value) });
108922
+ index += value.length;
108923
+ }
108924
+ return tokens;
108925
+ }
108926
+ function wrapTranscriptLine(line, columns) {
108927
+ if (line === "") return [""];
108928
+ const tokens = tokenizeAnsiLine(line);
108929
+ const result = [];
108930
+ let activeStyles = [];
108931
+ const applyStyleToken = (value) => {
108932
+ if (SGR_RESET_REGEX.test(value)) {
108933
+ activeStyles = [];
108934
+ } else {
108935
+ activeStyles.push(value);
108936
+ }
108937
+ };
108938
+ let start = 0;
108939
+ while (start < tokens.length) {
108940
+ if (tokens.slice(start).every((token) => token.kind === "sgr")) {
108941
+ if (result.length > 0) break;
108942
+ }
108943
+ const openingStyles = [...activeStyles];
108944
+ let width = 0;
108945
+ let end = start;
108946
+ let lastBreak = -1;
108947
+ while (end < tokens.length) {
108948
+ const token = tokens[end];
108949
+ if (token.kind === "sgr") {
108950
+ end += 1;
108951
+ continue;
108952
+ }
108953
+ if (width + token.width > columns && width > 0) break;
108954
+ width += token.width;
108955
+ end += 1;
108956
+ if (token.value === " " || token.value === " ") {
108957
+ lastBreak = end;
108958
+ }
108959
+ }
108960
+ let segmentEnd = end;
108961
+ if (end < tokens.length && lastBreak > start) {
108962
+ const overflowToken = tokens[end];
108963
+ if (overflowToken.kind === "char" && overflowToken.value !== " " && overflowToken.width === 1) {
108964
+ segmentEnd = lastBreak;
108965
+ }
108966
+ }
108967
+ if (segmentEnd === start) segmentEnd = start + 1;
108968
+ let segment = "";
108969
+ let sawStyle = openingStyles.length > 0;
108970
+ for (let i = start; i < segmentEnd; i += 1) {
108971
+ const token = tokens[i];
108972
+ segment += token.value;
108973
+ if (token.kind === "sgr") {
108974
+ sawStyle = true;
108975
+ applyStyleToken(token.value);
108976
+ }
108977
+ }
108978
+ const prefix = openingStyles.join("");
108979
+ const needsReset = (sawStyle || activeStyles.length > 0) && !segment.endsWith("\x1B[0m");
108980
+ result.push(`${prefix}${segment}${needsReset ? "\x1B[0m" : ""}`);
108981
+ start = segmentEnd;
108982
+ while (start < tokens.length) {
108983
+ const token = tokens[start];
108984
+ if (token.kind === "char" && token.value === " ") {
108985
+ start += 1;
108986
+ continue;
108987
+ }
108988
+ break;
108989
+ }
108990
+ }
108991
+ return result.length > 0 ? result : [""];
108992
+ }
108993
+ function wrapTextToLines(text, columns) {
108994
+ const result = [];
108995
+ for (const logicalLine of text.split("\n")) {
108996
+ if (logicalLine === "") {
108997
+ result.push("");
108998
+ continue;
108999
+ }
109000
+ let remaining = logicalLine;
109001
+ while (remaining.length > 0) {
109002
+ const { prefix, rest } = takeDisplayWidth(remaining, columns);
109003
+ result.push(prefix);
109004
+ remaining = rest;
109005
+ }
109006
+ }
109007
+ return result;
109008
+ }
109009
+ var ANSI_ESCAPE_REGEX, OSC_HYPERLINK_REGEX, SGR_SEQUENCE_REGEX, TRAILING_SGR_REGEX, SGR_RESET_REGEX;
109010
+ var init_tuiAnsi = __esm({
109011
+ "packages/cli/tui/tuiAnsi.ts"() {
109012
+ "use strict";
109013
+ init_i18n2();
109014
+ ANSI_ESCAPE_REGEX = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
109015
+ OSC_HYPERLINK_REGEX = /\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)\x1b\]8;;(?:\x07|\x1b\\)|\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/g;
109016
+ SGR_SEQUENCE_REGEX = /^\x1b\[[0-9;]*m/;
109017
+ TRAILING_SGR_REGEX = /(?:\x1b\[[0-9;]*m)+$/;
109018
+ SGR_RESET_REGEX = /^\x1b\[0?m$/;
109019
+ }
109020
+ });
109021
+
108054
109022
  // packages/cli/tui/sessionRender.ts
108055
109023
  function formatCwd(cwd) {
108056
109024
  const parts = cwd.split(/[/\\]/);
@@ -108168,18 +109136,18 @@ function buildColoredScene(isDark, frame = 0, maxFrames = 0) {
108168
109136
  ` ${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
109137
  ].join("\n");
108170
109138
  }
108171
- function renderWelcome(state4, frame = 0, maxFrames = 0) {
109139
+ function renderWelcome(state4, frame = 0, maxFrames = 0, columns) {
108172
109140
  const colorEnabled = resolveCliColorEnabled();
108173
109141
  const brightness = resolveTuiBrightness();
108174
109142
  const isDark = brightness === "dark";
108175
- const sceneArt = colorEnabled ? buildColoredScene(isDark, frame, maxFrames) : buildPlainScene(isDark, frame, maxFrames);
109143
+ let sceneArt = colorEnabled ? buildColoredScene(isDark, frame, maxFrames) : buildPlainScene(isDark, frame, maxFrames);
109144
+ if (typeof columns === "number" && columns > 0) {
109145
+ const widestSceneCol = sceneArt.split("\n").reduce((max, line) => Math.max(max, displayWidth2(stripAnsi(line))), 0);
109146
+ if (columns < widestSceneCol) sceneArt = "";
109147
+ }
108176
109148
  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");
109149
+ const body = sceneArt ? [sceneArt, versionLine, t("welcomeHint"), ""] : [versionLine, t("welcomeHint"), ""];
109150
+ return body.join("\n");
108183
109151
  }
108184
109152
  function renderPrompt(_state) {
108185
109153
  return t("promptLabel");
@@ -108287,6 +109255,7 @@ var init_sessionRender = __esm({
108287
109255
  init_dialogFrame();
108288
109256
  init_i18n2();
108289
109257
  init_readlineWorkspace();
109258
+ init_tuiAnsi();
108290
109259
  init_theme();
108291
109260
  init_processRegistry();
108292
109261
  }
@@ -109422,278 +110391,6 @@ var init_chatQueueTuiBinding = __esm({
109422
110391
  }
109423
110392
  });
109424
110393
 
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
110394
  // packages/cli/tui/tuiScrollbar.ts
109698
110395
  function parseScrollAction(sequence) {
109699
110396
  const mouse = SGR_MOUSE_REGEX.exec(sequence);
@@ -110664,58 +111361,27 @@ async function startTuiWorkspace(options) {
110664
111361
  stdout: output2
110665
111362
  });
110666
111363
  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");
111364
+ const bannerColumns = output2.columns;
111365
+ output2.write(renderWelcome(state4, 0, 0, bannerColumns));
110677
111366
  let fixedInput = createNoopFixedInput();
110678
111367
  let buffer = "";
110679
111368
  let cursorPos = 0;
110680
111369
  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();
111370
+ const activityIndicator = createActivityIndicator({
111371
+ isTurnActive: () => activeTurnAbort !== null,
111372
+ fallbackLabel: () => `${state4.agentName} -> working`,
111373
+ onRepaint: () => {
110714
111374
  if (fixedInput.active && !fixedInput.isPaused()) {
110715
- fixedInput.repaint(buffer);
111375
+ output2.write("\x1B[?2026h\x1B[?25l");
111376
+ try {
111377
+ fixedInput.repaint(buffer);
111378
+ } finally {
111379
+ output2.write("\x1B[?25h\x1B[?2026l");
111380
+ }
110716
111381
  }
110717
111382
  }
110718
- };
111383
+ });
111384
+ const activityReporter = (label) => activityIndicator.report(label);
110719
111385
  let explicitAgentSwitch = false;
110720
111386
  let copyViewExitResolver = null;
110721
111387
  const history = createTurnHistory();
@@ -110814,25 +111480,15 @@ ${t("copyViewHint")}
110814
111480
  scheduleRender();
110815
111481
  }) : output2;
110816
111482
  const requestUserChoice = isInteractiveInput(input2) && dialogHost ? async (req) => {
110817
- const items = req.choices.map((c) => ({
110818
- label: c.label
110819
- }));
110820
111483
  try {
110821
- const pickResult = await dialogHost.run(
110822
- (anchor) => runSelectDialog({
110823
- items,
110824
- title: req.question,
111484
+ return await dialogHost.run(
111485
+ (anchor) => runAskChoiceDialog({
111486
+ request: req,
110825
111487
  input: input2,
110826
111488
  output: output2,
110827
111489
  ...anchor
110828
111490
  })
110829
111491
  );
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
111492
  } catch {
110837
111493
  return { kind: "cancelled" };
110838
111494
  }
@@ -110885,7 +111541,7 @@ ${t("copyViewHint")}
110885
111541
  }
110886
111542
  return { ok: !wasAborted, aborted: wasAborted };
110887
111543
  } finally {
110888
- stopActivity();
111544
+ activityIndicator.stop();
110889
111545
  activeTurnAbort = null;
110890
111546
  explicitAgentSwitch = false;
110891
111547
  }
@@ -111244,17 +111900,15 @@ ${err.message}` : ""}`
111244
111900
  return base;
111245
111901
  },
111246
111902
  getActivityLine: () => {
111247
- if (activityLabel === null) return null;
111903
+ const view = activityIndicator.getView();
111904
+ if (view === null) return null;
111248
111905
  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);
111906
+ const elapsed = formatElapsedSeconds(view.elapsedSec);
111253
111907
  const stopHint = t("stopHint");
111254
111908
  if (!colorEnabled) {
111255
- return `${frame} ${activityLabel} (${elapsed}) \xB7 ${stopHint}`;
111909
+ return `${view.frame} ${view.label} (${elapsed}) \xB7 ${stopHint}`;
111256
111910
  }
111257
- return themeText(frame, "accent") + " " + themeText(activityLabel, "muted") + themeText(` (${elapsed})`, "chrome") + themeText(` \xB7 ${stopHint}`, "chrome");
111911
+ return themeText(view.frame, "accent") + " " + themeText(view.label, "muted") + themeText(` (${elapsed})`, "chrome") + themeText(` \xB7 ${stopHint}`, "chrome");
111258
111912
  },
111259
111913
  getQueueLines: () => {
111260
111914
  if (!chatQueueBinding || chatQueueBinding.queueLength() === 0) return [];
@@ -111343,12 +111997,27 @@ ${err.message}` : ""}`
111343
111997
  if (busyLock) {
111344
111998
  const submittedText2 = result.submit;
111345
111999
  const trimmedText = submittedText2.trim();
111346
- if (trimmedText === "/context" || trimmedText === "/ctx") {
112000
+ const busySlashCommand = trimmedText.split(/\s+/)[0]?.toLowerCase();
112001
+ const isBusyLocalSlash = busySlashCommand === "/context" || busySlashCommand === "/ctx" || busySlashCommand === "/switch";
112002
+ if (isBusyLocalSlash) {
112003
+ const beforeAgentKey = state4.agentKey;
111347
112004
  const res = handleTuiInput(submittedText2, state4);
111348
- state4 = res.nextState;
111349
- if (res.output) {
111350
- output2.write(`${res.output}
112005
+ if (res.action) {
112006
+ output2.write(
112007
+ "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"
112008
+ );
112009
+ } else {
112010
+ state4 = res.nextState;
112011
+ let msg = res.output;
112012
+ if (busySlashCommand === "/switch" && res.nextState.agentKey !== beforeAgentKey) {
112013
+ 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.";
112014
+ msg = msg ? `${msg}
112015
+ ${hint}` : hint;
112016
+ }
112017
+ if (msg) {
112018
+ output2.write(`${msg}
111351
112019
  `);
112020
+ }
111352
112021
  }
111353
112022
  buffer = "";
111354
112023
  cursorPos2 = 0;
@@ -111571,8 +112240,9 @@ var init_readlineWorkspace = __esm({
111571
112240
  init_updateCommands();
111572
112241
  init_processSpawn();
111573
112242
  init_confirmDialog();
111574
- init_selectDialog();
112243
+ init_askChoiceDialog();
111575
112244
  init_dialogHost();
112245
+ init_activityIndicator();
111576
112246
  init_agentPicker();
111577
112247
  init_agentCatalog();
111578
112248
  init_dialogPicker();