@crazyhappyone/dsh-tui 0.1.0-alpha.42 → 0.1.0-alpha.44

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.
package/dist/index.js CHANGED
@@ -69421,25 +69421,44 @@ function renderCode(value, width, rowOffset, sourceStart) {
69421
69421
  const lines = value.split("\n");
69422
69422
  const out = [];
69423
69423
  let row = rowOffset;
69424
+ const digits = Math.max(2, String(lines.length).length);
69425
+ const gutterWidth = digits + 4;
69426
+ const codeWidth = Math.max(1, width - gutterWidth);
69424
69427
  for (const [index2, line5] of lines.entries()) {
69425
- const wrapped = wrapDisplayLines(escapeContent(line5), Math.max(1, width - 2));
69428
+ const lineNum = index2 + 1;
69429
+ const gutterPrefix = ` ${String(lineNum).padStart(digits, " ")} \u2502 `;
69430
+ const continuationGutter = ` ${" ".repeat(digits)} \u2502 `;
69431
+ const wrapped = wrapDisplayLines(escapeContent(line5), codeWidth);
69426
69432
  if (wrapped.length === 0) {
69427
- out.push(freezeEmpty(row, index2 === 0 ? sourceStart : -1));
69433
+ const buffer = makeBuffer();
69434
+ appendSegment(buffer, { text: gutterPrefix, token: "fgDim", bold: false });
69435
+ const emptyLine2 = freezeLine(buffer, row, index2 === 0 ? sourceStart : -1);
69436
+ out.push({
69437
+ ...emptyLine2,
69438
+ background: "codeBg",
69439
+ backgroundColumns: Math.max(width, emptyLine2.displayWidth)
69440
+ });
69428
69441
  row += 1;
69429
69442
  continue;
69430
69443
  }
69431
69444
  for (const [partIndex, part] of wrapped.entries()) {
69432
69445
  const buffer = makeBuffer();
69433
- const tokens = tokenizeCodeLine(` ${part}`);
69446
+ const gutter = partIndex === 0 ? gutterPrefix : continuationGutter;
69447
+ appendSegment(buffer, { text: gutter, token: "fgDim", bold: false });
69448
+ const tokens = tokenizeCodeLine(part);
69434
69449
  for (const tk of tokens) {
69435
69450
  appendSegment(buffer, { text: tk.text, token: tk.token, bold: tk.bold });
69436
69451
  }
69437
- const line6 = freezeLine(
69452
+ const codeLine = freezeLine(
69438
69453
  buffer,
69439
69454
  row,
69440
69455
  index2 === 0 && partIndex === 0 ? sourceStart : -1
69441
69456
  );
69442
- out.push({ ...line6, background: "codeBg" });
69457
+ out.push({
69458
+ ...codeLine,
69459
+ background: "codeBg",
69460
+ backgroundColumns: Math.max(width, codeLine.displayWidth)
69461
+ });
69443
69462
  row += 1;
69444
69463
  }
69445
69464
  }
@@ -72510,6 +72529,7 @@ var ScreenAtlas = class {
72510
72529
  snapshotRanges = [];
72511
72530
  snapshotRows = /* @__PURE__ */ new Map();
72512
72531
  snapshotRail;
72532
+ snapshotGeometry;
72513
72533
  /**
72514
72534
  * @param width - terminal columns.
72515
72535
  * @param height - terminal rows.
@@ -72694,6 +72714,7 @@ var ScreenAtlas = class {
72694
72714
  this.snapshotRanges = [];
72695
72715
  this.snapshotRows.clear();
72696
72716
  this.snapshotRail = snapshot.geometry.rail;
72717
+ this.snapshotGeometry = snapshot.geometry;
72697
72718
  const saved = {
72698
72719
  col: this.cursorCol,
72699
72720
  row: this.cursorRow,
@@ -72764,6 +72785,34 @@ var ScreenAtlas = class {
72764
72785
  }
72765
72786
  return this.cells[(row - 1) * this.width + (col - 1)];
72766
72787
  }
72788
+ /** Bounding terminal rows for the transcript content area. */
72789
+ get transcriptBounds() {
72790
+ if (this.snapshotGeometry !== void 0 && this.snapshotGeometry.rows >= 10) {
72791
+ const top = this.snapshotGeometry.transcriptTop;
72792
+ const rows = this.snapshotGeometry.transcriptRows;
72793
+ return { top, bottom: Math.max(top, top + rows - 1) };
72794
+ }
72795
+ return { top: 1, bottom: this.height };
72796
+ }
72797
+ /** Clamp screen point to selectable transcript rows and valid columns. */
72798
+ clampToTranscript(point3) {
72799
+ const { top, bottom } = this.transcriptBounds;
72800
+ return {
72801
+ col: Math.max(1, Math.min(this.width, point3.col)),
72802
+ row: Math.max(top, Math.min(bottom, point3.row))
72803
+ };
72804
+ }
72805
+ /** Plain text for a horizontal slice of one row with rail excluded. */
72806
+ extractRowText(row, fromCol = 1, toCol = this.width) {
72807
+ let line5 = "";
72808
+ for (let col = fromCol; col <= toCol; col++) {
72809
+ if (this.isSnapshotRailControlCell(col, row)) continue;
72810
+ const cell = this.cellAt(col, row);
72811
+ if (cell === void 0 || cell.ch === "") continue;
72812
+ line5 += cell.ch;
72813
+ }
72814
+ return line5.replace(/ +$/u, "");
72815
+ }
72767
72816
  /**
72768
72817
  * Reading-order plain text between two inclusive endpoints. Published rail
72769
72818
  * cells are excluded, trailing spaces are trimmed, and rows join with `\n`.
@@ -72777,14 +72826,7 @@ var ScreenAtlas = class {
72777
72826
  for (let row = start.row; row <= end.row; row++) {
72778
72827
  const from2 = row === start.row ? start.col : 1;
72779
72828
  const to = row === end.row ? end.col : this.width;
72780
- let line5 = "";
72781
- for (let col = from2; col <= to; col++) {
72782
- if (this.isSnapshotRailControlCell(col, row)) continue;
72783
- const cell = this.cellAt(col, row);
72784
- if (cell === void 0 || cell.ch === "") continue;
72785
- line5 += cell.ch;
72786
- }
72787
- lines.push(line5.replace(/ +$/u, ""));
72829
+ lines.push(this.extractRowText(row, from2, to));
72788
72830
  }
72789
72831
  return lines.join("\n");
72790
72832
  }
@@ -73153,6 +73195,9 @@ var MouseSession = class {
73153
73195
  selection;
73154
73196
  paintedSelection;
73155
73197
  railDrag;
73198
+ dragVirtualStartRow = 0;
73199
+ scrolledOffAbove = [];
73200
+ scrolledOffBelow = [];
73156
73201
  edgeTimer;
73157
73202
  openHref;
73158
73203
  copy;
@@ -73180,7 +73225,7 @@ var MouseSession = class {
73180
73225
  */
73181
73226
  handle(event) {
73182
73227
  if (event.kind === "wheel" && event.delta !== void 0) {
73183
- this.onScroll(event.delta);
73228
+ this.scroll(event.delta);
73184
73229
  return "";
73185
73230
  }
73186
73231
  const point3 = { col: event.col, row: event.row };
@@ -73194,8 +73239,12 @@ var MouseSession = class {
73194
73239
  this.onRail(railFraction(region, event.row));
73195
73240
  return this.repaintSelection();
73196
73241
  }
73197
- this.drag = { start: point3, moved: false };
73198
- this.selection = { start: point3, end: point3 };
73242
+ const clampedPoint = this.atlas.clampToTranscript(point3);
73243
+ this.drag = { start: clampedPoint, moved: false };
73244
+ this.dragVirtualStartRow = clampedPoint.row;
73245
+ this.scrolledOffAbove = [];
73246
+ this.scrolledOffBelow = [];
73247
+ this.selection = { start: clampedPoint, end: clampedPoint };
73199
73248
  return "";
73200
73249
  }
73201
73250
  if (event.kind === "drag" && this.railDrag !== void 0 && event.button === "left") {
@@ -73208,27 +73257,52 @@ var MouseSession = class {
73208
73257
  return "";
73209
73258
  }
73210
73259
  if (event.kind === "drag" && this.drag !== void 0 && event.button === "left") {
73211
- if (event.col !== this.drag.start.col || event.row !== this.drag.start.row) {
73260
+ const clampedPoint = this.atlas.clampToTranscript(point3);
73261
+ if (clampedPoint.col !== this.drag.start.col || clampedPoint.row !== this.drag.start.row) {
73212
73262
  this.drag.moved = true;
73213
73263
  }
73214
- this.selection = { start: this.drag.start, end: point3 };
73264
+ const { top, bottom } = this.atlas.transcriptBounds;
73265
+ const visualStartRow = Math.max(top, Math.min(bottom, this.dragVirtualStartRow));
73266
+ this.selection = {
73267
+ start: { col: this.drag.start.col, row: visualStartRow },
73268
+ end: clampedPoint
73269
+ };
73215
73270
  this.syncEdge(event.row);
73216
73271
  return this.repaintSelection();
73217
73272
  }
73218
73273
  if (event.kind === "release" && this.drag !== void 0) {
73219
73274
  this.stopEdge();
73220
- if (event.col !== this.drag.start.col || event.row !== this.drag.start.row) {
73275
+ const clampedPoint = this.atlas.clampToTranscript(point3);
73276
+ if (clampedPoint.col !== this.drag.start.col || clampedPoint.row !== this.drag.start.row) {
73221
73277
  this.drag.moved = true;
73222
- this.selection = { start: this.drag.start, end: point3 };
73223
73278
  }
73279
+ const { top, bottom } = this.atlas.transcriptBounds;
73280
+ const visualStartRow = Math.max(top, Math.min(bottom, this.dragVirtualStartRow));
73281
+ this.selection = {
73282
+ start: { col: this.drag.start.col, row: visualStartRow },
73283
+ end: clampedPoint
73284
+ };
73224
73285
  if (this.drag.moved) {
73225
73286
  const range = this.selection;
73226
- this.copy(this.atlas.extract(range.start, range.end));
73287
+ if (this.scrolledOffAbove.length === 0 && this.scrolledOffBelow.length === 0) {
73288
+ this.copy(this.atlas.extract(range.start, range.end));
73289
+ } else {
73290
+ const visibleText = this.atlas.extract(range.start, range.end);
73291
+ const parts = [];
73292
+ if (this.scrolledOffAbove.length > 0) parts.push(...this.scrolledOffAbove);
73293
+ if (visibleText.length > 0) parts.push(visibleText);
73294
+ if (this.scrolledOffBelow.length > 0) parts.push(...this.scrolledOffBelow);
73295
+ const textToCopy = parts.join("\n");
73296
+ if (textToCopy.length > 0) this.copy(textToCopy);
73297
+ }
73227
73298
  } else {
73228
73299
  const href = this.atlas.urlAt(event.col, event.row);
73229
73300
  if (href !== void 0) this.openHref(href);
73230
73301
  }
73231
73302
  this.drag = void 0;
73303
+ this.dragVirtualStartRow = 0;
73304
+ this.scrolledOffAbove = [];
73305
+ this.scrolledOffBelow = [];
73232
73306
  this.selection = void 0;
73233
73307
  return this.repaintSelection();
73234
73308
  }
@@ -73270,19 +73344,41 @@ var MouseSession = class {
73270
73344
  this.railDrag = void 0;
73271
73345
  }
73272
73346
  syncEdge(row) {
73273
- const top = row <= 2;
73274
- const bottom = row >= this.atlas.height - 1;
73275
- if (!top && !bottom) {
73347
+ if (this.atlas.height < 6) return;
73348
+ const top = 2;
73349
+ const bottom = this.atlas.height - 1;
73350
+ const atTop = row <= top;
73351
+ const atBottom = row >= bottom;
73352
+ if (!atTop && !atBottom) {
73276
73353
  this.stopEdge();
73277
73354
  return;
73278
73355
  }
73279
- const delta = top ? 1 : -1;
73356
+ const delta = atTop ? 1 : -1;
73280
73357
  if (this.edgeTimer !== void 0) return;
73281
- this.onScroll(delta);
73358
+ this.scroll(delta);
73282
73359
  this.edgeTimer = this.startInterval(() => {
73283
- this.onScroll(delta);
73360
+ this.scroll(delta);
73284
73361
  }, 80);
73285
73362
  }
73363
+ scroll(delta) {
73364
+ if (this.drag !== void 0 && this.drag.moved) {
73365
+ const { top, bottom } = this.atlas.transcriptBounds;
73366
+ if (delta > 0) {
73367
+ if (this.dragVirtualStartRow > bottom) {
73368
+ const text4 = this.atlas.extractRowText(bottom);
73369
+ if (text4.length > 0) this.scrolledOffBelow.unshift(text4);
73370
+ }
73371
+ this.dragVirtualStartRow += delta;
73372
+ } else if (delta < 0) {
73373
+ if (this.dragVirtualStartRow < top) {
73374
+ const text4 = this.atlas.extractRowText(top);
73375
+ if (text4.length > 0) this.scrolledOffAbove.push(text4);
73376
+ }
73377
+ this.dragVirtualStartRow += delta;
73378
+ }
73379
+ }
73380
+ this.onScroll(delta);
73381
+ }
73286
73382
  stopEdge() {
73287
73383
  if (this.edgeTimer === void 0) return;
73288
73384
  this.stopInterval(this.edgeTimer);
@@ -79119,7 +79215,7 @@ function mapKeyEvent(state, key, keyInfo, commands, pane, search2, timeline = {
79119
79215
  renaming: state.renaming
79120
79216
  };
79121
79217
  }
79122
- if (commandMode && (keyInfo.downArrow || keyInfo.upArrow)) {
79218
+ if (commandMode && state.historyIndex === void 0 && (keyInfo.downArrow || keyInfo.upArrow)) {
79123
79219
  const query = state.commandQuery ?? state.text.slice(1);
79124
79220
  const count = filterCommands(commands, query).length;
79125
79221
  const delta = keyInfo.downArrow ? 1 : -1;
@@ -79397,6 +79493,7 @@ function mapKeyEvent(state, key, keyInfo, commands, pane, search2, timeline = {
79397
79493
  commandQuery: void 0,
79398
79494
  prefixG: false,
79399
79495
  renaming: state.renaming,
79496
+ commandDismissed: true,
79400
79497
  caretIndex: nextText.length,
79401
79498
  historyIndex: nextIndex,
79402
79499
  historyDraft: savedDraft
@@ -79423,6 +79520,7 @@ function mapKeyEvent(state, key, keyInfo, commands, pane, search2, timeline = {
79423
79520
  commandQuery: void 0,
79424
79521
  prefixG: false,
79425
79522
  renaming: state.renaming,
79523
+ commandDismissed: true,
79426
79524
  caretIndex: nextText.length,
79427
79525
  historyIndex: nextIndex,
79428
79526
  historyDraft: state.historyDraft
@@ -79796,18 +79894,18 @@ function TuiLoop({
79796
79894
  helpLinesRef.current = helpPane.lines;
79797
79895
  const historyLengthRef = (0, import_react39.useRef)(model.history.length);
79798
79896
  historyLengthRef.current = model.history.length;
79799
- const sentHistoryRef = (0, import_react39.useRef)([]);
79897
+ const [sentHistory, setSentHistory] = (0, import_react39.useState)([]);
79800
79898
  const inputHistory = (0, import_react39.useMemo)(() => {
79801
79899
  const fromModel = model.history.filter((m) => m.kind === "user" && typeof m.text === "string" && m.text.trim().length > 0).map((m) => m.text);
79802
79900
  const combined = [];
79803
- for (const item of [...fromModel, ...sentHistoryRef.current]) {
79901
+ for (const item of [...fromModel, ...sentHistory]) {
79804
79902
  const trimmed = item.trim();
79805
79903
  if (trimmed.length > 0 && combined.at(-1) !== trimmed) {
79806
79904
  combined.push(trimmed);
79807
79905
  }
79808
79906
  }
79809
79907
  return combined;
79810
- }, [model.history]);
79908
+ }, [model.history, sentHistory]);
79811
79909
  const chordTimerRef = (0, import_react39.useRef)(
79812
79910
  void 0
79813
79911
  );
@@ -79961,19 +80059,19 @@ function TuiLoop({
79961
80059
  issueViewportCommand({ kind: "edge", edge: action.edge });
79962
80060
  } else if (action.kind === "send") {
79963
80061
  const trimmed = action.text.trim();
79964
- if (trimmed.length > 0 && sentHistoryRef.current.at(-1) !== trimmed) {
79965
- sentHistoryRef.current.push(trimmed);
80062
+ if (trimmed.length > 0) {
80063
+ setSentHistory((prev) => prev.at(-1) === trimmed ? prev : [...prev, trimmed]);
79966
80064
  }
79967
80065
  issueViewportCommand({ kind: "reset" });
79968
80066
  controller.dispatch(action);
79969
80067
  } else if (action.kind === "command") {
79970
80068
  const queryText = action.query.trim().startsWith("/") ? action.query.trim() : `/${action.query.trim()}`;
79971
- if (queryText.length > 0 && sentHistoryRef.current.at(-1) !== queryText) {
79972
- sentHistoryRef.current.push(queryText);
80069
+ if (queryText.length > 0) {
80070
+ setSentHistory((prev) => prev.at(-1) === queryText ? prev : [...prev, queryText]);
79973
80071
  }
79974
80072
  controller.dispatch(action);
79975
80073
  } else if (action.kind === "new-session" || action.kind === "select-session") {
79976
- sentHistoryRef.current = [];
80074
+ setSentHistory([]);
79977
80075
  issueViewportCommand({ kind: "reset" });
79978
80076
  controller.dispatch(action);
79979
80077
  } else if (action.kind === "intake-clipboard-image") {
@@ -80141,7 +80239,13 @@ function TuiLoop({
80141
80239
  let status;
80142
80240
  let statusRowCount;
80143
80241
  if (feedback !== void 0) {
80144
- statusRowCount = 1;
80242
+ const rawLines = feedback.split("\n");
80243
+ const maxBudget = Math.max(1, Math.min(rawLines.length, Math.max(4, Math.floor((rows - 8) / 2))));
80244
+ const visibleLines = rawLines.slice(0, maxBudget);
80245
+ if (rawLines.length > maxBudget) {
80246
+ visibleLines[visibleLines.length - 1] = `${visibleLines[visibleLines.length - 1]} \u2026 (+${rawLines.length - maxBudget} \u884C)`;
80247
+ }
80248
+ statusRowCount = visibleLines.length;
80145
80249
  status = (0, import_react39.createElement)(
80146
80250
  Box_default,
80147
80251
  {
@@ -80149,11 +80253,14 @@ function TuiLoop({
80149
80253
  width: "100%",
80150
80254
  backgroundColor: inkColor("bg")
80151
80255
  },
80152
- (0, import_react39.createElement)(
80153
- Text,
80154
- null,
80155
- paintBackgroundRow([feedbackLine(feedback)], "bg", columns)
80156
- )
80256
+ ...visibleLines.map((line5, index2) => {
80257
+ const painted = index2 === 0 ? feedbackLine(line5) : paintRow([styled(escapeContent(line5), line5.includes("*") || line5.includes("\u6D3B\u8DC3") ? "accentText" : "fg")]);
80258
+ return (0, import_react39.createElement)(
80259
+ Text,
80260
+ { key: index2 },
80261
+ paintBackgroundRow([painted], "bg", columns)
80262
+ );
80263
+ })
80157
80264
  );
80158
80265
  } else if (adaptiveRows.length > 0) {
80159
80266
  const goalLine = goalRuns === void 0 ? void 0 : `${goalRuns.head} \xB7 ${goalRuns.objective}`;
@@ -82205,6 +82312,7 @@ var LOCAL_COMMANDS = [
82205
82312
  { name: "key", description: "Set or update API key", input: { hint: "[KEY_NAME] <SECRET>" } },
82206
82313
  { name: "login", description: "Set or update API key", input: { hint: "[KEY_NAME] <SECRET>" } },
82207
82314
  { name: "model", description: "Switch the active model" },
82315
+ { name: "new", description: "Start a new session" },
82208
82316
  { name: "reload", description: "Relaunch this process and resume the session" },
82209
82317
  { name: "resume", description: "Switch or resume a session" },
82210
82318
  { name: "settings", description: "Edit settings, catalogs, and General" }
@@ -82437,6 +82545,7 @@ var RuntimeController = class _RuntimeController {
82437
82545
  modelOpen = false;
82438
82546
  modelFilter = "";
82439
82547
  modelRows = [];
82548
+ cachedModelRows;
82440
82549
  modelSelectedIndex = 0;
82441
82550
  modelStatus = "idle";
82442
82551
  modelError;
@@ -84418,7 +84527,8 @@ var RuntimeController = class _RuntimeController {
84418
84527
  turnEndReason: last.kind,
84419
84528
  ...last.abortCause === void 0 ? {} : { abortCause: last.abortCause },
84420
84529
  ...last.errorText === void 0 ? {} : { errorText: last.errorText },
84421
- sessionTitle: this.notifySessionTitle()
84530
+ sessionTitle: this.notifySessionTitle(),
84531
+ currentPrompt: this.currentPromptText()
84422
84532
  }));
84423
84533
  }
84424
84534
  /**
@@ -84433,7 +84543,8 @@ var RuntimeController = class _RuntimeController {
84433
84543
  secondsSinceLastInput: this.secondsSinceLastInput(),
84434
84544
  toolName: input.toolName,
84435
84545
  questionText: input.questionText,
84436
- sessionTitle: this.notifySessionTitle()
84546
+ sessionTitle: this.notifySessionTitle(),
84547
+ currentPrompt: this.currentPromptText()
84437
84548
  }));
84438
84549
  }
84439
84550
  /**
@@ -84483,6 +84594,15 @@ var RuntimeController = class _RuntimeController {
84483
84594
  notifySessionTitle() {
84484
84595
  return this.session === void 0 ? void 0 : notifySessionTitle(getSessionEvents(this.session));
84485
84596
  }
84597
+ currentPromptText() {
84598
+ if (this.session === void 0) return void 0;
84599
+ const events = getSessionEvents(this.session);
84600
+ if (!Array.isArray(events)) return void 0;
84601
+ const lastUser = events.findLast(isHumanUserMessage);
84602
+ if (lastUser === void 0) return void 0;
84603
+ const text4 = joinTextBlocks(lastUser.data.content, " ").trim();
84604
+ return text4.length > 0 ? text4 : void 0;
84605
+ }
84486
84606
  /** Record local user input for the notification quiet window. */
84487
84607
  noteUserActivity() {
84488
84608
  this.lastInputAt = Date.now();
@@ -85271,6 +85391,12 @@ var RuntimeController = class _RuntimeController {
85271
85391
  this.ownWork(this.exportLive(), "session export");
85272
85392
  return;
85273
85393
  }
85394
+ if (query === "new" || query === "new ") {
85395
+ if (this.blockingHead() !== void 0) return;
85396
+ this.closeOtherPanels();
85397
+ this.dispatch({ kind: "new-session" });
85398
+ return;
85399
+ }
85274
85400
  if (query === "reload") {
85275
85401
  this.requestReload();
85276
85402
  return;
@@ -85351,7 +85477,7 @@ var RuntimeController = class _RuntimeController {
85351
85477
  return;
85352
85478
  }
85353
85479
  if (execution.result.kind !== "success") {
85354
- this.reportCommandFailure(firstLine2(execution.result.text));
85480
+ this.reportCommandFailure(execution.result.text.trim());
85355
85481
  return;
85356
85482
  }
85357
85483
  const text4 = execution.result.text;
@@ -85371,7 +85497,7 @@ var RuntimeController = class _RuntimeController {
85371
85497
  if (wasPermission || wasPlanDirectory) this.emit();
85372
85498
  return;
85373
85499
  }
85374
- this.setFeedback(`\u2713 ${firstLine2(text4)}`);
85500
+ this.setFeedback(`\u2713 ${text4.trim()}`);
85375
85501
  },
85376
85502
  (error51) => {
85377
85503
  if (seq !== this.commandSeq) return;
@@ -86030,7 +86156,7 @@ var RuntimeController = class _RuntimeController {
86030
86156
  "\u2191\u2193/jk \u6EDA\u52A8 \xB7 g s \u4F1A\u8BDD\u5217\u8868 \xB7 Tab \u8865\u5168 \xB7 Esc \u5173\u95ED \xB7 / \u547D\u4EE4 \xB7 @ \u63D0\u53CA",
86031
86157
  "\u6EDA\u8F6E\u6EDA\u52A8 \xB7 \u70B9\u51FB\u6253\u5F00\u94FE\u63A5 \xB7 \u62D6\u9009\u590D\u5236",
86032
86158
  "/plan \u8BA1\u5212 \xB7 /goal \u76EE\u6807 \xB7 /compact \u538B\u7F29",
86033
- "/model \u6A21\u578B\u9009\u62E9 \xB7 /help \u5E2E\u52A9 \xB7 /export \u5BFC\u51FA\u4F1A\u8BDD \xB7 /settings \u8BBE\u7F6E \xB7 /resume \u4F1A\u8BDD \xB7 /reload \u91CD\u8F7D"
86159
+ "/model \u6A21\u578B\u9009\u62E9 \xB7 /new \u65B0\u4F1A\u8BDD \xB7 /help \u5E2E\u52A9 \xB7 /export \u5BFC\u51FA\u4F1A\u8BDD \xB7 /settings \u8BBE\u7F6E \xB7 /resume \u4F1A\u8BDD \xB7 /reload \u91CD\u8F7D"
86034
86160
  ];
86035
86161
  }
86036
86162
  /**
@@ -86048,11 +86174,21 @@ var RuntimeController = class _RuntimeController {
86048
86174
  * the panel transients, then load the provider/model catalog concurrently.
86049
86175
  */
86050
86176
  openModelPane() {
86051
- this.resetModelState();
86052
86177
  this.closeOtherPanels();
86178
+ this.modelFilter = "";
86179
+ this.modelSelectedIndex = 0;
86053
86180
  this.modelOpen = true;
86181
+ if (this.cachedModelRows !== void 0 && this.cachedModelRows.length > 0) {
86182
+ this.modelRows = [...this.cachedModelRows];
86183
+ this.modelStatus = "idle";
86184
+ this.emit();
86185
+ this.ownWork(this.loadModelCatalog(true), "model catalog background refresh");
86186
+ return;
86187
+ }
86188
+ this.modelRows = [];
86189
+ this.modelStatus = "loading";
86054
86190
  this.emit();
86055
- this.ownWork(this.loadModelCatalog(), "model catalog load");
86191
+ this.ownWork(this.loadModelCatalog(false), "model catalog load");
86056
86192
  }
86057
86193
  /**
86058
86194
  * The catalog rows narrowed by the live filter: case-insensitive substring
@@ -86074,18 +86210,22 @@ var RuntimeController = class _RuntimeController {
86074
86210
  * degrades to a single `provider 当前默认` row carrying the live default
86075
86211
  * model when the provider is the current one.
86076
86212
  */
86077
- async loadModelCatalog() {
86213
+ async loadModelCatalog(isBackground = false) {
86078
86214
  const seq = ++this.modelSeq;
86079
86215
  const llm = this.ctx.get("llm");
86080
86216
  const defaultModel = this.ctx.get("agentDefaultModel");
86081
86217
  if (llm === void 0 || defaultModel === void 0) {
86082
- this.modelStatus = "error";
86083
- this.modelError = "\u6A21\u578B\u670D\u52A1\u4E0D\u53EF\u7528";
86084
- this.emit();
86218
+ if (!isBackground) {
86219
+ this.modelStatus = "error";
86220
+ this.modelError = "\u6A21\u578B\u670D\u52A1\u4E0D\u53EF\u7528";
86221
+ this.emit();
86222
+ }
86085
86223
  return;
86086
86224
  }
86087
- this.modelStatus = "loading";
86088
- this.emit();
86225
+ if (!isBackground) {
86226
+ this.modelStatus = "loading";
86227
+ this.emit();
86228
+ }
86089
86229
  const current = defaultModel.currentSelection();
86090
86230
  try {
86091
86231
  const providers = llm.listProviders();
@@ -86107,7 +86247,9 @@ var RuntimeController = class _RuntimeController {
86107
86247
  );
86108
86248
  }));
86109
86249
  if (seq !== this.modelSeq) return;
86110
- this.modelRows = groups.flat();
86250
+ const rows = groups.flat();
86251
+ this.cachedModelRows = rows;
86252
+ this.modelRows = [...rows];
86111
86253
  if (this.modelSelectedIndex >= this.modelRows.length) {
86112
86254
  this.modelSelectedIndex = Math.max(0, this.modelRows.length - 1);
86113
86255
  }
@@ -86116,10 +86258,12 @@ var RuntimeController = class _RuntimeController {
86116
86258
  } catch (error51) {
86117
86259
  if (seq !== this.modelSeq) return;
86118
86260
  this.ctx.logger.warn(`model catalog load failed: ${String(error51)}`);
86119
- this.modelRows = [];
86120
- this.modelStatus = "error";
86121
- this.modelError = error51 instanceof Error ? error51.message : String(error51);
86122
- this.emit();
86261
+ if (!isBackground) {
86262
+ this.modelRows = [];
86263
+ this.modelStatus = "error";
86264
+ this.modelError = error51 instanceof Error ? error51.message : String(error51);
86265
+ this.emit();
86266
+ }
86123
86267
  }
86124
86268
  }
86125
86269
  /** Fold one advertised model into a catalog row. */
@@ -86464,7 +86608,7 @@ var RuntimeController = class _RuntimeController {
86464
86608
  isEmpty: isSessionEmpty(events)
86465
86609
  };
86466
86610
  } catch {
86467
- return { id, title: id, updatedAt: 0, isEmpty: false };
86611
+ return { id, title: id, updatedAt: 0, isEmpty: true };
86468
86612
  }
86469
86613
  }
86470
86614
  /** Queue the newest query and enter loading before its debounce delay. */
@@ -86935,9 +87079,16 @@ function foldTitle(events) {
86935
87079
  if (!Array.isArray(events)) return void 0;
86936
87080
  const folded = foldSessionTitle(events);
86937
87081
  if (folded !== void 0) return folded.title;
86938
- const firstUser = events.find(isHumanUserMessage);
86939
- if (firstUser !== void 0) {
86940
- const text4 = joinTextBlocks(firstUser.data.content, " ");
87082
+ const userMessages = events.filter(isHumanUserMessage);
87083
+ if (userMessages.length > 0) {
87084
+ const nonGreeting = userMessages.find((m) => {
87085
+ const text5 = joinTextBlocks(m.data.content, " ").trim().toLowerCase();
87086
+ return !["\u4F60\u597D", "hello", "hi", "hey", "\u60A8\u597D", "test", "say hi"].includes(text5) && text5.length > 2;
87087
+ });
87088
+ const firstUser = userMessages[0];
87089
+ if (firstUser === void 0) return void 0;
87090
+ const chosenUser = nonGreeting ?? firstUser;
87091
+ const text4 = joinTextBlocks(chosenUser.data.content, " ");
86941
87092
  const fallback = fallbackSessionTitle(
86942
87093
  text4,
86943
87094
  LIST_TITLE_MAX_WORDS,
@@ -86967,10 +87118,6 @@ function isSessionEmpty(events) {
86967
87118
  if (isBootstrapOnlySession(events)) return true;
86968
87119
  return !events.some(isHumanUserMessage) && foldTitle(events) === void 0;
86969
87120
  }
86970
- function firstLine2(text4) {
86971
- const line5 = text4.split("\n", 1)[0];
86972
- return line5.trimEnd();
86973
- }
86974
87121
  function planReviewOf(questions) {
86975
87122
  if (questions.length !== 1) return void 0;
86976
87123
  const question = questions[0];
package/dist/zen-proxy.js CHANGED
@@ -814,6 +814,7 @@ defineMethod("transform", [
814
814
  ], ({ inner }, isInner) => inner.toString(isInner));
815
815
 
816
816
  // ../dsh-tui/plugins/zen-proxy/index.js
817
+ import crypto from "node:crypto";
817
818
  import http from "node:http";
818
819
  import https from "node:https";
819
820
  var name = "zen-proxy";
@@ -822,28 +823,40 @@ var Config = Schema.object({
822
823
  port: Schema.number().default(4097),
823
824
  upstreamHost: Schema.string().default("opencode.ai"),
824
825
  upstreamBasePath: Schema.string().default("/zen/v1"),
825
- userAgent: Schema.string().default("opencode/1.15.5 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14"),
826
+ userAgent: Schema.string().default("opencode/1.18.30"),
826
827
  clientHeader: Schema.string().default("cli"),
827
828
  projectHeader: Schema.string().default("global")
828
829
  });
829
830
  var inject = [];
830
- var rnd = (p) => `${p}${Math.random().toString(36).slice(2, 12)}`;
831
+ var rnd = (p) => `${p}${crypto.randomBytes(13).toString("hex")}`;
831
832
  function apply(ctx, config) {
832
833
  const server = http.createServer((req, res) => {
833
834
  const { method, url } = req;
834
- if (method === "GET" && url === "/v1/models") {
835
+ const parsed = new URL(url, "http://127.0.0.1");
836
+ const pathname = parsed.pathname.replace(/^(\/v1)+/, "/v1").replace(/\/+$/, "") || "/";
837
+ if (method === "GET" && (pathname === "/v1/models" || pathname === "/models")) {
835
838
  forward(req, res, { method: "GET", path: `${config.upstreamBasePath}/models`, body: null });
836
839
  return;
837
840
  }
838
- if (method !== "POST" || url !== "/v1/chat/completions") {
841
+ let targetRoute = null;
842
+ if (pathname === "/v1/chat/completions" || pathname === "/chat/completions") {
843
+ targetRoute = "/chat/completions";
844
+ } else if (pathname === "/v1/responses" || pathname === "/responses") {
845
+ targetRoute = "/responses";
846
+ } else if (pathname === "/v1/messages" || pathname === "/messages") {
847
+ targetRoute = "/messages";
848
+ }
849
+ if (method !== "POST" || !targetRoute) {
839
850
  res.writeHead(404, { "content-type": "application/json" });
840
851
  res.end(JSON.stringify({ error: { type: "not_found", message: `zen-proxy: unsupported ${method} ${url}` } }));
841
852
  return;
842
853
  }
843
854
  let body = "";
844
- req.on("data", (c) => body += c);
855
+ req.on("data", (c) => {
856
+ body += c;
857
+ });
845
858
  req.on("end", () => {
846
- forward(req, res, { method: "POST", path: `${config.upstreamBasePath}/chat/completions`, body });
859
+ forward(req, res, { method: "POST", path: `${config.upstreamBasePath}${targetRoute}`, body });
847
860
  });
848
861
  });
849
862
  server.on("error", (error) => {
@@ -860,17 +873,30 @@ function apply(ctx, config) {
860
873
  };
861
874
  }, "zen-proxy.server");
862
875
  function forward(req, res, { method, path, body }) {
876
+ const sessionId = rnd("ses_");
863
877
  const headers = {
864
878
  "content-type": "application/json",
865
- // Pass through the caller's Authorization if present; nothing else.
866
- ...req.headers.authorization ? { authorization: req.headers.authorization } : {},
867
879
  "user-agent": config.userAgent,
868
880
  "x-opencode-client": config.clientHeader,
869
881
  "x-opencode-project": config.projectHeader,
870
- "x-opencode-session": rnd("ses_"),
882
+ "x-opencode-session": sessionId,
883
+ "x-session-id": sessionId,
871
884
  "x-opencode-request": rnd("msg_")
872
885
  };
873
- if (body !== null) headers["content-length"] = Buffer.byteLength(body);
886
+ if (req.headers.authorization !== void 0) {
887
+ headers.authorization = req.headers.authorization;
888
+ }
889
+ if (req.headers["anthropic-version"] !== void 0) {
890
+ headers["anthropic-version"] = req.headers["anthropic-version"];
891
+ } else if (path.endsWith("/messages")) {
892
+ headers["anthropic-version"] = "2023-06-01";
893
+ }
894
+ if (req.headers["anthropic-beta"] !== void 0) {
895
+ headers["anthropic-beta"] = req.headers["anthropic-beta"];
896
+ }
897
+ if (body !== null) {
898
+ headers["content-length"] = Buffer.byteLength(body);
899
+ }
874
900
  const out = https.request(
875
901
  {
876
902
  host: config.upstreamHost,
@@ -889,8 +915,11 @@ function apply(ctx, config) {
889
915
  res.writeHead(502);
890
916
  res.end(String(e));
891
917
  });
892
- if (body !== null) out.end(body);
893
- else out.end();
918
+ if (body === null) {
919
+ out.end();
920
+ } else {
921
+ out.end(body);
922
+ }
894
923
  }
895
924
  }
896
925
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crazyhappyone/dsh-tui",
3
- "version": "0.1.0-alpha.42",
3
+ "version": "0.1.0-alpha.44",
4
4
  "description": "Source-runtime launcher for the DeepSeek Harness terminal interface",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,7 +19,7 @@
19
19
  "LICENSE"
20
20
  ],
21
21
  "scripts": {
22
- "test": "node --test",
22
+ "test": "node --test tests/*.test.js",
23
23
  "pack:check": "npm pack --dry-run --json"
24
24
  },
25
25
  "engines": {
@@ -1,3 +1,4 @@
1
+ # yaml-language-server: $schema=none
1
2
  # The deepseek-tui bundle patch: terminal-surface mode directly over dsh-base.
2
3
  # It mounts no Host, HTTP server, Web runtime, or browser plugin. An ordinary
3
4
  # provider plugin parses --resume/--cwd and the task positional, then the
@@ -9,8 +10,10 @@
9
10
  - id: system-prompt
10
11
  config:
11
12
  persona: >-
12
- You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.
13
- You are interacting with the user through the deepseek-tui terminal interface.
13
+ You are a coding agent powered by the {{model}} model.
14
+ Your working directory is {{cwd}}.
15
+ You are interacting with the user through the deepseek-tui
16
+ terminal interface.
14
17
 
15
18
  # The shared module-reload HMR row stays off; the launcher's watch-only
16
19
  # fallback still keeps the user patch layers live until the run exits.
@@ -20,11 +23,16 @@
20
23
  - id: tools
21
24
  config:
22
25
  # Keep the same temporary process-wide Code Mode opt-in as the Web surface.
26
+ # pi-lens-ignore: YAML:0
23
27
  mode: !!js process.env.DSH_TOOLS_MODE
24
28
 
25
29
  - id: approval
30
+ # pi-lens-ignore: YAML:0
26
31
  config:
27
- policy: !!js "(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'never' : 'ask'"
32
+ policy: !!js >-
33
+ (process.env.DSH_PERMISSION_MODE ?? 'workspace-write')
34
+ === 'danger-full-access'
35
+ ? 'never' : 'ask'
28
36
 
29
37
  # Full-text session search stays disabled until the first Ctrl+K search (the
30
38
  # base row's `openAt: never`). The TUI has no browser-latency concern, so the
@@ -39,11 +47,15 @@
39
47
  models:
40
48
  - id: deepseek-v4-flash
41
49
  name: DeepSeek-V4-Flash
42
- description: Fast, efficient, and economical; suited to focused, routine, or parallel tasks.
50
+ description: >-
51
+ Fast, efficient, and economical; suited to focused, routine,
52
+ or parallel tasks.
43
53
  contextWindow: 1000000
44
54
  - id: deepseek-v4-pro
45
55
  name: DeepSeek-V4-Pro
46
- description: Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or quality-critical tasks at higher cost.
56
+ description: >-
57
+ Stronger agentic coding, knowledge, and difficult reasoning;
58
+ suited to complex or quality-critical tasks at higher cost.
47
59
  contextWindow: 1000000
48
60
  - id: deepseek-v4-flash-vision-exp
49
61
  name: DeepSeek-V4-Flash-Vision-Exp
@@ -51,15 +63,29 @@
51
63
  inputModalities: [text, image]
52
64
  - id: deepseek-v4.1-flash-expires-on-0910
53
65
  name: DeepSeek-V4.1-Flash
54
- description: DeepSeek V4.1 Flash intermediate beta with native multimodal support, higher capability, faster speed, and lower cost.
66
+ description: >-
67
+ DeepSeek V4.1 Flash intermediate beta with native multimodal
68
+ support, higher capability, faster speed, and lower cost.
55
69
  contextWindow: 1000000
56
70
  inputModalities: [text, image]
57
71
 
58
72
  - id: agent-default-model
59
73
  config:
74
+ # pi-lens-ignore: YAML:0
60
75
  provider: deepseek-official
76
+ # pi-lens-ignore: YAML:0
61
77
  model: !!js "process.env.DSH_MODEL ?? 'deepseek-v4-flash'"
62
78
 
79
+ # pi-lens-ignore: YAML:0
80
+ - id: session-title-llm
81
+ name: "@deepseek-ai/dsh-session-title-all-prompts-llm"
82
+ config:
83
+ targetWords: 5
84
+ targetCjkCharacters: 10
85
+ maxInputBytes: 16384
86
+ maxOutputTokens: 64
87
+ timeoutMs: 60000
88
+
63
89
  - insert:
64
90
  # Code Mode is a core execution capability, not a Web component.
65
91
  - id: code-runtime
@@ -92,17 +118,32 @@
92
118
  - id: llm-anthropic
93
119
  name: "@deepseek-ai/dsh-llm-anthropic"
94
120
 
121
+ # In-process OpenCode Zen header proxy (vendored from
122
+ # zhangyang-crazy-one/dsh-zen-proxy, MIT). Listens on localhost
123
+ # only; point an opencode provider baseURL at
124
+ # http://127.0.0.1:4097/v1 to reach the Zen free tier.
125
+ # Remove this row to disable the listener.
126
+ - id: zen-proxy
127
+ name: "./zen-proxy/index.js"
128
+ config:
129
+ host: 127.0.0.1
130
+ port: 4097
131
+
95
132
  # Reads its task from the ordinary tuiStartup provider.
96
133
  - id: tui-runtime
134
+ # pi-lens-ignore: YAML:0
97
135
  name: "@deepseek-ai/dsh-tui"
98
136
  inject: [tuiStartup]
137
+ # pi-lens-ignore: YAML:0
99
138
  config:
139
+ # pi-lens-ignore: YAML:0
100
140
  task: !!js ctx.tuiStartup.task
101
141
  resume: !!js ctx.tuiStartup.resume
142
+ # pi-lens-ignore: YAML:0
102
143
  cwd: !!js ctx.tuiStartup.cwd
103
144
  model: !!js ctx.tuiStartup.model
104
145
  frameStats: !!js ctx.tuiStartup.frameStats
105
- # Render policy (overscan + stream pacing + scroll pacing + cache budgets).
146
+ # Render policy (overscan, stream pacing, scroll pacing, cache).
106
147
  # Loaded by the runtime Config schema; the values below mirror the
107
148
  # schema defaults so the deployment layer owns the tunables. Override
108
149
  # here to retune without touching the plugin.