@nghyane/arcane 0.1.24 → 0.1.26

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 CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.1.26] - 2026-03-08
6
+
7
+ ### Fixed
8
+
9
+ - Improve session search by indexing all user messages instead of only the first
10
+ - Fix quoted phrase parsing in find_thread search queries
11
+ - Sort search results by relevance instead of date
12
+
13
+ ### Removed
14
+
15
+ - Remove gemini-image and save-memory tools
16
+
17
+ ### Changed
18
+
19
+ - Rename reviewer-tool.ts to reviewer.ts, subagent-tool.ts to subagent.ts
20
+ - RenderMermaid tool now displays diagram directly in TUI output
21
+
22
+ ## [0.1.25] - 2026-03-08
23
+
24
+ ### Added
25
+
26
+ - Propagate OAuth flag from auth storage to Anthropic client, replacing token-format heuristic
27
+
5
28
  ## [0.1.24] - 2026-03-08
6
29
 
7
30
  ### Changed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@nghyane/arcane",
4
- "version": "0.1.24",
4
+ "version": "0.1.26",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/nghyane/arcane",
7
7
  "author": "Can Bölük",
@@ -44,9 +44,9 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@mozilla/readability": "0.6.0",
47
- "@nghyane/arcane-stats": "^0.1.13",
48
- "@nghyane/arcane-agent": "^0.1.17",
49
- "@nghyane/arcane-ai": "^0.1.13",
47
+ "@nghyane/arcane-stats": "^0.1.14",
48
+ "@nghyane/arcane-agent": "^0.1.18",
49
+ "@nghyane/arcane-ai": "^0.1.14",
50
50
  "@nghyane/arcane-natives": "^0.1.11",
51
51
  "@nghyane/arcane-tui": "^0.1.15",
52
52
  "@nghyane/arcane-utils": "^0.1.8",
@@ -979,6 +979,10 @@ export class ModelRegistry {
979
979
  return this.authStorage.getApiKey(provider, sessionId, { baseUrl });
980
980
  }
981
981
 
982
+ isOAuthProvider(provider: string): boolean {
983
+ return this.authStorage.hasOAuth(provider);
984
+ }
985
+
982
986
  async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
