nolo-cli 0.6.1 → 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 +1089 -385
  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(/[/\\]/);
@@ -108103,58 +109071,83 @@ function renderStatusLine(state4) {
108103
109071
  if (!surface) return body;
108104
109072
  return `${surface} ${body} \x1B[49m`;
108105
109073
  }
108106
- function buildPlainScene(isDark) {
108107
- const sky = isDark ? "\u263E" : "\u2600";
109074
+ function buildPlainScene(isDark, frame = 0, maxFrames = 0) {
109075
+ let skyLine = ` ${isDark ? "\u{1F319}" : "\u2600"}`;
109076
+ if (isDark) {
109077
+ const star1 = frame % 6 < 3 ? "\u2726" : "\u22C6";
109078
+ const star2 = frame % 4 < 2 ? "\u22C6" : "\xB7";
109079
+ const star3 = frame % 5 < 2 ? "\xB7" : "\u2726";
109080
+ skyLine = ` ${star1} \u{1F319} ${star2} ${star3}`;
109081
+ }
109082
+ const wavePattern = "_.~^~.";
109083
+ const longWave = wavePattern.repeat(10);
109084
+ const offset = frame % wavePattern.length;
109085
+ const wave2 = longWave.slice(offset, offset + 25);
108108
109086
  return [
108109
- ` ${sky}`,
108110
- isDark ? " \u2571\u2572 \xB7" : " \u2571\u2572",
108111
- " \u2571 \u2572",
108112
- isDark ? " \u2571 \u2572 \u2726" : " \u2571 \u2572",
108113
- " \u2571 \u2660 \u2660 \u2572",
108114
- " \u2581\u2581\u2581\u2581\u2581\u2581\u2571 \u2660\u2660 \u2572\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581",
108115
- " \u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2588 \u2584\u2580\u2580\u2584",
108116
- " \u2588 \u2580\u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
108117
- " \u2580 \u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580\u2580",
108118
- " \u2570\u256E~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~\u2570\u256E\u2248~"
109087
+ skyLine,
109088
+ " \u2571\u2572 \u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2588 \u2584\u2580\u2580\u2584",
109089
+ " \u2571 \u2572 \u2571\u2572 \u2588 \u2580\u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
109090
+ " \u2571 \u2572\u2571 \u2572 \u2580 \u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580\u2580",
109091
+ ` \u2571 \u2660 \u2572 ${wave2}`,
109092
+ " \u2581\u2581\u2581\u2581\u2581\u2581\u2571 \u2660 \u2660 \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"
108119
109093
  ].join("\n");
108120
109094
  }
108121
- function buildColoredScene(isDark) {
109095
+ function buildColoredScene(isDark, frame = 0, maxFrames = 0) {
108122
109096
  const pk = themeColorSequence("chrome");
108123
109097
  const sk = themeColorSequence("warning");
108124
- const st = themeColorSequence("muted");
108125
109098
  const tr = themeColorSequence("success");
108126
109099
  const wv = themeColorSequence("info");
108127
109100
  const ac = themeColorSequence("accent");
108128
- const cr = themeColorSequence("chrome");
109101
+ const mu = themeColorSequence("muted");
108129
109102
  const r = "\x1B[39m";
108130
109103
  const b = "\x1B[1m";
108131
109104
  const rs = "\x1B[0m";
108132
- const sky = isDark ? "\u263E" : "\u2600";
109105
+ let skyLine = ` ${sk}${isDark ? "\u{1F319}" : "\u2600"}${r}`;
109106
+ if (isDark) {
109107
+ const star1 = frame % 6 < 3 ? "\u2726" : "\u22C6";
109108
+ const star2 = frame % 4 < 2 ? "\u22C6" : "\xB7";
109109
+ const star3 = frame % 5 < 2 ? "\xB7" : "\u2726";
109110
+ skyLine = ` ${mu}${star1}${r} ${sk}\u{1F319}${r} ${mu}${star2}${r} ${mu}${star3}${r}`;
109111
+ }
109112
+ const wavePattern = "_.~^~.";
109113
+ const longWave = wavePattern.repeat(10);
109114
+ const offset = frame % wavePattern.length;
109115
+ const wave2 = longWave.slice(offset, offset + 25);
109116
+ const waveStr = `${wv}${wave2.slice(0, 20)}${r}${mu}${wave2.slice(20, 25)}${r}`;
109117
+ const line1 = "\u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2588 \u2584\u2580\u2580\u2584";
109118
+ const line2 = "\u2588 \u2580\u2588 \u2588 \u2588 \u2588 \u2588 \u2588";
109119
+ const line3 = "\u2580 \u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580\u2580";
109120
+ const sweepEndFrame = maxFrames > 3 ? maxFrames - 3 : maxFrames;
109121
+ const sweepLen = maxFrames === 0 ? 21 : Math.min(21, Math.floor(frame / sweepEndFrame * 21));
109122
+ const colorLine = (str, bold) => {
109123
+ const active = str.slice(0, sweepLen);
109124
+ const dimmed = str.slice(sweepLen);
109125
+ return `${bold ? b : ""}${ac}${active}${r}${mu}${dimmed}${r}${bold ? rs : ""}`;
109126
+ };
109127
+ const nolo1 = colorLine(line1, true);
109128
+ const nolo2 = colorLine(line2, false);
109129
+ const nolo3 = colorLine(line3, false);
108133
109130
  return [
108134
- ` ${sk}${sky}${r}`,
108135
- isDark ? ` ${pk}\u2571\u2572${r} ${st}\xB7${r}` : ` ${pk}\u2571\u2572${r}`,
108136
- ` ${pk}\u2571 \u2572${r}`,
108137
- isDark ? ` ${pk}\u2571 \u2572${r} ${st}\u2726${r}` : ` ${pk}\u2571 \u2572${r}`,
108138
- ` ${pk}\u2571${r} ${tr}\u2660 \u2660${r} ${pk}\u2572${r}`,
108139
- ` ${pk}\u2581\u2581\u2581\u2581\u2581\u2581\u2571${r} ${tr}\u2660\u2660${r} ${pk}\u2572\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581${r}`,
108140
- ` ${b}${ac}\u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2588 \u2584\u2580\u2580\u2584${rs}`,
108141
- ` ${ac}\u2588 \u2580\u2588 \u2588 \u2588 \u2588 \u2588 \u2588${r}`,
108142
- ` ${cr}\u2580 \u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580\u2580${r}`,
108143
- ` ${wv}\u2570\u256E~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~\u2570\u256E\u2248~${r}`
109131
+ skyLine,
109132
+ ` ${pk}\u2571\u2572${r} ${nolo1}`,
109133
+ ` ${pk}\u2571 \u2572 \u2571\u2572${r} ${nolo2}`,
109134
+ ` ${pk}\u2571 \u2572\u2571 \u2572${r} ${nolo3}`,
109135
+ ` ${pk}\u2571${r} ${tr}\u2660${r} ${pk}\u2572${r} ${waveStr}`,
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}`
108144
109137
  ].join("\n");
108145
109138
  }
108146
- function renderWelcome(state4) {
109139
+ function renderWelcome(state4, frame = 0, maxFrames = 0, columns) {
108147
109140
  const colorEnabled = resolveCliColorEnabled();
108148
109141
  const brightness = resolveTuiBrightness();
108149
109142
  const isDark = brightness === "dark";
108150
- const sceneArt = colorEnabled ? buildColoredScene(isDark) : buildPlainScene(isDark);
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
+ }
108151
109148
  const versionLine = colorEnabled ? `\x1B[1m${themeColorSequence("accent")}nolo\x1B[0m ${state4.cliVersion ?? ""} | server ${state4.serverUrl}`.replace(" |", " |") : `nolo ${state4.cliVersion ?? ""} | server ${state4.serverUrl}`.replace(" |", " |");
108152
- return [
108153
- sceneArt,
108154
- versionLine,
108155
- t("welcomeHint"),
108156
- ""
108157
- ].join("\n");
109149
+ const body = sceneArt ? [sceneArt, versionLine, t("welcomeHint"), ""] : [versionLine, t("welcomeHint"), ""];
109150
+ return body.join("\n");
108158
109151
  }
108159
109152
  function renderPrompt(_state) {
108160
109153
  return t("promptLabel");
@@ -108262,6 +109255,7 @@ var init_sessionRender = __esm({
108262
109255
  init_dialogFrame();
108263
109256
  init_i18n2();
108264
109257
  init_readlineWorkspace();
109258
+ init_tuiAnsi();
108265
109259
  init_theme();
108266
109260
  init_processRegistry();
108267
109261
  }
@@ -109397,278 +110391,6 @@ var init_chatQueueTuiBinding = __esm({
109397
110391
  }
109398
110392
  });
109399
110393
 
109400
- // packages/cli/tui/tuiAnsi.ts
109401
- function stripAnsi(text) {
109402
- return text.replace(OSC_HYPERLINK_REGEX, "").replace(ANSI_ESCAPE_REGEX, "");
109403
- }
109404
- function applyTerminalOutputToText(existing, chunk) {
109405
- if (!chunk) return existing;
109406
- let text = existing;
109407
- let index = 0;
109408
- while (index < chunk.length) {
109409
- if (chunk[index] === "\x1B") {
109410
- const sgr = SGR_SEQUENCE_REGEX.exec(chunk.slice(index));
109411
- if (sgr) {
109412
- text += sgr[0];
109413
- index += sgr[0].length;
109414
- continue;
109415
- }
109416
- const osc = chunk.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
109417
- if (osc) {
109418
- index += osc[0].length;
109419
- continue;
109420
- }
109421
- const csi = chunk.slice(index).match(/^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/);
109422
- if (csi) {
109423
- index += csi[0].length;
109424
- continue;
109425
- }
109426
- index += 1;
109427
- continue;
109428
- }
109429
- const ch = chunk[index];
109430
- if (ch === "\r") {
109431
- const lastNl = text.lastIndexOf("\n");
109432
- text = lastNl === -1 ? "" : text.slice(0, lastNl + 1);
109433
- index += 1;
109434
- continue;
109435
- }
109436
- if (ch === "\n") {
109437
- text += "\n";
109438
- index += 1;
109439
- continue;
109440
- }
109441
- if (ch === "\b") {
109442
- const trailing = TRAILING_SGR_REGEX.exec(text);
109443
- const sgrTail = trailing ? trailing[0] : "";
109444
- const head = sgrTail ? text.slice(0, -sgrTail.length) : text;
109445
- if (head.length > 0 && head[head.length - 1] !== "\n") {
109446
- text = head.slice(0, -1) + sgrTail;
109447
- }
109448
- index += 1;
109449
- continue;
109450
- }
109451
- const code = ch.charCodeAt(0);
109452
- if (code < 32 && ch !== " " || code === 127) {
109453
- index += 1;
109454
- continue;
109455
- }
109456
- text += ch;
109457
- index += 1;
109458
- }
109459
- return text;
109460
- }
109461
- function displayWidth2(str) {
109462
- let width = 0;
109463
- for (const char of str) {
109464
- const code = char.codePointAt(0) ?? 0;
109465
- if (code < 32 || code === 127) continue;
109466
- if (code >= 4352 && code <= 4447 || // 0x2768-0x2775 (ornamental brackets, incl. the ❯ prompt at U+276F)
109467
- // render narrow in terminals; counting them wide drifts the cursor.
109468
- code >= 9728 && code <= 10175 && !(code >= 10088 && code <= 10101) || // 0x2B00-0x2BFF (Misc Symbols and Arrows, incl. ⬢ U+2B22 used as the
109469
- // status-line agent icon) render double-wide in common monospace fonts;
109470
- // counting ⬢ as width 1 drifts the whole status line right of the icon.
109471
- 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 ‘ ’).
109472
- // In CJK locale + CJK fonts these render double-wide like 全角 punctuation;
109473
- // in English fonts they stay width 1. Follow the active locale so Chinese
109474
- // replies align correctly without breaking English terminal output.
109475
- getCliLocale() === "zh" && (code === 8220 || code === 8221 || code === 8216 || code === 8217)) {
109476
- width += 2;
109477
- } else {
109478
- width += 1;
109479
- }
109480
- }
109481
- return width;
109482
- }
109483
- function visibleWidth(str) {
109484
- return displayWidth2(stripAnsi(str));
109485
- }
109486
- function truncateAnsi(text, maxWidth) {
109487
- if (maxWidth <= 0) return "";
109488
- if (visibleWidth(text) <= maxWidth) return text;
109489
- let width = 0;
109490
- let out = "";
109491
- let i = 0;
109492
- let sawAnsi = false;
109493
- while (i < text.length) {
109494
- if (text[i] === "\x1B" && text[i + 1] === "[") {
109495
- sawAnsi = true;
109496
- let j = i + 2;
109497
- while (j < text.length) {
109498
- const code = text.charCodeAt(j);
109499
- j += 1;
109500
- if (code >= 64 && code <= 126) break;
109501
- }
109502
- out += text.slice(i, j);
109503
- i = j;
109504
- continue;
109505
- }
109506
- const codePoint = text.codePointAt(i) ?? 0;
109507
- const char = String.fromCodePoint(codePoint);
109508
- const charWidth = displayWidth2(char);
109509
- if (width + charWidth > maxWidth) break;
109510
- out += char;
109511
- width += charWidth;
109512
- i += char.length;
109513
- }
109514
- return sawAnsi ? `${out}\x1B[0m` : out;
109515
- }
109516
- function fitAnsiLine(text, width, ellipsis = "\u2026") {
109517
- if (width <= 0) return "";
109518
- if (visibleWidth(text) <= width) return text;
109519
- const ellipsisWidth = displayWidth2(ellipsis);
109520
- if (width < ellipsisWidth) return truncateAnsi(text, width);
109521
- if (width === ellipsisWidth) return truncateAnsi(ellipsis, width) || truncateAnsi(text, width);
109522
- return `${truncateAnsi(text, width - ellipsisWidth)}${ellipsis}`;
109523
- }
109524
- function countPhysicalLines(text, columns) {
109525
- const lines = text.split("\n");
109526
- let total = 0;
109527
- for (const line of lines) {
109528
- const width = displayWidth2(line);
109529
- total += Math.max(1, Math.ceil(width / columns));
109530
- }
109531
- return Math.max(total, 1);
109532
- }
109533
- function takeDisplayWidth(text, width) {
109534
- let used = 0;
109535
- let index = 0;
109536
- for (const char of text) {
109537
- const charWidth = displayWidth2(char);
109538
- if (used + charWidth > width && used > 0) break;
109539
- used += charWidth;
109540
- index += char.length;
109541
- }
109542
- return { prefix: text.slice(0, index), rest: text.slice(index) };
109543
- }
109544
- function padOrTruncateToWidth(text, width) {
109545
- const textWidth = visibleWidth(text);
109546
- if (textWidth > width) {
109547
- return truncateAnsi(text, width);
109548
- }
109549
- return `${text}${" ".repeat(width - textWidth)}`;
109550
- }
109551
- function tokenizeAnsiLine(line) {
109552
- const tokens = [];
109553
- let index = 0;
109554
- while (index < line.length) {
109555
- if (line[index] === "\x1B") {
109556
- const osc = line.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
109557
- if (osc) {
109558
- tokens.push({ kind: "sgr", value: osc[0], width: 0 });
109559
- index += osc[0].length;
109560
- continue;
109561
- }
109562
- const sgr = SGR_SEQUENCE_REGEX.exec(line.slice(index));
109563
- if (sgr) {
109564
- tokens.push({ kind: "sgr", value: sgr[0], width: 0 });
109565
- index += sgr[0].length;
109566
- continue;
109567
- }
109568
- }
109569
- const codePoint = line.codePointAt(index) ?? 0;
109570
- const value = String.fromCodePoint(codePoint);
109571
- tokens.push({ kind: "char", value, width: displayWidth2(value) });
109572
- index += value.length;
109573
- }
109574
- return tokens;
109575
- }
109576
- function wrapTranscriptLine(line, columns) {
109577
- if (line === "") return [""];
109578
- const tokens = tokenizeAnsiLine(line);
109579
- const result = [];
109580
- let activeStyles = [];
109581
- const applyStyleToken = (value) => {
109582
- if (SGR_RESET_REGEX.test(value)) {
109583
- activeStyles = [];
109584
- } else {
109585
- activeStyles.push(value);
109586
- }
109587
- };
109588
- let start = 0;
109589
- while (start < tokens.length) {
109590
- if (tokens.slice(start).every((token) => token.kind === "sgr")) {
109591
- if (result.length > 0) break;
109592
- }
109593
- const openingStyles = [...activeStyles];
109594
- let width = 0;
109595
- let end = start;
109596
- let lastBreak = -1;
109597
- while (end < tokens.length) {
109598
- const token = tokens[end];
109599
- if (token.kind === "sgr") {
109600
- end += 1;
109601
- continue;
109602
- }
109603
- if (width + token.width > columns && width > 0) break;
109604
- width += token.width;
109605
- end += 1;
109606
- if (token.value === " " || token.value === " ") {
109607
- lastBreak = end;
109608
- }
109609
- }
109610
- let segmentEnd = end;
109611
- if (end < tokens.length && lastBreak > start) {
109612
- const overflowToken = tokens[end];
109613
- if (overflowToken.kind === "char" && overflowToken.value !== " " && overflowToken.width === 1) {
109614
- segmentEnd = lastBreak;
109615
- }
109616
- }
109617
- if (segmentEnd === start) segmentEnd = start + 1;
109618
- let segment = "";
109619
- let sawStyle = openingStyles.length > 0;
109620
- for (let i = start; i < segmentEnd; i += 1) {
109621
- const token = tokens[i];
109622
- segment += token.value;
109623
- if (token.kind === "sgr") {
109624
- sawStyle = true;
109625
- applyStyleToken(token.value);
109626
- }
109627
- }
109628
- const prefix = openingStyles.join("");
109629
- const needsReset = (sawStyle || activeStyles.length > 0) && !segment.endsWith("\x1B[0m");
109630
- result.push(`${prefix}${segment}${needsReset ? "\x1B[0m" : ""}`);
109631
- start = segmentEnd;
109632
- while (start < tokens.length) {
109633
- const token = tokens[start];
109634
- if (token.kind === "char" && token.value === " ") {
109635
- start += 1;
109636
- continue;
109637
- }
109638
- break;
109639
- }
109640
- }
109641
- return result.length > 0 ? result : [""];
109642
- }
109643
- function wrapTextToLines(text, columns) {
109644
- const result = [];
109645
- for (const logicalLine of text.split("\n")) {
109646
- if (logicalLine === "") {
109647
- result.push("");
109648
- continue;
109649
- }
109650
- let remaining = logicalLine;
109651
- while (remaining.length > 0) {
109652
- const { prefix, rest } = takeDisplayWidth(remaining, columns);
109653
- result.push(prefix);
109654
- remaining = rest;
109655
- }
109656
- }
109657
- return result;
109658
- }
109659
- var ANSI_ESCAPE_REGEX, OSC_HYPERLINK_REGEX, SGR_SEQUENCE_REGEX, TRAILING_SGR_REGEX, SGR_RESET_REGEX;
109660
- var init_tuiAnsi = __esm({
109661
- "packages/cli/tui/tuiAnsi.ts"() {
109662
- "use strict";
109663
- init_i18n2();
109664
- ANSI_ESCAPE_REGEX = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
109665
- OSC_HYPERLINK_REGEX = /\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)\x1b\]8;;(?:\x07|\x1b\\)|\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/g;
109666
- SGR_SEQUENCE_REGEX = /^\x1b\[[0-9;]*m/;
109667
- TRAILING_SGR_REGEX = /(?:\x1b\[[0-9;]*m)+$/;
109668
- SGR_RESET_REGEX = /^\x1b\[0?m$/;
109669
- }
109670
- });
109671
-
109672
110394
  // packages/cli/tui/tuiScrollbar.ts
109673
110395
  function parseScrollAction(sequence) {
109674
110396
  const mouse = SGR_MOUSE_REGEX.exec(sequence);
@@ -110639,49 +111361,27 @@ async function startTuiWorkspace(options) {
110639
111361
  stdout: output2
110640
111362
  });
110641
111363
  if (detected) setActiveBrightness(detected);
110642
- output2.write(renderWelcome(state4));
111364
+ const bannerColumns = output2.columns;
111365
+ output2.write(renderWelcome(state4, 0, 0, bannerColumns));
110643
111366
  let fixedInput = createNoopFixedInput();
110644
111367
  let buffer = "";
110645
111368
  let cursorPos = 0;
110646
111369
  let activeTurnAbort = null;
110647
- let activityLabel = null;
110648
- let activityStartedAt = 0;
110649
- let activityFrameIndex = 0;
110650
- let activityTimer = null;
110651
- const stopActivity = () => {
110652
- if (activityTimer !== null) {
110653
- clearInterval(activityTimer);
110654
- activityTimer = null;
110655
- }
110656
- activityLabel = null;
110657
- activityStartedAt = 0;
110658
- };
110659
- const activityReporter = (label) => {
110660
- if (label !== null) {
110661
- activityLabel = label;
110662
- if (activityStartedAt === 0) {
110663
- activityStartedAt = Date.now();
110664
- }
110665
- if (activityTimer === null) {
110666
- activityTimer = setInterval(() => {
110667
- activityFrameIndex += 1;
110668
- if (fixedInput.active && !fixedInput.isPaused()) {
110669
- output2.write("\x1B[?2026h\x1B[?25l");
110670
- try {
110671
- fixedInput.repaint(buffer);
110672
- } finally {
110673
- output2.write("\x1B[?25h\x1B[?2026l");
110674
- }
110675
- }
110676
- }, 150);
110677
- }
110678
- } else {
110679
- stopActivity();
111370
+ const activityIndicator = createActivityIndicator({
111371
+ isTurnActive: () => activeTurnAbort !== null,
111372
+ fallbackLabel: () => `${state4.agentName} -> working`,
111373
+ onRepaint: () => {
110680
111374
  if (fixedInput.active && !fixedInput.isPaused()) {
110681
- 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
+ }
110682
111381
  }
110683
111382
  }
110684
- };
111383
+ });
111384
+ const activityReporter = (label) => activityIndicator.report(label);
110685
111385
  let explicitAgentSwitch = false;
110686
111386
  let copyViewExitResolver = null;
110687
111387
  const history = createTurnHistory();
@@ -110780,25 +111480,15 @@ ${t("copyViewHint")}
110780
111480
  scheduleRender();
110781
111481
  }) : output2;
110782
111482
  const requestUserChoice = isInteractiveInput(input2) && dialogHost ? async (req) => {
110783
- const items = req.choices.map((c) => ({
110784
- label: c.label
110785
- }));
110786
111483
  try {
110787
- const pickResult = await dialogHost.run(
110788
- (anchor) => runSelectDialog({
110789
- items,
110790
- title: req.question,
111484
+ return await dialogHost.run(
111485
+ (anchor) => runAskChoiceDialog({
111486
+ request: req,
110791
111487
  input: input2,
110792
111488
  output: output2,
110793
111489
  ...anchor
110794
111490
  })
110795
111491
  );
110796
- if (pickResult.kind === "selected") {
110797
- const choice = req.choices[pickResult.index];
110798
- const userMessage = choice?.userMessage?.trim() || choice?.label || "";
110799
- return { kind: "selected", userMessage, label: choice?.label ?? "" };
110800
- }
110801
- return { kind: "cancelled" };
110802
111492
  } catch {
110803
111493
  return { kind: "cancelled" };
110804
111494
  }
@@ -110851,7 +111541,7 @@ ${t("copyViewHint")}
110851
111541
  }
110852
111542
  return { ok: !wasAborted, aborted: wasAborted };
110853
111543
  } finally {
110854
- stopActivity();
111544
+ activityIndicator.stop();
110855
111545
  activeTurnAbort = null;
110856
111546
  explicitAgentSwitch = false;
110857
111547
  }
@@ -111210,17 +111900,15 @@ ${err.message}` : ""}`
111210
111900
  return base;
111211
111901
  },
111212
111902
  getActivityLine: () => {
111213
- if (activityLabel === null) return null;
111903
+ const view = activityIndicator.getView();
111904
+ if (view === null) return null;
111214
111905
  const colorEnabled = resolveCliColorEnabled();
111215
- const FRAMES = ["\xB7", "~", "\u2248", "\u223F", "\u2248", "~"];
111216
- const frame = FRAMES[activityFrameIndex % FRAMES.length];
111217
- const elapsedSec = activityStartedAt > 0 ? Math.max(0, Math.floor((Date.now() - activityStartedAt) / 1e3)) : 0;
111218
- const elapsed = formatElapsedSeconds(elapsedSec);
111906
+ const elapsed = formatElapsedSeconds(view.elapsedSec);
111219
111907
  const stopHint = t("stopHint");
111220
111908
  if (!colorEnabled) {
111221
- return `${frame} ${activityLabel} (${elapsed}) \xB7 ${stopHint}`;
111909
+ return `${view.frame} ${view.label} (${elapsed}) \xB7 ${stopHint}`;
111222
111910
  }
111223
- 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");
111224
111912
  },
111225
111913
  getQueueLines: () => {
111226
111914
  if (!chatQueueBinding || chatQueueBinding.queueLength() === 0) return [];
@@ -111309,12 +111997,27 @@ ${err.message}` : ""}`
111309
111997
  if (busyLock) {
111310
111998
  const submittedText2 = result.submit;
111311
111999
  const trimmedText = submittedText2.trim();
111312
- 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;
111313
112004
  const res = handleTuiInput(submittedText2, state4);
111314
- state4 = res.nextState;
111315
- if (res.output) {
111316
- 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}
111317
112019
  `);
112020
+ }
111318
112021
  }
111319
112022
  buffer = "";
111320
112023
  cursorPos2 = 0;
@@ -111537,8 +112240,9 @@ var init_readlineWorkspace = __esm({
111537
112240
  init_updateCommands();
111538
112241
  init_processSpawn();
111539
112242
  init_confirmDialog();
111540
- init_selectDialog();
112243
+ init_askChoiceDialog();
111541
112244
  init_dialogHost();
112245
+ init_activityIndicator();
111542
112246
  init_agentPicker();
111543
112247
  init_agentCatalog();
111544
112248
  init_dialogPicker();