@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.
Files changed (74) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/package.json +7 -15
  3. package/src/cli/setup-cli.ts +2 -62
  4. package/src/commands/setup.ts +1 -1
  5. package/src/config/keybindings.ts +1 -4
  6. package/src/config/settings-schema.ts +23 -98
  7. package/src/config/settings.ts +0 -1
  8. package/src/exa/mcp-client.ts +57 -2
  9. package/src/extensibility/custom-tools/types.ts +2 -2
  10. package/src/extensibility/custom-tools/wrapper.ts +1 -1
  11. package/src/extensibility/extensions/wrapper.ts +1 -1
  12. package/src/extensibility/hooks/tool-wrapper.ts +1 -1
  13. package/src/internal-urls/docs-index.generated.ts +1 -2
  14. package/src/internal-urls/index.ts +2 -4
  15. package/src/internal-urls/router.ts +2 -2
  16. package/src/internal-urls/types.ts +2 -2
  17. package/src/mcp/oauth-flow.ts +1 -1
  18. package/src/modes/components/custom-editor.ts +6 -2
  19. package/src/modes/controllers/command-controller.ts +4 -46
  20. package/src/modes/controllers/input-controller.ts +123 -6
  21. package/src/modes/interactive-mode.ts +1 -84
  22. package/src/modes/types.ts +0 -1
  23. package/src/patch/edit-tool.ts +2 -11
  24. package/src/patch/hashline.ts +42 -0
  25. package/src/prompts/agents/explore.md +4 -2
  26. package/src/prompts/agents/librarian.md +4 -6
  27. package/src/prompts/agents/reviewer.md +1 -1
  28. package/src/prompts/agents/task.md +5 -1
  29. package/src/prompts/system/system-prompt.md +29 -18
  30. package/src/prompts/thread-extract.md +16 -0
  31. package/src/prompts/tools/render-mermaid.md +9 -0
  32. package/src/sdk.ts +12 -37
  33. package/src/session/agent-session.ts +5 -10
  34. package/src/session/retry-utils.ts +1 -1
  35. package/src/session/session-index.ts +329 -0
  36. package/src/session/session-manager.ts +0 -30
  37. package/src/session/streaming-edit.ts +1 -36
  38. package/src/slash-commands/builtin-registry.ts +0 -16
  39. package/src/task/index.ts +1 -1
  40. package/src/tools/ask.ts +9 -6
  41. package/src/tools/bash-skill-urls.ts +3 -3
  42. package/src/tools/bash.ts +2 -1
  43. package/src/tools/create-tools.ts +28 -33
  44. package/src/tools/fetch.ts +1 -1
  45. package/src/tools/find-thread.ts +120 -0
  46. package/src/tools/grep.ts +2 -1
  47. package/src/tools/index.ts +5 -0
  48. package/src/tools/python.ts +53 -1
  49. package/src/tools/read-thread.ts +409 -0
  50. package/src/tools/read.ts +4 -3
  51. package/src/tools/render-mermaid.ts +68 -0
  52. package/src/tools/save-memory.ts +182 -0
  53. package/src/tools/write.ts +1 -1
  54. package/src/web/search/index.ts +4 -1
  55. package/src/web/search/provider.ts +3 -0
  56. package/src/web/search/providers/anthropic.ts +1 -0
  57. package/src/web/search/providers/gemini.ts +122 -37
  58. package/src/web/search/providers/kagi.ts +163 -0
  59. package/src/web/search/types.ts +1 -0
  60. package/src/internal-urls/memory-protocol.ts +0 -133
  61. package/src/memories/index.ts +0 -1099
  62. package/src/memories/storage.ts +0 -563
  63. package/src/patch/normative.ts +0 -72
  64. package/src/prompts/memories/consolidation.md +0 -30
  65. package/src/prompts/memories/read_path.md +0 -11
  66. package/src/prompts/memories/stage_one_input.md +0 -6
  67. package/src/prompts/memories/stage_one_system.md +0 -21
  68. package/src/stt/downloader.ts +0 -68
  69. package/src/stt/index.ts +0 -3
  70. package/src/stt/recorder.ts +0 -351
  71. package/src/stt/setup.ts +0 -50
  72. package/src/stt/stt-controller.ts +0 -160
  73. package/src/stt/transcribe.py +0 -70
  74. 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 { hsvToRgb, isEnoent, logger, postmortem } from "@nghyane/arcane-utils";
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
  }
@@ -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>;
@@ -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, TInput>,
235
+ _onUpdate?: AgentToolUpdateCallback<EditToolDetails>,
237
236
  context?: AgentToolContext,
238
- ): Promise<AgentToolResult<EditToolDetails, TInput>> {
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
 
@@ -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
- Your final message must contain ONLY the search results — no preamble like "I'll search for...".
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>Specialized remote repository understanding agent. Explore GitHub repositories, trace code flow across repos, explain architecture, find implementations, and surface relevant history.</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
- <github>
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
- </search>
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>Senior engineer reviewing a proposed change. Identify bugs the author would want fixed before merge.</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 agents instructions and AGENTS.md conventions.
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
- After completing changes, verify using commands from AGENTS.md or the project's config. Format typecheck/lint test (if relevant) build (if required).
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
- <contract>
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
- </contract>
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://, memory://, skill://, rule://)
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
- // Discover custom commands (TypeScript slash commands)
941
- const customCommandsResult: CustomCommandsLoadResult = options.disableExtensionDiscovery
942
- ? { commands: [], errors: [] }
943
- : await loadCustomCommandsInternal({ cwd, agentDir });
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 rebuildSystemPrompt(initialToolNames, toolRegistry);
1080
- time("buildSystemPrompt");
1081
-
1082
- const promptTemplates = options.promptTemplates ?? (await discoverPromptTemplates(cwd, agentDir));
1083
- time("discoverPromptTemplates");
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, $normative, toolCallId, details, isError, content } = event.message as {
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 a previous session. Use this context to continue the work seamlessly.`;
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
  /**