983
987
  if (this.#keylessProviders.has(provider)) {
984
988
  return kNoAuth;
@@ -415,9 +415,7 @@ export class CommandController {
415
415
  }
416
416
 
417
417
  async handleMemoryCommand(_text: string): Promise<void> {
418
- this.ctx.showWarning(
419
- "The /memory command has been removed. Use save_memory tool or add facts to AGENTS.md directly.",
420
- );
418
+ this.ctx.showWarning("The /memory command has been removed. Add facts to AGENTS.md directly.");
421
419
  }
422
420
 
423
421
  async handleClearCommand(): Promise<void> {
@@ -18,7 +18,7 @@ import {
18
18
  setTheme,
19
19
  theme,
20
20
  } from "../../theme/theme";
21
- import { setPreferredImageProvider, setPreferredSearchProvider } from "../../tools";
21
+ import { setPreferredSearchProvider } from "../../tools";
22
22
  import { AssistantMessageComponent } from "../components/assistant-message";
23
23
  import { ExtensionDashboard } from "../components/extensions";
24
24
  import { HistorySearchComponent } from "../components/history-search";
@@ -289,9 +289,6 @@ export class SelectorController {
289
289
  value as "auto" | "exa" | "jina" | "zai" | "perplexity" | "anthropic" | "gemini" | "codex",
290
290
  );
291
291
  break;
292
- case "imageProvider":
293
- setPreferredImageProvider(value as "auto" | "gemini" | "openrouter");
294
- break;
295
292
 
296
293
  // All other settings are handled by the definitions (get/set on SettingsManager)
297
294
  // No additional side effects needed
@@ -171,7 +171,7 @@ Best practices:
171
171
 
172
172
  ### Cross-session Knowledge
173
173
 
174
- Tools: `find_thread`, `read_thread`, `save_memory`
174
+ Tools: `find_thread`, `read_thread`
175
175
  **Proactive search triggers** — use `find_thread` when:
176
176
  - User mentions past work: "we did this before", "last time", "in a previous session"
177
177
  - User asks "what did we do about X" or "how did we solve Y"
@@ -181,7 +181,6 @@ Tools: `find_thread`, `read_thread`, `save_memory`
181
181
  - Question is about current session context
182
182
  - Generic coding question with no project-specific history
183
183
  - User explicitly provides all needed context
184
- **save_memory**: only when user says "remember this" or states a clear preference. If unsure, ask.
185
184
 
186
185
  ### Verification
187
186
  Work incrementally. Make a small change, verify it works, then continue. Prefer a sequence of small, validated edits over one large change. Use commands from AGENTS.md or the project's config to verify. Address all errors caused by your changes before yielding.
package/src/sdk.ts CHANGED
@@ -71,7 +71,6 @@ import {
71
71
  PythonTool,
72
72
  ReadTool,
73
73
  type SubagentContext,
74
- setPreferredImageProvider,
75
74
  setPreferredSearchProvider,
76
75
  type Tool,
77
76
  type ToolSession,
@@ -79,7 +78,6 @@ import {
79
78
  warmupLspServers,
80
79
  } from "./tools";
81
80
  import { ToolContextStore } from "./tools/context";
82
- import { getGeminiImageTools } from "./tools/gemini-image";
83
81
  import { EventBus } from "./utils/event-bus";
84
82
  import { time } from "./utils/timings";
85
83
 
@@ -559,7 +557,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
559
557
 
560
558
  // Initialize provider preferences from settings
561
559
  setPreferredSearchProvider(settings.get("providers.webSearch") ?? "auto");
562
- setPreferredImageProvider(settings.get("providers.image") ?? "auto");
563
560
 
564
561
  const sessionManager = options.sessionManager ?? SessionManager.create(cwd);
565
562
  time("sessionManager");
@@ -803,13 +800,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
803
800
  }
804
801
  }
805
802
 
806
- // Add Gemini image tools if GEMINI_AARCANE_KEY (or GOOGLE_AARCANE_KEY) is available
807
- const geminiImageTools = await getGeminiImageTools();
808
- time("getGeminiImageTools");
809
- if (geminiImageTools.length > 0) {
810
- customTools.push(...(geminiImageTools as unknown as CustomTool[]));
811
- }
812
-
813
803
  // Add specialized Exa web search tools if EXA_AARCANE_KEY is available
814
804
  const exaSettings = settings.getGroup("exa");
815
805
  if (exaSettings.enabled && exaSettings.enableSearch) {
@@ -1164,7 +1154,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1164
1154
  if (!key) {
1165
1155
  throw new Error(`No API key found for provider "${provider}"`);
1166
1156
  }
1167
- return key;
1157
+ return { key, isOAuth: modelRegistry.isOAuthProvider(provider) };
1168
1158
  },
1169
1159
  cursorExecHandlers,
1170
1160
  transformToolCallArguments: obfuscator?.hasSecrets() ? args => obfuscator!.deobfuscateObject(args) : undefined,
@@ -7,8 +7,7 @@ import { getAgentDir } from "@nghyane/arcane-utils/dirs";
7
7
  export interface SessionIndexEntry {
8
8
  sessionId: string;
9
9
  title: string;
10
- firstMessage: string;
11
- files: string;
10
+ content: string;
12
11
  cwd: string;
13
12
  createdAt: number;
14
13
  messageCount: number;
@@ -30,6 +29,7 @@ interface SearchRow {
30
29
  snippet: string;
31
30
  }
32
31
 
32
+ const CONTENT_MAX_LENGTH = 5000;
33
33
  const FILE_PATH_PARAMS = new Set(["path", "file", "filePath", "glob", "pattern", "command_working_directory"]);
34
34
 
35
35
  export class SessionIndex {
@@ -49,46 +49,68 @@ export class SessionIndex {
49
49
  PRAGMA journal_mode=WAL;
50
50
  PRAGMA synchronous=NORMAL;
51
51
  PRAGMA busy_timeout=5000;
52
+ `);
53
+
54
+ this.#migrateIfNeeded();
52
55
 
56
+ this.#db.exec(`
53
57
  CREATE TABLE IF NOT EXISTS session_index (
54
58
  session_id TEXT PRIMARY KEY,
55
59
  title TEXT,
56
- first_message TEXT,
57
- files TEXT,
60
+ content TEXT,
58
61
  cwd TEXT,
59
62
  created_at INTEGER,
60
63
  message_count INTEGER
61
64
  );
62
65
 
63
66
  CREATE VIRTUAL TABLE IF NOT EXISTS session_fts USING fts5(
64
- title, first_message, files,
67
+ title, content,
65
68
  content='session_index', content_rowid='rowid'
66
69
  );
67
70
 
68
71
  CREATE TRIGGER IF NOT EXISTS session_index_ai AFTER INSERT ON session_index BEGIN
69
- INSERT INTO session_fts(rowid, title, first_message, files)
70
- VALUES (new.rowid, new.title, new.first_message, new.files);
72
+ INSERT INTO session_fts(rowid, title, content)
73
+ VALUES (new.rowid, new.title, new.content);
71
74
  END;
72
75
 
73
76
  CREATE TRIGGER IF NOT EXISTS session_index_ad AFTER DELETE ON session_index BEGIN
74
- INSERT INTO session_fts(session_fts, rowid, title, first_message, files)
75
- VALUES ('delete', old.rowid, old.title, old.first_message, old.files);
77
+ INSERT INTO session_fts(session_fts, rowid, title, content)
78
+ VALUES ('delete', old.rowid, old.title, old.content);
76
79
  END;
77
80
 
78
81
  CREATE TRIGGER IF NOT EXISTS session_index_au AFTER UPDATE ON session_index BEGIN
79
- INSERT INTO session_fts(session_fts, rowid, title, first_message, files)
80
- VALUES ('delete', old.rowid, old.title, old.first_message, old.files);
81
- INSERT INTO session_fts(rowid, title, first_message, files)
82
- VALUES (new.rowid, new.title, new.first_message, new.files);
82
+ INSERT INTO session_fts(session_fts, rowid, title, content)
83
+ VALUES ('delete', old.rowid, old.title, old.content);
84
+ INSERT INTO session_fts(rowid, title, content)
85
+ VALUES (new.rowid, new.title, new.content);
83
86
  END;
84
87
  `);
85
88
 
86
89
  this.#upsertStmt = this.#db.prepare(
87
- "INSERT OR REPLACE INTO session_index (session_id, title, first_message, files, cwd, created_at, message_count) VALUES (?, ?, ?, ?, ?, ?, ?)",
90
+ "INSERT OR REPLACE INTO session_index (session_id, title, content, cwd, created_at, message_count) VALUES (?, ?, ?, ?, ?, ?)",
88
91
  );
89
92
  this.#hasStmt = this.#db.prepare("SELECT 1 FROM session_index WHERE session_id = ?");
90
93
  }
91
94
 
95
+ #migrateIfNeeded(): void {
96
+ try {
97
+ const row = this.#db.prepare("PRAGMA table_info(session_index)").all() as Array<{ name: string }>;
98
+ const columns = new Set(row.map(r => r.name));
99
+ if (columns.has("first_message") || !columns.has("content")) {
100
+ this.#db.exec(`
101
+ DROP TRIGGER IF EXISTS session_index_ai;
102
+ DROP TRIGGER IF EXISTS session_index_ad;
103
+ DROP TRIGGER IF EXISTS session_index_au;
104
+ DROP TABLE IF EXISTS session_fts;
105
+ DROP TABLE IF EXISTS session_index;
106
+ `);
107
+ logger.debug("SessionIndex: migrated to schema v2 (dropped old tables)");
108
+ }
109
+ } catch (error) {
110
+ logger.warn("SessionIndex migration check failed", { error: String(error) });
111
+ }
112
+ }
113
+
92
114
  static open(dbPath: string = path.join(getAgentDir(), "session-index.db")): SessionIndex {
93
115
  if (!SessionIndex.#instance) {
94
116
  SessionIndex.#instance = new SessionIndex(dbPath);
@@ -101,8 +123,7 @@ END;
101
123
  this.#upsertStmt.run(
102
124
  entry.sessionId,
103
125
  entry.title,
104
- entry.firstMessage,
105
- entry.files,
126
+ entry.content,
106
127
  entry.cwd,
107
128
  entry.createdAt,
108
129
  entry.messageCount,
@@ -139,7 +160,7 @@ END;
139
160
  FROM session_fts f
140
161
  JOIN session_index si ON si.rowid = f.rowid
141
162
  WHERE ${conditions.join(" AND ")}
142
- ORDER BY si.created_at DESC
163
+ ORDER BY rank
143
164
  LIMIT ?`;
144
165
 
145
166
  try {
@@ -159,8 +180,8 @@ LIMIT ?`;
159
180
 
160
181
  async indexSessionFile(filePath: string): Promise<void> {
161
182
  try {
162
- const content = await Bun.file(filePath).text();
163
- const entries = parseJsonlLenient<Record<string, unknown>>(content);
183
+ const raw = await Bun.file(filePath).text();
184
+ const entries = parseJsonlLenient<Record<string, unknown>>(raw);
164
185
  if (entries.length === 0) return;
165
186
 
166
187
  const header = entries.find(e => e.type === "session") as
@@ -168,7 +189,7 @@ LIMIT ?`;
168
189
  | undefined;
169
190
  if (!header?.id) return;
170
191
 
171
- let firstMessage = "";
192
+ const userMessages: string[] = [];
172
193
  let messageCount = 0;
173
194
  const fileSet = new Set<string>();
174
195
 
@@ -179,8 +200,9 @@ LIMIT ?`;
179
200
 
180
201
  messageCount++;
181
202
 
182
- if (msg.role === "user" && !firstMessage) {
183
- firstMessage = extractTextContent(msg.content);
203
+ if (msg.role === "user") {
204
+ const text = extractTextContent(msg.content);
205
+ if (text) userMessages.push(text);
184
206
  }
185
207
 
186
208
  if (msg.role === "assistant") {
@@ -188,14 +210,15 @@ LIMIT ?`;
188
210
  }
189
211
  }
190
212
 
213
+ const firstMessage = userMessages[0] ?? "";
191
214
  const title = header.title || firstMessage.slice(0, 100) || "Untitled";
215
+ const content = [userMessages.join("\n"), [...fileSet].join(" ")].join("\n").slice(0, CONTENT_MAX_LENGTH);
192
216
  const createdAt = header.timestamp ? Math.floor(new Date(header.timestamp).getTime() / 1000) : 0;
193
217
 
194
218
  this.upsert({
195
219
  sessionId: header.id,
196
220
  title,
197
- firstMessage: firstMessage.slice(0, 500),
198
- files: [...fileSet].join(" "),
221
+ content,
199
222
  cwd: header.cwd || "",
200
223
  createdAt,
201
224
  messageCount,
@@ -283,21 +306,25 @@ LIMIT ?`;
283
306
  return null;
284
307
  }
285
308
 
286
- #buildFtsQuery(query: string): string | null {
287
- const tokens = query
309
+ #buildFtsQuery(raw: string): string | null {
310
+ const phrases: string[] = [];
311
+ const withoutPhrases = raw.replace(/"([^"]+)"/g, (_, phrase: string) => {
312
+ const trimmed = phrase.trim();
313
+ if (trimmed) phrases.push(`"${trimmed}"`);
314
+ return "";
315
+ });
316
+
317
+ const words = withoutPhrases
288
318
  .trim()
289
319
  .split(/\s+/)
290
- .map(t => t.trim())
291
- .filter(Boolean);
292
-
293
- if (tokens.length === 0) return null;
294
-
295
- return tokens
296
- .map(token => {
297
- const escaped = token.replace(/"/g, '""');
320
+ .filter(Boolean)
321
+ .map(w => {
322
+ const escaped = w.replace(/"/g, '""');
298
323
  return `"${escaped}"*`;
299
- })
300
- .join(" ");
324
+ });
325
+
326
+ const parts = [...phrases, ...words];
327
+ return parts.length > 0 ? parts.join(" ") : null;
301
328
  }
302
329
  }
303
330
 
@@ -23,11 +23,10 @@ import { PythonTool } from "./python";
23
23
  import { ReadTool } from "./read";
24
24
  import { ReadThreadTool } from "./read-thread";
25
25
  import { RenderMermaidTool } from "./render-mermaid";
26
- import { reviewerConfig } from "./reviewer-tool";
27
- import { SaveMemoryTool } from "./save-memory";
26
+ import { reviewerConfig } from "./reviewer";
28
27
  import { SearchCodeTool } from "./search-code";
29
28
  import { loadSshTool } from "./ssh";
30
- import { SubagentTool } from "./subagent-tool";
29
+ import { SubagentTool } from "./subagent";
31
30
  import { TodoWriteTool } from "./todo-write";
32
31
  import { UndoEditTool } from "./undo-edit";
33
32
  import { WriteTool } from "./write";
@@ -60,7 +59,6 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
60
59
  fetch: s => new FetchTool(s),
61
60
  web_search: () => new SearchTool(),
62
61
  search_code: () => new SearchCodeTool(),
63
- save_memory: s => new SaveMemoryTool(s),
64
62
  write: s => new WriteTool(s),
65
63
  };
66
64
 
@@ -1,5 +1,5 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import type { SubagentConfig } from "./subagent-tool";
2
+ import type { SubagentConfig } from "./subagent";
3
3
 
4
4
  const schema = Type.Object({
5
5
  query: Type.String({ description: "What to search for in the codebase" }),
@@ -72,7 +72,6 @@ export {
72
72
  type FindToolOptions,
73
73
  } from "./find";
74
74
  export { FindThreadTool, type FindThreadToolDetails } from "./find-thread";
75
- export { setPreferredImageProvider } from "./gemini-image";
76
75
  export { GitHubTool, type GitHubToolDetails } from "./github";
77
76
  export { GrepTool, type GrepToolDetails, type GrepToolInput } from "./grep";
78
77
  export { librarianConfig } from "./librarian";
@@ -86,10 +85,9 @@ export {
86
85
  export { ReadTool, type ReadToolDetails, type ReadToolInput } from "./read";
87
86
  export { ReadThreadTool, type ReadThreadToolDetails } from "./read-thread";
88
87
  export { RenderMermaidTool, type RenderMermaidToolDetails } from "./render-mermaid";
89
- export { reviewerConfig } from "./reviewer-tool";
90
- export { SaveMemoryTool, type SaveMemoryToolDetails } from "./save-memory";
88
+ export { reviewerConfig } from "./reviewer";
91
89
  export { loadSshTool, type SSHToolDetails, SshTool } from "./ssh";
92
- export { type SubagentConfig, SubagentTool } from "./subagent-tool";
90
+ export { type SubagentConfig, SubagentTool } from "./subagent";
93
91
  export {
94
92
  type TodoItem,
95
93
  TodoWriteTool,
@@ -1,5 +1,5 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import type { SubagentConfig } from "./subagent-tool";
2
+ import type { SubagentConfig } from "./subagent";
3
3
 
4
4
  const schema = Type.Object({
5
5
  query: Type.String({ description: "What to look up across repositories" }),
@@ -1,5 +1,5 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import type { SubagentConfig } from "./subagent-tool";
2
+ import type { SubagentConfig } from "./subagent";
3
3
 
4
4
  const schema = Type.Object({
5
5
  task: Type.String({ description: "Question or task for the oracle to reason about" }),
@@ -1,6 +1,12 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@nghyane/arcane-agent";
2
+ import type { Component } from "@nghyane/arcane-tui";
3
+ import { Text } from "@nghyane/arcane-tui";
2
4
  import { type AsciiRenderOptions, renderMermaidAscii } from "@nghyane/arcane-utils";
3
5
  import { type Static, Type } from "@sinclair/typebox";
6
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
7
+ import type { Theme } from "../theme/theme";
8
+ import { renderStatusLine } from "../tui";
9
+ import { PREVIEW_LIMITS, replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../ui/render-utils";
4
10
  import type { ToolSession } from "./index";
5
11
  import { allocateOutputArtifact } from "./output-utils";
6
12
 
@@ -30,14 +36,15 @@ function sanitizeRenderConfig(config: AsciiRenderOptions | undefined): AsciiRend
30
36
  }
31
37
 
32
38
  export interface RenderMermaidToolDetails {
39
+ ascii?: string;
33
40
  artifactId?: string;
34
41
  }
35
42
 
36
- export class RenderMermaidTool implements AgentTool<typeof renderMermaidSchema, RenderMermaidToolDetails> {
43
+ export class RenderMermaidTool implements AgentTool<typeof renderMermaidSchema, RenderMermaidToolDetails, Theme> {
37
44
  readonly name = "render_mermaid";
38
45
  readonly label = "RenderMermaid";
39
46
  readonly description =
40
- "Convert Mermaid graph source into ASCII diagram output. Returns ASCII diagram text. Saves full output to an artifact URL when artifact storage is available.";
47
+ "Convert Mermaid graph source into ASCII diagram output. Returns ASCII diagram text. Saves full output to an artifact URL when artifact storage is available. The diagram is displayed directly in the output — do not reproduce it.";
41
48
  readonly parameters = renderMermaidSchema;
42
49
  readonly strict = true;
43
50
 
@@ -55,10 +62,59 @@ export class RenderMermaidTool implements AgentTool<typeof renderMermaidSchema,
55
62
  await Bun.write(artifactPath, ascii);
56
63
  }
57
64
 
58
- const artifactLine = artifactId ? `\n\nSaved artifact: artifact://${artifactId}` : "";
59
65
  return {
60
- content: [{ type: "text", text: `${ascii}${artifactLine}` }],
61
- details: { artifactId },
66
+ content: [
67
+ { type: "text", text: artifactId ? `Saved artifact: artifact://${artifactId}` : "Diagram rendered." },
68
+ ],
69
+ details: { ascii, artifactId },
62
70
  };
63
71
  }
72
+
73
+ renderCall(_args: RenderMermaidParams, options: RenderResultOptions, uiTheme: Theme): Component {
74
+ const text = renderStatusLine(
75
+ {
76
+ icon: "running",
77
+ spinnerFrame: options.spinnerFrame,
78
+ title: "RenderMermaid",
79
+ description: "Rendering diagram…",
80
+ },
81
+ uiTheme,
82
+ );
83
+ return new Text(text, 0, 0);
84
+ }
85
+
86
+ renderResult(
87
+ result: {
88
+ content: Array<{ type: string; text?: string }>;
89
+ details?: RenderMermaidToolDetails;
90
+ isError?: boolean;
91
+ },
92
+ options: RenderResultOptions,
93
+ uiTheme: Theme,
94
+ _args?: RenderMermaidParams,
95
+ ): Component {
96
+ const icon = result.isError ? "error" : "success";
97
+ const ascii = result.details?.ascii ?? "";
98
+ const outputLines = ascii ? ascii.split("\n") : [];
99
+ const lines: string[] = [];
100
+
101
+ const meta: string[] = [];
102
+ if (outputLines.length > 0) meta.push(`${outputLines.length} lines`);
103
+ if (result.details?.artifactId) meta.push(`artifact://${result.details.artifactId}`);
104
+
105
+ lines.push(renderStatusLine({ icon, title: "RenderMermaid", meta }, uiTheme));
106
+
107
+ if (outputLines.length > 0) {
108
+ const maxLines = options.expanded ? PREVIEW_LIMITS.OUTPUT_EXPANDED : PREVIEW_LIMITS.OUTPUT_COLLAPSED;
109
+ const displayLines = outputLines.slice(0, maxLines);
110
+ for (const line of displayLines) {
111
+ lines.push(uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), TRUNCATE_LENGTHS.CONTENT)));
112
+ }
113
+ if (outputLines.length > maxLines) {
114
+ lines.push(uiTheme.fg("dim", `… (${outputLines.length - maxLines} more lines)`));
115
+ }
116
+ }
117
+
118
+ return new Text(lines.join("\n"), 0, 0);
119
+ }
64
120
  }
@@ -1,5 +1,5 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import type { SubagentConfig } from "./subagent-tool";
2
+ import type { SubagentConfig } from "./subagent";
3
3
 
4
4
  const schema = Type.Object({
5
5
  diff_description: Type.String({ description: "Description of the diff or change to review" }),