@toddzheng024/dscode-bundle 0.4.0 → 0.5.0

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 (91) hide show
  1. package/THIRD_PARTY_NOTICES.md +15 -0
  2. package/cordis.patch.yml +3 -3
  3. package/package.json +7 -1
  4. package/plugins/clipboard-image/clipboard-image.swift +56 -0
  5. package/plugins/clipboard-image/index.mjs +70 -0
  6. package/plugins/code-review/git.mjs +95 -0
  7. package/plugins/code-review/index.mjs +88 -0
  8. package/plugins/dscode/index.mjs +17 -3
  9. package/plugins/session-metrics/index.mjs +6 -2
  10. package/plugins/session-metrics/rate.mjs +62 -0
  11. package/plugins/session-metrics/view.mjs +15 -9
  12. package/plugins/tui-tools/doctor-cli.mjs +22 -0
  13. package/plugins/tui-tools/doctor.mjs +141 -0
  14. package/plugins/tui-tools/index.mjs +14 -6
  15. package/plugins/ultra/policy.mjs +12 -1
  16. package/plugins/worktree-subagent/worktree.mjs +50 -0
  17. package/presets/dscode/agent.cordis.yml +7 -2
  18. package/vendor/deepseek/index.js +12 -2
  19. package/vendor/subagent/index.js +29 -9
  20. package/vendor/subagent-core/LICENSE +21 -0
  21. package/vendor/subagent-core/index.js +3232 -0
  22. package/vendor/subagent-core/invariant.js +76 -0
  23. package/vendor/subagent-core/typert.host.d.ts +3 -0
  24. package/vendor/subagent-core/typert.host.js +964 -0
  25. package/vendor/subagent-core/typert.remote-client.d.ts +27 -0
  26. package/vendor/subagent-core/typert.remote-client.js +160 -0
  27. package/vendor/subagent-core/types/assistant-output.d.ts +48 -0
  28. package/vendor/subagent-core/types/assistant-output.js +73 -0
  29. package/vendor/subagent-core/types/catalog.d.ts +77 -0
  30. package/vendor/subagent-core/types/catalog.js +103 -0
  31. package/vendor/subagent-core/types/child-agent.d.ts +150 -0
  32. package/vendor/subagent-core/types/child-agent.js +207 -0
  33. package/vendor/subagent-core/types/client.d.ts +8 -0
  34. package/vendor/subagent-core/types/client.js +7 -0
  35. package/vendor/subagent-core/types/continuation-activation.d.ts +251 -0
  36. package/vendor/subagent-core/types/continuation-activation.js +663 -0
  37. package/vendor/subagent-core/types/continuation-messages.d.ts +62 -0
  38. package/vendor/subagent-core/types/continuation-messages.js +102 -0
  39. package/vendor/subagent-core/types/continuation.d.ts +124 -0
  40. package/vendor/subagent-core/types/continuation.js +455 -0
  41. package/vendor/subagent-core/types/control-types.d.ts +146 -0
  42. package/vendor/subagent-core/types/control-types.js +9 -0
  43. package/vendor/subagent-core/types/control.d.ts +71 -0
  44. package/vendor/subagent-core/types/control.js +116 -0
  45. package/vendor/subagent-core/types/depth.d.ts +31 -0
  46. package/vendor/subagent-core/types/depth.js +39 -0
  47. package/vendor/subagent-core/types/descriptor.d.ts +144 -0
  48. package/vendor/subagent-core/types/descriptor.js +193 -0
  49. package/vendor/subagent-core/types/error.d.ts +11 -0
  50. package/vendor/subagent-core/types/error.js +14 -0
  51. package/vendor/subagent-core/types/inbox.d.ts +43 -0
  52. package/vendor/subagent-core/types/inbox.js +61 -0
  53. package/vendor/subagent-core/types/index.d.ts +316 -0
  54. package/vendor/subagent-core/types/index.js +511 -0
  55. package/vendor/subagent-core/types/internal.d.ts +58 -0
  56. package/vendor/subagent-core/types/internal.js +58 -0
  57. package/vendor/subagent-core/types/invariant.d.ts +13 -0
  58. package/vendor/subagent-core/types/invariant.js +91 -0
  59. package/vendor/subagent-core/types/lifecycle.d.ts +113 -0
  60. package/vendor/subagent-core/types/lifecycle.js +178 -0
  61. package/vendor/subagent-core/types/list-children.d.ts +65 -0
  62. package/vendor/subagent-core/types/list-children.js +381 -0
  63. package/vendor/subagent-core/types/out-of-process.d.ts +118 -0
  64. package/vendor/subagent-core/types/out-of-process.js +219 -0
  65. package/vendor/subagent-core/types/projection-types.d.ts +74 -0
  66. package/vendor/subagent-core/types/projection-types.js +7 -0
  67. package/vendor/subagent-core/types/projection.d.ts +95 -0
  68. package/vendor/subagent-core/types/projection.js +151 -0
  69. package/vendor/subagent-core/types/run-settlement.d.ts +17 -0
  70. package/vendor/subagent-core/types/run-settlement.js +70 -0
  71. package/vendor/subagent-core/types/types.d.ts +377 -0
  72. package/vendor/subagent-core/types/types.js +19 -0
  73. package/vendor/subagent-driver/LICENSE +21 -0
  74. package/vendor/subagent-driver/index.js +255 -0
  75. package/vendor/subagent-driver/types/index.d.ts +33 -0
  76. package/vendor/subagent-driver/types/structured.d.ts +42 -0
  77. package/vendor/subagent-fork/LICENSE +21 -0
  78. package/vendor/subagent-fork/index.js +61 -0
  79. package/vendor/subagent-fork/types/index.d.ts +20 -0
  80. package/vendor/subagent-spawn/LICENSE +21 -0
  81. package/vendor/subagent-spawn/index.js +45 -0
  82. package/vendor/subagent-spawn/types/index.d.ts +19 -0
  83. package/vendor/terminal/LICENSE +21 -0
  84. package/vendor/terminal/index.js +1013 -0
  85. package/vendor/terminal/types/config.d.ts +71 -0
  86. package/vendor/terminal/types/index.d.ts +36 -0
  87. package/vendor/terminal/types/sanitize.d.ts +47 -0
  88. package/vendor/terminal/types/session.d.ts +87 -0
  89. package/vendor/tui/dscode-clipboard-image/clipboard-image.swift +56 -0
  90. package/vendor/tui/dscode-clipboard-image/index.mjs +70 -0
  91. package/vendor/tui/index.mjs +301 -78
