@oh-my-pi/pi-coding-agent 15.13.1 → 15.13.3
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/CHANGELOG.md +47 -0
- package/dist/cli.js +1057 -289
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/models-config-schema.d.ts +3 -0
- package/dist/types/config/models-config.d.ts +3 -0
- package/dist/types/config/settings-schema.d.ts +97 -0
- package/dist/types/edit/hashline/block-resolver.d.ts +1 -1
- package/dist/types/edit/index.d.ts +2 -0
- package/dist/types/eval/js/context-manager.d.ts +15 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +4 -4
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +2 -1
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/session-dump-format.d.ts +2 -1
- package/dist/types/session/unexpected-stop-classifier.d.ts +13 -0
- package/dist/types/stt/asr-client.d.ts +1 -1
- package/dist/types/system-prompt.d.ts +11 -0
- package/dist/types/tiny/title-client.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +2 -0
- package/dist/types/tools/ast-edit.d.ts +2 -0
- package/dist/types/tools/ast-grep.d.ts +2 -0
- package/dist/types/tools/browser.d.ts +2 -0
- package/dist/types/tools/debug.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +2 -0
- package/dist/types/tools/find.d.ts +2 -0
- package/dist/types/tools/inspect-image.d.ts +2 -1
- package/dist/types/tools/irc.d.ts +2 -0
- package/dist/types/tools/job.d.ts +1 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo.d.ts +2 -0
- package/dist/types/tts/tts-client.d.ts +1 -1
- package/dist/types/tui/tree-list.d.ts +1 -0
- package/dist/types/utils/thinking-display.d.ts +1 -17
- package/package.json +12 -12
- package/src/cli.ts +25 -12
- package/src/config/model-registry.ts +16 -2
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/models-config.ts +1 -0
- package/src/config/settings-schema.ts +78 -0
- package/src/edit/hashline/block-resolver.ts +1 -1
- package/src/edit/hashline/execute.ts +1 -6
- package/src/edit/index.ts +48 -0
- package/src/eval/__tests__/agent-bridge.test.ts +106 -46
- package/src/eval/__tests__/js-context-manager.test.ts +53 -3
- package/src/eval/js/context-manager.ts +132 -29
- package/src/eval/js/worker-core.ts +1 -1
- package/src/eval/js/worker-entry.ts +7 -0
- package/src/export/html/template.js +18 -22
- package/src/internal-urls/docs-index.generated.ts +12 -3
- package/src/main.ts +15 -5
- package/src/modes/acp/acp-agent.ts +2 -2
- package/src/modes/acp/acp-event-mapper.ts +2 -2
- package/src/modes/components/agent-hub.ts +31 -7
- package/src/modes/components/assistant-message.ts +24 -15
- package/src/modes/components/snapcompact-shape-preview-doc.md +2 -2
- package/src/modes/components/snapcompact-shape-preview.ts +2 -2
- package/src/modes/components/tree-selector.ts +3 -2
- package/src/modes/components/welcome.ts +14 -4
- package/src/modes/controllers/event-controller.ts +3 -3
- package/src/modes/controllers/input-controller.ts +28 -39
- package/src/modes/controllers/streaming-reveal.ts +4 -4
- package/src/modes/interactive-mode.ts +2 -0
- package/src/modes/rpc/rpc-mode.ts +1 -0
- package/src/modes/rpc/rpc-types.ts +2 -2
- package/src/modes/types.ts +6 -0
- package/src/modes/utils/ui-helpers.ts +3 -3
- package/src/prompts/agents/oracle.md +0 -1
- package/src/prompts/agents/reviewer.md +0 -1
- package/src/prompts/system/system-prompt.md +17 -21
- package/src/prompts/system/unexpected-stop-classifier.md +17 -0
- package/src/prompts/system/unexpected-stop-retry.md +4 -0
- package/src/prompts/tools/ask.md +0 -8
- package/src/prompts/tools/ast-edit.md +0 -15
- package/src/prompts/tools/ast-grep.md +0 -13
- package/src/prompts/tools/browser.md +0 -21
- package/src/prompts/tools/debug.md +0 -13
- package/src/prompts/tools/eval.md +0 -9
- package/src/prompts/tools/find.md +0 -13
- package/src/prompts/tools/inspect-image.md +0 -9
- package/src/prompts/tools/irc.md +0 -15
- package/src/prompts/tools/patch.md +0 -13
- package/src/prompts/tools/ssh.md +0 -9
- package/src/prompts/tools/todo.md +1 -19
- package/src/sdk.ts +19 -0
- package/src/session/agent-session.ts +289 -29
- package/src/session/session-dump-format.ts +17 -49
- package/src/session/unexpected-stop-classifier.ts +129 -0
- package/src/stt/asr-client.ts +1 -1
- package/src/system-prompt.ts +31 -0
- package/src/tiny/title-client.ts +1 -1
- package/src/tools/ask.ts +41 -0
- package/src/tools/ast-edit.ts +46 -0
- package/src/tools/ast-grep.ts +24 -0
- package/src/tools/browser/tab-supervisor.ts +1 -1
- package/src/tools/browser/tab-worker-entry.ts +12 -4
- package/src/tools/browser.ts +52 -0
- package/src/tools/debug.ts +17 -0
- package/src/tools/eval.ts +20 -1
- package/src/tools/find.ts +24 -0
- package/src/tools/inspect-image.ts +27 -1
- package/src/tools/irc.ts +41 -0
- package/src/tools/job.ts +1 -0
- package/src/tools/ssh.ts +16 -0
- package/src/tools/todo.ts +82 -3
- package/src/tts/tts-client.ts +1 -1
- package/src/tui/tree-list.ts +68 -19
- package/src/utils/thinking-display.ts +8 -34
package/src/tools/irc.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
13
|
+
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
13
14
|
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
14
15
|
import { formatAge, formatDuration, prompt } from "@oh-my-pi/pi-utils";
|
|
15
16
|
import { z } from "zod/v4";
|
|
@@ -96,6 +97,46 @@ export class IrcTool implements AgentTool<typeof ircSchema, IrcDetails> {
|
|
|
96
97
|
readonly description: string;
|
|
97
98
|
readonly parameters = ircSchema;
|
|
98
99
|
readonly strict = true;
|
|
100
|
+
|
|
101
|
+
readonly examples: readonly ToolExample<z.input<typeof ircSchema>>[] = [
|
|
102
|
+
{
|
|
103
|
+
caption: "List peers",
|
|
104
|
+
call: { op: "list" },
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
caption: "Fire-and-forget DM — same send wakes idle/parked peers",
|
|
108
|
+
call: {
|
|
109
|
+
op: "send",
|
|
110
|
+
to: "AuthLoader",
|
|
111
|
+
message: "Still touching src/server/auth.ts? I need to add a 401 path.",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
caption: "Round-trip when you cannot proceed without the answer",
|
|
116
|
+
call: {
|
|
117
|
+
op: "send",
|
|
118
|
+
to: "Main",
|
|
119
|
+
message: "JWT or session cookies for the auth flow?",
|
|
120
|
+
await: true,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
caption: "Block until a specific peer answers",
|
|
125
|
+
call: { op: "wait", from: "AuthLoader", timeoutMs: 60000 },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
caption: "Drain pending messages",
|
|
129
|
+
call: { op: "inbox" },
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
caption: "Broadcast to live peers (no replies expected)",
|
|
133
|
+
call: {
|
|
134
|
+
op: "send",
|
|
135
|
+
to: "all",
|
|
136
|
+
message: "About to refactor src/server/middleware/*. Anyone already in there?",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
];
|
|
99
140
|
readonly loadMode = "discoverable";
|
|
100
141
|
constructor(private readonly session: ToolSession) {
|
|
101
142
|
this.description = prompt.render(ircDescription);
|
package/src/tools/job.ts
CHANGED
|
@@ -87,6 +87,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
87
87
|
readonly description: string;
|
|
88
88
|
readonly parameters = jobSchema;
|
|
89
89
|
readonly strict = true;
|
|
90
|
+
readonly interruptible = true;
|
|
90
91
|
readonly loadMode = "discoverable";
|
|
91
92
|
constructor(private readonly session: ToolSession) {
|
|
92
93
|
this.description = prompt.render(jobDescription);
|
package/src/tools/ssh.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { z } from "zod/v4";
|
|
@@ -135,6 +136,21 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
|
|
|
135
136
|
readonly concurrency = "exclusive";
|
|
136
137
|
readonly strict = true;
|
|
137
138
|
|
|
139
|
+
readonly examples: readonly ToolExample<z.input<typeof sshSchema>>[] = [
|
|
140
|
+
{
|
|
141
|
+
caption: "List files: Linux (on server1 (10.0.0.1) | linux/bash)",
|
|
142
|
+
call: { host: "server1", command: "ls -la /home/user" },
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
caption: "Show running processes: Windows cmd (on winbox (192.168.1.5) | windows/cmd)",
|
|
146
|
+
call: { host: "winbox", command: "tasklist /v" },
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
caption: "Get system info: macOS (on macbook (10.0.0.20) | macos/zsh)",
|
|
150
|
+
call: { host: "macbook", command: "uname -a && sw_vers" },
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
|
|
138
154
|
readonly #allowedHosts: Set<string>;
|
|
139
155
|
|
|
140
156
|
constructor(
|
package/src/tools/todo.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
4
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
5
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
@@ -270,8 +271,20 @@ function getTaskTargets(phases: TodoPhase[], entry: TodoOpEntryValue, errors: st
|
|
|
270
271
|
return phases.flatMap(phase => phase.tasks);
|
|
271
272
|
}
|
|
272
273
|
|
|
274
|
+
/** Phase name for `init` given a flat `items` list with no explicit `phase`. */
|
|
275
|
+
const DEFAULT_INIT_PHASE = "Tasks";
|
|
276
|
+
|
|
273
277
|
function initPhases(entry: TodoOpEntryValue, errors: string[]): TodoPhase[] {
|
|
274
|
-
|
|
278
|
+
// Models routinely flatten the single-phase init into `{op:"init", items:[...]}`
|
|
279
|
+
// (optionally with a bare `phase`) instead of the canonical
|
|
280
|
+
// `list: [{phase, items}]`. Accept that shape by synthesizing a one-phase list
|
|
281
|
+
// so a common, recoverable mistake isn't a hard error.
|
|
282
|
+
const list =
|
|
283
|
+
entry.list ??
|
|
284
|
+
(entry.items && entry.items.length > 0
|
|
285
|
+
? [{ phase: entry.phase ?? DEFAULT_INIT_PHASE, items: entry.items }]
|
|
286
|
+
: undefined);
|
|
287
|
+
if (!list) {
|
|
275
288
|
errors.push("Missing list for init operation");
|
|
276
289
|
return [];
|
|
277
290
|
}
|
|
@@ -279,7 +292,7 @@ function initPhases(entry: TodoOpEntryValue, errors: string[]): TodoPhase[] {
|
|
|
279
292
|
// (every targeting op resolves the first match), so reject them up front.
|
|
280
293
|
const seenPhases = new Set<string>();
|
|
281
294
|
const seenTasks = new Set<string>();
|
|
282
|
-
for (const listEntry of
|
|
295
|
+
for (const listEntry of list) {
|
|
283
296
|
if (seenPhases.has(listEntry.phase)) {
|
|
284
297
|
errors.push(`Duplicate phase "${listEntry.phase}" in init list`);
|
|
285
298
|
}
|
|
@@ -291,7 +304,7 @@ function initPhases(entry: TodoOpEntryValue, errors: string[]): TodoPhase[] {
|
|
|
291
304
|
seenTasks.add(content);
|
|
292
305
|
}
|
|
293
306
|
}
|
|
294
|
-
return
|
|
307
|
+
return list.map(listEntry => ({
|
|
295
308
|
name: listEntry.phase,
|
|
296
309
|
tasks: listEntry.items.map<TodoItem>(content => ({ content, status: "pending" })),
|
|
297
310
|
}));
|
|
@@ -551,6 +564,71 @@ export class TodoTool implements AgentTool<typeof todoSchema, TodoToolDetails> {
|
|
|
551
564
|
readonly parameters = todoSchema;
|
|
552
565
|
readonly concurrency = "exclusive";
|
|
553
566
|
readonly strict = true;
|
|
567
|
+
|
|
568
|
+
readonly examples: readonly ToolExample<z.input<typeof todoSchema>>[] = [
|
|
569
|
+
{
|
|
570
|
+
caption: "Initial setup (multi-phase)",
|
|
571
|
+
call: {
|
|
572
|
+
ops: [
|
|
573
|
+
{
|
|
574
|
+
op: "init",
|
|
575
|
+
list: [
|
|
576
|
+
{ phase: "Foundation", items: ["Scaffold crate", "Wire workspace"] },
|
|
577
|
+
{ phase: "Auth", items: ["Port credential store", "Wire OAuth providers"] },
|
|
578
|
+
{ phase: "Verification", items: ["Run cargo test"] },
|
|
579
|
+
],
|
|
580
|
+
},
|
|
581
|
+
],
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
caption: "View current state (read-only)",
|
|
586
|
+
call: {
|
|
587
|
+
ops: [{ op: "view" }],
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
caption: "Initial setup (single phase)",
|
|
592
|
+
call: {
|
|
593
|
+
ops: [
|
|
594
|
+
{
|
|
595
|
+
op: "init",
|
|
596
|
+
list: [{ phase: "Implementation", items: ["Apply fix", "Run tests"] }],
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
caption: "Complete one task",
|
|
603
|
+
call: {
|
|
604
|
+
ops: [{ op: "done", task: "Wire workspace" }],
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
caption: "Complete a whole phase",
|
|
609
|
+
call: {
|
|
610
|
+
ops: [{ op: "done", phase: "Auth" }],
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
caption: "Remove all tasks",
|
|
615
|
+
call: {
|
|
616
|
+
ops: [{ op: "rm" }],
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
caption: "Drop one task",
|
|
621
|
+
call: {
|
|
622
|
+
ops: [{ op: "drop", task: "Run cargo test" }],
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
caption: "Append tasks to a phase",
|
|
627
|
+
call: {
|
|
628
|
+
ops: [{ op: "append", phase: "Auth", items: ["Handle retries", "Run tests"] }],
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
];
|
|
554
632
|
readonly loadMode = "discoverable";
|
|
555
633
|
constructor(private readonly session: ToolSession) {
|
|
556
634
|
this.description = prompt.render(todoDescription);
|
|
@@ -839,6 +917,7 @@ export const todoToolRenderer = {
|
|
|
839
917
|
expanded,
|
|
840
918
|
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
841
919
|
itemType: "todo",
|
|
920
|
+
truncateFrom: "start",
|
|
842
921
|
renderItem: todo => formatTodoLine(todo, uiTheme, "", completionKeys, spinnerFrame),
|
|
843
922
|
},
|
|
844
923
|
uiTheme,
|
package/src/tts/tts-client.ts
CHANGED
|
@@ -142,7 +142,7 @@ const SMOKE_TEST_TIMEOUT_MS = 30_000;
|
|
|
142
142
|
* Hidden subcommand on the main CLI that boots the TTS worker in the spawned
|
|
143
143
|
* subprocess. Kept in sync with the dispatch in `cli.ts` (Main-owned).
|
|
144
144
|
*/
|
|
145
|
-
export const TTS_WORKER_ARG = "
|
|
145
|
+
export const TTS_WORKER_ARG = "__omp_worker_tts";
|
|
146
146
|
|
|
147
147
|
function readTinyModelSetting(path: "providers.tinyModelDevice" | "providers.tinyModelDtype"): string | undefined {
|
|
148
148
|
try {
|
package/src/tui/tree-list.ts
CHANGED
|
@@ -17,23 +17,45 @@ export interface TreeListOptions<T> {
|
|
|
17
17
|
*/
|
|
18
18
|
maxCollapsedLines?: number;
|
|
19
19
|
itemType?: string;
|
|
20
|
+
truncateFrom?: "start" | "end";
|
|
20
21
|
/** Called once per item with `isLast: false` during budget calculation;
|
|
21
22
|
* line count MUST NOT vary based on `isLast`. */
|
|
22
23
|
renderItem: (item: T, context: TreeContext) => string | string[];
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function renderTreeList<T>(options: TreeListOptions<T>, theme: Theme): string[] {
|
|
26
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
items,
|
|
29
|
+
expanded = false,
|
|
30
|
+
maxCollapsed = 8,
|
|
31
|
+
maxCollapsedLines,
|
|
32
|
+
itemType = "item",
|
|
33
|
+
truncateFrom = "end",
|
|
34
|
+
renderItem,
|
|
35
|
+
} = options;
|
|
27
36
|
const maxItems = expanded ? items.length : Math.min(items.length, maxCollapsed);
|
|
28
37
|
const linesBudget = !expanded && maxCollapsedLines !== undefined ? maxCollapsedLines : Infinity;
|
|
29
38
|
|
|
39
|
+
const candidateIndices: number[] = [];
|
|
40
|
+
if (truncateFrom === "start") {
|
|
41
|
+
const startCandidateIdx = Math.max(0, items.length - maxItems);
|
|
42
|
+
for (let i = startCandidateIdx; i < items.length; i++) {
|
|
43
|
+
candidateIndices.push(i);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
for (let i = 0; i < maxItems; i++) {
|
|
47
|
+
candidateIndices.push(i);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
// Pre-render each candidate item once.
|
|
31
52
|
// isLast cannot be known at this point (fittingCount is not yet determined);
|
|
32
53
|
// renderItem implementations MUST NOT vary line count based on isLast.
|
|
33
54
|
const preRendered: string[][] = [];
|
|
34
|
-
for (let i = 0; i <
|
|
35
|
-
const
|
|
36
|
-
|
|
55
|
+
for (let i = 0; i < candidateIndices.length; i++) {
|
|
56
|
+
const itemIdx = candidateIndices[i];
|
|
57
|
+
const rendered = renderItem(items[itemIdx], {
|
|
58
|
+
index: itemIdx,
|
|
37
59
|
isLast: false,
|
|
38
60
|
depth: 0,
|
|
39
61
|
theme,
|
|
@@ -43,28 +65,55 @@ export function renderTreeList<T>(options: TreeListOptions<T>, theme: Theme): st
|
|
|
43
65
|
preRendered.push(Array.isArray(rendered) ? rendered : rendered ? [rendered] : []);
|
|
44
66
|
}
|
|
45
67
|
|
|
46
|
-
|
|
47
|
-
let
|
|
68
|
+
let displayedSlice: { start: number; end: number };
|
|
69
|
+
let remaining: number;
|
|
48
70
|
let fittedLineCount = 0;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
|
|
72
|
+
if (truncateFrom === "start") {
|
|
73
|
+
let fittingCount = candidateIndices.length;
|
|
74
|
+
if (linesBudget !== Infinity) {
|
|
75
|
+
fittingCount = 0;
|
|
76
|
+
for (let i = candidateIndices.length - 1; i >= 0; i--) {
|
|
77
|
+
const count = preRendered[i].length;
|
|
78
|
+
const remainingBefore = candidateIndices[i];
|
|
79
|
+
const reservedSummaryLines = remainingBefore > 0 ? 1 : 0;
|
|
80
|
+
if (fittedLineCount + count + reservedSummaryLines > linesBudget) break;
|
|
81
|
+
fittedLineCount += count;
|
|
82
|
+
fittingCount++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const start = candidateIndices.length - fittingCount;
|
|
86
|
+
displayedSlice = { start, end: candidateIndices.length };
|
|
87
|
+
remaining = candidateIndices.length > 0 ? candidateIndices[start] : 0;
|
|
88
|
+
} else {
|
|
89
|
+
let fittingCount = candidateIndices.length;
|
|
90
|
+
if (linesBudget !== Infinity) {
|
|
91
|
+
fittingCount = 0;
|
|
92
|
+
for (let i = 0; i < candidateIndices.length; i++) {
|
|
93
|
+
const count = preRendered[i].length;
|
|
94
|
+
const remainingAfter = items.length - (i + 1);
|
|
95
|
+
const reservedSummaryLines = remainingAfter > 0 ? 1 : 0;
|
|
96
|
+
if (fittedLineCount + count + reservedSummaryLines > linesBudget) break;
|
|
97
|
+
fittedLineCount += count;
|
|
98
|
+
fittingCount = i + 1;
|
|
99
|
+
}
|
|
58
100
|
}
|
|
101
|
+
displayedSlice = { start: 0, end: fittingCount };
|
|
102
|
+
remaining = items.length - fittingCount;
|
|
59
103
|
}
|
|
60
104
|
|
|
61
|
-
const remaining = items.length - fittingCount;
|
|
62
105
|
const hasSummary = !expanded && remaining > 0 && (linesBudget === Infinity || fittedLineCount < linesBudget);
|
|
63
106
|
|
|
64
107
|
// Emit pre-rendered content with correct isLast-based branch prefixes.
|
|
65
108
|
const lines: string[] = [];
|
|
66
|
-
|
|
67
|
-
|
|
109
|
+
|
|
110
|
+
if (truncateFrom === "start" && hasSummary) {
|
|
111
|
+
lines.push(`${theme.fg("dim", theme.tree.branch)} ${theme.fg("muted", formatMoreItems(remaining, itemType))}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (let i = displayedSlice.start; i < displayedSlice.end; i++) {
|
|
115
|
+
const isLast =
|
|
116
|
+
truncateFrom === "start" ? i === displayedSlice.end - 1 : !hasSummary && i === displayedSlice.end - 1;
|
|
68
117
|
const branch = getTreeBranch(isLast, theme);
|
|
69
118
|
const prefix = `${theme.fg("dim", branch)} `;
|
|
70
119
|
const continuePrefix = `${theme.fg("dim", getTreeContinuePrefix(isLast, theme))}`;
|
|
@@ -76,7 +125,7 @@ export function renderTreeList<T>(options: TreeListOptions<T>, theme: Theme): st
|
|
|
76
125
|
}
|
|
77
126
|
}
|
|
78
127
|
|
|
79
|
-
if (hasSummary) {
|
|
128
|
+
if (truncateFrom === "end" && hasSummary) {
|
|
80
129
|
lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, itemType))}`);
|
|
81
130
|
}
|
|
82
131
|
|
|
@@ -1,37 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
for (let i = 0; i < text.length; i++) {
|
|
9
|
-
const code = text.charCodeAt(i);
|
|
10
|
-
if (code === 0x2e || code === 0x2026) {
|
|
11
|
-
sawDot = true;
|
|
12
|
-
continue;
|
|
1
|
+
export function canonicalizeMessage(text: string | null | undefined): string {
|
|
2
|
+
if (!text) return "";
|
|
3
|
+
const trimmed = text.trim();
|
|
4
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
5
|
+
const code = trimmed.charCodeAt(i);
|
|
6
|
+
if (code !== 0x2e && code !== 0x2026 && code !== 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d) {
|
|
7
|
+
return trimmed;
|
|
13
8
|
}
|
|
14
|
-
if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) continue;
|
|
15
|
-
return false;
|
|
16
9
|
}
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Returns the operator-visible thinking text for a block.
|
|
22
|
-
*
|
|
23
|
-
* Some OpenAI-compatible reasoning gateways require a non-empty
|
|
24
|
-
* `reasoning_content` field on historical assistant tool-call turns even when
|
|
25
|
-
* the model did not emit any reasoning. The provider adapter uses a single dot
|
|
26
|
-
* as the wire-only placeholder those gateways accept; if that value is later
|
|
27
|
-
* replayed or echoed as a thinking block, it should not render as model thought.
|
|
28
|
-
*/
|
|
29
|
-
export function getVisibleThinkingText(block: ThinkingBlock): string {
|
|
30
|
-
const text = block.thinking.trim();
|
|
31
|
-
if (text.length === 0) return "";
|
|
32
|
-
return isDotOnlyThinking(text) ? "" : text;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function hasVisibleThinking(block: ThinkingBlock): boolean {
|
|
36
|
-
return getVisibleThinkingText(block).length > 0;
|
|
10
|
+
return "";
|
|
37
11
|
}
|