agent-sh 0.15.0 → 0.15.2

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 (124) hide show
  1. package/dist/agent/agent-loop.js +11 -8
  2. package/dist/agent/events.d.ts +4 -0
  3. package/docs/README.md +14 -0
  4. package/docs/agent.md +398 -0
  5. package/docs/architecture.md +196 -0
  6. package/docs/context-management.md +200 -0
  7. package/docs/extensions.md +951 -0
  8. package/docs/library.md +84 -0
  9. package/docs/troubleshooting.md +65 -0
  10. package/docs/tui-composition.md +294 -0
  11. package/docs/usage.md +306 -0
  12. package/examples/extensions/ash-scheme/package.json +1 -1
  13. package/examples/extensions/ashi/EXTENDING.md +2 -2
  14. package/examples/extensions/ashi/README.md +2 -2
  15. package/examples/extensions/ashi/docs/ui-surface-protocol.md +1 -1
  16. package/examples/extensions/ashi/package.json +5 -3
  17. package/examples/extensions/ashi/src/chat/tool-group.ts +3 -2
  18. package/examples/extensions/ashi/src/cli.ts +9 -8
  19. package/examples/extensions/ashi/src/dialogs.ts +16 -1
  20. package/examples/extensions/ashi/src/events.ts +1 -0
  21. package/examples/extensions/ashi/src/frontend.ts +26 -6
  22. package/examples/extensions/ashi/src/renderer.ts +24 -4
  23. package/examples/extensions/ashi/src/renderers/pi-tui/schema-mount.ts +4 -3
  24. package/examples/extensions/ashi/src/renderers/pi-tui/tool-group.ts +5 -8
  25. package/examples/extensions/ashi/src/ui.ts +11 -0
  26. package/examples/extensions/ashi-ink/package.json +2 -2
  27. package/examples/extensions/claude-code-bridge/package.json +1 -1
  28. package/examples/extensions/opencode-bridge/package.json +1 -1
  29. package/package.json +3 -1
  30. package/src/agent/agent-loop.ts +1566 -0
  31. package/src/agent/entry-format.ts +19 -0
  32. package/src/agent/events.ts +153 -0
  33. package/src/agent/extensions/rolling-history/constants.ts +1 -0
  34. package/src/agent/extensions/rolling-history/index.ts +202 -0
  35. package/src/agent/extensions/rolling-history/recall.ts +131 -0
  36. package/src/agent/extensions/rolling-history/strategy.ts +404 -0
  37. package/src/agent/host-types.ts +192 -0
  38. package/src/agent/index.ts +591 -0
  39. package/src/agent/live-view.ts +279 -0
  40. package/src/agent/llm-client.ts +111 -0
  41. package/src/agent/llm-facade.ts +43 -0
  42. package/src/agent/normalize-args.ts +61 -0
  43. package/src/agent/nuclear-form.ts +382 -0
  44. package/src/agent/providers/deepseek.ts +39 -0
  45. package/src/agent/providers/ollama.ts +92 -0
  46. package/src/agent/providers/openai-compatible.ts +36 -0
  47. package/src/agent/providers/openai.ts +52 -0
  48. package/src/agent/providers/opencode.ts +142 -0
  49. package/src/agent/providers/openrouter.ts +105 -0
  50. package/src/agent/providers/zai-coding-plan.ts +33 -0
  51. package/src/agent/session-store.ts +336 -0
  52. package/src/agent/skills.ts +228 -0
  53. package/src/agent/store.ts +310 -0
  54. package/src/agent/subagent.ts +305 -0
  55. package/src/agent/system-prompt.ts +151 -0
  56. package/src/agent/token-budget.ts +12 -0
  57. package/src/agent/tool-protocol.ts +722 -0
  58. package/src/agent/tool-registry.ts +66 -0
  59. package/src/agent/tools/bash.ts +95 -0
  60. package/src/agent/tools/edit-file.ts +154 -0
  61. package/src/agent/tools/expand-home.ts +7 -0
  62. package/src/agent/tools/glob.ts +108 -0
  63. package/src/agent/tools/grep.ts +228 -0
  64. package/src/agent/tools/list-skills.ts +37 -0
  65. package/src/agent/tools/ls.ts +81 -0
  66. package/src/agent/tools/pwsh.ts +140 -0
  67. package/src/agent/tools/read-file.ts +164 -0
  68. package/src/agent/tools/write-file.ts +72 -0
  69. package/src/agent/types.ts +149 -0
  70. package/src/cli/args.ts +91 -0
  71. package/src/cli/auth/cli.ts +244 -0
  72. package/src/cli/auth/discover.ts +52 -0
  73. package/src/cli/auth/keys.ts +143 -0
  74. package/src/cli/index.ts +295 -0
  75. package/src/cli/init.ts +74 -0
  76. package/src/cli/install.ts +439 -0
  77. package/src/cli/shell-env.ts +68 -0
  78. package/src/cli/subcommands.ts +24 -0
  79. package/src/core/event-bus.ts +252 -0
  80. package/src/core/extension-loader.ts +347 -0
  81. package/src/core/index.ts +152 -0
  82. package/src/core/settings.ts +398 -0
  83. package/src/core/types.ts +61 -0
  84. package/src/extensions/file-autocomplete.ts +71 -0
  85. package/src/extensions/index.ts +38 -0
  86. package/src/extensions/slash-commands/events.ts +14 -0
  87. package/src/extensions/slash-commands/index.ts +269 -0
  88. package/src/shell/events.ts +73 -0
  89. package/src/shell/host-types.ts +150 -0
  90. package/src/shell/index.ts +159 -0
  91. package/src/shell/input-handler.ts +505 -0
  92. package/src/shell/output-parser.ts +156 -0
  93. package/src/shell/shell-context.ts +193 -0
  94. package/src/shell/shell.ts +414 -0
  95. package/src/shell/strategies/bash.ts +83 -0
  96. package/src/shell/strategies/fish.ts +77 -0
  97. package/src/shell/strategies/index.ts +24 -0
  98. package/src/shell/strategies/types.ts +64 -0
  99. package/src/shell/strategies/zsh.ts +92 -0
  100. package/src/shell/terminal.ts +124 -0
  101. package/src/shell/tui-input-view.ts +222 -0
  102. package/src/shell/tui-renderer.ts +1126 -0
  103. package/src/utils/ansi.ts +140 -0
  104. package/src/utils/box-frame.ts +138 -0
  105. package/src/utils/compositor.ts +157 -0
  106. package/src/utils/diff-renderer.ts +829 -0
  107. package/src/utils/diff.ts +244 -0
  108. package/src/utils/executor.ts +305 -0
  109. package/src/utils/file-watcher.ts +110 -0
  110. package/src/utils/floating-panel.ts +1160 -0
  111. package/src/utils/handler-registry.ts +110 -0
  112. package/src/utils/line-editor.ts +636 -0
  113. package/src/utils/markdown.ts +437 -0
  114. package/src/utils/message-utils.ts +113 -0
  115. package/src/utils/package-version.ts +12 -0
  116. package/src/utils/palette.ts +64 -0
  117. package/src/utils/ref-counter.ts +9 -0
  118. package/src/utils/ripgrep-path.ts +17 -0
  119. package/src/utils/shell-output-spill.ts +76 -0
  120. package/src/utils/stream-transform.ts +292 -0
  121. package/src/utils/terminal-buffer.ts +213 -0
  122. package/src/utils/tool-display.ts +315 -0
  123. package/src/utils/tool-interactive.ts +71 -0
  124. package/src/utils/tty.ts +14 -0
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Subagent runner — executes a focused agent loop with its own context.
3
+ *
4
+ * Unlike the main AgentLoop, a subagent:
5
+ * - Has its own conversation (starts fresh, stays focused)
6
+ * - Has its own system prompt (specialized for the task)
7
+ * - Runs to completion and returns the final text
8
+ * - Optionally emits tool events to the bus for TUI rendering
9
+ *
10
+ * Used by the subagent extension to delegate tasks from the main agent.
11
+ */
12
+ import type { EventBus } from "../core/event-bus.js";
13
+ import type { LlmClient } from "./llm-client.js";
14
+ import { contentText, type ToolDefinition } from "./types.js";
15
+ import { LiveView } from "./live-view.js";
16
+ import { normalizeToolArgs } from "./normalize-args.js";
17
+ import { wrapTrailingWithDynamicContext } from "../utils/message-utils.js";
18
+
19
+ interface PendingToolCall {
20
+ id: string;
21
+ name: string;
22
+ argumentsJson: string;
23
+ }
24
+
25
+ export interface SubagentOptions {
26
+ /** LLM client to use. */
27
+ llmClient: LlmClient;
28
+ /** Tools available to the subagent. */
29
+ tools: ToolDefinition[];
30
+ /** System prompt for this subagent. */
31
+ systemPrompt: string;
32
+ /** The task to perform. */
33
+ task: string;
34
+ /** Model override (optional, defaults to llmClient's model). */
35
+ model?: string;
36
+ /** Event bus for TUI events (optional — silent if omitted). */
37
+ bus?: EventBus;
38
+ /** Abort signal for cancellation. */
39
+ signal?: AbortSignal;
40
+ /** Max tool loop iterations (default 20). */
41
+ maxIterations?: number;
42
+ /**
43
+ * Ambient context rebuilt per iteration, same shape the parent's
44
+ * streamResponse uses. If provided, the subagent sees budget,
45
+ * metacognitive signals, in-flight siblings, etc.
46
+ */
47
+ dynamicContext?: string;
48
+ /**
49
+ * Per-subagent completion-token budget. When the cumulative
50
+ * completion_tokens across iterations exceeds this, the subagent
51
+ * terminates gracefully on the next iteration. We deliberately don't
52
+ * count prompt tokens: the full history is resent each iteration, so
53
+ * prompt-inclusive counting double-charges context and makes a budget
54
+ * of N exhaust after O(log N) tool calls. Completion tokens measure
55
+ * the work the subagent actually produces. The parent's daily budget
56
+ * still sees real prompt+completion via onUsage.
57
+ */
58
+ budgetTokens?: number;
59
+ /**
60
+ * Invoked after every streamed LLM response with its usage totals.
61
+ * The parent uses this to forward to its event bus so global budget
62
+ * tracking stays accurate.
63
+ */
64
+ onUsage?: (usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number }) => void;
65
+ }
66
+
67
+ /**
68
+ * Run a subagent to completion.
69
+ * Returns the final response text.
70
+ */
71
+ export async function runSubagent(opts: SubagentOptions): Promise<string> {
72
+ const {
73
+ llmClient,
74
+ tools,
75
+ systemPrompt,
76
+ task,
77
+ model,
78
+ bus,
79
+ signal,
80
+ maxIterations = 20,
81
+ dynamicContext,
82
+ budgetTokens,
83
+ onUsage,
84
+ } = opts;
85
+
86
+ const toolMap = new Map(tools.map(t => [t.name, t]));
87
+ const apiTools = tools.map(t => ({
88
+ type: "function" as const,
89
+ function: {
90
+ name: t.name,
91
+ description: t.description,
92
+ parameters: t.input_schema,
93
+ },
94
+ }));
95
+
96
+ const conversation = new LiveView();
97
+ conversation.addUserMessage(task);
98
+
99
+ let fullResponseText = "";
100
+ let iterations = 0;
101
+ let tokensConsumed = 0;
102
+ let budgetExhausted = false;
103
+
104
+ while (iterations++ < maxIterations) {
105
+ if (signal?.aborted) break;
106
+ if (budgetTokens != null && tokensConsumed >= budgetTokens) {
107
+ budgetExhausted = true;
108
+ break;
109
+ }
110
+
111
+ // Stream LLM response
112
+ const { text, toolCalls, assistantContent, assistantToolCalls, extras, usage } =
113
+ await streamOnce(llmClient, systemPrompt, conversation, apiTools, model, signal, dynamicContext);
114
+
115
+ if (usage) {
116
+ tokensConsumed += usage.completion_tokens || 0;
117
+ onUsage?.(usage);
118
+ }
119
+
120
+ fullResponseText += text;
121
+
122
+ conversation.addAssistantMessage(assistantContent, assistantToolCalls, extras);
123
+
124
+ // No tool calls → done
125
+ if (toolCalls.length === 0) break;
126
+
127
+ // Execute tools
128
+ for (const tc of toolCalls) {
129
+ if (signal?.aborted) break;
130
+
131
+ const tool = toolMap.get(tc.name);
132
+ if (!tool) {
133
+ conversation.addToolResult(tc.id, `Error: Unknown tool "${tc.name}"`, true);
134
+ continue;
135
+ }
136
+
137
+ let args: Record<string, unknown>;
138
+ try {
139
+ args = JSON.parse(tc.argumentsJson);
140
+ } catch {
141
+ conversation.addToolResult(tc.id, `Error: Invalid JSON arguments for ${tc.name}`, true);
142
+ continue;
143
+ }
144
+ args = normalizeToolArgs(args, tool.input_schema);
145
+
146
+ // Emit tool events for TUI (if bus provided)
147
+ if (bus) {
148
+ const display = tool.getDisplayInfo?.(args) ?? { kind: "execute" };
149
+ bus.emit("agent:tool-started", {
150
+ title: tc.name,
151
+ name: tc.name,
152
+ toolCallId: tc.id,
153
+ kind: display.kind,
154
+ locations: display.locations,
155
+ rawInput: args,
156
+ });
157
+ }
158
+
159
+ const onChunk = bus && tool.showOutput !== false
160
+ ? (chunk: string) => { bus.emit("agent:tool-output-chunk", { chunk }); }
161
+ : undefined;
162
+
163
+ const result = await tool.execute(args, onChunk);
164
+
165
+ if (bus) {
166
+ const display = tool.getDisplayInfo?.(args) ?? { kind: "execute" };
167
+ const resultDisplay = tool.formatResult?.(args, result);
168
+ bus.emitTransform("agent:tool-completed", {
169
+ toolCallId: tc.id,
170
+ exitCode: result.exitCode,
171
+ rawOutput: result.content,
172
+ kind: display.kind,
173
+ resultDisplay,
174
+ });
175
+ }
176
+
177
+ const content = result.isError
178
+ ? `Error: ${contentText(result.content)}`
179
+ : result.content;
180
+ conversation.addToolResult(tc.id, content, !!result.isError);
181
+ }
182
+ }
183
+
184
+ if (budgetExhausted) {
185
+ const note = `\n\n[Subagent terminated: completion-token budget (${budgetTokens}) exhausted after ${tokensConsumed} completion tokens. Returning partial progress.]`;
186
+ return fullResponseText + note;
187
+ }
188
+
189
+ return fullResponseText;
190
+ }
191
+
192
+ /** Stream a single LLM response. */
193
+ async function streamOnce(
194
+ llmClient: LlmClient,
195
+ systemPrompt: string,
196
+ conversation: LiveView,
197
+ apiTools: any[],
198
+ model: string | undefined,
199
+ signal: AbortSignal | undefined,
200
+ dynamicContext?: string,
201
+ ): Promise<{
202
+ text: string;
203
+ toolCalls: PendingToolCall[];
204
+ assistantContent: string | null;
205
+ assistantToolCalls: { id: string; function: { name: string; arguments: string } }[] | undefined;
206
+ extras?: Record<string, unknown>;
207
+ usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number } | null;
208
+ }> {
209
+ let text = "";
210
+ let reasoning = "";
211
+ let reasoningField: string | null = null;
212
+ const reasoningDetailsByIndex = new Map<number, Record<string, unknown>>();
213
+ const pendingToolCalls: PendingToolCall[] = [];
214
+ let usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number } | null = null;
215
+
216
+ const stream = await llmClient.stream({
217
+ messages: [
218
+ { role: "system", content: systemPrompt },
219
+ ...wrapTrailingWithDynamicContext(conversation.forLLM(), dynamicContext ?? ""),
220
+ ],
221
+ tools: apiTools.length > 0 ? apiTools : undefined,
222
+ model,
223
+ signal,
224
+ });
225
+
226
+ for await (const chunk of stream) {
227
+ if (signal?.aborted) break;
228
+
229
+ if ((chunk as any).usage) {
230
+ const u = (chunk as any).usage;
231
+ usage = {
232
+ prompt_tokens: u.prompt_tokens ?? 0,
233
+ completion_tokens: u.completion_tokens ?? 0,
234
+ total_tokens: u.total_tokens ?? 0,
235
+ };
236
+ }
237
+
238
+ const choice = chunk.choices[0];
239
+ if (!choice) continue;
240
+ const delta = choice.delta;
241
+
242
+ if (delta?.content) {
243
+ text += delta.content;
244
+ }
245
+
246
+ const d = delta as any;
247
+ for (const name of ["reasoning", "reasoning_content"] as const) {
248
+ if (typeof d?.[name] === "string" && d[name].length > 0) {
249
+ reasoning += d[name];
250
+ reasoningField ??= name;
251
+ }
252
+ }
253
+ if (Array.isArray(d?.reasoning_details)) {
254
+ for (const x of d.reasoning_details) {
255
+ const idx = typeof x?.index === "number" ? x.index : reasoningDetailsByIndex.size;
256
+ const prev = reasoningDetailsByIndex.get(idx);
257
+ if (!prev) {
258
+ reasoningDetailsByIndex.set(idx, { ...x });
259
+ } else {
260
+ if (typeof x.text === "string") prev.text = (prev.text ?? "") + x.text;
261
+ for (const [k, v] of Object.entries(x)) if (k !== "text" && prev[k] === undefined) prev[k] = v;
262
+ }
263
+ }
264
+ }
265
+
266
+ if (delta?.tool_calls) {
267
+ for (const tc of delta.tool_calls) {
268
+ const idx = tc.index;
269
+ if (!pendingToolCalls[idx]) {
270
+ pendingToolCalls[idx] = { id: tc.id!, name: tc.function!.name!, argumentsJson: "" };
271
+ }
272
+ if (tc.function?.arguments) {
273
+ pendingToolCalls[idx].argumentsJson += tc.function.arguments;
274
+ }
275
+ }
276
+ }
277
+ }
278
+
279
+ // Normalize arguments JSON (same fix as agent-loop): strict providers
280
+ // reject empty "" on replay next turn even though OpenAI is lenient.
281
+ for (const tc of pendingToolCalls) {
282
+ const s = tc.argumentsJson.trim();
283
+ if (s === "") { tc.argumentsJson = "{}"; continue; }
284
+ try { JSON.parse(s); } catch { tc.argumentsJson = "{}"; }
285
+ }
286
+
287
+ const assistantToolCalls = pendingToolCalls.length
288
+ ? pendingToolCalls.map(tc => ({ id: tc.id, function: { name: tc.name, arguments: tc.argumentsJson } }))
289
+ : undefined;
290
+
291
+ const extras: Record<string, unknown> = {};
292
+ if (reasoning && reasoningField) extras[reasoningField] = reasoning;
293
+ if (reasoningDetailsByIndex.size > 0) {
294
+ extras.reasoning_details = [...reasoningDetailsByIndex.entries()]
295
+ .sort((a, b) => a[0] - b[0]).map(([, v]) => v);
296
+ }
297
+ return {
298
+ text,
299
+ toolCalls: pendingToolCalls,
300
+ assistantContent: text || null,
301
+ assistantToolCalls,
302
+ extras: Object.keys(extras).length > 0 ? extras : undefined,
303
+ usage,
304
+ };
305
+ }
@@ -0,0 +1,151 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { discoverProjectSkills, type Skill } from "./skills.js";
5
+
6
+ /**
7
+ * Format skills for inline display in prompt.
8
+ * Shows name, description, and file path so the model can decide immediately
9
+ * whether to load a skill — no extra round-trip needed.
10
+ */
11
+ export function formatSkillsBlock(skills: Skill[]): string {
12
+ if (skills.length === 0) return "";
13
+ return "# Available Skills\n\n"
14
+ + "Load a skill's full content from its file path with your file-reading tool when needed.\n\n"
15
+ + skills.map(s => `- **${s.name}**: ${s.description}\n Path: ${s.filePath}`).join("\n\n");
16
+ }
17
+
18
+ import { CONFIG_DIR } from "../core/settings.js";
19
+ const GLOBAL_AGENTS_MD = path.join(CONFIG_DIR, "AGENTS.md");
20
+
21
+ // ── File caches ─────────────────────────────────────────────────────
22
+ // Convention files (CLAUDE.md/AGENT.md) are walked synchronously from
23
+ // CWD to root on every query. In practice they almost never change,
24
+ // so a short TTL cache keyed by CWD avoids redundant filesystem walks.
25
+ // The 5-second TTL is short enough to pick up edits quickly but long
26
+ // enough to eliminate repeated walks within a multi-tool agent loop.
27
+
28
+ const CACHE_TTL_MS = 5_000;
29
+
30
+ /** TTL cache for convention files, keyed by resolved CWD. */
31
+ let conventionCache: { cwd: string; result: string[]; expiry: number } | null = null;
32
+
33
+ /** TTL cache for global AGENTS.md — changes extremely rarely. */
34
+ let agentsMdCache: { result: string | null; expiry: number } | null = null;
35
+
36
+ export function loadGlobalAgentsMd(): string | null {
37
+ const now = Date.now();
38
+ if (agentsMdCache && now < agentsMdCache.expiry) {
39
+ return agentsMdCache.result;
40
+ }
41
+ try {
42
+ const content = fs.readFileSync(GLOBAL_AGENTS_MD, "utf-8").trim();
43
+ const result = content || null;
44
+ agentsMdCache = { result, expiry: now + CACHE_TTL_MS };
45
+ return result;
46
+ } catch {
47
+ agentsMdCache = { result: null, expiry: now + CACHE_TTL_MS };
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /** Resolve the absolute path to agent-sh's own docs directory. */
53
+ const CODE_DIR = path.resolve(
54
+ path.dirname(fileURLToPath(import.meta.url)),
55
+ "../../",
56
+ );
57
+
58
+ /** File names to scan for project conventions (checked in order). */
59
+ const CONVENTION_FILES = ["CLAUDE.md", "AGENT.md"];
60
+
61
+ /**
62
+ * Scan from `dir` upward for project convention files.
63
+ * Returns contents ordered root-first (general → specific).
64
+ * Results are cached for CACHE_TTL_MS, keyed by resolved directory.
65
+ */
66
+ function loadConventionFiles(dir: string): string[] {
67
+ const cwd = path.resolve(dir);
68
+ const now = Date.now();
69
+
70
+ if (conventionCache && conventionCache.cwd === cwd && now < conventionCache.expiry) {
71
+ return conventionCache.result;
72
+ }
73
+
74
+ const files: { path: string; content: string }[] = [];
75
+ let current = cwd;
76
+
77
+ while (true) {
78
+ for (const name of CONVENTION_FILES) {
79
+ const candidate = path.join(current, name);
80
+ try {
81
+ const content = fs.readFileSync(candidate, "utf-8").trim();
82
+ if (content) {
83
+ files.push({ path: candidate, content });
84
+ break;
85
+ }
86
+ } catch {
87
+ // File doesn't exist
88
+ }
89
+ }
90
+
91
+ const parent = path.dirname(current);
92
+ if (parent === current) break;
93
+ current = parent;
94
+ }
95
+
96
+ files.reverse();
97
+ const result = files.map(f => `<!-- ${f.path} -->\n${f.content}`);
98
+ conventionCache = { cwd, result, expiry: now + CACHE_TTL_MS };
99
+ return result;
100
+ }
101
+
102
+ /**
103
+ * Identity — paragraph one of the system prompt. Surface-agnostic, cacheable.
104
+ */
105
+ export const STATIC_IDENTITY = `You are ash, an AI coding assistant running inside agent-sh — a composable agent runtime with a small core and everything else, including the frontend you're attached to, layered on as extensions.`;
106
+
107
+ /**
108
+ * The rest of the static prompt — code map, tool guidance, envelope contract.
109
+ * Follows the frontend surface description in the assembled prompt.
110
+ */
111
+ export const STATIC_GUIDE = `agent-sh source and documentation live at ${CODE_DIR}. Read them when you need to understand how the runtime works, or when the user asks how to modify or extend it:
112
+ - ${path.join(CODE_DIR, "docs")} — start with README.md; architecture.md and extensions.md cover the kernel boundary and extension API
113
+ - ${path.join(CODE_DIR, "src")} — kernel in src/core, default backend in src/agent, shell host in src/shell, built-in extensions in src/extensions
114
+ - ${path.join(CODE_DIR, "examples/extensions")} — reference extensions to study or copy when adding functionality
115
+
116
+ # Tools
117
+
118
+ Use your registered tools to investigate, search, read, and modify files.
119
+ Each tool's description tells you when and how to use it; follow that
120
+ guidance rather than assuming a particular tool exists. Tool output is
121
+ returned to you for reasoning — the user doesn't see it directly.
122
+
123
+ # Context Envelopes
124
+
125
+ A turn may be preceded by either of two wrappers:
126
+ - \`<query_context>\`: the user's situation when they sent this turn — the frontend and extensions inject what grounds the request here. Trust the most recent values over anything referenced earlier in history.
127
+ - \`<dynamic_context>\`: current system state — in-flight work, mode markers, warnings.
128
+
129
+ Either may be absent on any turn.`;
130
+
131
+ /**
132
+ * CWD-scoped static context: project conventions (CLAUDE.md / AGENT.md)
133
+ * and discovered skills. Stable for a given cwd — callers should cache
134
+ * on cwd identity rather than rebuilding per LLM iteration.
135
+ */
136
+ export function buildStaticByCwd(cwd: string): string {
137
+ const sections: string[] = [];
138
+
139
+ const conventions = loadConventionFiles(cwd);
140
+ if (conventions.length > 0) {
141
+ sections.push("# Project Conventions\n\n" + conventions.join("\n\n"));
142
+ }
143
+
144
+ const projectSkills = discoverProjectSkills(cwd);
145
+ const skillsBlock = formatSkillsBlock(projectSkills);
146
+ if (skillsBlock) {
147
+ sections.push(skillsBlock);
148
+ }
149
+
150
+ return sections.join("\n\n");
151
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared token-budget constants used by auto-compaction.
3
+ *
4
+ * RESPONSE_RESERVE: tokens reserved for the model's output.
5
+ * DEFAULT_CONTEXT_WINDOW: fallback when the active mode doesn't declare one.
6
+ */
7
+
8
+ /** Response reserve — tokens reserved for the model's output. */
9
+ export const RESPONSE_RESERVE = 8192;
10
+
11
+ /** Fallback when contextWindow is unknown. */
12
+ export const DEFAULT_CONTEXT_WINDOW = 60_000;