@@ -1,3 +1,126 @@
1
+ import { readClipboardImage as dscodeReadClipboardImage } from "./dscode-clipboard-image/index.mjs";
2
+ // dscode-clipboard-image-v1
3
+ // dscode-large-paste-v1
4
+ function collapseLargePaste(text, draft, pastes) {
5
+ const safe = text.replaceAll('\r\n', '\n').replaceAll('\r', '\n').replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, '').replaceAll('\t', ' ');
6
+ const count = [...safe].length;
7
+ if (count <= LARGE_PASTE_CHARS) return safe;
8
+ // Keep an executable-looking first line visible to the command router.
9
+ if (draft.trim() === '' && /^\s*[!/]/u.test(safe)) return safe;
10
+ const base = `[Pasted Content ${count} chars]`;
11
+ let marker = base;
12
+ for (let suffix = 2; draft.includes(marker) || pastes.has(marker); suffix++) marker = `${base} #${suffix}`;
13
+ pastes.set(marker, safe);
14
+ return marker;
15
+ }
16
+ function expandLargePastes(value, pastes) {
17
+ const markers = [...pastes.keys()].sort((left, right) => right.length - left.length);
18
+ let expanded = '';
19
+ let offset = 0;
20
+ while (offset < value.length) {
21
+ let found = -1;
22
+ let match;
23
+ for (const marker of markers) {
24
+ const at = value.indexOf(marker, offset);
25
+ if (at >= 0 && (found < 0 || at < found)) {
26
+ found = at;
27
+ match = marker;
28
+ }
29
+ }
30
+ if (match === undefined) break;
31
+ expanded += value.slice(offset, found) + pastes.get(match);
32
+ offset = found + match.length;
33
+ }
34
+ return expanded + value.slice(offset);
35
+ }
36
+ function pasteAtomicEdit(before, edit, pastes) {
37
+ let prefix = 0;
38
+ while (prefix < before.length && prefix < edit.value.length && before[prefix] === edit.value[prefix]) prefix++;
39
+ let suffix = 0;
40
+ while (suffix < before.length - prefix && suffix < edit.value.length - prefix && before[before.length - suffix - 1] === edit.value[edit.value.length - suffix - 1]) suffix++;
41
+ let from = prefix, to = before.length - suffix;
42
+ const inserted = edit.value.slice(prefix, edit.value.length - suffix);
43
+ let touched = false;
44
+ const originalPastes = new Map(pastes);
45
+ for (const marker of pastes.keys()) {
46
+ const start = before.indexOf(marker);
47
+ if (start < 0) continue;
48
+ const end = start + marker.length;
49
+ if (from < end && to > start || from === to && from > start && from < end) {
50
+ from = Math.min(from, start);
51
+ to = Math.max(to, end);
52
+ pastes.delete(marker);
53
+ touched = true;
54
+ }
55
+ }
56
+ if (touched) edit = { ...edit, value: before.slice(0, from) + inserted + before.slice(to), cursor: from + inserted.length,
57
+ killed: edit.killed === undefined ? undefined : expandLargePastes(before.slice(from, to), originalPastes) };
58
+ for (const marker of pastes.keys()) if (!edit.value.includes(marker) && before.includes(marker)) pastes.delete(marker);
59
+ return edit;
60
+ }
61
+ function pasteCursorEdge(value, previous, next, pastes) {
62
+ for (const marker of pastes.keys()) {
63
+ const start = value.indexOf(marker);
64
+ if (start < 0) continue;
65
+ const end = start + marker.length;
66
+ if (next > start && next < end) return next < previous ? start : end;
67
+ }
68
+ return next;
69
+ }
70
+ const LARGE_PASTE_CHARS = 200;
71
+ // dscode-user-background-v1
72
+ function userBackgroundRows(rows, columns, measure) {
73
+ const width = Math.max(1, Math.floor(columns));
74
+ return rows.map(row => {
75
+ const used = row.segments.reduce((sum, segment) => sum + measure(segment.text), 0);
76
+ return {
77
+ ...row,
78
+ background: 'user',
79
+ segments: [...row.segments, { text: ' '.repeat(Math.max(0, width - used)), style: 'plain' }],
80
+ };
81
+ });
82
+ }
83
+ // dscode-turn-divider-v1
84
+ function turnDividedLines(entry, columns, rendered, remaining) {
85
+ if (entry?.turnEnded !== true || (rendered.length > 0 && rendered.length >= remaining)) return rendered;
86
+ const width = Math.max(1, Math.min(72, columns - 6));
87
+ return [...rendered, { segments: [{ text: ` ${'─'.repeat(width)}`, style: 'dim' }] }];
88
+ }
89
+ // dscode-scroll-v1
90
+ function transcriptWindow(lines, rows, offset) {
91
+ if (rows <= 0) return [];
92
+ const end = Math.max(0, lines.length - Math.max(0, offset));
93
+ return lines.slice(Math.max(0, end - rows), end);
94
+ }
95
+ function mouseWheelDirection(unit) {
96
+ const match = /^\x1b\[<(\d+);\d+;\d+([Mm])$/.exec(unit);
97
+ if (!match) return null;
98
+ if (match[2] !== 'M') return 0;
99
+ const button = Number(match[1]) & ~(4 | 8 | 16);
100
+ return button === 64 ? 1 : button === 65 ? -1 : 0;
101
+ }
102
+ const DSCODE_MOUSE_ENABLE = "\x1b[?1000h\x1b[?1006h";
103
+ const DSCODE_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1000l";
104
+ const dscodeWheelListeners = new Set();
105
+ function dscodeHandleMouseUnit(unit) {
106
+ const direction = mouseWheelDirection(unit);
107
+ if (direction === null) return false;
108
+ if (direction !== 0) for (const listener of dscodeWheelListeners) listener(direction);
109
+ return true;
110
+ }
111
+ // dscode-interrupt-v1
112
+ function ctrlCAction(armed, { interrupt, busy, preparingImages, active, hasDraft }) {
113
+ if (armed) return 'quit';
114
+ if (interrupt() || busy) return 'interrupt';
115
+ if (preparingImages) return 'cancel-prepare';
116
+ if (!active) return 'wait';
117
+ return hasDraft ? 'clear-draft' : 'quit';
118
+ }
119
+ // dscode-welcome-scroll-v1
120
+ function welcomeVisibleRows(capacity, maximum, demand, canScroll = true) {
121
+ if (!canScroll || capacity < maximum) return maximum;
122
+ return Math.max(0, Math.min(maximum, capacity - demand));
123
+ }
1
124
  // dscode-email-v4
2
125
  import { createImapConnector as dscodeCreateImapConnector } from "./dscode-email/imap.mjs";
3
126
  import { createGmailConnector as dscodeCreateGmailConnector } from "./dscode-email/gmail.mjs";
@@ -148,7 +271,7 @@ function visibleSettledLines(entries, settled, budget, columns, showReasoning, r
148
271
  const chunks = [];
149
272
  let remaining = budget;
150
273
  for (let index = settled - 1; index >= 0 && remaining > 0; index--) {
151
- const lines = renderEntry(entries[index], Math.max(10, columns - 2), showReasoning);
274
+ const lines = turnDividedLines(entries[index], columns, renderEntry(entries[index], Math.max(10, columns - 2), showReasoning), remaining);
152
275
  const kept = lines.slice(-remaining);
153
276
  if (kept.length) {
154
277
  chunks.unshift(kept);
@@ -159,6 +282,40 @@ function visibleSettledLines(entries, settled, budget, columns, showReasoning, r
159
282
  }
160
283
  // dscode-style-v1
161
284
 
285
+
286
+ // dscode-tps-colors-v1
287
+ function dscodeTpsTone(rate) {
288
+ if (!Number.isFinite(rate) || rate < 0) return null;
289
+ if (rate < 75) return "yellow";
290
+ if (rate < 150) return "green";
291
+ if (rate <= 250) return "blue";
292
+ return "purple";
293
+ }
294
+ function dscodeTpsInkColor(tone) {
295
+ const palette = getPalette();
296
+ if (tone === "yellow") return inkColor(palette.warn);
297
+ if (tone === "green") return inkColor(palette.success);
298
+ if (tone === "blue") return inkColor(palette.brandBright);
299
+ if (tone === "purple") return inkColor(getTheme() === "light" ? [126, 34, 206] : [192, 132, 252]);
300
+ return void 0;
301
+ }
302
+ function dscodeTelemetryParts(value) {
303
+ const result = [];
304
+ for (const [index, part] of value.split(" | ").entries()) {
305
+ if (index > 0) result.push({ text: " | ", tone: null });
306
+ const prefix = part.startsWith("current: ") ? "current: " : part.startsWith("average: ") ? "average: " : "";
307
+ if (!prefix || !part.endsWith(" tps")) { result.push({ text: part, tone: null }); continue; }
308
+ const display = part.slice(prefix.length, -4);
309
+ const rate = Number(display.startsWith("~") ? display.slice(1) : display);
310
+ result.push({ text: prefix, tone: null }, { text: display + " tps", tone: display && dscodeTpsTone(rate) });
311
+ }
312
+ return result;
313
+ }
314
+ function dscodeTelemetryNodes(value, key) {
315
+ return dscodeTelemetryParts(value).map((part, index) => (0, import_react.createElement)(Text,
316
+ { key: key + "p" + index, color: dscodeTpsInkColor(part.tone) }, part.text));
317
+ }
318
+
162
319
  function dscodeActivity(entries, streaming) {
163
320
  const running = entries.filter(entry => entry.kind === "tool" && entry.state === "running");
164
321
  const tool = running.at(-1);
@@ -240,7 +397,8 @@ function dscodeChatLines(entry, columns) {
240
397
  if (!entry.text && !entry.interrupted) return [];
241
398
  entry = { ...entry, reasoning: "" };
242
399
  }
243
- return transcriptEntryLines(entry, columns, false, false, false);
400
+ const rows = transcriptEntryLines(entry, columns, false, false, false);
401
+ return entry.kind === "user" && !entry.notice ? userBackgroundRows(rows, columns, visibleColumns) : rows;
244
402
  }
245
403
  import { n as __require, r as __toESM, t as __commonJSMin } from "./rolldown-runtime-CMFfr-1z.mjs";
246
404
  import { a as inkColor, c as chalk, i as getTheme, n as dim, o as parseThemeName, r as getPalette, s as setTheme } from "./theme-DCT8Y2xf.mjs";
@@ -25874,9 +26032,18 @@ function replayProjectEvent(acc, event) {
25874
26032
  acc.busy = false;
25875
26033
  acc.busySince = 0;
25876
26034
  for (const entry of appended) appendReplayEntry(acc, entry);
25877
- return true;
25878
- }
25879
- case "llm/retry": {
26035
+ for (let index = acc.entries.length - 1; index >= 0; index--) {
26036
+ const entry = acc.entries[index];
26037
+ if (entry === void 0 || entry.kind === "pending") continue;
26038
+ if (entry.turnEnded !== true) {
26039
+ acc.entries[index] = { ...entry, turnEnded: true };
26040
+ acc.ops += 1;
26041
+ }
26042
+ break;
26043
+ }
26044
+ return true;
26045
+ }
26046
+ case "llm/retry": {
25880
26047
  const data = event.data;
25881
26048
  acc.streaming = "";
25882
26049
  acc.streamingReasoning = "";
@@ -28353,7 +28520,7 @@ function cacheHitPercent(usage) {
28353
28520
  return usage.inputTokens === 0 ? null : Math.round(usage.cacheReadTokens / usage.inputTokens * 1e3) / 10;
28354
28521
  }
28355
28522
  /** Separator between trailing state spans. */
28356
- const STATUS_ITEM_SEPARATOR = " · ";
28523
+ const STATUS_ITEM_SEPARATOR = " | ";
28357
28524
  /** The Codex-style mode cycle hint appended to the permission badge. */
28358
28525
  const STATUS_CYCLE_HINT = " (shift+tab to cycle)";
28359
28526
  /** Occupancy at which the usage readout flips from brand blue to amber. */
@@ -28568,7 +28735,7 @@ function safe(text) {
28568
28735
  /** Dim junction separator span inside a cluster. */
28569
28736
  function sep() {
28570
28737
  return {
28571
- text: " · ",
28738
+ text: " | ",
28572
28739
  tone: "label"
28573
28740
  };
28574
28741
  }
@@ -28599,7 +28766,7 @@ function buildCandidates(facts, stats, busy, enabled, contextWidth) {
28599
28766
  if (model !== "" && enabled.has("model")) {
28600
28767
  const effort = safe(facts.effort ?? stats.reasoningEffort);
28601
28768
  push({
28602
- text: effort === "" ? model : `${model} · ${effort}`,
28769
+ text: effort === "" ? model : `${model} | ${effort}`,
28603
28770
  tone: "model"
28604
28771
  });
28605
28772
  }
@@ -28796,7 +28963,7 @@ function layoutStatusBar(facts, stats, columns, options = {}) {
28796
28963
  const budget = Math.max(1, Math.floor(columns) - WIDTH_SAFETY);
28797
28964
  const maxContextWidth = Math.max(CONTEXT_MIN_WIDTH, Math.min(budget, Math.floor(options.contextWidth ?? 24)));
28798
28965
  const { left, right, badge, row2 } = buildCandidates(facts, stats, busy, enabled, maxContextWidth);
28799
- const groupSeparator = visibleColumns(" | ");
28966
+ const groupSeparator = visibleColumns(" | ");
28800
28967
  const itemSeparator = visibleColumns(STATUS_ITEM_SEPARATOR);
28801
28968
  const position = new Map(items.map((id, index) => [id, index]));
28802
28969
  const byPosition = (a, b) => (position.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (position.get(b.id) ?? Number.MAX_SAFE_INTEGER);
@@ -28897,7 +29064,7 @@ function layoutStatusBar(facts, stats, columns, options = {}) {
28897
29064
  };
28898
29065
  }
28899
29066
  const row2Kept = [...orderedRow2];
28900
- const row2Budget = Math.max(0, budget - 2 - (facts.telemetry ? visibleColumns(facts.telemetry) + 3 : 0));
29067
+ const row2Budget = Math.max(0, budget - 1 - (facts.telemetry ? visibleColumns(facts.telemetry) + 3 : 0));
28901
29068
  const row2Width = () => joinWidth(row2Kept.map((entry) => spansWidth(entry.group.spans)), groupSeparator);
28902
29069
  while (row2Width() > row2Budget && row2Kept.length > 0) {
28903
29070
  let dropIndex = 0;
@@ -32003,8 +32170,10 @@ function lineStyleProps(style) {
32003
32170
  function StyledRows({ lines }) {
32004
32171
  return (0, import_react.createElement)(Box, { flexDirection: "column" }, ...lines.map((line, index) => (0, import_react.createElement)(Text, {
32005
32172
  key: index,
32006
- wrap: "truncate-end"
32007
- }, line.segments.length === 0 ? " " : line.segments.map((segment, at) => (0, import_react.createElement)(Text, {
32173
+ wrap: "truncate-end",
32174
+ backgroundColor: line.background === "user" ? inkColor(getPalette().composerBand) : void 0,
32175
+ color: line.background === "user" ? inkColor(getPalette().text) : void 0
32176
+ }, line.segments.length === 0 ? " " : line.segments.map((segment, at) => (0, import_react.createElement)(Text, {
32008
32177
  key: at,
32009
32178
  ...lineStyleProps(segment.style)
32010
32179
  }, segment.text)))));
@@ -32076,7 +32245,7 @@ function Header({ cwd = "", model = "", effort = "" }) {
32076
32245
  if (!full) return (0, import_react.createElement)(Box, { flexDirection: "column", paddingX: 2, marginBottom: 1 },
32077
32246
  (0, import_react.createElement)(Text, { wrap: "truncate-end" },
32078
32247
  (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright), bold: true }, "❄ DSCODE"),
32079
- (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, " v0.4.0")),
32248
+ (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, " v0.5.0")),
32080
32249
  (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns(modelName + " · " + effortName, width)),
32081
32250
  (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim), wrap: "truncate-end" }, welcomePath(cwd, width)));
32082
32251
  return (0, import_react.createElement)(Box, { flexDirection: "column", width, borderStyle: "round", borderColor: inkColor(getPalette().brand), paddingX: 1, marginBottom: 1 },
@@ -32095,7 +32264,7 @@ function Header({ cwd = "", model = "", effort = "" }) {
32095
32264
  (0, import_react.createElement)(Box, { flexDirection: "column", width: detailsWidth, marginTop: 2 },
32096
32265
  (0, import_react.createElement)(Text, { color: inkColor(getPalette().text), bold: true }, "DSCODE"),
32097
32266
  (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandDeep) }, "────────────"),
32098
- (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, "v0.4.0"),
32267
+ (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, "v0.5.0"),
32099
32268
  (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns("model " + modelName, detailsWidth)),
32100
32269
  (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns("effort " + effortName, detailsWidth)),
32101
32270
  (0, import_react.createElement)(Text, null, " "),
@@ -32306,7 +32475,7 @@ function deepseekWaveHues(tier) {
32306
32475
  function StatusLine({ facts, stats, busy, columns, items }) {
32307
32476
  const [, refreshMetrics] = (0, import_react.useState)(0);
32308
32477
  (0, import_react.useEffect)(() => { const timer = setInterval(() => refreshMetrics(n => n + 1), 1000); return () => clearInterval(timer); }, []);
32309
- facts = { ...facts, telemetry: columns >= 64 ? dscodeFooterFor(facts.fullSessionId, stats, Math.max(1, Math.floor(columns / 2) - 4)) : "" };
32478
+ facts = { ...facts, telemetry: columns >= 48 ? dscodeFooterFor(facts.fullSessionId, stats, Math.max(1, Math.min(columns - 8, Math.max(40, Math.floor(columns * 0.8) - 4)))) : "" };
32310
32479
  const layout = (0, import_react.useMemo)(() => layoutStatusBar(facts, stats, Math.max(8, columns - 2), {
32311
32480
  busy,
32312
32481
  items,
@@ -32337,7 +32506,7 @@ function StatusLine({ facts, stats, busy, columns, items }) {
32337
32506
  if (groupIndex > 0) leftParts.push((0, import_react.createElement)(Text, {
32338
32507
  key: key + "gs" + groupIndex,
32339
32508
  color: inkColor(getPalette().dim)
32340
- }, " | "));
32509
+ }, " | "));
32341
32510
  group.spans.forEach((span, spanIndex) => {
32342
32511
  leftParts.push((0, import_react.createElement)(Text, {
32343
32512
  key: key + "g" + groupIndex + "s" + spanIndex,
@@ -32347,6 +32516,7 @@ function StatusLine({ facts, stats, busy, columns, items }) {
32347
32516
  });
32348
32517
  });
32349
32518
  const rightParts = [];
32519
+ if (key === "s2" && row.left.length > 0 && row.right.length > 0) rightParts.push((0, import_react.createElement)(Text, { key: key + "divider", color: inkColor(getPalette().dim) }, "| "));
32350
32520
  row.right.forEach((span, index) => {
32351
32521
  if (index > 0) rightParts.push((0, import_react.createElement)(Text, {
32352
32522
  key: key + "rs" + index,
@@ -32356,7 +32526,7 @@ function StatusLine({ facts, stats, busy, columns, items }) {
32356
32526
  key: key + "r" + index,
32357
32527
  wrap: "truncate-end",
32358
32528
  ...statusToneProps(span.tone)
32359
- }, span.text));
32529
+ }, key === "s2" && index === 0 ? dscodeTelemetryNodes(span.text, key + "r" + index) : span.text));
32360
32530
  });
32361
32531
  if (row.hint) rightParts.push((0, import_react.createElement)(Text, {
32362
32532
  key: key + "hint",
@@ -33820,7 +33990,7 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
33820
33990
  key: "key-cancel",
33821
33991
  dimColor: true,
33822
33992
  wrap: "truncate-end"
33823
- }, " esc interrupt the running turn · ctrl+c cancel / clear / quit · ctrl+d exit"),
33993
+ }, " ctrl+c stop running turn · ctrl+c again exit · ctrl+d exit"),
33824
33994
  (0, import_react.createElement)(Text, {
33825
33995
  key: "key-queue",
33826
33996
  dimColor: true,
@@ -34340,7 +34510,7 @@ function CompletionMenu({ active, mention, index, rows, error }) {
34340
34510
  * While a modal (approval / question / model panel) owns the keys, the
34341
34511
  * box passes every key through untouched.
34342
34512
  */
34343
- function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openEmail, emailFill, emailConsumed, openLogin, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openJobs, openStatusline, openTheme, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, applyEditorKeys, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, inspectImages, prepareImages, inspectFiles, prepareFiles, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, animations, applyAnimations, waveTier, waveStyle, maxRows, onEditorRows, onMenuRows, sessionKey }) {
34513
+ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openEmail, emailFill, emailConsumed, openLogin, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openJobs, openStatusline, openTheme, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, applyEditorKeys, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, inspectImages, prepareImages, inspectFiles, prepareFiles, readClipboardImage, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, animations, applyAnimations, waveTier, waveStyle, maxRows, onEditorRows, onMenuRows, onTranscriptScroll, sessionKey }) {
34344
34514
  const columns = useStdout().stdout?.columns ?? 80;
34345
34515
  const inputTerminalRows = useStdout().stdout?.rows ?? 30;
34346
34516
  const dscodeImeStdout = useStdout().stdout;
@@ -34357,6 +34527,8 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34357
34527
  const [cursor, setCursor] = (0, import_react.useState)(0);
34358
34528
  const valueRef = (0, import_react.useRef)(value);
34359
34529
  const cursorRef = (0, import_react.useRef)(cursor);
34530
+ const pendingPastesRef = (0, import_react.useRef)(new Map());
34531
+ const ctrlCArmedRef = (0, import_react.useRef)(false);
34360
34532
  valueRef.current = value;
34361
34533
  cursorRef.current = cursor;
34362
34534
  const [draftImages, setDraftImages] = (0, import_react.useState)([]);
@@ -34400,6 +34572,7 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34400
34572
  (0, import_react.useEffect)(() => {
34401
34573
  if (historyFill === void 0) return;
34402
34574
  const safe = sanitizeDraftText(historyFill.text);
34575
+ pendingPastesRef.current.clear();
34403
34576
  draftImagesRef.current = [];
34404
34577
  setDraftImages([]);
34405
34578
  draftFilesRef.current = [];
@@ -34436,6 +34609,9 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34436
34609
  return next.length === current.length ? current : next;
34437
34610
  });
34438
34611
  }, [value]);
34612
+ (0, import_react.useEffect)(() => {
34613
+ for (const marker of pendingPastesRef.current.keys()) if (!value.includes(marker)) pendingPastesRef.current.delete(marker);
34614
+ }, [value]);
34439
34615
  (0, import_react.useEffect)(() => {
34440
34616
  if (stdin === void 0) return;
34441
34617
  const originalRead = stdin.read.bind(stdin);
@@ -34481,11 +34657,12 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34481
34657
  const uniqueImageMarker = (name, source, reserved = [], kind = "image") => {
34482
34658
  const safeName = singleLineText(sanitizeDraftText(name));
34483
34659
  const label = kind === "file" ? "file" : "image";
34484
- let marker = source === "mention" ? `@${safeName}` : `[${label}: ${safeName}]`;
34660
+ // dscode-image-marker-v1
34661
+ let marker = source === "mention" ? `@${safeName}` : kind === "image" ? "[Image 1]" : `[${label}: ${safeName}]`;
34485
34662
  let suffix = 2;
34486
34663
  const taken = (candidate) => valueRef.current.includes(candidate) || draftImagesRef.current.some((image) => image.marker === candidate) || draftFilesRef.current.some((file) => file.marker === candidate) || reserved.includes(candidate);
34487
34664
  while (taken(marker)) {
34488
- marker = source === "mention" ? `@${safeName} (${suffix})` : `[${label}: ${safeName} ${suffix}]`;
34665
+ marker = source === "mention" ? `@${safeName} (${suffix})` : kind === "image" ? `[Image ${suffix}]` : `[${label}: ${safeName} ${suffix}]`;
34489
34666
  suffix += 1;
34490
34667
  }
34491
34668
  return marker;
@@ -34508,9 +34685,7 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34508
34685
  * durable image blocks, files ride the 0.1.5 file blocks, and both register
34509
34686
  * visible draft markers anchored at the drop point.
34510
34687
  */
34511
- const insertDroppedAttachments = (imagePaths, filePaths) => {
34512
- const originalValue = valueRef.current;
34513
- const originalCursor = cursorRef.current;
34688
+ const insertDroppedAttachments = (imagePaths, filePaths, originalValue = valueRef.current, originalCursor = cursorRef.current) => {
34514
34689
  const total = imagePaths.length + filePaths.length;
34515
34690
  if (total === 0) return;
34516
34691
  notify(`checking ${total} attachment${total === 1 ? "" : "s"}…`);
@@ -34695,6 +34870,7 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34695
34870
  };
34696
34871
  /** Apply one editor edit: draft, cursor, kill buffer, menu reset. */
34697
34872
  const applyEdit = (edit) => {
34873
+ edit = pasteAtomicEdit(valueRef.current, edit, pendingPastesRef.current);
34698
34874
  if (edit.killed !== void 0 && edit.killed !== "") killRef.current = edit.killed;
34699
34875
  valueRef.current = edit.value;
34700
34876
  cursorRef.current = edit.cursor;
@@ -34709,6 +34885,7 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34709
34885
  const moveCursorTo = (next) => {
34710
34886
  resetCursorBlink();
34711
34887
  if (next === cursorRef.current) return;
34888
+ next = pasteCursorEdge(valueRef.current, cursorRef.current, next, pendingPastesRef.current);
34712
34889
  cursorRef.current = next;
34713
34890
  setCursor(next);
34714
34891
  preferredColumnRef.current = null;
@@ -34719,23 +34896,24 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34719
34896
  let nextCursor = cursorRef.current;
34720
34897
  for (const token of tokens) {
34721
34898
  if (token.kind === "text") {
34722
- const edit = insertText(nextValue, nextCursor, token.text);
34899
+ const edit = pasteAtomicEdit(nextValue, insertText(nextValue, nextCursor, collapseLargePaste(token.text, nextValue, pendingPastesRef.current)), pendingPastesRef.current);
34723
34900
  nextValue = edit.value;
34724
34901
  nextCursor = edit.cursor;
34725
34902
  continue;
34726
34903
  }
34727
34904
  if (token.kind === "home") {
34728
- nextCursor = moveToLineStart(nextValue, nextCursor, false);
34905
+ nextCursor = pasteCursorEdge(nextValue, nextCursor, moveToLineStart(nextValue, nextCursor, false), pendingPastesRef.current);
34729
34906
  continue;
34730
34907
  }
34731
34908
  if (token.kind === "end") {
34732
- nextCursor = moveToLineEnd(nextValue, nextCursor, false);
34909
+ nextCursor = pasteCursorEdge(nextValue, nextCursor, moveToLineEnd(nextValue, nextCursor, false), pendingPastesRef.current);
34733
34910
  continue;
34734
34911
  }
34735
34912
  const edit = token.kind === "delete-backward" ? deleteBackward(nextValue, nextCursor) : token.kind === "delete-word-backward" ? deleteWordBackward(nextValue, nextCursor) : token.kind === "delete-forward" ? deleteForward(nextValue, nextCursor) : deleteWordForward(nextValue, nextCursor);
34736
- if (edit.killed !== void 0 && edit.killed !== "") killRef.current = edit.killed;
34737
- nextValue = edit.value;
34738
- nextCursor = edit.cursor;
34913
+ const atomic = pasteAtomicEdit(nextValue, edit, pendingPastesRef.current);
34914
+ if (atomic.killed !== void 0 && atomic.killed !== "") killRef.current = atomic.killed;
34915
+ nextValue = atomic.value;
34916
+ nextCursor = atomic.cursor;
34739
34917
  }
34740
34918
  valueRef.current = nextValue;
34741
34919
  cursorRef.current = nextCursor;
@@ -34762,8 +34940,9 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34762
34940
  const preferred = preferredColumnRef.current ?? caretSite(model, currentCursor).column;
34763
34941
  const next = moveCursorVertically(model, currentCursor, preferred, direction);
34764
34942
  if (next !== currentCursor) {
34765
- cursorRef.current = next;
34766
- setCursor(next);
34943
+ const edge = pasteCursorEdge(currentValue, currentCursor, next, pendingPastesRef.current);
34944
+ cursorRef.current = edge;
34945
+ setCursor(edge);
34767
34946
  resetCursorBlink();
34768
34947
  preferredColumnRef.current = preferred;
34769
34948
  return;
@@ -34784,10 +34963,29 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34784
34963
  resetCursorBlink();
34785
34964
  };
34786
34965
  useStableInput((input, key) => {
34787
- if (!active) return;
34788
- const liveValue = valueRef.current;
34789
- const liveCursor = cursorRef.current;
34790
- if (preparingImages) {
34966
+ if (!active && !(key.ctrl && input === "c")) { ctrlCArmedRef.current = false; return; }
34967
+ const liveValue = valueRef.current;
34968
+ const liveCursor = cursorRef.current;
34969
+ if (key.ctrl && input === "c") {
34970
+ const repeated = ctrlCArmedRef.current;
34971
+ const action = ctrlCAction(ctrlCArmedRef.current, { interrupt, busy, preparingImages, active: active && deleteConfirm === void 0, hasDraft: liveValue !== "" });
34972
+ ctrlCArmedRef.current = action !== "quit";
34973
+ if (action === "quit") quit(repeated);
34974
+ else if (action === "cancel-prepare") cancelImageSubmission();
34975
+ else if (action === "wait" && deleteConfirm !== void 0) cancelDelete();
34976
+ else if (action === "clear-draft") {
34977
+ pendingPastesRef.current.clear();
34978
+ valueRef.current = ""; cursorRef.current = 0;
34979
+ setValue(""); setCursor(0); resetCursorBlink();
34980
+ draftImagesRef.current = []; setDraftImages([]);
34981
+ draftFilesRef.current = []; setDraftFiles([]);
34982
+ setCompletionIndex(0); setDismissedMenuValue(void 0);
34983
+ }
34984
+ return;
34985
+ }
34986
+ ctrlCArmedRef.current = false;
34987
+ if (key.pageUp || key.pageDown) { onTranscriptScroll?.(key.pageUp ? 1 : -1, true); return; }
34988
+ if (preparingImages) {
34791
34989
  if (key.escape || key.ctrl && input === "c") cancelImageSubmission();
34792
34990
  return;
34793
34991
  }
@@ -34810,27 +35008,16 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34810
35008
  toggleReasoning();
34811
35009
  return;
34812
35010
  }
35011
+ if ((key.ctrl || key.meta) && input === "v") {
35012
+ const atValue = liveValue, atCursor = liveCursor;
35013
+ notify("reading clipboard image…");
35014
+ readClipboardImage().then(path => insertDroppedAttachments([path], [], atValue, atCursor), reason => notify(reason instanceof Error ? reason.message : String(reason), "warning"));
35015
+ return;
35016
+ }
34813
35017
  if (key.ctrl && input === "o") {
34814
35018
  openVerbose();
34815
35019
  return;
34816
35020
  }
34817
- if (key.ctrl && input === "c") {
34818
- if (busy) interrupt();
34819
- else if (liveValue !== "") {
34820
- valueRef.current = "";
34821
- cursorRef.current = 0;
34822
- setValue("");
34823
- setCursor(0);
34824
- resetCursorBlink();
34825
- draftImagesRef.current = [];
34826
- setDraftImages([]);
34827
- draftFilesRef.current = [];
34828
- setDraftFiles([]);
34829
- setCompletionIndex(0);
34830
- setDismissedMenuValue(void 0);
34831
- } else quit();
34832
- return;
34833
- }
34834
35021
  if (key.ctrl && input === "d") {
34835
35022
  if (liveValue !== "") {
34836
35023
  applyEdit(deleteForward(liveValue, liveCursor));
@@ -34873,8 +35060,9 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34873
35060
  return;
34874
35061
  }
34875
35062
  }
34876
- const trimmed = liveValue.trim();
34877
- const text = submissionPayload(liveValue);
35063
+ const expandedValue = expandLargePastes(liveValue, pendingPastesRef.current);
35064
+ const trimmed = expandedValue.trim();
35065
+ const text = submissionPayload(expandedValue);
34878
35066
  if (/^\/email(?:\s|$)/.test(trimmed)) {
34879
35067
  if (trimmed !== "/email") { notify("Use /email to open the inbox.", "warning"); return; }
34880
35068
  valueRef.current = ""; cursorRef.current = 0;
@@ -34906,6 +35094,7 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34906
35094
  if (controller.signal.aborted || prepareEpochRef.current !== epoch) return;
34907
35095
  prepareAbortRef.current = void 0;
34908
35096
  setPreparingImages(false);
35097
+ pendingPastesRef.current.clear();
34909
35098
  valueRef.current = "";
34910
35099
  cursorRef.current = 0;
34911
35100
  setValue("");
@@ -34933,6 +35122,7 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34933
35122
  });
34934
35123
  return;
34935
35124
  }
35125
+ pendingPastesRef.current.clear();
34936
35126
  valueRef.current = "";
34937
35127
  cursorRef.current = 0;
34938
35128
  setValue("");
@@ -34979,7 +35169,8 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
34979
35169
  return;
34980
35170
  }
34981
35171
  if (text === "/review" || text.startsWith("/review ")) {
34982
- reviewChanges(text.slice(7));
35172
+ // dscode-review-command-v1
35173
+ dispatch(text);
34983
35174
  return;
34984
35175
  }
34985
35176
  if (text === "/model" || text.startsWith("/model ")) {
@@ -35176,12 +35367,12 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
35176
35367
  clearTimeout(timer);
35177
35368
  pasteBracketCancelRef.current = void 0;
35178
35369
  };
35179
- text = text.replaceAll(PASTE_START_MARKER, "");
35370
+ text = text.replaceAll(`\x1b${PASTE_START_MARKER}`, "").replaceAll(PASTE_START_MARKER, "");
35180
35371
  }
35181
35372
  if (text.includes("[201~")) {
35182
35373
  pasteBracketRef.current = false;
35183
35374
  pasteBracketCancelRef.current?.();
35184
- text = text.replaceAll(PASTE_END_MARKER, "");
35375
+ text = text.replaceAll(`\x1b${PASTE_END_MARKER}`, "").replaceAll(PASTE_END_MARKER, "");
35185
35376
  }
35186
35377
  if (text === "") return;
35187
35378
  if (text.length > 1) {
@@ -35191,9 +35382,9 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
35191
35382
  return;
35192
35383
  }
35193
35384
  }
35194
- applyEdit(insertText(valueRef.current, cursorRef.current, text));
35385
+ applyEdit(insertText(valueRef.current, cursorRef.current, collapseLargePaste(text, valueRef.current, pendingPastesRef.current)));
35195
35386
  }
35196
- }, active);
35387
+ }, true);
35197
35388
  const waveKey = waveTier !== null && waveStyle !== null ? `${waveTier}:${waveStyle}` : null;
35198
35389
  const [wavePlayedKey, setWavePlayedKey] = (0, import_react.useState)(null);
35199
35390
  (0, import_react.useEffect)(() => {
@@ -35782,19 +35973,46 @@ function App(props) {
35782
35973
  const composerEditorCap = composerMaxRows(terminalRows);
35783
35974
  const transcriptVisible = !emailOpen && !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !jobsOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !todosOpen && !verboseOpen && diffView === void 0 && !approvalPending && !questionPending;
35784
35975
  const welcomeFull = terminalRows >= 24 && terminalColumns >= 64;
35785
- const welcomeChromeRows = welcomeFull ? 22 : terminalRows >= 10 ? 13 : 10;
35786
- const settledBudget = transcriptVisible ? Math.max(0, terminalRows - welcomeChromeRows - composerGutterRows - (composerRows - 1) - menuRows) : 0;
35787
- const settledViewportRows = busy || view.streaming !== "" ? Math.floor(settledBudget / 3) : settledBudget;
35788
- const renderedSettled = (0, import_react.useMemo)(() => visibleSettledLines(view.entries, settled, settledViewportRows, terminalColumns, showReasoning, settledEntryLines), [settled, view.entries[settled - 1], settledViewportRows, terminalColumns, showReasoning]);
35976
+ const welcomeMaxRows = welcomeFull ? 13 : terminalRows >= 10 ? 4 : 1;
35977
+ // The nine fixed rows belong to the composer, footer and their gutters.
35978
+ const transcriptCapacity = transcriptVisible ? Math.max(0, terminalRows - 9 - composerGutterRows - (composerRows - 1) - menuRows) : 0;
35979
+ const streamingActive = view.streaming !== "";
35980
+ const deepDivingVisible = busy;
35981
+ const allLiveLines = (0, import_react.useMemo)(() => view.entries.slice(settled).flatMap((entry) => dscodeChatLines(entry, Math.max(1, terminalColumns - 2))), [
35982
+ view.entries,
35983
+ settled,
35984
+ terminalColumns,
35985
+ showReasoning
35986
+ ]);
35987
+ const allSettledLines = (0, import_react.useMemo)(() => visibleSettledLines(view.entries, settled, Infinity, terminalColumns, showReasoning, settledEntryLines), [settled, view.entries[settled - 1], terminalColumns, showReasoning]);
35988
+ const settledTail = allSettledLines.slice(-transcriptCapacity);
35989
+ const streamingDemand = streamingActive ? textLines(view.streaming.slice(-Math.max(1, terminalColumns * transcriptCapacity)), Math.max(10, terminalColumns - 2)).length : 0;
35990
+ const demand = settledTail.length + allLiveLines.length + streamingDemand + (busy ? 1 : 0) + (agentRows.length > 0 ? 1 : 0);
35991
+ const welcomeRows = welcomeVisibleRows(transcriptCapacity, welcomeMaxRows, demand, transcriptVisible);
35992
+ const settledBudget = transcriptVisible ? Math.max(0, transcriptCapacity - welcomeRows) : 0;
35993
+ const settledViewportRows = busy || streamingActive ? Math.floor(settledBudget / 3) : settledBudget;
35994
+ const [scrollOffset, setScrollOffset] = (0, import_react.useState)(0);
35995
+ const previousHistory = (0, import_react.useRef)({ sessionKey: props.sessionKey, count: allSettledLines.length });
35996
+ const sameHistory = previousHistory.current.sessionKey === props.sessionKey;
35997
+ const growth = sameHistory ? Math.max(0, allSettledLines.length - previousHistory.current.count) : 0;
35998
+ const maxScrollOffset = Math.max(0, allSettledLines.length - settledViewportRows);
35999
+ const effectiveScrollOffset = sameHistory ? Math.min(maxScrollOffset, scrollOffset === 0 ? 0 : scrollOffset + growth) : 0;
36000
+ (0, import_react.useEffect)(() => {
36001
+ previousHistory.current = { sessionKey: props.sessionKey, count: allSettledLines.length };
36002
+ if (scrollOffset !== effectiveScrollOffset) setScrollOffset(effectiveScrollOffset);
36003
+ }, [props.sessionKey, allSettledLines.length, effectiveScrollOffset]);
36004
+ const scrollTranscript = (direction, page = false) => {
36005
+ if (!transcriptVisible || settledViewportRows <= 0) return;
36006
+ const step = page ? Math.max(1, settledViewportRows - 1) : 3;
36007
+ setScrollOffset(current => Math.max(0, Math.min(maxScrollOffset, (current === scrollOffset ? effectiveScrollOffset : current) + direction * step)));
36008
+ };
36009
+ (0, import_react.useEffect)(() => {
36010
+ const listener = direction => scrollTranscript(direction);
36011
+ dscodeWheelListeners.add(listener);
36012
+ return () => dscodeWheelListeners.delete(listener);
36013
+ });
36014
+ const renderedSettled = transcriptWindow(allSettledLines, settledViewportRows, effectiveScrollOffset);
35789
36015
  const dynamicRows = Math.max(0, settledBudget - settledViewportRows);
35790
- const streamingActive = view.streaming !== "";
35791
- const deepDivingVisible = busy;
35792
- const allLiveLines = (0, import_react.useMemo)(() => view.entries.slice(settled).flatMap((entry) => dscodeChatLines(entry, Math.max(1, terminalColumns - 2))), [
35793
- view.entries,
35794
- settled,
35795
- terminalColumns,
35796
- showReasoning
35797
- ]);
35798
36016
  const liveBudget = dynamicRows === 0 ? 0 : busy || streamingActive ? Math.max(1, Math.floor(dynamicRows / 3)) : Math.max(0, dynamicRows - (deepDivingVisible ? 1 : 0));
35799
36017
  const visibleLiveLines = liveBudget === 0 ? [] : allLiveLines.slice(-liveBudget);
35800
36018
  const streamRows = Math.max(1, dynamicRows - visibleLiveLines.length);
@@ -36062,7 +36280,8 @@ function App(props) {
36062
36280
  });
36063
36281
  }
36064
36282
  return (0, import_react.createElement)(Box, { flexDirection: "column", height: Math.max(1, terminalRows - 1) },
36065
- !emailOpen && terminalRows >= 10 ? (0, import_react.createElement)(Header, { cwd: props.workspaceRoot ?? props.cwd, model: modelLabel, effort: effortLabel }) : (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright), bold: true }, "DSCODE"),
36283
+ welcomeRows > 0 ? (0, import_react.createElement)(Box, { height: welcomeRows, overflowY: "hidden", flexDirection: "column", justifyContent: "flex-end", flexShrink: 0 },
36284
+ (0, import_react.createElement)(Box, { flexShrink: 0 }, !emailOpen && terminalRows >= 10 ? (0, import_react.createElement)(Header, { cwd: props.workspaceRoot ?? props.cwd, model: modelLabel, effort: effortLabel }) : (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright), bold: true }, "DSCODE"))) : void 0,
36066
36285
  emailOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(DscodeEmailPanel, {
36067
36286
  gmail, imap, columns: terminalColumns, rows: Math.max(3, terminalRows - 8 - composerGutterRows - composerRows),
36068
36287
  close: () => setEmailOpen(false),
@@ -36355,7 +36574,8 @@ function App(props) {
36355
36574
  waveStyle,
36356
36575
  maxRows: composerEditorCap,
36357
36576
  onEditorRows: handleEditorRows,
36358
- onMenuRows: handleMenuRows
36577
+ onMenuRows: handleMenuRows,
36578
+ onTranscriptScroll: scrollTranscript
36359
36579
  }), (0, import_react.createElement)(StatusLine, {
36360
36580
  facts: {
36361
36581
  fullSessionId: props.sessionKey,
@@ -36588,7 +36808,7 @@ function createSplitStdin(source) {
36588
36808
  stalePasteTimer.unref?.();
36589
36809
  };
36590
36810
  const onChunk = (chunk) => {
36591
- for (const unit of splitter.push(String(chunk))) stream.write(unit);
36811
+ for (const unit of splitter.push(String(chunk))) if (!dscodeHandleMouseUnit(unit)) stream.write(unit);
36592
36812
  if (splitter.openPaste()) armStalePasteTimer();
36593
36813
  else disarmStalePasteTimer();
36594
36814
  };
@@ -36633,7 +36853,7 @@ const internals = {
36633
36853
  if (process.stdin.isTTY === true) process.stdin.setRawMode?.(true);
36634
36854
  try {
36635
36855
  if (process.stdout.isTTY === true) process.stdout.write("\x1B[r\x1B[0m\x1B[H\x1B[2J\x1B[3J\x1B[H");
36636
- process.stdout.write((keyboardEnhanced ? KEYBOARD_ENHANCE_ENABLE : "") + BRACKETED_PASTE_ENABLE + (focusReporting ? TERMINAL_FOCUS_REPORT_ENABLE : ""));
36856
+ process.stdout.write((keyboardEnhanced ? KEYBOARD_ENHANCE_ENABLE : "") + BRACKETED_PASTE_ENABLE + (focusReporting ? TERMINAL_FOCUS_REPORT_ENABLE : "") + (process.stdout.isTTY === true ? DSCODE_MOUSE_ENABLE : ""));
36637
36857
  const tuiStdin = createSplitStdin(process.stdin);
36638
36858
  const instance = render(element, {
36639
36859
  exitOnCtrlC: false,
@@ -36646,7 +36866,7 @@ const internals = {
36646
36866
  },
36647
36867
  unmount() {
36648
36868
  try {
36649
- process.stdout.write((keyboardEnhanced ? KEYBOARD_ENHANCE_DISABLE : "") + BRACKETED_PASTE_DISABLE + (focusReporting ? TERMINAL_FOCUS_REPORT_DISABLE : ""));
36869
+ process.stdout.write((keyboardEnhanced ? KEYBOARD_ENHANCE_DISABLE : "") + BRACKETED_PASTE_DISABLE + (focusReporting ? TERMINAL_FOCUS_REPORT_DISABLE : "") + (process.stdout.isTTY === true ? DSCODE_MOUSE_DISABLE : ""));
36650
36870
  } finally {
36651
36871
  try {
36652
36872
  instance.unmount();
@@ -36658,6 +36878,7 @@ const internals = {
36658
36878
  }
36659
36879
  };
36660
36880
  } catch (error) {
36881
+ if (process.stdout.isTTY === true) process.stdout.write(DSCODE_MOUSE_DISABLE);
36661
36882
  if (process.stdin.isTTY === true) process.stdin.setRawMode?.(false);
36662
36883
  throw error;
36663
36884
  }
@@ -38724,7 +38945,7 @@ async function run(ctx, startup, io) {
38724
38945
  };
38725
38946
  const mountRef = {};
38726
38947
  let quitting = false;
38727
- const quit = () => {
38948
+ const quit = (fast = false) => {
38728
38949
  if (quitting) return;
38729
38950
  quitting = true;
38730
38951
  switchQueue.cancel();
@@ -38744,6 +38965,7 @@ async function run(ctx, startup, io) {
38744
38965
  } catch (error) {
38745
38966
  report("unmount", error);
38746
38967
  }
38968
+ if (fast) setTimeout(() => process.exit(0), 250);
38747
38969
  runQuitSequence([
38748
38970
  ...currentSession === void 0 || currentActive === void 0 ? [] : [{
38749
38971
  name: "flush",
@@ -39451,6 +39673,7 @@ async function run(ctx, startup, io) {
39451
39673
  openAuthorizationUrl,
39452
39674
  copyTextValue: copyText,
39453
39675
  loadMentions: (query, signal) => mentions.candidates(query, signal),
39676
+ readClipboardImage: dscodeReadClipboardImage,
39454
39677
  inspectImages: (paths) => inspectImagePaths(paths, ctx.get("attachments"), session?.header.cwd ?? cwd),
39455
39678
  prepareImages: (paths, signal) => saveImagePaths(paths, ctx.get("attachments"), signal),
39456
39679
  inspectFiles: (paths) => inspectFilePaths(paths, ctx.get("attachments"), session?.header.cwd ?? cwd),