@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 +23 -0
- package/package.json +4 -4
- package/src/config/model-registry.ts +4 -0
- package/src/modes/controllers/command-controller.ts +1 -3
- package/src/modes/controllers/selector-controller.ts +1 -4
- package/src/prompts/system/system-prompt.md +1 -2
- package/src/sdk.ts +1 -11
- package/src/session/session-index.ts +63 -36
- package/src/tools/create-tools.ts +2 -4
- package/src/tools/explore.ts +1 -1
- package/src/tools/index.ts +2 -4
- package/src/tools/librarian.ts +1 -1
- package/src/tools/oracle.ts +1 -1
- package/src/tools/render-mermaid.ts +61 -5
- package/src/tools/{reviewer-tool.ts → reviewer.ts} +1 -1
- package/src/tools/gemini-image.ts +0 -905
- package/src/tools/save-memory.ts +0 -185
- /package/src/tools/{subagent-tool.ts → subagent.ts} +0 -0
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.
|
|
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.
|
|
48
|
-
"@nghyane/arcane-agent": "^0.1.
|
|
49
|
-
"@nghyane/arcane-ai": "^0.1.
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
70
|
-
VALUES (new.rowid, new.title, new.
|
|
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,
|
|
75
|
-
VALUES ('delete', old.rowid, old.title, old.
|
|
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,
|
|
80
|
-
VALUES ('delete', old.rowid, old.title, old.
|
|
81
|
-
INSERT INTO session_fts(rowid, title,
|
|
82
|
-
VALUES (new.rowid, new.title, new.
|
|
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,
|
|
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.
|
|
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
|
|
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
|
|
163
|
-
const entries = parseJsonlLenient<Record<string, unknown>>(
|
|
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
|
-
|
|
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"
|
|
183
|
-
|
|
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
|
-
|
|
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(
|
|
287
|
-
const
|
|
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
|
-
.
|
|
291
|
-
.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
package/src/tools/explore.ts
CHANGED
package/src/tools/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
90
|
+
export { type SubagentConfig, SubagentTool } from "./subagent";
|
|
93
91
|
export {
|
|
94
92
|
type TodoItem,
|
|
95
93
|
TodoWriteTool,
|
package/src/tools/librarian.ts
CHANGED
package/src/tools/oracle.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import type { SubagentConfig } from "./subagent
|
|
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: [
|
|
61
|
-
|
|
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
|
|
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" }),
|