@nghyane/arcane 0.1.16 → 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/CHANGELOG.md +21 -0
- package/package.json +7 -15
- 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 +23 -98
- package/src/config/settings.ts +0 -1
- package/src/exa/mcp-client.ts +57 -2
- 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/internal-urls/docs-index.generated.ts +1 -2
- package/src/internal-urls/index.ts +2 -4
- package/src/internal-urls/router.ts +2 -2
- package/src/internal-urls/types.ts +2 -2
- package/src/mcp/oauth-flow.ts +1 -1
- package/src/modes/components/custom-editor.ts +6 -2
- package/src/modes/controllers/command-controller.ts +4 -46
- 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/patch/hashline.ts +42 -0
- 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 +29 -18
- package/src/prompts/thread-extract.md +16 -0
- package/src/prompts/tools/render-mermaid.md +9 -0
- package/src/sdk.ts +12 -37
- package/src/session/agent-session.ts +5 -10
- package/src/session/retry-utils.ts +1 -1
- package/src/session/session-index.ts +329 -0
- package/src/session/session-manager.ts +0 -30
- package/src/session/streaming-edit.ts +1 -36
- package/src/slash-commands/builtin-registry.ts +0 -16
- package/src/task/index.ts +1 -1
- package/src/tools/ask.ts +9 -6
- package/src/tools/bash-skill-urls.ts +3 -3
- package/src/tools/bash.ts +2 -1
- package/src/tools/create-tools.ts +28 -33
- package/src/tools/fetch.ts +1 -1
- package/src/tools/find-thread.ts +120 -0
- package/src/tools/grep.ts +2 -1
- package/src/tools/index.ts +5 -0
- package/src/tools/python.ts +53 -1
- package/src/tools/read-thread.ts +409 -0
- package/src/tools/read.ts +4 -3
- package/src/tools/render-mermaid.ts +68 -0
- package/src/tools/save-memory.ts +182 -0
- package/src/tools/write.ts +1 -1
- package/src/web/search/index.ts +4 -1
- package/src/web/search/provider.ts +3 -0
- package/src/web/search/providers/anthropic.ts +1 -0
- package/src/web/search/providers/gemini.ts +122 -37
- package/src/web/search/providers/kagi.ts +163 -0
- package/src/web/search/types.ts +1 -0
- package/src/internal-urls/memory-protocol.ts +0 -133
- package/src/memories/index.ts +0 -1099
- package/src/memories/storage.ts +0 -563
- package/src/patch/normative.ts +0 -72
- package/src/prompts/memories/consolidation.md +0 -30
- package/src/prompts/memories/read_path.md +0 -11
- package/src/prompts/memories/stage_one_input.md +0 -6
- package/src/prompts/memories/stage_one_system.md +0 -21
- 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
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
Text,
|
|
16
16
|
TUI,
|
|
17
17
|
} from "@nghyane/arcane-tui";
|
|
18
|
-
import {
|
|
18
|
+
import { isEnoent, logger, postmortem } from "@nghyane/arcane-utils";
|
|
19
19
|
import { APP_NAME, getProjectDir } from "@nghyane/arcane-utils/dirs";
|
|
20
20
|
import chalk from "chalk";
|
|
21
21
|
import { KeybindingsManager } from "../config/keybindings";
|
|
@@ -27,7 +27,6 @@ import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
|
27
27
|
import { HistoryStorage } from "../session/history-storage";
|
|
28
28
|
import type { SessionContext, SessionManager } from "../session/session-manager";
|
|
29
29
|
import { getRecentSessions } from "../session/session-manager";
|
|
30
|
-
import { STTController, type SttState } from "../stt";
|
|
31
30
|
import { setMermaidRenderCallback } from "../theme/mermaid-cache";
|
|
32
31
|
import type { Theme } from "../theme/theme";
|
|
33
32
|
import { getEditorTheme, getMarkdownTheme, onThemeChange, theme } from "../theme/theme";
|
|
@@ -145,11 +144,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
145
144
|
readonly #inputController: InputController;
|
|
146
145
|
readonly #selectorController: SelectorController;
|
|
147
146
|
readonly #uiHelpers: UiHelpers;
|
|
148
|
-
#sttController: STTController | undefined;
|
|
149
|
-
#voiceAnimationInterval: NodeJS.Timeout | undefined;
|
|
150
|
-
#voiceHue = 0;
|
|
151
|
-
#voicePreviousShowHardwareCursor: boolean | null = null;
|
|
152
|
-
#voicePreviousUseTerminalCursor: boolean | null = null;
|
|
153
147
|
#resizeHandler?: () => void;
|
|
154
148
|
|
|
155
149
|
constructor(
|
|
@@ -501,11 +495,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
501
495
|
this.loadingAnimation.stop();
|
|
502
496
|
this.loadingAnimation = undefined;
|
|
503
497
|
}
|
|
504
|
-
this.#cleanupMicAnimation();
|
|
505
|
-
if (this.#sttController) {
|
|
506
|
-
this.#sttController.dispose();
|
|
507
|
-
this.#sttController = undefined;
|
|
508
|
-
}
|
|
509
498
|
this.#extensionUiController.clearExtensionTerminalInputListeners();
|
|
510
499
|
this.statusLine.dispose();
|
|
511
500
|
if (this.#resizeHandler) {
|
|
@@ -726,78 +715,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
726
715
|
return this.#commandController.handleMemoryCommand(text);
|
|
727
716
|
}
|
|
728
717
|
|
|
729
|
-
async handleSTTToggle(): Promise<void> {
|
|
730
|
-
if (!settings.get("stt.enabled")) {
|
|
731
|
-
this.showWarning("Speech-to-text is disabled. Enable it in settings: stt.enabled");
|
|
732
|
-
return;
|
|
733
|
-
}
|
|
734
|
-
if (!this.#sttController) {
|
|
735
|
-
this.#sttController = new STTController();
|
|
736
|
-
}
|
|
737
|
-
await this.#sttController.toggle(this.editor, {
|
|
738
|
-
showWarning: (msg: string) => this.showWarning(msg),
|
|
739
|
-
showStatus: (msg: string) => this.showStatus(msg),
|
|
740
|
-
onStateChange: (state: SttState) => {
|
|
741
|
-
if (state === "recording") {
|
|
742
|
-
this.#voicePreviousShowHardwareCursor = this.ui.getShowHardwareCursor();
|
|
743
|
-
this.#voicePreviousUseTerminalCursor = this.editor.getUseTerminalCursor();
|
|
744
|
-
this.ui.setShowHardwareCursor(false);
|
|
745
|
-
this.editor.setUseTerminalCursor(false);
|
|
746
|
-
this.#startMicAnimation();
|
|
747
|
-
} else if (state === "transcribing") {
|
|
748
|
-
this.#stopMicAnimation();
|
|
749
|
-
this.editor.cursorOverride = `\x1b[38;2;200;200;200m${theme.icon.mic}\x1b[0m`;
|
|
750
|
-
this.editor.cursorOverrideWidth = 1;
|
|
751
|
-
} else {
|
|
752
|
-
this.#cleanupMicAnimation();
|
|
753
|
-
}
|
|
754
|
-
this.updateEditorTopBorder();
|
|
755
|
-
this.ui.requestRender();
|
|
756
|
-
},
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
#updateMicIcon(): void {
|
|
761
|
-
const { r, g, b } = hsvToRgb({ h: this.#voiceHue, s: 0.9, v: 1.0 });
|
|
762
|
-
this.editor.cursorOverride = `\x1b[38;2;${r};${g};${b}m${theme.icon.mic}\x1b[0m`;
|
|
763
|
-
this.editor.cursorOverrideWidth = 1;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
#startMicAnimation(): void {
|
|
767
|
-
if (this.#voiceAnimationInterval) return;
|
|
768
|
-
this.#voiceHue = 0;
|
|
769
|
-
this.#updateMicIcon();
|
|
770
|
-
this.#voiceAnimationInterval = setInterval(() => {
|
|
771
|
-
this.#voiceHue = (this.#voiceHue + 8) % 360;
|
|
772
|
-
this.#updateMicIcon();
|
|
773
|
-
this.ui.requestRender();
|
|
774
|
-
}, 60);
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
#stopMicAnimation(): void {
|
|
778
|
-
if (this.#voiceAnimationInterval) {
|
|
779
|
-
clearInterval(this.#voiceAnimationInterval);
|
|
780
|
-
this.#voiceAnimationInterval = undefined;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
#cleanupMicAnimation(): void {
|
|
785
|
-
if (this.#voiceAnimationInterval) {
|
|
786
|
-
clearInterval(this.#voiceAnimationInterval);
|
|
787
|
-
this.#voiceAnimationInterval = undefined;
|
|
788
|
-
}
|
|
789
|
-
this.editor.cursorOverride = undefined;
|
|
790
|
-
this.editor.cursorOverrideWidth = undefined;
|
|
791
|
-
if (this.#voicePreviousShowHardwareCursor !== null) {
|
|
792
|
-
this.ui.setShowHardwareCursor(this.#voicePreviousShowHardwareCursor);
|
|
793
|
-
this.#voicePreviousShowHardwareCursor = null;
|
|
794
|
-
}
|
|
795
|
-
if (this.#voicePreviousUseTerminalCursor !== null) {
|
|
796
|
-
this.editor.setUseTerminalCursor(this.#voicePreviousUseTerminalCursor);
|
|
797
|
-
this.#voicePreviousUseTerminalCursor = null;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
718
|
showDebugSelector(): void {
|
|
802
719
|
this.#selectorController.showDebugSelector();
|
|
803
720
|
}
|
package/src/modes/types.ts
CHANGED
|
@@ -149,7 +149,6 @@ export interface InteractiveModeContext {
|
|
|
149
149
|
handleHandoffCommand(customInstructions?: string): Promise<void>;
|
|
150
150
|
handleMoveCommand(targetPath: string): Promise<void>;
|
|
151
151
|
handleMemoryCommand(text: string): Promise<void>;
|
|
152
|
-
handleSTTToggle(): Promise<void>;
|
|
153
152
|
executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto?: boolean): Promise<void>;
|
|
154
153
|
openInBrowser(urlOrPath: string): void;
|
|
155
154
|
refreshSlashCommandState(cwd?: string): Promise<void>;
|
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
|
|
package/src/patch/hashline.ts
CHANGED
|
@@ -566,6 +566,37 @@ export function validateLineRef(ref: { line: number; hash: string }, fileLines:
|
|
|
566
566
|
// Edit Application
|
|
567
567
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
568
568
|
|
|
569
|
+
/**
|
|
570
|
+
* Detect suspicious Unicode escape placeholders in edit lines.
|
|
571
|
+
* LLMs sometimes emit literal `\uDDDD` strings instead of actual Unicode characters.
|
|
572
|
+
* Returns a warning message if detected, undefined otherwise.
|
|
573
|
+
*/
|
|
574
|
+
function detectUnicodeEscapePlaceholders(lines: string[]): string | undefined {
|
|
575
|
+
for (const line of lines) {
|
|
576
|
+
if (/\\u[0-9A-Fa-f]{4}/.test(line)) {
|
|
577
|
+
return "Warning: edit content contains literal Unicode escape sequences (\\uXXXX). These may be intended as actual Unicode characters.";
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Auto-correct escaped tab indentation in edit lines.
|
|
585
|
+
* When enabled via ARCANE_HASHLINE_AUTOCORRECT_ESCAPED_TABS=1, replaces
|
|
586
|
+
* leading `\\t` sequences (literal backslash-t from JSON) with real tab characters.
|
|
587
|
+
*/
|
|
588
|
+
function autocorrectEscapedTabs(lines: string[]): string[] {
|
|
589
|
+
if (Bun.env.ARCANE_HASHLINE_AUTOCORRECT_ESCAPED_TABS !== "1") {
|
|
590
|
+
return lines;
|
|
591
|
+
}
|
|
592
|
+
return lines.map(line => {
|
|
593
|
+
const match = line.match(/^((?:\\t)+)/);
|
|
594
|
+
if (!match) return line;
|
|
595
|
+
const tabCount = match[1].length / 2; // each \\t is 2 chars
|
|
596
|
+
return "\t".repeat(tabCount) + line.slice(match[1].length);
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
569
600
|
/**
|
|
570
601
|
* Apply an array of hashline edits to file content.
|
|
571
602
|
*
|
|
@@ -599,6 +630,16 @@ export function applyHashlineEdits(
|
|
|
599
630
|
|
|
600
631
|
const autocorrect = Bun.env.ARCANE_HL_AUTOCORRECT === "1";
|
|
601
632
|
|
|
633
|
+
// Collect warnings and auto-correct edit content
|
|
634
|
+
const warnings: string[] = [];
|
|
635
|
+
for (const edit of edits) {
|
|
636
|
+
const unicodeWarning = detectUnicodeEscapePlaceholders(edit.content);
|
|
637
|
+
if (unicodeWarning && !warnings.includes(unicodeWarning)) {
|
|
638
|
+
warnings.push(unicodeWarning);
|
|
639
|
+
}
|
|
640
|
+
edit.content = autocorrectEscapedTabs(edit.content);
|
|
641
|
+
}
|
|
642
|
+
|
|
602
643
|
function collectExplicitlyTouchedLines(): Set<number> {
|
|
603
644
|
const touched = new Set<number>();
|
|
604
645
|
for (const edit of edits) {
|
|
@@ -914,6 +955,7 @@ export function applyHashlineEdits(
|
|
|
914
955
|
return {
|
|
915
956
|
content: finalContent,
|
|
916
957
|
firstChangedLine,
|
|
958
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
917
959
|
...(noopEdits.length > 0 ? { noopEdits } : {}),
|
|
918
960
|
};
|
|
919
961
|
|
|
@@ -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.
|
|
@@ -162,11 +169,22 @@ Best practices:
|
|
|
162
169
|
- Run multiple sub-agents concurrently if tasks are independent with disjoint write targets.
|
|
163
170
|
{{/has}}
|
|
164
171
|
|
|
172
|
+
### Cross-session Knowledge
|
|
173
|
+
|
|
174
|
+
Tools: `find_thread`, `read_thread`, `save_memory`
|
|
175
|
+
**Proactive search triggers** — use `find_thread` when:
|
|
176
|
+
- User mentions past work: "we did this before", "last time", "in a previous session"
|
|
177
|
+
- User asks "what did we do about X" or "how did we solve Y"
|
|
178
|
+
- Task seems related to work that may have been done before
|
|
179
|
+
- Handoff context references a parent thread and you need more detail
|
|
180
|
+
**Do NOT search when:**
|
|
181
|
+
- Question is about current session context
|
|
182
|
+
- Generic coding question with no project-specific history
|
|
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
|
+
|
|
165
186
|
### Verification
|
|
166
|
-
|
|
167
|
-
Report evidence concisely: counts, pass/fail, error summary.
|
|
168
|
-
If unrelated pre-existing failures block you, say so and scope your change.
|
|
169
|
-
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.
|
|
170
188
|
|
|
171
189
|
### Concurrency Awareness
|
|
172
190
|
You are not alone in the codebase. Others may edit concurrently.
|
|
@@ -185,7 +203,7 @@ Never run destructive git commands, bulk overwrites, or delete code you didn't w
|
|
|
185
203
|
- Resolve blockers before yielding.
|
|
186
204
|
</procedure>
|
|
187
205
|
|
|
188
|
-
<
|
|
206
|
+
<critical>
|
|
189
207
|
These are inviolable. Violation is system failure.
|
|
190
208
|
1. Never claim unverified correctness. Verify the effect — confirm behavioral changes are observable.
|
|
191
209
|
2. Never yield unless your deliverable is complete. Fix errors you introduced before yielding.
|
|
@@ -194,7 +212,10 @@ These are inviolable. Violation is system failure.
|
|
|
194
212
|
5. Never solve the wished-for problem instead of the actual problem.
|
|
195
213
|
6. Never ask for information obtainable from tools, repo context, or files.
|
|
196
214
|
7. Full cutover within scope — update every call site. No backwards-compat shims.
|
|
197
|
-
|
|
215
|
+
|
|
216
|
+
Keep going until fully resolved. This matters.
|
|
217
|
+
</critical>
|
|
218
|
+
|
|
198
219
|
|
|
199
220
|
<project>
|
|
200
221
|
{{#if contextFiles.length}}
|
|
@@ -252,16 +273,6 @@ Scan descriptions vs task domain — read skill if ≥50% likely relevant.
|
|
|
252
273
|
</rules>
|
|
253
274
|
{{/if}}
|
|
254
275
|
|
|
255
|
-
{{#if memories.length}}
|
|
256
|
-
<memories>
|
|
257
|
-
{{#each memories}}
|
|
258
|
-
<memory path="{{path}}">
|
|
259
|
-
{{content}}
|
|
260
|
-
</memory>
|
|
261
|
-
{{/each}}
|
|
262
|
-
</memories>
|
|
263
|
-
{{/if}}
|
|
264
|
-
|
|
265
276
|
{{#if preloadedSkills.length}}
|
|
266
277
|
{{#each preloadedSkills}}
|
|
267
278
|
<skill name="{{name}}">
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
You are helping extract relevant information from a conversation thread based on a goal.
|
|
2
|
+
|
|
3
|
+
## Task
|
|
4
|
+
|
|
5
|
+
I am providing a conversation thread rendered as markdown, along with a goal describing what information to extract.
|
|
6
|
+
|
|
7
|
+
Your job is to:
|
|
8
|
+
1. Analyze the thread content
|
|
9
|
+
2. Identify information that is relevant to the goal
|
|
10
|
+
3. Extract and preserve those relevant parts with full fidelity
|
|
11
|
+
|
|
12
|
+
## Rules
|
|
13
|
+
- Be concise but complete — include all relevant details
|
|
14
|
+
- Preserve code snippets, file paths, commands, and decisions exactly as they appear
|
|
15
|
+
- Omit pleasantries, failed attempts, and thinking-out-loud unless the goal asks for them
|
|
16
|
+
- If nothing relevant is found, say so briefly
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Convert Mermaid graph source into ASCII diagram output.
|
|
2
|
+
|
|
3
|
+
Parameters:
|
|
4
|
+
- `mermaid` (required): Mermaid graph text to render.
|
|
5
|
+
- `config` (optional): JSON render configuration (spacing and layout options).
|
|
6
|
+
Behavior:
|
|
7
|
+
- Returns ASCII diagram text.
|
|
8
|
+
- Saves full ASCII output to an artifact URL (`artifact://<id>`) when artifact storage is available.
|
|
9
|
+
- Returns an error when the Mermaid input is invalid or rendering fails.
|
package/src/sdk.ts
CHANGED
|
@@ -42,13 +42,11 @@ import {
|
|
|
42
42
|
ArtifactProtocolHandler,
|
|
43
43
|
DocsProtocolHandler,
|
|
44
44
|
InternalUrlRouter,
|
|
45
|
-
MemoryProtocolHandler,
|
|
46
45
|
RuleProtocolHandler,
|
|
47
46
|
SkillProtocolHandler,
|
|
48
47
|
} from "./internal-urls";
|
|
49
48
|
import { disposeAllKernelSessions } from "./ipy/executor";
|
|
50
49
|
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp";
|
|
51
|
-
import { buildMemoryToolDeveloperInstructions, getMemoryRoot, startMemoryStartupTask } from "./memories";
|
|
52
50
|
import { collectEnvSecrets, loadSecrets, obfuscateMessages, SecretObfuscator } from "./secrets";
|
|
53
51
|
import { AgentSession } from "./session/agent-session";
|
|
54
52
|
import { AuthStorage } from "./session/auth-storage";
|
|
@@ -735,7 +733,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
735
733
|
settings,
|
|
736
734
|
};
|
|
737
735
|
|
|
738
|
-
// Initialize internal URL router for internal protocols (agent://, artifact://,
|
|
736
|
+
// Initialize internal URL router for internal protocols (agent://, artifact://, skill://, rule://)
|
|
739
737
|
const internalRouter = new InternalUrlRouter();
|
|
740
738
|
const getArtifactsDir = () => {
|
|
741
739
|
const sessionFile = sessionManager.getSessionFile();
|
|
@@ -743,11 +741,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
743
741
|
};
|
|
744
742
|
internalRouter.register(new AgentProtocolHandler({ getArtifactsDir }));
|
|
745
743
|
internalRouter.register(new ArtifactProtocolHandler({ getArtifactsDir }));
|
|
746
|
-
internalRouter.register(
|
|
747
|
-
new MemoryProtocolHandler({
|
|
748
|
-
getMemoryRoot: () => getMemoryRoot(agentDir, settings.getCwd()),
|
|
749
|
-
}),
|
|
750
|
-
);
|
|
751
744
|
internalRouter.register(
|
|
752
745
|
new SkillProtocolHandler({
|
|
753
746
|
getSkills: () => skills,
|
|
@@ -937,16 +930,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
937
930
|
}
|
|
938
931
|
}
|
|
939
932
|
|
|
940
|
-
//
|
|
941
|
-
const
|
|
942
|
-
? { commands: [], errors: [] }
|
|
943
|
-
:
|
|
944
|
-
time("discoverCustomCommands");
|
|
945
|
-
if (!options.disableExtensionDiscovery) {
|
|
946
|
-
for (const { path, error } of customCommandsResult.errors) {
|
|
947
|
-
logger.error("Failed to load custom command", { path, error });
|
|
948
|
-
}
|
|
949
|
-
}
|
|
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 });
|
|
950
937
|
|
|
951
938
|
let extensionRunner: ExtensionRunner | undefined;
|
|
952
939
|
if (extensionsResult.extensions.length > 0) {
|
|
@@ -1027,7 +1014,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1027
1014
|
|
|
1028
1015
|
const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
|
|
1029
1016
|
toolContextStore.setToolNames(toolNames);
|
|
1030
|
-
const memoryInstructions = await buildMemoryToolDeveloperInstructions(agentDir, settings);
|
|
1031
1017
|
const defaultPrompt = await buildSystemPromptInternal({
|
|
1032
1018
|
cwd,
|
|
1033
1019
|
skills,
|
|
@@ -1037,7 +1023,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1037
1023
|
toolNames,
|
|
1038
1024
|
rules: rulebookRules,
|
|
1039
1025
|
skillsSettings: settings.getGroup("skills") as SkillsSettings,
|
|
1040
|
-
appendSystemPrompt: memoryInstructions,
|
|
1041
1026
|
});
|
|
1042
1027
|
|
|
1043
1028
|
if (options.systemPrompt === undefined) {
|
|
@@ -1054,7 +1039,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1054
1039
|
rules: rulebookRules,
|
|
1055
1040
|
skillsSettings: settings.getGroup("skills") as SkillsSettings,
|
|
1056
1041
|
customPrompt: options.systemPrompt,
|
|
1057
|
-
appendSystemPrompt: memoryInstructions,
|
|
1058
1042
|
});
|
|
1059
1043
|
}
|
|
1060
1044
|
return options.systemPrompt(defaultPrompt);
|
|
@@ -1076,16 +1060,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1076
1060
|
}
|
|
1077
1061
|
}
|
|
1078
1062
|
|
|
1079
|
-
const systemPrompt = await
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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");
|
|
1084
1070
|
toolSession.promptTemplates = promptTemplates;
|
|
1085
1071
|
|
|
1086
|
-
const slashCommands = options.slashCommands ?? (await discoverSlashCommands(cwd));
|
|
1087
|
-
time("discoverSlashCommands");
|
|
1088
|
-
|
|
1089
1072
|
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
1090
1073
|
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
|
|
1091
1074
|
const converted = convertToLlm(messages);
|
|
@@ -1258,14 +1241,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1258
1241
|
}
|
|
1259
1242
|
}
|
|
1260
1243
|
|
|
1261
|
-
startMemoryStartupTask({
|
|
1262
|
-
session,
|
|
1263
|
-
settings,
|
|
1264
|
-
modelRegistry,
|
|
1265
|
-
agentDir,
|
|
1266
|
-
isSubagent,
|
|
1267
|
-
});
|
|
1268
|
-
|
|
1269
1244
|
return {
|
|
1270
1245
|
session,
|
|
1271
1246
|
extensionsResult,
|
|
@@ -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());
|
|
@@ -2067,9 +2061,10 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
2067
2061
|
return undefined;
|
|
2068
2062
|
}
|
|
2069
2063
|
|
|
2070
|
-
// Start a new session
|
|
2064
|
+
// Start a new session with parent reference
|
|
2065
|
+
const parentThreadId = this.sessionManager.getSessionId();
|
|
2071
2066
|
await this.sessionManager.flush();
|
|
2072
|
-
await this.sessionManager.newSession();
|
|
2067
|
+
await this.sessionManager.newSession({ parentSession: parentThreadId });
|
|
2073
2068
|
this.agent.reset();
|
|
2074
2069
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
2075
2070
|
this.#steeringMessages = [];
|
|
@@ -2078,7 +2073,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
2078
2073
|
this.#todoReminderCount = 0;
|
|
2079
2074
|
|
|
2080
2075
|
// Inject the handoff document as a custom message
|
|
2081
|
-
const handoffContent = `<handoff-context>\n${handoffText}\n</handoff-context>\n\nThe above is a handoff document from
|
|
2076
|
+
const handoffContent = `<handoff-context thread="${parentThreadId}">\n${handoffText}\n</handoff-context>\n\nThe above is a handoff document from thread \`${parentThreadId}\`. Use this context to continue the work seamlessly. If you need additional details not covered above, use \`read_thread("${parentThreadId}", "your specific question")\` to query the original session.`;
|
|
2082
2077
|
this.sessionManager.appendCustomMessageEntry("handoff", handoffContent, true);
|
|
2083
2078
|
|
|
2084
2079
|
// Rebuild agent messages from session
|
|
@@ -17,7 +17,7 @@ export function isRetryableErrorMessage(errorMessage: string): boolean {
|
|
|
17
17
|
* Check if an error message indicates a usage/billing limit (non-transient).
|
|
18
18
|
*/
|
|
19
19
|
export function isUsageLimitErrorMessage(errorMessage: string): boolean {
|
|
20
|
-
return /usage.?limit|usage_limit_reached|limit_reached/i.test(errorMessage);
|
|
20
|
+
return /usage.?limit|usage_limit_reached|limit_reached|quota.?exhaust/i.test(errorMessage);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|