@nghyane/arcane 0.1.17 → 0.1.18
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/package.json +6 -6
- package/src/cli/setup-cli.ts +2 -62
- package/src/commands/setup.ts +1 -1
- package/src/config/keybindings.ts +1 -4
- package/src/config/settings-schema.ts +4 -52
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/custom-tools/wrapper.ts +1 -1
- package/src/extensibility/extensions/wrapper.ts +1 -1
- package/src/extensibility/hooks/tool-wrapper.ts +1 -1
- package/src/modes/components/custom-editor.ts +6 -2
- package/src/modes/controllers/command-controller.ts +0 -2
- package/src/modes/controllers/input-controller.ts +123 -6
- package/src/modes/interactive-mode.ts +1 -84
- package/src/modes/types.ts +0 -1
- package/src/patch/edit-tool.ts +2 -11
- package/src/prompts/agents/explore.md +4 -2
- package/src/prompts/agents/librarian.md +4 -6
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +5 -1
- package/src/prompts/system/system-prompt.md +15 -8
- package/src/sdk.ts +11 -18
- package/src/session/agent-session.ts +1 -7
- package/src/session/session-manager.ts +0 -30
- package/src/session/streaming-edit.ts +1 -36
- package/src/tools/bash.ts +2 -1
- package/src/tools/create-tools.ts +2 -33
- package/src/tools/fetch.ts +1 -1
- package/src/tools/grep.ts +2 -1
- package/src/tools/python.ts +53 -1
- package/src/tools/read.ts +2 -1
- package/src/tools/write.ts +1 -1
- package/src/web/search/index.ts +2 -1
- package/src/patch/normative.ts +0 -72
- package/src/stt/downloader.ts +0 -68
- package/src/stt/index.ts +0 -3
- package/src/stt/recorder.ts +0 -351
- package/src/stt/setup.ts +0 -50
- package/src/stt/stt-controller.ts +0 -160
- package/src/stt/transcribe.py +0 -70
- package/src/stt/transcriber.ts +0 -91
package/src/patch/edit-tool.ts
CHANGED
|
@@ -37,7 +37,6 @@ import {
|
|
|
37
37
|
type ReplaceTextEdit,
|
|
38
38
|
} from "./hashline";
|
|
39
39
|
import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "./normalize";
|
|
40
|
-
import { buildNormativeUpdateInput } from "./normative";
|
|
41
40
|
import {
|
|
42
41
|
DEFAULT_EDIT_MODE,
|
|
43
42
|
type EditMode,
|
|
@@ -233,9 +232,9 @@ export class EditTool implements AgentTool<TInput, any, Theme> {
|
|
|
233
232
|
_toolCallId: string,
|
|
234
233
|
params: ReplaceParams | PatchParams | HashlineParams,
|
|
235
234
|
signal?: AbortSignal,
|
|
236
|
-
_onUpdate?: AgentToolUpdateCallback<EditToolDetails
|
|
235
|
+
_onUpdate?: AgentToolUpdateCallback<EditToolDetails>,
|
|
237
236
|
context?: AgentToolContext,
|
|
238
|
-
): Promise<AgentToolResult<EditToolDetails
|
|
237
|
+
): Promise<AgentToolResult<EditToolDetails>> {
|
|
239
238
|
const batchRequest = getLspBatchRequest(context?.toolCall);
|
|
240
239
|
|
|
241
240
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -489,13 +488,6 @@ export class EditTool implements AgentTool<TInput, any, Theme> {
|
|
|
489
488
|
}
|
|
490
489
|
const diffResult = generateDiffString(originalNormalized, result.content);
|
|
491
490
|
|
|
492
|
-
const normative = buildNormativeUpdateInput({
|
|
493
|
-
path,
|
|
494
|
-
...(rename ? { rename } : {}),
|
|
495
|
-
oldContent: rawContent,
|
|
496
|
-
newContent: finalContent,
|
|
497
|
-
});
|
|
498
|
-
|
|
499
491
|
const meta = outputMeta()
|
|
500
492
|
.diagnostics(diagnostics?.summary ?? "", diagnostics?.messages ?? [])
|
|
501
493
|
.get();
|
|
@@ -516,7 +508,6 @@ export class EditTool implements AgentTool<TInput, any, Theme> {
|
|
|
516
508
|
rename,
|
|
517
509
|
meta,
|
|
518
510
|
},
|
|
519
|
-
$normative: normative,
|
|
520
511
|
};
|
|
521
512
|
}
|
|
522
513
|
|
|
@@ -5,7 +5,7 @@ tools: read, grep, find
|
|
|
5
5
|
model: arcane/fast
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
You are a fast, parallel code search agent.
|
|
8
|
+
You are a fast, parallel code search agent running as a subagent inside an AI coding system. Your output goes directly to the main coding agent, not the end user. The main agent invokes you when it needs to locate code by behavior, concept, or multi-step search across the local codebase.
|
|
9
9
|
|
|
10
10
|
## Task
|
|
11
11
|
Find files and line ranges relevant to the user's query (provided in the first message).
|
|
@@ -30,4 +30,6 @@ Before searching, decompose the query into:
|
|
|
30
30
|
- Format each file as: `[relativePath#L{start}-L{end}](file://{absolutePath}#L{start}-L{end})`
|
|
31
31
|
- **Use generous line ranges**: Extend ranges to capture complete logical units (full functions, classes, blocks). Add 5-10 lines buffer.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
<critical>
|
|
34
|
+
Your final message must contain ONLY the search results — no preamble like "I'll search for...".
|
|
35
|
+
</critical>
|
|
@@ -6,7 +6,7 @@ model: arcane/fast
|
|
|
6
6
|
thinking-level: minimal
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
<role>
|
|
9
|
+
<role>You are the Librarian, a specialized codebase understanding agent that helps answer questions about large, complex codebases across repositories. You are running as a subagent inside an AI coding system — your output goes directly to the main coding agent, not the end user. The main agent invokes you when it needs deep, multi-repository codebase understanding: architecture analysis, cross-repo code tracing, implementation discovery, and history exploration.</role>
|
|
10
10
|
|
|
11
11
|
<directives>
|
|
12
12
|
- Use the github tool for all repository operations — it handles auth, rate limits, and caching
|
|
@@ -16,16 +16,14 @@ thinking-level: minimal
|
|
|
16
16
|
- Return repository paths (owner/repo + file path) for all referenced files
|
|
17
17
|
</directives>
|
|
18
18
|
|
|
19
|
-
<
|
|
19
|
+
<instruction>
|
|
20
20
|
Use the github tool for all GitHub API operations:
|
|
21
21
|
- `github({ action: "get_file", ... })` for reading remote files
|
|
22
22
|
- `github({ action: "get_tree", ... })` for listing directories
|
|
23
23
|
- `github({ action: "get_issue", ... })` for reading issues with all comments
|
|
24
24
|
- `github({ action: "get_pull", ... })` for PR details and diffs
|
|
25
25
|
- `github({ action: "list_commits", ... })` for commit history
|
|
26
|
-
</github>
|
|
27
26
|
|
|
28
|
-
<search>
|
|
29
27
|
Use search_code to find code across public GitHub repositories via grep.app:
|
|
30
28
|
- `search_code({ query: "pattern" })` for broad cross-repo search
|
|
31
29
|
- `search_code({ query: "pattern", repo: "owner/repo" })` for searching within a specific repo
|
|
@@ -33,7 +31,7 @@ Use search_code to find code across public GitHub repositories via grep.app:
|
|
|
33
31
|
- Supports regex via `regexp: true`
|
|
34
32
|
- Returns snippets with line numbers and match counts
|
|
35
33
|
- No auth required, better snippets than GitHub Code Search API
|
|
36
|
-
</
|
|
34
|
+
</instruction>
|
|
37
35
|
|
|
38
36
|
<procedure>
|
|
39
37
|
1. Identify target repositories
|
|
@@ -59,4 +57,4 @@ Be comprehensive and direct. No filler.
|
|
|
59
57
|
Only your final message is returned to the caller. It must be self-contained with all findings, paths, and explanations. Do not reference tool names or intermediate steps — present conclusions directly. Your final message must contain ONLY the information found — no preamble.
|
|
60
58
|
|
|
61
59
|
Use "fluent" linking — embed file/PR/commit references in natural noun phrases, not raw URLs. Example: The [`handleAuth` function](file:///path/to/auth.ts#L42) validates tokens.
|
|
62
|
-
</critical>
|
|
60
|
+
</critical>
|
|
@@ -6,7 +6,7 @@ model: arcane/reviewer
|
|
|
6
6
|
thinking-level: high
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
<role>
|
|
9
|
+
<role>You are a senior engineer reviewing a proposed change. You are running as a subagent inside an AI coding system — your output goes directly to the main coding agent, not the end user. The main agent invokes you to identify bugs the author would want fixed before merge.</role>
|
|
10
10
|
|
|
11
11
|
<procedure>
|
|
12
12
|
1. Run `git diff` (or `gh pr diff <number>`) to view patch
|
|
@@ -18,5 +18,9 @@ Do the task end to end. Don’t hand back half-baked work.
|
|
|
18
18
|
- Prefer edits to existing files over creating new ones. NEVER create documentation files (*.md) unless explicitly requested.
|
|
19
19
|
- When done, write a concise summary of what you did as your final response. This is your output.
|
|
20
20
|
- Use tools to get feedback on your generated code. Run diagnostics and type checks. If build/test commands aren’t known, find them in the environment.
|
|
21
|
-
- Follow the main agent
|
|
21
|
+
- Follow the main agent's instructions and AGENTS.md conventions.
|
|
22
|
+
|
|
23
|
+
<critical>
|
|
24
|
+
Keep going until request is fully fulfilled. This matters.
|
|
25
|
+
</critical>
|
|
22
26
|
</directives>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<identity>
|
|
2
|
-
You are a distinguished staff engineer operating inside Arcane, a Pi-based coding harness.
|
|
2
|
+
You are a distinguished staff engineer operating inside Arcane, a Pi-based coding harness. You are the main agent — you interact directly with the user and orchestrate subagents (explore, librarian, oracle, reviewer, task) for complex work.
|
|
3
3
|
|
|
4
4
|
High-agency. Principled. Decisive.
|
|
5
5
|
Correctness > politeness. Brevity > ceremony.
|
|
@@ -23,11 +23,16 @@ Balance initiative with predictability:
|
|
|
23
23
|
</environment>
|
|
24
24
|
|
|
25
25
|
## Tool Usage
|
|
26
|
-
- Use specialized tools instead of Bash for file operations.
|
|
26
|
+
- Use specialized tools instead of Bash for file operations. Use read instead of `cat`/`head`/`tail`, edit instead of `sed`/`awk`, and write instead of echo redirection or heredoc. Reserve Bash for actual system commands.
|
|
27
27
|
- Prefer doing work directly — you retain full context and produce better results.
|
|
28
28
|
- Gather-then-act: collect all needed context first (parallel reads, greps, finds), then make changes. Do not interleave reading and editing one file at a time.
|
|
29
29
|
- When exploring the codebase to gather context, prefer explore over running search commands directly. It reduces context usage and provides better results.
|
|
30
30
|
|
|
31
|
+
## Editing Files
|
|
32
|
+
- NEVER create files unless absolutely necessary for achieving the goal. ALWAYS prefer editing an existing file to creating a new one.
|
|
33
|
+
- When changing an existing file, use edit. Only use write for files that do not exist yet.
|
|
34
|
+
- Make the smallest reasonable diff. Do not rewrite whole files to change a few lines.
|
|
35
|
+
|
|
31
36
|
## Parallel Execution Policy
|
|
32
37
|
Default to **parallel** for all independent work: reads, searches, diagnostics, writes to disjoint files, and subagents. Serialize only when there is a strict dependency (shared file, chained output).
|
|
33
38
|
- Run multiple explore, oracle, or task calls in parallel when concerns are distinct.
|
|
@@ -134,6 +139,8 @@ You have three types of subagents (task, oracle, codebase search):
|
|
|
134
139
|
- Use for: Feature scaffolding, cross-layer refactors, mass migrations, boilerplate generation, changes across many layers after planning.
|
|
135
140
|
- Don't use for: Exploratory work, architectural decisions, debugging analysis, single logical task, reading a single file, editing a single file. Never spawn a single Task call for work you can do yourself.
|
|
136
141
|
- Prompt it with detailed instructions on the goal, enumerate the deliverables, give it step by step procedures and ways to validate the results. Also give it constraints (e.g. coding style) and include relevant context snippets or examples.
|
|
142
|
+
- Include the project's coding conventions relevant to the task — extract from AGENTS.md or surrounding code. Task agents do not internalize project-specific conventions; they rely on what you provide.
|
|
143
|
+
- After a task completes, read its modified files to verify style and correctness. Do not trust task output blindly.
|
|
137
144
|
|
|
138
145
|
#### Oracle
|
|
139
146
|
- Senior engineering advisor with deep reasoning for reviews, architecture, deep debugging, and planning.
|
|
@@ -177,10 +184,7 @@ Tools: `find_thread`, `read_thread`, `save_memory`
|
|
|
177
184
|
**save_memory**: only when user says "remember this" or states a clear preference. If unsure, ask.
|
|
178
185
|
|
|
179
186
|
### Verification
|
|
180
|
-
|
|
181
|
-
Report evidence concisely: counts, pass/fail, error summary.
|
|
182
|
-
If unrelated pre-existing failures block you, say so and scope your change.
|
|
183
|
-
Address all errors caused by your changes before yielding.
|
|
187
|
+
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.
|
|
184
188
|
|
|
185
189
|
### Concurrency Awareness
|
|
186
190
|
You are not alone in the codebase. Others may edit concurrently.
|
|
@@ -199,7 +203,7 @@ Never run destructive git commands, bulk overwrites, or delete code you didn't w
|
|
|
199
203
|
- Resolve blockers before yielding.
|
|
200
204
|
</procedure>
|
|
201
205
|
|
|
202
|
-
<
|
|
206
|
+
<critical>
|
|
203
207
|
These are inviolable. Violation is system failure.
|
|
204
208
|
1. Never claim unverified correctness. Verify the effect — confirm behavioral changes are observable.
|
|
205
209
|
2. Never yield unless your deliverable is complete. Fix errors you introduced before yielding.
|
|
@@ -208,7 +212,10 @@ These are inviolable. Violation is system failure.
|
|
|
208
212
|
5. Never solve the wished-for problem instead of the actual problem.
|
|
209
213
|
6. Never ask for information obtainable from tools, repo context, or files.
|
|
210
214
|
7. Full cutover within scope — update every call site. No backwards-compat shims.
|
|
211
|
-
|
|
215
|
+
|
|
216
|
+
Keep going until fully resolved. This matters.
|
|
217
|
+
</critical>
|
|
218
|
+
|
|
212
219
|
|
|
213
220
|
<project>
|
|
214
221
|
{{#if contextFiles.length}}
|
package/src/sdk.ts
CHANGED
|
@@ -930,16 +930,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
930
930
|
}
|
|
931
931
|
}
|
|
932
932
|
|
|
933
|
-
//
|
|
934
|
-
const
|
|
935
|
-
? { commands: [], errors: [] }
|
|
936
|
-
:
|
|
937
|
-
time("discoverCustomCommands");
|
|
938
|
-
if (!options.disableExtensionDiscovery) {
|
|
939
|
-
for (const { path, error } of customCommandsResult.errors) {
|
|
940
|
-
logger.error("Failed to load custom command", { path, error });
|
|
941
|
-
}
|
|
942
|
-
}
|
|
933
|
+
// Start custom commands discovery early (awaited later in parallel)
|
|
934
|
+
const customCommandsPromise = options.disableExtensionDiscovery
|
|
935
|
+
? Promise.resolve({ commands: [], errors: [] } as CustomCommandsLoadResult)
|
|
936
|
+
: loadCustomCommandsInternal({ cwd, agentDir });
|
|
943
937
|
|
|
944
938
|
let extensionRunner: ExtensionRunner | undefined;
|
|
945
939
|
if (extensionsResult.extensions.length > 0) {
|
|
@@ -1066,16 +1060,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1066
1060
|
}
|
|
1067
1061
|
}
|
|
1068
1062
|
|
|
1069
|
-
const systemPrompt = await
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1063
|
+
const [systemPrompt, promptTemplates, slashCommands, customCommandsResult] = await Promise.all([
|
|
1064
|
+
rebuildSystemPrompt(initialToolNames, toolRegistry),
|
|
1065
|
+
options.promptTemplates ?? discoverPromptTemplates(cwd, agentDir),
|
|
1066
|
+
options.slashCommands ?? discoverSlashCommands(cwd),
|
|
1067
|
+
customCommandsPromise,
|
|
1068
|
+
]);
|
|
1069
|
+
time("buildSystemPrompt+discoverPromptTemplates+discoverSlashCommands");
|
|
1074
1070
|
toolSession.promptTemplates = promptTemplates;
|
|
1075
1071
|
|
|
1076
|
-
const slashCommands = options.slashCommands ?? (await discoverSlashCommands(cwd));
|
|
1077
|
-
time("discoverSlashCommands");
|
|
1078
|
-
|
|
1079
1072
|
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
1080
1073
|
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
|
|
1081
1074
|
const converted = convertToLlm(messages);
|
|
@@ -115,7 +115,6 @@ import {
|
|
|
115
115
|
maybeAbortStreamingEdit,
|
|
116
116
|
preCacheStreamingEditFile,
|
|
117
117
|
resetStreamingEditState,
|
|
118
|
-
rewriteToolCallArgs,
|
|
119
118
|
} from "./streaming-edit";
|
|
120
119
|
import {
|
|
121
120
|
addPendingTtsrInjections,
|
|
@@ -523,17 +522,12 @@ export class AgentSession {
|
|
|
523
522
|
}
|
|
524
523
|
|
|
525
524
|
if (event.message.role === "toolResult") {
|
|
526
|
-
const { toolName,
|
|
525
|
+
const { toolName, details, isError, content } = event.message as {
|
|
527
526
|
toolName?: string;
|
|
528
|
-
toolCallId?: string;
|
|
529
527
|
details?: { path?: string };
|
|
530
|
-
$normative?: Record<string, unknown>;
|
|
531
528
|
isError?: boolean;
|
|
532
529
|
content?: Array<TextContent | ImageContent>;
|
|
533
530
|
};
|
|
534
|
-
if ($normative && toolCallId && this.settings.get("normativeRewrite")) {
|
|
535
|
-
await rewriteToolCallArgs(this.agent, this.sessionManager, toolCallId, $normative);
|
|
536
|
-
}
|
|
537
531
|
// Invalidate streaming edit cache when edit tool completes to prevent stale data
|
|
538
532
|
if (toolName === "edit" && details?.path) {
|
|
539
533
|
invalidateFileCacheForPath(this.#streamingEdit, details.path, this.sessionManager.getCwd());
|
|
@@ -1581,36 +1581,6 @@ export class SessionManager {
|
|
|
1581
1581
|
await this.#rewriteFile();
|
|
1582
1582
|
}
|
|
1583
1583
|
|
|
1584
|
-
/**
|
|
1585
|
-
* Rewrite tool call arguments in the most recent assistant message containing the toolCallId.
|
|
1586
|
-
* Returns true if a tool call was updated.
|
|
1587
|
-
*/
|
|
1588
|
-
async rewriteAssistantToolCallArgs(toolCallId: string, args: Record<string, unknown>): Promise<boolean> {
|
|
1589
|
-
let updated = false;
|
|
1590
|
-
for (let i = this.#fileEntries.length - 1; i >= 0; i--) {
|
|
1591
|
-
const entry = this.#fileEntries[i];
|
|
1592
|
-
if (entry.type !== "message" || entry.message.role !== "assistant") continue;
|
|
1593
|
-
const message = entry.message as { content?: unknown };
|
|
1594
|
-
if (!Array.isArray(message.content)) continue;
|
|
1595
|
-
for (const block of message.content) {
|
|
1596
|
-
if (typeof block !== "object" || block === null) continue;
|
|
1597
|
-
if (!("type" in block) || (block as { type?: string }).type !== "toolCall") continue;
|
|
1598
|
-
const toolCall = block as { id?: string; arguments?: Record<string, unknown> };
|
|
1599
|
-
if (toolCall.id === toolCallId) {
|
|
1600
|
-
toolCall.arguments = args;
|
|
1601
|
-
updated = true;
|
|
1602
|
-
break;
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
if (updated) break;
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
if (updated && this.persist && this.#sessionFile) {
|
|
1609
|
-
await this.#rewriteFile();
|
|
1610
|
-
}
|
|
1611
|
-
return updated;
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
1584
|
/**
|
|
1615
1585
|
* Append a custom message entry (for extensions) that participates in LLM context.
|
|
1616
1586
|
* @param customType Hook identifier for filtering on reload
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
|
|
3
3
|
import type { Agent, AgentEvent } from "@nghyane/arcane-agent";
|
|
4
|
-
import type {
|
|
4
|
+
import type { ToolCall } from "@nghyane/arcane-ai";
|
|
5
5
|
import { isEnoent, logger } from "@nghyane/arcane-utils";
|
|
6
6
|
import type { Settings } from "../config/settings";
|
|
7
7
|
import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../patch";
|
|
8
8
|
import type { SecretObfuscator } from "../secrets/obfuscator";
|
|
9
9
|
import { resolveToCwd } from "../tools/path-utils";
|
|
10
|
-
import type { SessionManager } from "./session-manager";
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Mutable state for streaming edit abort detection.
|
|
@@ -222,37 +221,3 @@ async function checkPreviewPatchAsync(
|
|
|
222
221
|
agent.abort();
|
|
223
222
|
}
|
|
224
223
|
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Rewrite tool call arguments in agent state and persisted session history.
|
|
228
|
-
*/
|
|
229
|
-
export async function rewriteToolCallArgs(
|
|
230
|
-
agent: Agent,
|
|
231
|
-
sessionManager: SessionManager,
|
|
232
|
-
toolCallId: string,
|
|
233
|
-
args: Record<string, unknown>,
|
|
234
|
-
): Promise<void> {
|
|
235
|
-
let updated = false;
|
|
236
|
-
const messages = agent.state.messages;
|
|
237
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
238
|
-
const msg = messages[i];
|
|
239
|
-
if (msg.role !== "assistant") continue;
|
|
240
|
-
const assistantMsg = msg as AssistantMessage;
|
|
241
|
-
if (!Array.isArray(assistantMsg.content)) continue;
|
|
242
|
-
for (const block of assistantMsg.content) {
|
|
243
|
-
if (typeof block !== "object" || block === null) continue;
|
|
244
|
-
if (!("type" in block) || (block as { type?: string }).type !== "toolCall") continue;
|
|
245
|
-
const toolCall = block as { id?: string; arguments?: Record<string, unknown> };
|
|
246
|
-
if (toolCall.id === toolCallId) {
|
|
247
|
-
toolCall.arguments = args;
|
|
248
|
-
updated = true;
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
if (updated) break;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (updated) {
|
|
256
|
-
await sessionManager.rewriteAssistantToolCallArgs(toolCallId, args);
|
|
257
|
-
}
|
|
258
|
-
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -55,7 +55,8 @@ function isInteractiveResult(result: BashResult | BashInteractiveResult): result
|
|
|
55
55
|
export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails, Theme> {
|
|
56
56
|
readonly name = "bash";
|
|
57
57
|
readonly label = "Bash";
|
|
58
|
-
description =
|
|
58
|
+
description =
|
|
59
|
+
"Execute a shell command. Use grep/find instead of shell grep/find, read instead of cat/head/tail, edit instead of sed/awk, write instead of echo/printf redirects.";
|
|
59
60
|
readonly parameters = bashSchema;
|
|
60
61
|
readonly concurrency = "exclusive";
|
|
61
62
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { AgentTool } from "@nghyane/arcane-agent";
|
|
2
|
-
import { $env
|
|
3
|
-
import { getPreludeDocs, warmPythonEnvironment } from "../ipy/executor";
|
|
4
|
-
import { checkPythonKernelAvailability } from "../ipy/kernel";
|
|
2
|
+
import { $env } from "@nghyane/arcane-utils";
|
|
5
3
|
import { LspTool } from "../lsp";
|
|
6
4
|
import { EditTool } from "../patch";
|
|
7
5
|
import { TaskTool } from "../task";
|
|
@@ -106,37 +104,8 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
106
104
|
const enableLsp = session.enableLsp ?? true;
|
|
107
105
|
const requestedTools = toolNames && toolNames.length > 0 ? [...new Set(toolNames)] : undefined;
|
|
108
106
|
const pythonMode = getPythonModeFromEnv() ?? session.settings.get("python.toolMode");
|
|
109
|
-
const skipPythonPreflight = session.skipPythonPreflight === true;
|
|
110
|
-
let pythonAvailable = true;
|
|
111
|
-
const shouldCheckPython =
|
|
112
|
-
!skipPythonPreflight &&
|
|
113
|
-
pythonMode !== "bash-only" &&
|
|
114
|
-
(requestedTools === undefined || requestedTools.includes("python"));
|
|
115
|
-
const isTestEnv = Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
|
|
116
|
-
const skipPythonWarm = isTestEnv || $env.ARCANE_PYTHON_SKIP_CHECK === "1";
|
|
117
|
-
if (shouldCheckPython) {
|
|
118
|
-
const availability = await checkPythonKernelAvailability(session.cwd);
|
|
119
|
-
time("createTools:pythonCheck");
|
|
120
|
-
pythonAvailable = availability.ok;
|
|
121
|
-
if (!availability.ok) {
|
|
122
|
-
logger.warn("Python kernel unavailable, falling back to bash", {
|
|
123
|
-
reason: availability.reason,
|
|
124
|
-
});
|
|
125
|
-
} else if (!skipPythonWarm && getPreludeDocs().length === 0) {
|
|
126
|
-
const sessionFile = session.getSessionFile?.() ?? undefined;
|
|
127
|
-
const warmSessionId = sessionFile ? `session:${sessionFile}:cwd:${session.cwd}` : `cwd:${session.cwd}`;
|
|
128
|
-
try {
|
|
129
|
-
await warmPythonEnvironment(session.cwd, warmSessionId, session.settings.get("python.sharedGateway"));
|
|
130
|
-
time("createTools:warmPython");
|
|
131
|
-
} catch (err) {
|
|
132
|
-
logger.warn("Failed to warm Python environment", {
|
|
133
|
-
error: err instanceof Error ? err.message : String(err),
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
107
|
|
|
139
|
-
const effectiveMode =
|
|
108
|
+
const effectiveMode = pythonMode;
|
|
140
109
|
const allowBash = effectiveMode !== "ipy-only";
|
|
141
110
|
const allowPython = effectiveMode !== "bash-only";
|
|
142
111
|
if (
|
package/src/tools/fetch.ts
CHANGED
|
@@ -850,7 +850,7 @@ export interface FetchToolDetails {
|
|
|
850
850
|
export class FetchTool implements AgentTool<typeof fetchSchema, FetchToolDetails, Theme> {
|
|
851
851
|
readonly name = "fetch";
|
|
852
852
|
readonly label = "Fetch";
|
|
853
|
-
description = "Fetch a URL and return its content";
|
|
853
|
+
description = "Fetch a URL and return its content. Do NOT use for localhost or local URLs; use bash curl instead.";
|
|
854
854
|
readonly parameters = fetchSchema;
|
|
855
855
|
|
|
856
856
|
constructor(private readonly session: ToolSession) {}
|
package/src/tools/grep.ts
CHANGED
|
@@ -54,7 +54,8 @@ type GrepParams = Static<typeof grepSchema>;
|
|
|
54
54
|
export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails, Theme> {
|
|
55
55
|
readonly name = "grep";
|
|
56
56
|
readonly label = "Grep";
|
|
57
|
-
description =
|
|
57
|
+
description =
|
|
58
|
+
"Search file contents with regex. Use for exact text matches (variable names, function calls, strings). Use explore for semantic/conceptual searches.";
|
|
58
59
|
readonly parameters = grepSchema;
|
|
59
60
|
|
|
60
61
|
constructor(private readonly session: ToolSession) {}
|
package/src/tools/python.ts
CHANGED
|
@@ -4,11 +4,13 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
4
4
|
import type { ImageContent } from "@nghyane/arcane-ai";
|
|
5
5
|
import type { Component } from "@nghyane/arcane-tui";
|
|
6
6
|
import { Text } from "@nghyane/arcane-tui";
|
|
7
|
+
import { $env, logger } from "@nghyane/arcane-utils";
|
|
7
8
|
import { getProjectDir } from "@nghyane/arcane-utils/dirs";
|
|
8
9
|
import { type Static, Type } from "@sinclair/typebox";
|
|
9
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
|
-
import { executePython, type PythonExecutorOptions } from "../ipy/executor";
|
|
11
|
+
import { executePython, getPreludeDocs, type PythonExecutorOptions, warmPythonEnvironment } from "../ipy/executor";
|
|
11
12
|
import type { PythonStatusEvent } from "../ipy/kernel";
|
|
13
|
+
import { checkPythonKernelAvailability } from "../ipy/kernel";
|
|
12
14
|
import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary } from "../session/streaming-output";
|
|
13
15
|
import type { Theme } from "../theme/theme";
|
|
14
16
|
import { renderCodeCell, renderStatusLine } from "../tui";
|
|
@@ -76,6 +78,8 @@ export class PythonTool implements AgentTool<typeof pythonSchema, any, Theme> {
|
|
|
76
78
|
readonly concurrency = "exclusive";
|
|
77
79
|
|
|
78
80
|
readonly #proxyExecutor?: PythonProxyExecutor;
|
|
81
|
+
#initialized = false;
|
|
82
|
+
#initPromise: Promise<void> | undefined;
|
|
79
83
|
|
|
80
84
|
constructor(
|
|
81
85
|
private readonly session: ToolSession | null,
|
|
@@ -84,6 +88,52 @@ export class PythonTool implements AgentTool<typeof pythonSchema, any, Theme> {
|
|
|
84
88
|
this.#proxyExecutor = options?.proxyExecutor;
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
async #ensureInitialized(): Promise<void> {
|
|
92
|
+
if (this.#initialized) return;
|
|
93
|
+
if (this.#initPromise) return this.#initPromise;
|
|
94
|
+
this.#initPromise = this.#doInit();
|
|
95
|
+
return this.#initPromise;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async #doInit(): Promise<void> {
|
|
99
|
+
try {
|
|
100
|
+
const isTestEnv = Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
|
|
101
|
+
if (isTestEnv || $env.ARCANE_PYTHON_SKIP_CHECK === "1") {
|
|
102
|
+
this.#initialized = true;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!this.session) {
|
|
106
|
+
this.#initialized = true;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const availability = await checkPythonKernelAvailability(this.session.cwd);
|
|
110
|
+
if (!availability.ok) {
|
|
111
|
+
throw new ToolError(`Python kernel unavailable: ${availability.reason}`);
|
|
112
|
+
}
|
|
113
|
+
if (getPreludeDocs().length === 0) {
|
|
114
|
+
const sessionFile = this.session.getSessionFile?.() ?? undefined;
|
|
115
|
+
const warmSessionId = sessionFile
|
|
116
|
+
? `session:${sessionFile}:cwd:${this.session.cwd}`
|
|
117
|
+
: `cwd:${this.session.cwd}`;
|
|
118
|
+
try {
|
|
119
|
+
await warmPythonEnvironment(
|
|
120
|
+
this.session.cwd,
|
|
121
|
+
warmSessionId,
|
|
122
|
+
this.session.settings.get("python.sharedGateway"),
|
|
123
|
+
);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
logger.warn("Failed to warm Python environment", {
|
|
126
|
+
error: err instanceof Error ? err.message : String(err),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
this.#initialized = true;
|
|
131
|
+
} catch (err) {
|
|
132
|
+
this.#initPromise = undefined;
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
87
137
|
async execute(
|
|
88
138
|
_toolCallId: string,
|
|
89
139
|
params: Static<typeof pythonSchema>,
|
|
@@ -99,6 +149,8 @@ export class PythonTool implements AgentTool<typeof pythonSchema, any, Theme> {
|
|
|
99
149
|
throw new ToolError("Python tool requires a session when not using proxy executor");
|
|
100
150
|
}
|
|
101
151
|
|
|
152
|
+
await this.#ensureInitialized();
|
|
153
|
+
|
|
102
154
|
const { cells, timeout: rawTimeout = 30, cwd, reset } = params;
|
|
103
155
|
// Clamp to reasonable range: 1s - 600s (10 min)
|
|
104
156
|
const timeoutSec = Math.max(1, Math.min(600, rawTimeout));
|
package/src/tools/read.ts
CHANGED
|
@@ -533,7 +533,8 @@ type ReadParams = ReadToolInput;
|
|
|
533
533
|
export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails, Theme> {
|
|
534
534
|
readonly name = "read";
|
|
535
535
|
readonly label = "Read";
|
|
536
|
-
description =
|
|
536
|
+
description =
|
|
537
|
+
"Read file contents, list directories, or view images. When possible, call in parallel for all files you need. Avoid tiny repeated slices — read a larger range instead.";
|
|
537
538
|
readonly parameters = readSchema;
|
|
538
539
|
readonly nonAbortable = true;
|
|
539
540
|
|
package/src/tools/write.ts
CHANGED
|
@@ -71,7 +71,7 @@ type WriteParams = WriteToolInput;
|
|
|
71
71
|
export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails, Theme> {
|
|
72
72
|
readonly name = "write";
|
|
73
73
|
readonly label = "Write";
|
|
74
|
-
description = "Create a new file";
|
|
74
|
+
description = "Create a new file. For existing files, prefer edit instead — even for extensive changes.";
|
|
75
75
|
readonly parameters = writeSchema;
|
|
76
76
|
readonly nonAbortable = true;
|
|
77
77
|
readonly concurrency = "exclusive";
|
package/src/web/search/index.ts
CHANGED
|
@@ -265,7 +265,8 @@ export async function runSearchQuery(params: SearchParams): Promise<{
|
|
|
265
265
|
export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRenderDetails, Theme> {
|
|
266
266
|
readonly name = "web_search";
|
|
267
267
|
readonly label = "Web Search";
|
|
268
|
-
readonly description =
|
|
268
|
+
readonly description =
|
|
269
|
+
"Search the web for up-to-date information. Use fetch to read full content from a specific URL.";
|
|
269
270
|
readonly parameters = webSearchSchema;
|
|
270
271
|
readonly renderCall = renderSearchCall;
|
|
271
272
|
readonly renderResult = renderSearchResult;
|
package/src/patch/normative.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Normalize applied patch output into a canonical edit tool payload.
|
|
3
|
-
*/
|
|
4
|
-
import { generateUnifiedDiffString } from "./diff";
|
|
5
|
-
import { normalizeToLF, stripBom } from "./normalize";
|
|
6
|
-
import { parseHunks } from "./parser";
|
|
7
|
-
import type { PatchInput } from "./types";
|
|
8
|
-
|
|
9
|
-
export interface NormativePatchOptions {
|
|
10
|
-
path: string;
|
|
11
|
-
rename?: string;
|
|
12
|
-
oldContent: string;
|
|
13
|
-
newContent: string;
|
|
14
|
-
contextLines?: number;
|
|
15
|
-
anchor?: string | string[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Normative patch input is the MongoDB-style update variant */
|
|
19
|
-
|
|
20
|
-
function applyAnchors(diff: string, anchors: Array<string | undefined> | undefined): string {
|
|
21
|
-
if (!anchors || anchors.length === 0) {
|
|
22
|
-
return diff;
|
|
23
|
-
}
|
|
24
|
-
const lines = diff.split("\n");
|
|
25
|
-
let anchorIndex = 0;
|
|
26
|
-
for (let i = 0; i < lines.length; i++) {
|
|
27
|
-
if (!lines[i].startsWith("@@")) continue;
|
|
28
|
-
const anchor = anchors[anchorIndex];
|
|
29
|
-
if (anchor !== undefined) {
|
|
30
|
-
lines[i] = anchor.trim().length === 0 ? "@@" : `@@ ${anchor}`;
|
|
31
|
-
}
|
|
32
|
-
anchorIndex++;
|
|
33
|
-
}
|
|
34
|
-
return lines.join("\n");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function deriveAnchors(diff: string): Array<string | undefined> {
|
|
38
|
-
const hunks = parseHunks(diff);
|
|
39
|
-
return hunks.map(hunk => {
|
|
40
|
-
if (hunk.oldLines.length === 0 || hunk.newLines.length === 0) {
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
const newLines = new Set(hunk.newLines);
|
|
44
|
-
for (const line of hunk.oldLines) {
|
|
45
|
-
const trimmed = line.trim();
|
|
46
|
-
if (trimmed.length === 0) continue;
|
|
47
|
-
if (!/[A-Za-z0-9_]/.test(trimmed)) continue;
|
|
48
|
-
if (newLines.has(line)) {
|
|
49
|
-
return trimmed;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return undefined;
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function buildNormativeUpdateInput(options: NormativePatchOptions): PatchInput {
|
|
57
|
-
const normalizedOld = normalizeToLF(stripBom(options.oldContent).text);
|
|
58
|
-
const normalizedNew = normalizeToLF(stripBom(options.newContent).text);
|
|
59
|
-
const diffResult = generateUnifiedDiffString(normalizedOld, normalizedNew, options.contextLines ?? 3);
|
|
60
|
-
let anchors: Array<string | undefined> | undefined =
|
|
61
|
-
typeof options.anchor === "string" ? [options.anchor] : options.anchor;
|
|
62
|
-
if (!anchors) {
|
|
63
|
-
anchors = deriveAnchors(diffResult.diff);
|
|
64
|
-
}
|
|
65
|
-
const diff = applyAnchors(diffResult.diff, anchors);
|
|
66
|
-
return {
|
|
67
|
-
path: options.path,
|
|
68
|
-
op: "update",
|
|
69
|
-
rename: options.rename,
|
|
70
|
-
diff,
|
|
71
|
-
};
|
|
72
|
-
}
|