@xingjisoft/neutrino 0.1.6 → 0.2.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.
package/web/turns.js CHANGED
@@ -2,8 +2,9 @@
2
2
  //
3
3
  // A conversation is a sequence of turns: each one starts at a user message and
4
4
  // runs until the next one. Once the agent has finished a turn, the browser folds
5
- // it into a single summary line so scrolling back stays manageable, while the
6
- // complete transcript is still rendered on demand (click the summary).
5
+ // away the work it took — the prompt body, the thinking and the tool calls — and
6
+ // keeps the final reply on screen, so a finished exchange always ends on its
7
+ // answer. The complete round is still one click away.
7
8
  //
8
9
  // The thresholds mirror TURN_FOLD_MIN_BLOCKS / TURN_FOLD_MIN_CHARS in
9
10
  // src/screens/chat.ts, so the terminal and the browser fold the same turns.
@@ -22,25 +23,38 @@ export function turnRanges(messages) {
22
23
  return turns;
23
24
  }
24
25
 
25
- /** True when folding this turn saves more room than it costs. */
26
+ /** Index of the turn's reply — its last assistant message (-1 when it has none). */
27
+ export function turnAnswerIndex(messages) {
28
+ for (let i = messages.length - 1; i >= 0; i--) {
29
+ if (messages[i].role === "assistant") return i;
30
+ }
31
+ return -1;
32
+ }
33
+
34
+ /**
35
+ * True when folding the work behind this turn saves more room than it costs.
36
+ * The reply is never folded away, so it is not counted: a long answer on its own
37
+ * must never collapse the turn that carries it.
38
+ */
26
39
  export function turnFolds(messages) {
40
+ const reply = turnAnswerIndex(messages);
27
41
  let blocks = 0;
28
42
  let chars = 0;
29
- for (const message of messages) {
30
- if (message.role !== "user" && message.role !== "assistant") continue;
43
+ messages.forEach((message, index) => {
44
+ if (index === reply) return;
45
+ if (message.role !== "user" && message.role !== "assistant") return;
31
46
  blocks++;
32
47
  chars += String(message.content ?? "").length;
33
- }
48
+ });
34
49
  return blocks >= TURN_FOLD_MIN_BLOCKS || chars >= TURN_FOLD_MIN_CHARS;
35
50
  }
36
51
 
37
52
  /**
38
- * The first line of the first (or, with `last`, the last) message in `role`.
39
- * One line only: the preview is a signpost, the full text is a click away.
53
+ * The first line of the first message in `role`. One line only: the header is a
54
+ * signpost, and the full text is one click away.
40
55
  */
41
- export function turnPreview(messages, role, last = false) {
42
- const ordered = last ? [...messages].reverse() : messages;
43
- for (const message of ordered) {
56
+ export function turnPreview(messages, role) {
57
+ for (const message of messages) {
44
58
  if (message.role !== role) continue;
45
59
  const line = String(message.content ?? "")
46
60
  .split(/\r?\n/)