indusagi-coding-agent 0.1.0

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 (240) hide show
  1. package/CHANGELOG.md +2249 -0
  2. package/README.md +546 -0
  3. package/dist/cli/args.js +282 -0
  4. package/dist/cli/config-selector.js +30 -0
  5. package/dist/cli/file-processor.js +78 -0
  6. package/dist/cli/list-models.js +91 -0
  7. package/dist/cli/session-picker.js +31 -0
  8. package/dist/cli.js +10 -0
  9. package/dist/config.js +158 -0
  10. package/dist/core/agent-session.js +2097 -0
  11. package/dist/core/auth-storage.js +278 -0
  12. package/dist/core/bash-executor.js +211 -0
  13. package/dist/core/compaction/branch-summarization.js +241 -0
  14. package/dist/core/compaction/compaction.js +606 -0
  15. package/dist/core/compaction/index.js +6 -0
  16. package/dist/core/compaction/utils.js +137 -0
  17. package/dist/core/diagnostics.js +1 -0
  18. package/dist/core/event-bus.js +24 -0
  19. package/dist/core/exec.js +70 -0
  20. package/dist/core/export-html/ansi-to-html.js +248 -0
  21. package/dist/core/export-html/index.js +221 -0
  22. package/dist/core/export-html/template.css +905 -0
  23. package/dist/core/export-html/template.html +54 -0
  24. package/dist/core/export-html/template.js +1549 -0
  25. package/dist/core/export-html/tool-renderer.js +56 -0
  26. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  27. package/dist/core/export-html/vendor/marked.min.js +6 -0
  28. package/dist/core/extensions/index.js +8 -0
  29. package/dist/core/extensions/loader.js +395 -0
  30. package/dist/core/extensions/runner.js +499 -0
  31. package/dist/core/extensions/types.js +31 -0
  32. package/dist/core/extensions/wrapper.js +101 -0
  33. package/dist/core/footer-data-provider.js +133 -0
  34. package/dist/core/index.js +8 -0
  35. package/dist/core/keybindings.js +140 -0
  36. package/dist/core/messages.js +122 -0
  37. package/dist/core/model-registry.js +454 -0
  38. package/dist/core/model-resolver.js +309 -0
  39. package/dist/core/package-manager.js +1142 -0
  40. package/dist/core/prompt-templates.js +250 -0
  41. package/dist/core/resource-loader.js +569 -0
  42. package/dist/core/sdk.js +225 -0
  43. package/dist/core/session-manager.js +1078 -0
  44. package/dist/core/settings-manager.js +430 -0
  45. package/dist/core/skills.js +339 -0
  46. package/dist/core/system-prompt.js +136 -0
  47. package/dist/core/timings.js +24 -0
  48. package/dist/core/tools/bash.js +226 -0
  49. package/dist/core/tools/edit-diff.js +242 -0
  50. package/dist/core/tools/edit.js +145 -0
  51. package/dist/core/tools/find.js +205 -0
  52. package/dist/core/tools/grep.js +238 -0
  53. package/dist/core/tools/index.js +60 -0
  54. package/dist/core/tools/ls.js +117 -0
  55. package/dist/core/tools/path-utils.js +52 -0
  56. package/dist/core/tools/read.js +165 -0
  57. package/dist/core/tools/truncate.js +204 -0
  58. package/dist/core/tools/write.js +77 -0
  59. package/dist/index.js +41 -0
  60. package/dist/main.js +565 -0
  61. package/dist/migrations.js +260 -0
  62. package/dist/modes/index.js +7 -0
  63. package/dist/modes/interactive/components/armin.js +328 -0
  64. package/dist/modes/interactive/components/assistant-message.js +86 -0
  65. package/dist/modes/interactive/components/bash-execution.js +155 -0
  66. package/dist/modes/interactive/components/bordered-loader.js +47 -0
  67. package/dist/modes/interactive/components/branch-summary-message.js +41 -0
  68. package/dist/modes/interactive/components/compaction-summary-message.js +42 -0
  69. package/dist/modes/interactive/components/config-selector.js +458 -0
  70. package/dist/modes/interactive/components/countdown-timer.js +27 -0
  71. package/dist/modes/interactive/components/custom-editor.js +61 -0
  72. package/dist/modes/interactive/components/custom-message.js +80 -0
  73. package/dist/modes/interactive/components/diff.js +132 -0
  74. package/dist/modes/interactive/components/dynamic-border.js +19 -0
  75. package/dist/modes/interactive/components/extension-editor.js +96 -0
  76. package/dist/modes/interactive/components/extension-input.js +54 -0
  77. package/dist/modes/interactive/components/extension-selector.js +70 -0
  78. package/dist/modes/interactive/components/footer.js +213 -0
  79. package/dist/modes/interactive/components/index.js +31 -0
  80. package/dist/modes/interactive/components/keybinding-hints.js +60 -0
  81. package/dist/modes/interactive/components/login-dialog.js +138 -0
  82. package/dist/modes/interactive/components/model-selector.js +253 -0
  83. package/dist/modes/interactive/components/oauth-selector.js +91 -0
  84. package/dist/modes/interactive/components/scoped-models-selector.js +262 -0
  85. package/dist/modes/interactive/components/session-selector-search.js +145 -0
  86. package/dist/modes/interactive/components/session-selector.js +698 -0
  87. package/dist/modes/interactive/components/settings-selector.js +250 -0
  88. package/dist/modes/interactive/components/show-images-selector.js +33 -0
  89. package/dist/modes/interactive/components/skill-invocation-message.js +44 -0
  90. package/dist/modes/interactive/components/theme-selector.js +43 -0
  91. package/dist/modes/interactive/components/thinking-selector.js +45 -0
  92. package/dist/modes/interactive/components/tool-execution.js +608 -0
  93. package/dist/modes/interactive/components/tree-selector.js +892 -0
  94. package/dist/modes/interactive/components/user-message-selector.js +109 -0
  95. package/dist/modes/interactive/components/user-message.js +15 -0
  96. package/dist/modes/interactive/components/visual-truncate.js +32 -0
  97. package/dist/modes/interactive/interactive-mode.js +3576 -0
  98. package/dist/modes/interactive/theme/dark.json +85 -0
  99. package/dist/modes/interactive/theme/light.json +84 -0
  100. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  101. package/dist/modes/interactive/theme/theme.js +938 -0
  102. package/dist/modes/print-mode.js +96 -0
  103. package/dist/modes/rpc/rpc-client.js +390 -0
  104. package/dist/modes/rpc/rpc-mode.js +448 -0
  105. package/dist/modes/rpc/rpc-types.js +7 -0
  106. package/dist/utils/changelog.js +86 -0
  107. package/dist/utils/clipboard-image.js +116 -0
  108. package/dist/utils/clipboard.js +58 -0
  109. package/dist/utils/frontmatter.js +25 -0
  110. package/dist/utils/git.js +5 -0
  111. package/dist/utils/image-convert.js +34 -0
  112. package/dist/utils/image-resize.js +180 -0
  113. package/dist/utils/mime.js +25 -0
  114. package/dist/utils/photon.js +120 -0
  115. package/dist/utils/shell.js +164 -0
  116. package/dist/utils/sleep.js +16 -0
  117. package/dist/utils/tools-manager.js +186 -0
  118. package/docs/compaction.md +390 -0
  119. package/docs/custom-provider.md +538 -0
  120. package/docs/development.md +69 -0
  121. package/docs/extensions.md +1733 -0
  122. package/docs/images/doom-extension.png +0 -0
  123. package/docs/images/interactive-mode.png +0 -0
  124. package/docs/images/tree-view.png +0 -0
  125. package/docs/json.md +79 -0
  126. package/docs/keybindings.md +162 -0
  127. package/docs/models.md +193 -0
  128. package/docs/packages.md +163 -0
  129. package/docs/prompt-templates.md +67 -0
  130. package/docs/providers.md +147 -0
  131. package/docs/rpc.md +1048 -0
  132. package/docs/sdk.md +957 -0
  133. package/docs/session.md +412 -0
  134. package/docs/settings.md +216 -0
  135. package/docs/shell-aliases.md +13 -0
  136. package/docs/skills.md +226 -0
  137. package/docs/terminal-setup.md +65 -0
  138. package/docs/themes.md +295 -0
  139. package/docs/tree.md +219 -0
  140. package/docs/tui.md +887 -0
  141. package/docs/windows.md +17 -0
  142. package/examples/README.md +25 -0
  143. package/examples/extensions/README.md +192 -0
  144. package/examples/extensions/antigravity-image-gen.ts +414 -0
  145. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  146. package/examples/extensions/bookmark.ts +50 -0
  147. package/examples/extensions/claude-rules.ts +86 -0
  148. package/examples/extensions/confirm-destructive.ts +59 -0
  149. package/examples/extensions/custom-compaction.ts +115 -0
  150. package/examples/extensions/custom-footer.ts +65 -0
  151. package/examples/extensions/custom-header.ts +73 -0
  152. package/examples/extensions/custom-provider-anthropic/index.ts +605 -0
  153. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  154. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  155. package/examples/extensions/custom-provider-gitlab-duo/index.ts +350 -0
  156. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  157. package/examples/extensions/custom-provider-gitlab-duo/test.ts +83 -0
  158. package/examples/extensions/dirty-repo-guard.ts +56 -0
  159. package/examples/extensions/doom-overlay/README.md +46 -0
  160. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  161. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  162. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  163. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  164. package/examples/extensions/doom-overlay/doom-component.ts +133 -0
  165. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  166. package/examples/extensions/doom-overlay/doom-keys.ts +105 -0
  167. package/examples/extensions/doom-overlay/index.ts +74 -0
  168. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  169. package/examples/extensions/event-bus.ts +43 -0
  170. package/examples/extensions/file-trigger.ts +41 -0
  171. package/examples/extensions/git-checkpoint.ts +53 -0
  172. package/examples/extensions/handoff.ts +151 -0
  173. package/examples/extensions/hello.ts +25 -0
  174. package/examples/extensions/inline-bash.ts +94 -0
  175. package/examples/extensions/input-transform.ts +43 -0
  176. package/examples/extensions/interactive-shell.ts +196 -0
  177. package/examples/extensions/mac-system-theme.ts +47 -0
  178. package/examples/extensions/message-renderer.ts +60 -0
  179. package/examples/extensions/modal-editor.ts +86 -0
  180. package/examples/extensions/model-status.ts +31 -0
  181. package/examples/extensions/notify.ts +25 -0
  182. package/examples/extensions/overlay-qa-tests.ts +882 -0
  183. package/examples/extensions/overlay-test.ts +151 -0
  184. package/examples/extensions/permission-gate.ts +34 -0
  185. package/examples/extensions/pirate.ts +47 -0
  186. package/examples/extensions/plan-mode/README.md +65 -0
  187. package/examples/extensions/plan-mode/index.ts +341 -0
  188. package/examples/extensions/plan-mode/utils.ts +168 -0
  189. package/examples/extensions/preset.ts +399 -0
  190. package/examples/extensions/protected-paths.ts +30 -0
  191. package/examples/extensions/qna.ts +120 -0
  192. package/examples/extensions/question.ts +265 -0
  193. package/examples/extensions/questionnaire.ts +428 -0
  194. package/examples/extensions/rainbow-editor.ts +88 -0
  195. package/examples/extensions/sandbox/index.ts +318 -0
  196. package/examples/extensions/sandbox/package-lock.json +92 -0
  197. package/examples/extensions/sandbox/package.json +19 -0
  198. package/examples/extensions/send-user-message.ts +97 -0
  199. package/examples/extensions/session-name.ts +27 -0
  200. package/examples/extensions/shutdown-command.ts +63 -0
  201. package/examples/extensions/snake.ts +344 -0
  202. package/examples/extensions/space-invaders.ts +561 -0
  203. package/examples/extensions/ssh.ts +220 -0
  204. package/examples/extensions/status-line.ts +40 -0
  205. package/examples/extensions/subagent/README.md +172 -0
  206. package/examples/extensions/subagent/agents/planner.md +37 -0
  207. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  208. package/examples/extensions/subagent/agents/scout.md +50 -0
  209. package/examples/extensions/subagent/agents/worker.md +24 -0
  210. package/examples/extensions/subagent/agents.ts +127 -0
  211. package/examples/extensions/subagent/index.ts +964 -0
  212. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  213. package/examples/extensions/subagent/prompts/implement.md +10 -0
  214. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  215. package/examples/extensions/summarize.ts +196 -0
  216. package/examples/extensions/timed-confirm.ts +70 -0
  217. package/examples/extensions/todo.ts +300 -0
  218. package/examples/extensions/tool-override.ts +144 -0
  219. package/examples/extensions/tools.ts +147 -0
  220. package/examples/extensions/trigger-compact.ts +40 -0
  221. package/examples/extensions/truncated-tool.ts +193 -0
  222. package/examples/extensions/widget-placement.ts +17 -0
  223. package/examples/extensions/with-deps/index.ts +36 -0
  224. package/examples/extensions/with-deps/package-lock.json +31 -0
  225. package/examples/extensions/with-deps/package.json +22 -0
  226. package/examples/sdk/01-minimal.ts +22 -0
  227. package/examples/sdk/02-custom-model.ts +50 -0
  228. package/examples/sdk/03-custom-prompt.ts +55 -0
  229. package/examples/sdk/04-skills.ts +46 -0
  230. package/examples/sdk/05-tools.ts +56 -0
  231. package/examples/sdk/06-extensions.ts +88 -0
  232. package/examples/sdk/07-context-files.ts +40 -0
  233. package/examples/sdk/08-prompt-templates.ts +47 -0
  234. package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  235. package/examples/sdk/10-settings.ts +38 -0
  236. package/examples/sdk/11-sessions.ts +48 -0
  237. package/examples/sdk/12-full-control.ts +82 -0
  238. package/examples/sdk/13-codex-oauth.ts +37 -0
  239. package/examples/sdk/README.md +144 -0
  240. package/package.json +85 -0
