aura-code 0.6.3 → 0.10.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.
- package/README.md +47 -164
- package/dist/agent/affect.d.ts +13 -0
- package/dist/agent/affect.js +34 -0
- package/dist/agent/affect.js.map +1 -0
- package/dist/agent/compactor.d.ts +27 -4
- package/dist/agent/compactor.js +185 -54
- package/dist/agent/compactor.js.map +1 -1
- package/dist/agent/confess.d.ts +35 -0
- package/dist/agent/confess.js +296 -0
- package/dist/agent/confess.js.map +1 -0
- package/dist/agent/context.d.ts +0 -4
- package/dist/agent/context.js +0 -38
- package/dist/agent/context.js.map +1 -1
- package/dist/agent/domain-expertise.d.ts +22 -0
- package/dist/agent/domain-expertise.js +110 -0
- package/dist/agent/domain-expertise.js.map +1 -0
- package/dist/agent/executive-queue.d.ts +26 -0
- package/dist/agent/executive-queue.js +50 -0
- package/dist/agent/executive-queue.js.map +1 -0
- package/dist/agent/generational-flush.d.ts +28 -0
- package/dist/agent/generational-flush.js +48 -0
- package/dist/agent/generational-flush.js.map +1 -0
- package/dist/agent/loop-profile.d.ts +50 -0
- package/dist/agent/loop-profile.js +88 -0
- package/dist/agent/loop-profile.js.map +1 -0
- package/dist/agent/loop.d.ts +15 -5
- package/dist/agent/loop.js +182 -45
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/memory-consolidate.d.ts +14 -0
- package/dist/agent/memory-consolidate.js +141 -0
- package/dist/agent/memory-consolidate.js.map +1 -0
- package/dist/agent/mixture.d.ts +33 -0
- package/dist/agent/mixture.js +140 -0
- package/dist/agent/mixture.js.map +1 -0
- package/dist/agent/spawner.js +2 -2
- package/dist/agent/spawner.js.map +1 -1
- package/dist/agent/system-prompt.d.ts +1 -1
- package/dist/agent/system-prompt.js +50 -22
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/unified-memory.d.ts +15 -0
- package/dist/agent/unified-memory.js +164 -0
- package/dist/agent/unified-memory.js.map +1 -0
- package/dist/checkpoints/engine.d.ts +44 -0
- package/dist/checkpoints/engine.js +324 -0
- package/dist/checkpoints/engine.js.map +1 -0
- package/dist/cli/command-palette.d.ts +26 -0
- package/dist/cli/command-palette.js +133 -0
- package/dist/cli/command-palette.js.map +1 -0
- package/dist/cli/context-health.d.ts +51 -0
- package/dist/cli/context-health.js +187 -0
- package/dist/cli/context-health.js.map +1 -0
- package/dist/cli/diamond.d.ts +67 -0
- package/dist/cli/diamond.js +222 -12
- package/dist/cli/diamond.js.map +1 -1
- package/dist/cli/dic.d.ts +2 -0
- package/dist/cli/dic.js +106 -0
- package/dist/cli/dic.js.map +1 -0
- package/dist/cli/diff-view.d.ts +18 -0
- package/dist/cli/diff-view.js +114 -0
- package/dist/cli/diff-view.js.map +1 -0
- package/dist/cli/display.d.ts +20 -4
- package/dist/cli/display.js +77 -151
- package/dist/cli/display.js.map +1 -1
- package/dist/cli/help-data.d.ts +7 -0
- package/dist/cli/help-data.js +80 -0
- package/dist/cli/help-data.js.map +1 -0
- package/dist/cli/index.js +1018 -851
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/markdown.d.ts +5 -0
- package/dist/cli/markdown.js +128 -0
- package/dist/cli/markdown.js.map +1 -0
- package/dist/cli/model-select.d.ts +39 -0
- package/dist/cli/model-select.js +65 -0
- package/dist/cli/model-select.js.map +1 -0
- package/dist/cli/tui.d.ts +34 -0
- package/dist/cli/tui.js +1190 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/config/defaults.d.ts +6 -17
- package/dist/config/defaults.js +25 -46
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/project-config.d.ts +2 -6
- package/dist/config/project-config.js +2 -2
- package/dist/config/project-config.js.map +1 -1
- package/dist/doctor/checks.d.ts +18 -0
- package/dist/doctor/checks.js +524 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/doctor/index.d.ts +4 -0
- package/dist/doctor/index.js +229 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/doctor/repair.d.ts +19 -0
- package/dist/doctor/repair.js +126 -0
- package/dist/doctor/repair.js.map +1 -0
- package/dist/doctor/types.d.ts +38 -0
- package/dist/doctor/types.js +10 -0
- package/dist/doctor/types.js.map +1 -0
- package/dist/dream/dream.d.ts +79 -59
- package/dist/dream/dream.js +324 -144
- package/dist/dream/dream.js.map +1 -1
- package/dist/dream/episode.d.ts +17 -0
- package/dist/dream/episode.js +116 -0
- package/dist/dream/episode.js.map +1 -0
- package/dist/kanban/engine.d.ts +26 -0
- package/dist/kanban/engine.js +208 -0
- package/dist/kanban/engine.js.map +1 -0
- package/dist/kanban/types.d.ts +19 -0
- package/dist/kanban/types.js +9 -1
- package/dist/kanban/types.js.map +1 -1
- package/dist/machina/render-html.js +4 -2
- package/dist/machina/render-html.js.map +1 -1
- package/dist/machina/render-terminal.js +11 -4
- package/dist/machina/render-terminal.js.map +1 -1
- package/dist/machina/repair.d.ts +23 -0
- package/dist/machina/repair.js +98 -0
- package/dist/machina/repair.js.map +1 -0
- package/dist/machina/spec.js +12 -12
- package/dist/machina/spec.js.map +1 -1
- package/dist/machina/verify.d.ts +13 -1
- package/dist/machina/verify.js +12 -2
- package/dist/machina/verify.js.map +1 -1
- package/dist/mining/extract.d.ts +34 -0
- package/dist/mining/extract.js +229 -0
- package/dist/mining/extract.js.map +1 -0
- package/dist/mining/refine.d.ts +28 -0
- package/dist/mining/refine.js +256 -0
- package/dist/mining/refine.js.map +1 -0
- package/dist/perception/extractor.js +5 -3
- package/dist/perception/extractor.js.map +1 -1
- package/dist/perception/graph-store.d.ts +0 -11
- package/dist/perception/graph-store.js +0 -33
- package/dist/perception/graph-store.js.map +1 -1
- package/dist/perception/index.d.ts +1 -1
- package/dist/perception/index.js +1 -2
- package/dist/perception/index.js.map +1 -1
- package/dist/plugins/commands.d.ts +38 -0
- package/dist/plugins/commands.js +134 -0
- package/dist/plugins/commands.js.map +1 -0
- package/dist/plugins/frontmatter.d.ts +14 -0
- package/dist/plugins/frontmatter.js +78 -0
- package/dist/plugins/frontmatter.js.map +1 -0
- package/dist/plugins/hooks.d.ts +12 -0
- package/dist/plugins/hooks.js +148 -0
- package/dist/plugins/hooks.js.map +1 -0
- package/dist/plugins/loader.d.ts +7 -0
- package/dist/plugins/loader.js +255 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/market.d.ts +23 -0
- package/dist/plugins/market.js +281 -0
- package/dist/plugins/market.js.map +1 -0
- package/dist/plugins/types.d.ts +90 -0
- package/dist/plugins/types.js +20 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/providers/anthropic-oauth-draft.d.ts +119 -0
- package/dist/providers/anthropic-oauth-draft.js +414 -0
- package/dist/providers/anthropic-oauth-draft.js.map +1 -0
- package/dist/providers/anthropic.d.ts +4 -1
- package/dist/providers/anthropic.js +35 -7
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/factory.d.ts +43 -19
- package/dist/providers/factory.js +183 -129
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/google.js +9 -22
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/live-models.d.ts +23 -0
- package/dist/providers/live-models.js +145 -0
- package/dist/providers/live-models.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +1 -12
- package/dist/providers/openai-compatible.js +92 -108
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/providers/resilient-factory.js +1 -1
- package/dist/providers/resilient-factory.js.map +1 -1
- package/dist/providers/types.d.ts +1 -0
- package/dist/repl/queue.d.ts +22 -0
- package/dist/repl/queue.js +165 -0
- package/dist/repl/queue.js.map +1 -0
- package/dist/repl/side-channel.d.ts +23 -0
- package/dist/repl/side-channel.js +63 -0
- package/dist/repl/side-channel.js.map +1 -0
- package/dist/research/council.d.ts +2 -0
- package/dist/research/council.js +29 -4
- package/dist/research/council.js.map +1 -1
- package/dist/ruby/alternator.d.ts +11 -30
- package/dist/ruby/alternator.js +59 -38
- package/dist/ruby/alternator.js.map +1 -1
- package/dist/ruby/episode-capture.d.ts +0 -10
- package/dist/ruby/episode-capture.js +0 -33
- package/dist/ruby/episode-capture.js.map +1 -1
- package/dist/ruby/types.js +1 -1
- package/dist/ruby/types.js.map +1 -1
- package/dist/safety/path-jail.d.ts +17 -0
- package/dist/safety/path-jail.js +100 -0
- package/dist/safety/path-jail.js.map +1 -0
- package/dist/safety/permissions.d.ts +12 -21
- package/dist/safety/permissions.js +81 -216
- package/dist/safety/permissions.js.map +1 -1
- package/dist/safety/ssrf.d.ts +29 -0
- package/dist/safety/ssrf.js +185 -0
- package/dist/safety/ssrf.js.map +1 -0
- package/dist/server/index.js +64 -10
- package/dist/server/index.js.map +1 -1
- package/dist/setup/first-run.d.ts +0 -24
- package/dist/setup/first-run.js +10 -344
- package/dist/setup/first-run.js.map +1 -1
- package/dist/setup/key-store.d.ts +12 -0
- package/dist/setup/key-store.js +108 -0
- package/dist/setup/key-store.js.map +1 -0
- package/dist/setup/provider-registry.js +31 -18
- package/dist/setup/provider-registry.js.map +1 -1
- package/dist/setup/provider-test.d.ts +7 -0
- package/dist/setup/provider-test.js +73 -10
- package/dist/setup/provider-test.js.map +1 -1
- package/dist/setup/provider-wizard.d.ts +4 -1
- package/dist/setup/provider-wizard.js +92 -26
- package/dist/setup/provider-wizard.js.map +1 -1
- package/dist/setup/xiaomi.d.ts +1 -1
- package/dist/setup/xiaomi.js +3 -3
- package/dist/setup/xiaomi.js.map +1 -1
- package/dist/tools/browser.js +14 -10
- package/dist/tools/browser.js.map +1 -1
- package/dist/tools/clipboard.js +7 -1
- package/dist/tools/clipboard.js.map +1 -1
- package/dist/tools/dictate.d.ts +60 -0
- package/dist/tools/dictate.js +983 -0
- package/dist/tools/dictate.js.map +1 -0
- package/dist/tools/dictate_patched.js +1057 -0
- package/dist/tools/edit-file.js +10 -2
- package/dist/tools/edit-file.js.map +1 -1
- package/dist/tools/http-request.js +14 -4
- package/dist/tools/http-request.js.map +1 -1
- package/dist/tools/image-read.js +34 -2
- package/dist/tools/image-read.js.map +1 -1
- package/dist/tools/index.d.ts +16 -1
- package/dist/tools/index.js +59 -8
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp.js +12 -0
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/read-file.js +10 -14
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/telegram-audio-policy.d.ts +30 -0
- package/dist/tools/telegram-audio-policy.js +49 -0
- package/dist/tools/telegram-audio-policy.js.map +1 -0
- package/dist/tools/telegram-bot.js +1136 -537
- package/dist/tools/telegram-bot.js.map +1 -1
- package/dist/tools/telegram-voice.d.ts +18 -5
- package/dist/tools/telegram-voice.js +54 -12
- package/dist/tools/telegram-voice.js.map +1 -1
- package/dist/tools/telegram.d.ts +3 -3
- package/dist/tools/telegram.js +5 -84
- package/dist/tools/telegram.js.map +1 -1
- package/dist/tools/tools.d.ts +2 -14
- package/dist/tools/tools.js +79 -144
- package/dist/tools/tools.js.map +1 -1
- package/dist/tools/web-fetch.js +39 -7
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/web-search.d.ts +0 -20
- package/dist/tools/web-search.js +40 -137
- package/dist/tools/web-search.js.map +1 -1
- package/dist/util/errors.js +1 -15
- package/dist/util/errors.js.map +1 -1
- package/dist/util/json-repair.d.ts +11 -0
- package/dist/util/json-repair.js +31 -0
- package/dist/util/json-repair.js.map +1 -0
- package/dist/util/rate-limiter.js +2 -8
- package/dist/util/rate-limiter.js.map +1 -1
- package/dist/viz/index.js +793 -792
- package/dist/viz/index.js.map +1 -1
- package/package.json +12 -32
package/README.md
CHANGED
|
@@ -1,189 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
<img src="./README-hero.jpg" alt="Aura">
|
|
3
|
-
</p>
|
|
1
|
+
# Aura Code
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
<h1 align="center">Aura Code — Autonomous Coding Agent</h1>
|
|
7
|
-
|
|
8
|
-
<p align="center">
|
|
9
|
-
<em>I don't try. I verify.</em>
|
|
10
|
-
</p>
|
|
11
|
-
|
|
12
|
-
<p align="center">
|
|
13
|
-
<a href="https://www.npmjs.com/package/aura-code"><img src="https://img.shields.io/npm/v/aura-code?color=cc0000&label=npm&logo=npm" alt="npm version"></a>
|
|
14
|
-
<a href="https://github.com/milodule3-debug/aura-code/actions/workflows/ci.yml"><img src="https://github.com/milodule3-debug/aura-code/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
15
|
-
<img src="https://img.shields.io/badge/tests-1205%2B-brightgreen" alt="1205+ tests">
|
|
16
|
-
<a href="https://github.com/milodule3-debug/aura-code/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License"></a>
|
|
17
|
-
<img src="https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white" alt="Node.js 18+">
|
|
18
|
-
<a href="https://github.com/milodule3-debug/aura-code"><img src="https://img.shields.io/github/stars/milodule3-debug/aura-code?style=social" alt="GitHub Stars"></a>
|
|
19
|
-
</p>
|
|
20
|
-
|
|
21
|
-
<p align="center">
|
|
22
|
-
<a href="./docs/ARCHITECTURE.md">
|
|
23
|
-
<img src="./assets/architecture-diagram.png" alt="Architecture Diagram" width="800">
|
|
24
|
-
</a>
|
|
25
|
-
<br>
|
|
26
|
-
<em><a href="./docs/ARCHITECTURE.md">View full architecture documentation →</a></em>
|
|
27
|
-
</p>
|
|
28
|
-
|
|
29
|
-
<p align="center">
|
|
30
|
-
<img src="./assets/demo.gif" alt="Aura Code CLI Demo" width="800">
|
|
31
|
-
<br>
|
|
32
|
-
<em>CLI demo — help, models, and version overview</em>
|
|
33
|
-
</p>
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## What is Aura Code?
|
|
38
|
-
|
|
39
|
-
Designed and brought to life by **Dušan Milosavljević** using opencode.
|
|
40
|
-
Built by agents, for agents — AI-assisted engineering workflows with multiple AI agents contributing to design, implementation, testing, and verification.
|
|
41
|
-
Model-agnostic: works with Claude, GPT, Gemini, MiMo, and any OpenAI-compatible endpoint.
|
|
42
|
-
Written in TypeScript — not related to the Ruby programming language.
|
|
43
|
-
|
|
44
|
-
Built on the **Praktess** framework — from Ancient Greek: *she who acts and executes*.
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Quick Start
|
|
3
|
+
> Model-agnostic autonomous coding agent. Natural-language task → read codebase → plan → execute → verify → report.
|
|
49
4
|
|
|
50
5
|
```bash
|
|
51
6
|
npm install -g aura-code
|
|
52
|
-
aura '
|
|
7
|
+
aura 'fix the login bug'
|
|
53
8
|
```
|
|
54
9
|
|
|
55
|
-
|
|
10
|
+
## Quick Start
|
|
56
11
|
|
|
57
12
|
```bash
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export GOOGLE_API_KEY="..." # Gemini
|
|
61
|
-
export XIAOMI_API_KEY="tp-..." # MiMo
|
|
62
|
-
# Local — no API key needed:
|
|
63
|
-
# ollama pull qwen2.5-coder:1.5b
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## What Aura Does
|
|
69
|
-
|
|
70
|
-
1. **Reads** your codebase — files, structure, dependencies
|
|
71
|
-
2. **Plans** a strategy — decides what to change and how
|
|
72
|
-
3. **Executes** — writes code, runs commands, makes edits
|
|
73
|
-
4. **Verifies** — runs tests, checks file integrity, confirms changes
|
|
74
|
-
5. **Reports** — summarizes what was done and what passed
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## Modes
|
|
79
|
-
|
|
80
|
-
| Mode | What it does |
|
|
81
|
-
|------|-------------|
|
|
82
|
-
| `normal` | Single-agent loop: read → plan → execute → verify |
|
|
83
|
-
| `orchestrate` | Multi-agent: Researcher → Coder → Reviewer |
|
|
84
|
-
| `architect` | High-level design and planning before implementation |
|
|
85
|
-
| `verify` | Post-task checks with automatic retry on failure |
|
|
86
|
-
| `analyze` | Scan session history for failure patterns |
|
|
13
|
+
# Install
|
|
14
|
+
npm install -g aura-code
|
|
87
15
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
aura
|
|
91
|
-
aura --architect 'design the new auth system' # architect
|
|
92
|
-
aura --verify --test-command "npm test" 'fix the tests' # verify
|
|
93
|
-
aura --analyze # analyze
|
|
16
|
+
# Run (requires at least one provider API key)
|
|
17
|
+
export ANTHROPIC_API_KEY=sk-... # or OPENAI_API_KEY, GOOGLE_API_KEY, etc.
|
|
18
|
+
aura 'add a logout button'
|
|
94
19
|
```
|
|
95
20
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
## Providers
|
|
21
|
+
## Providers (model-agnostic)
|
|
99
22
|
|
|
100
23
|
| Provider | Models |
|
|
101
24
|
|----------|--------|
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
109
|
-
Any OpenAI-compatible endpoint also works via `openrouter/<model>`.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Testing
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
npm install
|
|
117
|
-
npm run build # compiles TypeScript, zero errors expected
|
|
118
|
-
npm test # runs the full suite (vitest)
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
For local development:
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
npm run test:watch # re-runs tests on file changes
|
|
125
|
-
npm run test:coverage # generates a coverage report
|
|
126
|
-
```
|
|
25
|
+
| Claude (Anthropic) | Opus, Sonnet, Haiku |
|
|
26
|
+
| GPT (OpenAI) | gpt-4o, gpt-4o-mini |
|
|
27
|
+
| Gemini (Google) | gemini-2.5-pro, gemini-2.5-flash |
|
|
28
|
+
| GLM (Zhipu / Z.ai) | glm-5.2, glm-5.1, glm-5 |
|
|
29
|
+
| MiMo (Xiaomi) | mimo-v2.5-pro, mimo-v2.5 |
|
|
30
|
+
| Ollama | any local model |
|
|
31
|
+
| OpenCode Go | Anthropic-compatible endpoint |
|
|
127
32
|
|
|
128
|
-
|
|
129
|
-
loop, all provider integrations, the tool registry, safety/permissions, the
|
|
130
|
-
orchestration and self-improvement layers, and the dashboard generator.
|
|
33
|
+
Run `aura` without arguments for the interactive setup wizard.
|
|
131
34
|
|
|
132
|
-
|
|
133
|
-
opening a pull request.
|
|
35
|
+
## Features
|
|
134
36
|
|
|
135
|
-
|
|
37
|
+
- **Autonomous execution** — reads files, edits code, runs commands, verifies results
|
|
38
|
+
- **Multi-provider fallback** — resilient API handling with circuit breakers
|
|
39
|
+
- **TUI mode** — full terminal UI with command palette, diff view, markdown rendering
|
|
40
|
+
- **Telegram bot** — voice replies, PC control, file sending
|
|
41
|
+
- **Benchmark harness** — reproducible coding tasks with verified outcomes
|
|
42
|
+
- **Context compaction** — adaptive memory for long sessions
|
|
136
43
|
|
|
137
|
-
##
|
|
44
|
+
## CLI Flags
|
|
138
45
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
fixes), but not all of it. If a model behaves unexpectedly with tools,
|
|
149
|
-
check whether the provider documents function-calling support before
|
|
150
|
-
assuming it's a bug in Aura.
|
|
151
|
-
|
|
152
|
-
- **Large refactors still need verification.** `--verify` and `--test-command`
|
|
153
|
-
catch regressions Aura's own changes introduce, but they don't replace
|
|
154
|
-
review for changes spanning many files or with subtle behavioral intent.
|
|
155
|
-
|
|
156
|
-
- **Conversation compaction is new.** Long sessions now auto-summarize older
|
|
157
|
-
turns to stay within context limits; this is recently added and still
|
|
158
|
-
being hardened against edge cases in unusual tool-call sequences.
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## Stats
|
|
163
|
-
|
|
164
|
-
| Metric | Value |
|
|
165
|
-
|--------|-------|
|
|
166
|
-
| Tests | 1205+ passing, 0 failures |
|
|
167
|
-
| Version | v0.6.2 |
|
|
168
|
-
| Language | TypeScript (strict) |
|
|
169
|
-
| License | MIT |
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## Repository
|
|
46
|
+
| Flag | Description |
|
|
47
|
+
|------|-------------|
|
|
48
|
+
| `--auto` | Fully autonomous (no confirmations) |
|
|
49
|
+
| `--model <id>` | Specific model (e.g. `claude-sonnet-4`) |
|
|
50
|
+
| `--provider <name>` | Force provider |
|
|
51
|
+
| `--orchestrate` | Multi-agent mode (Researcher → Coder → Reviewer) |
|
|
52
|
+
| `--verify` | Post-task checks with auto-retry |
|
|
53
|
+
| `--readonly` | Read-only analysis |
|
|
54
|
+
| `--doctor` | Run self-diagnostic checks |
|
|
174
55
|
|
|
175
|
-
|
|
176
|
-
(Repo renamed from milodule3-debug/rubyness — existing clone URLs redirect automatically)
|
|
56
|
+
## Configuration
|
|
177
57
|
|
|
178
|
-
|
|
58
|
+
- `.aura.json` — project-level model/provider config
|
|
59
|
+
- Global config via first-run wizard
|
|
60
|
+
- Environment overrides: `AURA_MODEL`, `AURA_FALLBACK_MODEL`, `AURA_MAX_RETRIES`
|
|
179
61
|
|
|
180
|
-
##
|
|
62
|
+
## Development
|
|
181
63
|
|
|
182
|
-
|
|
183
|
-
|
|
64
|
+
```bash
|
|
65
|
+
npm run build # tsc → dist/
|
|
66
|
+
npm run dev # ts-node src/cli/index.ts
|
|
67
|
+
npm test # vitest
|
|
68
|
+
```
|
|
184
69
|
|
|
185
|
-
|
|
70
|
+
## License
|
|
186
71
|
|
|
187
|
-
|
|
188
|
-
Built by <a href="https://lean-progress-iq-site.vercel.app">Lean Progress IQ</a>
|
|
189
|
-
</p>
|
|
72
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight affect heuristic — integrated into the loop, not a standalone
|
|
3
|
+
* NLU module. A lexical scan of recent user messages; when it detects
|
|
4
|
+
* frustration it returns one advisory line for the compaction recap.
|
|
5
|
+
* No LLM call; false positives cost a single recap line.
|
|
6
|
+
*/
|
|
7
|
+
import type { HistoryMessage } from '../providers/types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Scan the last `lookback` user messages. Requires ≥2 total hits, or ≥1 in
|
|
10
|
+
* the very last user message, so ordinary bug-fixing vocabulary ("fix this
|
|
11
|
+
* error") doesn't trip it. Returns an advisory line or null.
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectFrustration(history: HistoryMessage[], lookback?: number): string | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectFrustration = detectFrustration;
|
|
4
|
+
// Seeded from the dream reconciler's NEGATIVE_WORDS plus frustration-specific
|
|
5
|
+
// terms. Kept local: the agent core must not depend on src/dream/.
|
|
6
|
+
const FRUSTRATION_WORDS = [
|
|
7
|
+
'broken', 'fails', 'failing', 'wrong', 'bug', 'error', 'not working',
|
|
8
|
+
"doesn't work", 'never works', 'stop doing', 'again!!', 'still broken',
|
|
9
|
+
'still not', 'why does', 'why is this', 'frustrat', 'annoying', 'useless',
|
|
10
|
+
'wtf', 'terrible',
|
|
11
|
+
];
|
|
12
|
+
function hits(text) {
|
|
13
|
+
const t = text.toLowerCase();
|
|
14
|
+
return FRUSTRATION_WORDS.reduce((n, w) => (t.includes(w) ? n + 1 : n), 0);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Scan the last `lookback` user messages. Requires ≥2 total hits, or ≥1 in
|
|
18
|
+
* the very last user message, so ordinary bug-fixing vocabulary ("fix this
|
|
19
|
+
* error") doesn't trip it. Returns an advisory line or null.
|
|
20
|
+
*/
|
|
21
|
+
function detectFrustration(history, lookback = 3) {
|
|
22
|
+
const userMessages = history.filter(m => m.role === 'user').slice(-lookback);
|
|
23
|
+
if (userMessages.length === 0)
|
|
24
|
+
return null;
|
|
25
|
+
let total = 0;
|
|
26
|
+
for (const m of userMessages)
|
|
27
|
+
total += hits(m.content);
|
|
28
|
+
const lastHit = hits(userMessages[userMessages.length - 1].content) > 0;
|
|
29
|
+
if (total >= 2 || lastHit) {
|
|
30
|
+
return 'Note: recent user messages show signs of frustration — prioritize directness, verify before claiming success.';
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=affect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"affect.js","sourceRoot":"","sources":["../../src/agent/affect.ts"],"names":[],"mappings":";;AA2BA,8CAYC;AA/BD,8EAA8E;AAC9E,mEAAmE;AACnE,MAAM,iBAAiB,GAAG;IACxB,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa;IACpE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc;IACtE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS;IACzE,KAAK,EAAE,UAAU;CAClB,CAAC;AAEF,SAAS,IAAI,CAAC,IAAY;IACxB,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,OAAyB,EAAE,QAAQ,GAAG,CAAC;IACvE,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC7E,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,YAAY;QAAE,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAExE,IAAI,KAAK,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;QAC1B,OAAO,+GAA+G,CAAC;IACzH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { HistoryMessage } from '../providers/types.js';
|
|
2
|
+
export declare const ROLLOVER_AT_GENERATION = 3;
|
|
3
|
+
/** Generation of the current recap (0 if history hasn't been compacted yet). */
|
|
4
|
+
export declare function getRecapGeneration(history: HistoryMessage[]): number;
|
|
5
|
+
/** Index of the current recap message, or -1 if history hasn't been
|
|
6
|
+
* compacted yet. Used by generational-flush.ts to locate the recap it's
|
|
7
|
+
* about to flush and replace. */
|
|
8
|
+
export declare function findRecapIndex(history: HistoryMessage[]): number;
|
|
9
|
+
/** Tokens for a single string: exact via tokenizer, else char-ratio estimate. */
|
|
10
|
+
export declare function countText(text: string): number;
|
|
2
11
|
/**
|
|
3
12
|
* Provider-independent estimate of how many tokens the *next* prompt will
|
|
4
13
|
* carry, measured directly from the system prompt + current history. This is
|
|
@@ -13,14 +22,28 @@ import type { HistoryMessage } from '../providers/types.js';
|
|
|
13
22
|
* conservative char-ratio estimate. Result is identical in shape either way.
|
|
14
23
|
*/
|
|
15
24
|
export declare function estimateContextTokens(system: string, history: HistoryMessage[]): number;
|
|
25
|
+
/** Token estimate for a single history message. */
|
|
26
|
+
export declare function countMessage(msg: HistoryMessage): number;
|
|
16
27
|
/**
|
|
17
|
-
* Compact conversation history when context usage crosses
|
|
18
|
-
*
|
|
19
|
-
*
|
|
28
|
+
* Compact conversation history when context usage crosses the escalating
|
|
29
|
+
* LADDER threshold for the current recap generation (see thresholdRatio).
|
|
30
|
+
* Keeps the first message (task) and a recent tail of up to RETENTION_RATIO
|
|
31
|
+
* of the window verbatim; replaces the middle with a recap that can carry an
|
|
32
|
+
* executive digest and tone hint (see extras). If the middle already
|
|
33
|
+
* contains a prior recap, it's compressed via compressRecap rather than
|
|
34
|
+
* blindly truncated, and the new recap's generation increments — once it
|
|
35
|
+
* would exceed ROLLOVER_AT_GENERATION, callers should flush via
|
|
36
|
+
* generational-flush.ts instead of calling this again.
|
|
20
37
|
*
|
|
21
38
|
* Mutates `history` in place (clears and re-fills) so callers that hold a
|
|
22
39
|
* shared reference see the compacted version without reassignment.
|
|
23
40
|
*
|
|
24
41
|
* Returns `true` if compaction happened.
|
|
25
42
|
*/
|
|
26
|
-
export
|
|
43
|
+
export interface CompactionExtras {
|
|
44
|
+
/** Digest of recent state-altering tool calls (ExecutiveQueue.digest()). */
|
|
45
|
+
executiveDigest?: string;
|
|
46
|
+
/** Advisory tone hint (detectFrustration()). */
|
|
47
|
+
affectHint?: string;
|
|
48
|
+
}
|
|
49
|
+
export declare function compactHistory(history: HistoryMessage[], totalTokens: number, model: string, extras?: CompactionExtras): boolean;
|
package/dist/agent/compactor.js
CHANGED
|
@@ -1,11 +1,54 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ROLLOVER_AT_GENERATION = void 0;
|
|
4
|
+
exports.getRecapGeneration = getRecapGeneration;
|
|
5
|
+
exports.findRecapIndex = findRecapIndex;
|
|
6
|
+
exports.countText = countText;
|
|
3
7
|
exports.estimateContextTokens = estimateContextTokens;
|
|
8
|
+
exports.countMessage = countMessage;
|
|
4
9
|
exports.compactHistory = compactHistory;
|
|
5
10
|
const factory_js_1 = require("../providers/factory.js");
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Escalating trigger ladder, indexed by "recap generation" (how many times
|
|
13
|
+
* the current recap has already been through compaction). Later generations
|
|
14
|
+
* tolerate more fill before firing again — that content is already dense/
|
|
15
|
+
* pre-summarized, so there's less marginal value in acting early. Held at
|
|
16
|
+
* the last value once the ladder is exhausted; ROLLOVER_AT_GENERATION caps
|
|
17
|
+
* how many in-place rounds happen before a full generational flush (see
|
|
18
|
+
* generational-flush.ts) instead of a further round of lossy recompaction.
|
|
19
|
+
*/
|
|
20
|
+
const LADDER = [0.55, 0.70, 0.85];
|
|
21
|
+
exports.ROLLOVER_AT_GENERATION = 3;
|
|
22
|
+
/** Verbatim recent-history budget kept through compaction, as a share of the
|
|
23
|
+
* window. 40% preserves interaction nuance over long sequences. */
|
|
24
|
+
const RETENTION_RATIO = 0.40;
|
|
8
25
|
const DEFAULT_WINDOW = 128_000;
|
|
26
|
+
/** Messages kept when no later user turn exists to anchor the tail. */
|
|
27
|
+
const FALLBACK_KEEP = 3;
|
|
28
|
+
/** Per-tool-result cap applied by the churn guard (see compactHistory). */
|
|
29
|
+
const MAX_RESULT_CHARS = 4_000;
|
|
30
|
+
function thresholdRatio(generation) {
|
|
31
|
+
return LADDER[Math.min(generation, LADDER.length - 1)];
|
|
32
|
+
}
|
|
33
|
+
/** Marker prefixing every recap message; also carries the generation number. */
|
|
34
|
+
const RECAP_MARKER = '[Earlier conversation compacted';
|
|
35
|
+
function isRecap(msg) {
|
|
36
|
+
return msg.role === 'assistant' && msg.content.startsWith(RECAP_MARKER);
|
|
37
|
+
}
|
|
38
|
+
/** Generation of the current recap (0 if history hasn't been compacted yet). */
|
|
39
|
+
function getRecapGeneration(history) {
|
|
40
|
+
const recap = history.find(isRecap);
|
|
41
|
+
if (!recap)
|
|
42
|
+
return 0;
|
|
43
|
+
const match = recap.content.match(/\(gen (\d+)\)/);
|
|
44
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
45
|
+
}
|
|
46
|
+
/** Index of the current recap message, or -1 if history hasn't been
|
|
47
|
+
* compacted yet. Used by generational-flush.ts to locate the recap it's
|
|
48
|
+
* about to flush and replace. */
|
|
49
|
+
function findRecapIndex(history) {
|
|
50
|
+
return history.findIndex(isRecap);
|
|
51
|
+
}
|
|
9
52
|
/** Average characters per token used for the local size estimate. Deliberately
|
|
10
53
|
* conservative (real ratio is ~4 for English prose, lower for code/JSON) so we
|
|
11
54
|
* err toward compacting slightly early rather than overflowing. */
|
|
@@ -59,25 +102,77 @@ function countText(text) {
|
|
|
59
102
|
*/
|
|
60
103
|
function estimateContextTokens(system, history) {
|
|
61
104
|
let tokens = countText(system);
|
|
62
|
-
for (const msg of history)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
105
|
+
for (const msg of history)
|
|
106
|
+
tokens += countMessage(msg);
|
|
107
|
+
return tokens;
|
|
108
|
+
}
|
|
109
|
+
/** Token estimate for a single history message. */
|
|
110
|
+
function countMessage(msg) {
|
|
111
|
+
switch (msg.role) {
|
|
112
|
+
case 'user':
|
|
113
|
+
return countText(msg.content);
|
|
114
|
+
case 'assistant': {
|
|
115
|
+
let tokens = countText(msg.content ?? '');
|
|
116
|
+
for (const call of msg.toolCalls ?? []) {
|
|
117
|
+
tokens += countText(call.name) + countText(JSON.stringify(call.input));
|
|
118
|
+
}
|
|
119
|
+
return tokens;
|
|
120
|
+
}
|
|
121
|
+
case 'tool_result': {
|
|
122
|
+
let tokens = 0;
|
|
123
|
+
for (const r of msg.results) {
|
|
124
|
+
tokens += countText(r.name) + countText(r.content ?? '');
|
|
125
|
+
}
|
|
126
|
+
return tokens;
|
|
78
127
|
}
|
|
79
128
|
}
|
|
80
|
-
|
|
129
|
+
}
|
|
130
|
+
/** Generic English + prior-recap boilerplate excluded from term extraction. */
|
|
131
|
+
const RECAP_STOPWORDS = new Set([
|
|
132
|
+
'the', 'a', 'an', 'is', 'was', 'were', 'be', 'been', 'to', 'of', 'in', 'on',
|
|
133
|
+
'for', 'and', 'or', 'it', 'this', 'that', 'with', 'not', 'note', 'recent',
|
|
134
|
+
'user', 'assistant', 'tool', 'results', 'called', 'earlier', 'conversation',
|
|
135
|
+
'compacted', 'turns', 'removed', 'stay', 'within', 'context', 'limits',
|
|
136
|
+
]);
|
|
137
|
+
/** Top-N most frequent non-stopword tokens across a set of lines. Pure stats,
|
|
138
|
+
* no LLM — same style as dream.ts's topicKey / mining/extract.ts. */
|
|
139
|
+
function extractTopTerms(lines, n) {
|
|
140
|
+
const freq = new Map();
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const words = line.toLowerCase().replace(/[^a-z0-9\s./_-]/g, ' ').split(/\s+/);
|
|
143
|
+
for (const w of words) {
|
|
144
|
+
if (w.length <= 3 || RECAP_STOPWORDS.has(w))
|
|
145
|
+
continue;
|
|
146
|
+
freq.set(w, (freq.get(w) ?? 0) + 1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([w]) => w);
|
|
150
|
+
}
|
|
151
|
+
/** First non-empty line, truncated to ~8 words — a cheap topic label. */
|
|
152
|
+
function extractConcept(lines) {
|
|
153
|
+
const first = lines.find(l => l.trim().length > 0)?.trim() ?? '(no content)';
|
|
154
|
+
const words = first.split(/\s+/);
|
|
155
|
+
return words.length > 8 ? words.slice(0, 8).join(' ') + '…' : first;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Reduce an existing recap to: 1 concept + top 5 terms, plus its LAST body
|
|
159
|
+
* line preserved verbatim ("last thread, pre-compaction"). Replaces
|
|
160
|
+
* summariseMessage's blind slice(0,120) for recap messages specifically —
|
|
161
|
+
* that truncation was destroying the executive digest and prior context on
|
|
162
|
+
* any second compaction in the same run.
|
|
163
|
+
*/
|
|
164
|
+
function compressRecap(msg) {
|
|
165
|
+
const lines = msg.content.split('\n').filter(l => l.trim().length > 0);
|
|
166
|
+
const body = lines.slice(1); // drop the "[Earlier conversation compacted...]" header
|
|
167
|
+
const lastThread = body.length > 0 ? body[body.length - 1] : '';
|
|
168
|
+
const rest = body.slice(0, -1);
|
|
169
|
+
const concept = extractConcept(rest);
|
|
170
|
+
const terms = extractTopTerms(rest, 5);
|
|
171
|
+
return [
|
|
172
|
+
`Concept: ${concept}`,
|
|
173
|
+
terms.length > 0 ? `Terms: ${terms.join(', ')}` : '',
|
|
174
|
+
lastThread ? `Last thread: ${lastThread}` : '',
|
|
175
|
+
].filter(Boolean).join(' · ');
|
|
81
176
|
}
|
|
82
177
|
function summariseMessage(msg) {
|
|
83
178
|
switch (msg.role) {
|
|
@@ -94,56 +189,65 @@ function summariseMessage(msg) {
|
|
|
94
189
|
}
|
|
95
190
|
}
|
|
96
191
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
* window. Keeps the first message (task) and the most recent messages
|
|
100
|
-
* verbatim; replaces the middle with a recap.
|
|
101
|
-
*
|
|
102
|
-
* Mutates `history` in place (clears and re-fills) so callers that hold a
|
|
103
|
-
* shared reference see the compacted version without reassignment.
|
|
104
|
-
*
|
|
105
|
-
* Returns `true` if compaction happened.
|
|
106
|
-
*/
|
|
107
|
-
function compactHistory(history, totalTokens, model) {
|
|
192
|
+
function compactHistory(history, totalTokens, model, extras) {
|
|
193
|
+
const generation = getRecapGeneration(history);
|
|
108
194
|
const window = (0, factory_js_1.getContextWindow)(model) ?? DEFAULT_WINDOW;
|
|
109
|
-
const threshold = Math.floor(window *
|
|
195
|
+
const threshold = Math.floor(window * thresholdRatio(generation));
|
|
110
196
|
if (totalTokens < threshold)
|
|
111
197
|
return false;
|
|
112
|
-
|
|
113
|
-
if (history.length <= MIN_KEEP)
|
|
198
|
+
if (history.length <= 3)
|
|
114
199
|
return false;
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// collapsing the whole session, discarding a real, recent instruction.
|
|
122
|
-
let keepFrom = -1;
|
|
200
|
+
// Keep-boundary by token budget: walk backward accumulating message sizes
|
|
201
|
+
// until the verbatim tail would exceed RETENTION_RATIO of the window. The
|
|
202
|
+
// last message is always kept even if it alone busts the budget.
|
|
203
|
+
const retainBudget = Math.floor(window * RETENTION_RATIO);
|
|
204
|
+
let acc = 0;
|
|
205
|
+
let keepFrom = history.length;
|
|
123
206
|
for (let i = history.length - 1; i >= 1; i--) {
|
|
207
|
+
const cost = countMessage(history[i]);
|
|
208
|
+
if (acc + cost > retainBudget && keepFrom < history.length)
|
|
209
|
+
break;
|
|
210
|
+
acc += cost;
|
|
211
|
+
keepFrom = i;
|
|
212
|
+
}
|
|
213
|
+
if (keepFrom >= history.length)
|
|
214
|
+
keepFrom = history.length - 1;
|
|
215
|
+
// Snap the boundary to the start of a user turn when one is nearby, so the
|
|
216
|
+
// kept slice opens with user context and tool_use/tool_result pairs stay
|
|
217
|
+
// intact (a user message never sits between a tool_use and its result).
|
|
218
|
+
let snapped = -1;
|
|
219
|
+
for (let i = keepFrom; i >= Math.max(1, keepFrom - 6); i--) {
|
|
124
220
|
if (history[i].role === 'user') {
|
|
125
|
-
|
|
221
|
+
snapped = i;
|
|
126
222
|
break;
|
|
127
223
|
}
|
|
128
224
|
}
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
225
|
+
if (snapped !== -1) {
|
|
226
|
+
keepFrom = snapped;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// No user turn nearby (e.g. one long tool-heavy stretch, like the old
|
|
230
|
+
// FALLBACK_KEEP case) — never let the kept slice START with a
|
|
231
|
+
// tool_result whose tool_use was compacted away.
|
|
232
|
+
keepFrom = Math.max(keepFrom, Math.max(1, history.length - FALLBACK_KEEP));
|
|
233
|
+
while (keepFrom < history.length - 1 &&
|
|
234
|
+
history[keepFrom].role === 'tool_result')
|
|
137
235
|
keepFrom++;
|
|
138
236
|
}
|
|
139
237
|
const toCompact = history.slice(1, keepFrom);
|
|
140
|
-
if (toCompact.length === 0)
|
|
141
|
-
|
|
142
|
-
|
|
238
|
+
if (toCompact.length === 0) {
|
|
239
|
+
// Nothing droppable (the over-budget content IS the recent tail) — the
|
|
240
|
+
// churn guard is the only lever left.
|
|
241
|
+
return truncateOversizedResults(history, threshold);
|
|
242
|
+
}
|
|
243
|
+
const summaries = toCompact.map(msg => isRecap(msg) ? compressRecap(msg) : summariseMessage(msg));
|
|
244
|
+
const newGeneration = generation + 1;
|
|
143
245
|
const recap = {
|
|
144
246
|
role: 'assistant',
|
|
145
247
|
content: [
|
|
146
|
-
|
|
248
|
+
`${RECAP_MARKER} (gen ${newGeneration}): ${toCompact.length} turns removed to stay within context limits.]`,
|
|
249
|
+
...(extras?.affectHint ? [extras.affectHint] : []),
|
|
250
|
+
...(extras?.executiveDigest ? ['', extras.executiveDigest, ''] : []),
|
|
147
251
|
...summaries,
|
|
148
252
|
].join('\n'),
|
|
149
253
|
};
|
|
@@ -151,6 +255,33 @@ function compactHistory(history, totalTokens, model) {
|
|
|
151
255
|
history.length = 0;
|
|
152
256
|
for (const msg of preserved)
|
|
153
257
|
history.push(msg);
|
|
258
|
+
// Churn guard (see truncateOversizedResults).
|
|
259
|
+
truncateOversizedResults(history, threshold);
|
|
154
260
|
return true;
|
|
155
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Churn guard: a single oversized tool_result in the kept tail can hold the
|
|
264
|
+
* estimate above the trigger forever, so compaction would fire every turn
|
|
265
|
+
* with no shrinkage. If the history is still over threshold, cap result
|
|
266
|
+
* bodies. Returns true if anything was truncated.
|
|
267
|
+
*/
|
|
268
|
+
function truncateOversizedResults(history, threshold) {
|
|
269
|
+
let total = 0;
|
|
270
|
+
for (const msg of history)
|
|
271
|
+
total += countMessage(msg);
|
|
272
|
+
if (total < threshold)
|
|
273
|
+
return false;
|
|
274
|
+
let truncated = false;
|
|
275
|
+
for (const msg of history) {
|
|
276
|
+
if (msg.role !== 'tool_result')
|
|
277
|
+
continue;
|
|
278
|
+
for (const r of msg.results) {
|
|
279
|
+
if (r.content && r.content.length > MAX_RESULT_CHARS) {
|
|
280
|
+
r.content = r.content.slice(0, MAX_RESULT_CHARS) + '\n[truncated during compaction]';
|
|
281
|
+
truncated = true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return truncated;
|
|
286
|
+
}
|
|
156
287
|
//# sourceMappingURL=compactor.js.map
|