@@ -0,0 +1,186 @@
1
+ import chalk from "chalk";
2
+ import { spawnSync } from "child_process";
3
+ import { chmodSync, createWriteStream, existsSync, mkdirSync, renameSync, rmSync } from "fs";
4
+ import { arch, platform } from "os";
5
+ import { join } from "path";
6
+ import { Readable } from "stream";
7
+ import { finished } from "stream/promises";
8
+ import { APP_NAME, getBinDir } from "../config.js";
9
+ const TOOLS_DIR = getBinDir();
10
+ const TOOLS = {
11
+ fd: {
12
+ name: "fd",
13
+ repo: "sharkdp/fd",
14
+ binaryName: "fd",
15
+ tagPrefix: "v",
16
+ getAssetName: (version, plat, architecture) => {
17
+ if (plat === "darwin") {
18
+ const archStr = architecture === "arm64" ? "aarch64" : "x86_64";
19
+ return `fd-v${version}-${archStr}-apple-darwin.tar.gz`;
20
+ }
21
+ else if (plat === "linux") {
22
+ const archStr = architecture === "arm64" ? "aarch64" : "x86_64";
23
+ return `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;
24
+ }
25
+ else if (plat === "win32") {
26
+ const archStr = architecture === "arm64" ? "aarch64" : "x86_64";
27
+ return `fd-v${version}-${archStr}-pc-windows-msvc.zip`;
28
+ }
29
+ return null;
30
+ },
31
+ },
32
+ rg: {
33
+ name: "ripgrep",
34
+ repo: "BurntSushi/ripgrep",
35
+ binaryName: "rg",
36
+ tagPrefix: "",
37
+ getAssetName: (version, plat, architecture) => {
38
+ if (plat === "darwin") {
39
+ const archStr = architecture === "arm64" ? "aarch64" : "x86_64";
40
+ return `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;
41
+ }
42
+ else if (plat === "linux") {
43
+ if (architecture === "arm64") {
44
+ return `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;
45
+ }
46
+ return `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;
47
+ }
48
+ else if (plat === "win32") {
49
+ const archStr = architecture === "arm64" ? "aarch64" : "x86_64";
50
+ return `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;
51
+ }
52
+ return null;
53
+ },
54
+ },
55
+ };
56
+ // Check if a command exists in PATH by trying to run it
57
+ function commandExists(cmd) {
58
+ try {
59
+ const result = spawnSync(cmd, ["--version"], { stdio: "pipe" });
60
+ // Check for ENOENT error (command not found)
61
+ return result.error === undefined || result.error === null;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ // Get the path to a tool (system-wide or in our tools dir)
68
+ export function getToolPath(tool) {
69
+ const config = TOOLS[tool];
70
+ if (!config)
71
+ return null;
72
+ // Check our tools directory first
73
+ const localPath = join(TOOLS_DIR, config.binaryName + (platform() === "win32" ? ".exe" : ""));
74
+ if (existsSync(localPath)) {
75
+ return localPath;
76
+ }
77
+ // Check system PATH - if found, just return the command name (it's in PATH)
78
+ if (commandExists(config.binaryName)) {
79
+ return config.binaryName;
80
+ }
81
+ return null;
82
+ }
83
+ // Fetch latest release version from GitHub
84
+ async function getLatestVersion(repo) {
85
+ const response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
86
+ headers: { "User-Agent": `${APP_NAME}-coding-agent` },
87
+ });
88
+ if (!response.ok) {
89
+ throw new Error(`GitHub API error: ${response.status}`);
90
+ }
91
+ const data = (await response.json());
92
+ return data.tag_name.replace(/^v/, "");
93
+ }
94
+ // Download a file from URL
95
+ async function downloadFile(url, dest) {
96
+ const response = await fetch(url);
97
+ if (!response.ok) {
98
+ throw new Error(`Failed to download: ${response.status}`);
99
+ }
100
+ if (!response.body) {
101
+ throw new Error("No response body");
102
+ }
103
+ const fileStream = createWriteStream(dest);
104
+ await finished(Readable.fromWeb(response.body).pipe(fileStream));
105
+ }
106
+ // Download and install a tool
107
+ async function downloadTool(tool) {
108
+ const config = TOOLS[tool];
109
+ if (!config)
110
+ throw new Error(`Unknown tool: ${tool}`);
111
+ const plat = platform();
112
+ const architecture = arch();
113
+ // Get latest version
114
+ const version = await getLatestVersion(config.repo);
115
+ // Get asset name for this platform
116
+ const assetName = config.getAssetName(version, plat, architecture);
117
+ if (!assetName) {
118
+ throw new Error(`Unsupported platform: ${plat}/${architecture}`);
119
+ }
120
+ // Create tools directory
121
+ mkdirSync(TOOLS_DIR, { recursive: true });
122
+ const downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;
123
+ const archivePath = join(TOOLS_DIR, assetName);
124
+ const binaryExt = plat === "win32" ? ".exe" : "";
125
+ const binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);
126
+ // Download
127
+ await downloadFile(downloadUrl, archivePath);
128
+ // Extract
129
+ const extractDir = join(TOOLS_DIR, "extract_tmp");
130
+ mkdirSync(extractDir, { recursive: true });
131
+ try {
132
+ if (assetName.endsWith(".tar.gz")) {
133
+ spawnSync("tar", ["xzf", archivePath, "-C", extractDir], { stdio: "pipe" });
134
+ }
135
+ else if (assetName.endsWith(".zip")) {
136
+ spawnSync("unzip", ["-o", archivePath, "-d", extractDir], { stdio: "pipe" });
137
+ }
138
+ // Find the binary in extracted files
139
+ const extractedDir = join(extractDir, assetName.replace(/\.(tar\.gz|zip)$/, ""));
140
+ const extractedBinary = join(extractedDir, config.binaryName + binaryExt);
141
+ if (existsSync(extractedBinary)) {
142
+ renameSync(extractedBinary, binaryPath);
143
+ }
144
+ else {
145
+ throw new Error(`Binary not found in archive: ${extractedBinary}`);
146
+ }
147
+ // Make executable (Unix only)
148
+ if (plat !== "win32") {
149
+ chmodSync(binaryPath, 0o755);
150
+ }
151
+ }
152
+ finally {
153
+ // Cleanup
154
+ rmSync(archivePath, { force: true });
155
+ rmSync(extractDir, { recursive: true, force: true });
156
+ }
157
+ return binaryPath;
158
+ }
159
+ // Ensure a tool is available, downloading if necessary
160
+ // Returns the path to the tool, or null if unavailable
161
+ export async function ensureTool(tool, silent = false) {
162
+ const existingPath = getToolPath(tool);
163
+ if (existingPath) {
164
+ return existingPath;
165
+ }
166
+ const config = TOOLS[tool];
167
+ if (!config)
168
+ return undefined;
169
+ // Tool not found - download it
170
+ if (!silent) {
171
+ console.log(chalk.dim(`${config.name} not found. Downloading...`));
172
+ }
173
+ try {
174
+ const path = await downloadTool(tool);
175
+ if (!silent) {
176
+ console.log(chalk.dim(`${config.name} installed to ${path}`));
177
+ }
178
+ return path;
179
+ }
180
+ catch (e) {
181
+ if (!silent) {
182
+ console.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));
183
+ }
184
+ return undefined;
185
+ }
186
+ }
@@ -0,0 +1,390 @@
1
+ # Compaction & Branch Summarization
2
+
3
+ LLMs have limited context windows. When conversations grow too long, indusagi uses compaction to summarize older content while preserving recent work. This page covers both auto-compaction and branch summarization.
4
+
5
+ **Source files** ([indusagi-mono](https://github.com/badlogic/indusagi-mono)):
6
+ - [`packages/coding-agent/src/core/compaction/compaction.ts`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) - Auto-compaction logic
7
+ - [`packages/coding-agent/src/core/compaction/branch-summarization.ts`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts) - Branch summarization
8
+ - [`packages/coding-agent/src/core/compaction/utils.ts`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts) - Shared utilities (file tracking, serialization)
9
+ - [`packages/coding-agent/src/core/session-manager.ts`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) - Entry types (`CompactionEntry`, `BranchSummaryEntry`)
10
+ - [`packages/coding-agent/src/core/extensions/types.ts`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/extensions/types.ts) - Extension event types
11
+
12
+ For TypeScript definitions in your project, inspect `node_modules/indusagi-coding-agent/dist/`.
13
+
14
+ ## Overview
15
+
16
+ Indusagi has two summarization mechanisms:
17
+
18
+ | Mechanism | Trigger | Purpose |
19
+ |-----------|---------|---------|
20
+ | Compaction | Context exceeds threshold, or `/compact` | Summarize old messages to free up context |
21
+ | Branch summarization | `/tree` navigation | Preserve context when switching branches |
22
+
23
+ Both use the same structured summary format and track file operations cumulatively.
24
+
25
+ ## Compaction
26
+
27
+ ### When It Triggers
28
+
29
+ Auto-compaction triggers when:
30
+
31
+ ```
32
+ contextTokens > contextWindow - reserveTokens
33
+ ```
34
+
35
+ By default, `reserveTokens` is 16384 tokens (configurable in `~/.indusagi/agent/settings.json` or `<project-dir>/.indusagi/settings.json`). This leaves room for the LLM's response.
36
+
37
+ You can also trigger manually with `/compact [instructions]`, where optional instructions focus the summary.
38
+
39
+ ### How It Works
40
+
41
+ 1. **Find cut point**: Walk backwards from newest message, accumulating token estimates until `keepRecentTokens` (default 20k, configurable in `~/.indusagi/agent/settings.json` or `<project-dir>/.indusagi/settings.json`) is reached
42
+ 2. **Extract messages**: Collect messages from previous compaction (or start) up to cut point
43
+ 3. **Generate summary**: Call LLM to summarize with structured format
44
+ 4. **Append entry**: Save `CompactionEntry` with summary and `firstKeptEntryId`
45
+ 5. **Reload**: Session reloads, using summary + messages from `firstKeptEntryId` onwards
46
+
47
+ ```
48
+ Before compaction:
49
+
50
+ entry: 0 1 2 3 4 5 6 7 8 9
51
+ ┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┐
52
+ │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│
53
+ └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘
54
+ └────────┬───────┘ └──────────────┬──────────────┘
55
+ messagesToSummarize kept messages
56
+
57
+ firstKeptEntryId (entry 4)
58
+
59
+ After compaction (new entry appended):
60
+
61
+ entry: 0 1 2 3 4 5 6 7 8 9 10
62
+ ┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬─────┐
63
+ │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │
64
+ └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘
65
+ └──────────┬──────┘ └──────────────────────┬───────────────────┘
66
+ not sent to LLM sent to LLM
67
+
68
+ starts from firstKeptEntryId
69
+
70
+ What the LLM sees:
71
+
72
+ ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
73
+ │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
74
+ └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
75
+ ↑ ↑ └─────────────────┬────────────────┘
76
+ prompt from cmp messages from firstKeptEntryId
77
+ ```
78
+
79
+ ### Split Turns
80
+
81
+ A "turn" starts with a user message and includes all assistant responses and tool calls until the next user message. Normally, compaction cuts at turn boundaries.
82
+
83
+ When a single turn exceeds `keepRecentTokens`, the cut point lands mid-turn at an assistant message. This is a "split turn":
84
+
85
+ ```
86
+ Split turn (one huge turn exceeds budget):
87
+
88
+ entry: 0 1 2 3 4 5 6 7 8
89
+ ┌─────┬─────┬─────┬──────┬─────┬──────┬──────┬─────┬──────┐
90
+ │ hdr │ usr │ ass │ tool │ ass │ tool │ tool │ ass │ tool │
91
+ └─────┴─────┴─────┴──────┴─────┴──────┴──────┴─────┴──────┘
92
+ ↑ ↑
93
+ turnStartIndex = 1 firstKeptEntryId = 7
94
+ │ │
95
+ └──── turnPrefixMessages (1-6) ───────┘
96
+ └── kept (7-8)
97
+
98
+ isSplitTurn = true
99
+ messagesToSummarize = [] (no complete turns before)
100
+ turnPrefixMessages = [usr, ass, tool, ass, tool, tool]
101
+ ```
102
+
103
+ For split turns, indusagi generates two summaries and merges them:
104
+ 1. **History summary**: Previous context (if any)
105
+ 2. **Turn prefix summary**: The early part of the split turn
106
+
107
+ ### Cut Point Rules
108
+
109
+ Valid cut points are:
110
+ - User messages
111
+ - Assistant messages
112
+ - BashExecution messages
113
+ - Custom messages (custom_message, branch_summary)
114
+
115
+ Never cut at tool results (they must stay with their tool call).
116
+
117
+ ### CompactionEntry Structure
118
+
119
+ Defined in [`session-manager.ts`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts):
120
+
121
+ ```typescript
122
+ interface CompactionEntry<T = unknown> {
123
+ type: "compaction";
124
+ id: string;
125
+ parentId: string;
126
+ timestamp: number;
127
+ summary: string;
128
+ firstKeptEntryId: string;
129
+ tokensBefore: number;
130
+ fromHook?: boolean; // true if provided by extension (legacy field name)
131
+ details?: T; // implementation-specific data
132
+ }
133
+
134
+ // Default compaction uses this for details (from compaction.ts):
135
+ interface CompactionDetails {
136
+ readFiles: string[];
137
+ modifiedFiles: string[];
138
+ }
139
+ ```
140
+
141
+ Extensions can store any JSON-serializable data in `details`. The default compaction tracks file operations, but custom extension implementations can use their own structure.
142
+
143
+ See [`prepareCompaction()`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) and [`compact()`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) for the implementation.
144
+
145
+ ## Branch Summarization
146
+
147
+ ### When It Triggers
148
+
149
+ When you use `/tree` to navigate to a different branch, indusagi offers to summarize the work you're leaving. This injects context from the left branch into the new branch.
150
+
151
+ ### How It Works
152
+
153
+ 1. **Find common ancestor**: Deepest node shared by old and new positions
154
+ 2. **Collect entries**: Walk from old leaf back to common ancestor
155
+ 3. **Prepare with budget**: Include messages up to token budget (newest first)
156
+ 4. **Generate summary**: Call LLM with structured format
157
+ 5. **Append entry**: Save `BranchSummaryEntry` at navigation point
158
+
159
+ ```
160
+ Tree before navigation:
161
+
162
+ ┌─ B ─ C ─ D (old leaf, being abandoned)
163
+ A ───┤
164
+ └─ E ─ F (target)
165
+
166
+ Common ancestor: A
167
+ Entries to summarize: B, C, D
168
+
169
+ After navigation with summary:
170
+
171
+ ┌─ B ─ C ─ D ─ [summary of B,C,D]
172
+ A ───┤
173
+ └─ E ─ F (new leaf)
174
+ ```
175
+
176
+ ### Cumulative File Tracking
177
+
178
+ Both compaction and branch summarization track files cumulatively. When generating a summary, indusagi extracts file operations from:
179
+ - Tool calls in the messages being summarized
180
+ - Previous compaction or branch summary `details` (if any)
181
+
182
+ This means file tracking accumulates across multiple compactions or nested branch summaries, preserving the full history of read and modified files.
183
+
184
+ ### BranchSummaryEntry Structure
185
+
186
+ Defined in [`session-manager.ts`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts):
187
+
188
+ ```typescript
189
+ interface BranchSummaryEntry<T = unknown> {
190
+ type: "branch_summary";
191
+ id: string;
192
+ parentId: string;
193
+ timestamp: number;
194
+ summary: string;
195
+ fromId: string; // Entry we navigated from
196
+ fromHook?: boolean; // true if provided by extension (legacy field name)
197
+ details?: T; // implementation-specific data
198
+ }
199
+
200
+ // Default branch summarization uses this for details (from branch-summarization.ts):
201
+ interface BranchSummaryDetails {
202
+ readFiles: string[];
203
+ modifiedFiles: string[];
204
+ }
205
+ ```
206
+
207
+ Same as compaction, extensions can store custom data in `details`.
208
+
209
+ See [`collectEntriesForBranchSummary()`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts), [`prepareBranchEntries()`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts), and [`generateBranchSummary()`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts) for the implementation.
210
+
211
+ ## Summary Format
212
+
213
+ Both compaction and branch summarization use the same structured format:
214
+
215
+ ```markdown
216
+ ## Goal
217
+ [What the user is trying to accomplish]
218
+
219
+ ## Constraints & Preferences
220
+ - [Requirements mentioned by user]
221
+
222
+ ## Progress
223
+ ### Done
224
+ - [x] [Completed tasks]
225
+
226
+ ### In Progress
227
+ - [ ] [Current work]
228
+
229
+ ### Blocked
230
+ - [Issues, if any]
231
+
232
+ ## Key Decisions
233
+ - **[Decision]**: [Rationale]
234
+
235
+ ## Next Steps
236
+ 1. [What should happen next]
237
+
238
+ ## Critical Context
239
+ - [Data needed to continue]
240
+
241
+ <read-files>
242
+ path/to/file1.ts
243
+ path/to/file2.ts
244
+ </read-files>
245
+
246
+ <modified-files>
247
+ path/to/changed.ts
248
+ </modified-files>
249
+ ```
250
+
251
+ ### Message Serialization
252
+
253
+ Before summarization, messages are serialized to text via [`serializeConversation()`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts):
254
+
255
+ ```
256
+ [User]: What they said
257
+ [Assistant thinking]: Internal reasoning
258
+ [Assistant]: Response text
259
+ [Assistant tool calls]: read(path="foo.ts"); edit(path="bar.ts", ...)
260
+ [Tool result]: Output from tool
261
+ ```
262
+
263
+ This prevents the model from treating it as a conversation to continue.
264
+
265
+ ## Custom Summarization via Extensions
266
+
267
+ Extensions can intercept and customize both compaction and branch summarization. See [`extensions/types.ts`](https://github.com/badlogic/indusagi-mono/blob/main/packages/coding-agent/src/core/extensions/types.ts) for event type definitions.
268
+
269
+ ### session_before_compact
270
+
271
+ Fired before auto-compaction or `/compact`. Can cancel or provide custom summary. See `SessionBeforeCompactEvent` and `CompactionPreparation` in the types file.
272
+
273
+ ```typescript
274
+ indusagi.on("session_before_compact", async (event, ctx) => {
275
+ const { preparation, branchEntries, customInstructions, signal } = event;
276
+
277
+ // preparation.messagesToSummarize - messages to summarize
278
+ // preparation.turnPrefixMessages - split turn prefix (if isSplitTurn)
279
+ // preparation.previousSummary - previous compaction summary
280
+ // preparation.fileOps - extracted file operations
281
+ // preparation.tokensBefore - context tokens before compaction
282
+ // preparation.firstKeptEntryId - where kept messages start
283
+ // preparation.settings - compaction settings
284
+
285
+ // branchEntries - all entries on current branch (for custom state)
286
+ // signal - AbortSignal (pass to LLM calls)
287
+
288
+ // Cancel:
289
+ return { cancel: true };
290
+
291
+ // Custom summary:
292
+ return {
293
+ compaction: {
294
+ summary: "Your summary...",
295
+ firstKeptEntryId: preparation.firstKeptEntryId,
296
+ tokensBefore: preparation.tokensBefore,
297
+ details: { /* custom data */ },
298
+ }
299
+ };
300
+ });
301
+ ```
302
+
303
+ #### Converting Messages to Text
304
+
305
+ To generate a summary with your own model, convert messages to text using `serializeConversation`:
306
+
307
+ ```typescript
308
+ import { convertToLlm, serializeConversation } from "indusagi-coding-agent";
309
+
310
+ indusagi.on("session_before_compact", async (event, ctx) => {
311
+ const { preparation } = event;
312
+
313
+ // Convert AgentMessage[] to Message[], then serialize to text
314
+ const conversationText = serializeConversation(
315
+ convertToLlm(preparation.messagesToSummarize)
316
+ );
317
+ // Returns:
318
+ // [User]: message text
319
+ // [Assistant thinking]: thinking content
320
+ // [Assistant]: response text
321
+ // [Assistant tool calls]: read(path="..."); bash(command="...")
322
+ // [Tool result]: output text
323
+
324
+ // Now send to your model for summarization
325
+ const summary = await myModel.summarize(conversationText);
326
+
327
+ return {
328
+ compaction: {
329
+ summary,
330
+ firstKeptEntryId: preparation.firstKeptEntryId,
331
+ tokensBefore: preparation.tokensBefore,
332
+ }
333
+ };
334
+ });
335
+ ```
336
+
337
+ See [custom-compaction.ts](../examples/extensions/custom-compaction.ts) for a complete example using a different model.
338
+
339
+ ### session_before_tree
340
+
341
+ Fired before `/tree` navigation. Always fires regardless of whether user chose to summarize. Can cancel navigation or provide custom summary.
342
+
343
+ ```typescript
344
+ indusagi.on("session_before_tree", async (event, ctx) => {
345
+ const { preparation, signal } = event;
346
+
347
+ // preparation.targetId - where we're navigating to
348
+ // preparation.oldLeafId - current position (being abandoned)
349
+ // preparation.commonAncestorId - shared ancestor
350
+ // preparation.entriesToSummarize - entries that would be summarized
351
+ // preparation.userWantsSummary - whether user chose to summarize
352
+
353
+ // Cancel navigation entirely:
354
+ return { cancel: true };
355
+
356
+ // Provide custom summary (only used if userWantsSummary is true):
357
+ if (preparation.userWantsSummary) {
358
+ return {
359
+ summary: {
360
+ summary: "Your summary...",
361
+ details: { /* custom data */ },
362
+ }
363
+ };
364
+ }
365
+ });
366
+ ```
367
+
368
+ See `SessionBeforeTreeEvent` and `TreePreparation` in the types file.
369
+
370
+ ## Settings
371
+
372
+ Configure compaction in `~/.indusagi/agent/settings.json` or `<project-dir>/.indusagi/settings.json`:
373
+
374
+ ```json
375
+ {
376
+ "compaction": {
377
+ "enabled": true,
378
+ "reserveTokens": 16384,
379
+ "keepRecentTokens": 20000
380
+ }
381
+ }
382
+ ```
383
+
384
+ | Setting | Default | Description |
385
+ |---------|---------|-------------|
386
+ | `enabled` | `true` | Enable auto-compaction |
387
+ | `reserveTokens` | `16384` | Tokens to reserve for LLM response |
388
+ | `keepRecentTokens` | `20000` | Recent tokens to keep (not summarized) |
389
+
390
+ Disable auto-compaction with `"enabled": false`. You can still compact manually with `/compact`.