@ai-agent-forge/agent-forge 0.87.0 → 0.88.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.
- package/CHANGELOG.md +27 -0
- package/README.md +2 -2
- package/dist/bundle/.build-manifest.json +65 -87
- package/dist/bundle/chunks/{chunk-BFC2BDVJ.js → chunk-HLJEVRWP.js} +2 -2
- package/dist/bundle/chunks/{chunk-MOHOLL3F.js → chunk-YGHQME4V.js} +72 -72
- package/dist/bundle/chunks/image-resize-worker.js +2 -2
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/client.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/auth-check.d.ts.map +1 -1
- package/dist/cli/auth-check.js +14 -2
- package/dist/cli/auth-check.js.map +1 -1
- package/dist/cli/auth-command.d.ts.map +1 -1
- package/dist/cli/auth-command.js +1 -1
- package/dist/cli/auth-command.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -2
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts +6 -10
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +7 -111
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-internal.d.ts +0 -6
- package/dist/core/agent-session-internal.d.ts.map +1 -1
- package/dist/core/agent-session-internal.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +0 -1
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session.d.ts +0 -20
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +5 -33
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/bootstrap-compaction-policy.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-compaction-policy.js +0 -34
- package/dist/core/extensions/bootstrap-compaction-policy.js.map +1 -1
- package/dist/core/extensions/bootstrap-retry-policy.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-retry-policy.js +2 -2
- package/dist/core/extensions/bootstrap-retry-policy.js.map +1 -1
- package/dist/core/extensions/provider-failover.d.ts +7 -3
- package/dist/core/extensions/provider-failover.d.ts.map +1 -1
- package/dist/core/extensions/provider-failover.js +9 -8
- package/dist/core/extensions/provider-failover.js.map +1 -1
- package/dist/core/facade/coding-agent-facade.d.ts +0 -6
- package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
- package/dist/core/facade/coding-agent-facade.js +0 -3
- package/dist/core/facade/coding-agent-facade.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/cli.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/cli.js +3 -1
- package/dist/core/i18n/locales/zh-cn/cli.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js +2 -0
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -1
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.d.ts.map +1 -1
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js +0 -2
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js.map +1 -1
- package/dist/core/model-config.d.ts +0 -1
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +9 -1
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +3 -3
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/provider-composer.d.ts +2 -2
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +7 -8
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/sdk.d.ts +8 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +6 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -20
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +20 -54
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +0 -2
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +2 -7
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -6
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +6 -28
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +0 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +23 -36
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +0 -8
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +0 -12
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +0 -9
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +0 -19
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts +9 -2
- package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js +23 -0
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
- package/dist/plugins/ecosystem/plugin-bootstrap.d.ts +9 -1
- package/dist/plugins/ecosystem/plugin-bootstrap.d.ts.map +1 -1
- package/dist/plugins/ecosystem/plugin-bootstrap.js +5 -0
- package/dist/plugins/ecosystem/plugin-bootstrap.js.map +1 -1
- package/dist/utils/photon.d.ts +2 -2
- package/dist/utils/photon.d.ts.map +1 -1
- package/dist/utils/photon.js +2 -2
- package/dist/utils/photon.js.map +1 -1
- package/docs/compaction.md +1 -3
- package/docs/docs.json +4 -0
- package/docs/index.md +1 -0
- package/docs/mcp.md +103 -0
- package/docs/models.md +14 -22
- package/docs/providers.md +0 -1
- package/docs/rpc.md +1 -26
- package/docs/sdk.md +4 -6
- package/docs/settings.md +9 -12
- package/docs/skills.md +1 -9
- package/docs/usage.md +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/extensions/with-deps/plugin.json +1 -1
- package/examples/sdk/10-settings.ts +2 -4
- package/examples/sdk/12-full-control.ts +1 -2
- package/package.json +1 -3
- package/dist/core/extensions/microcompact.d.ts +0 -80
- package/dist/core/extensions/microcompact.d.ts.map +0 -1
- package/dist/core/extensions/microcompact.js +0 -134
- package/dist/core/extensions/microcompact.js.map +0 -1
- package/dist/server/create-harness.d.ts +0 -26
- package/dist/server/create-harness.d.ts.map +0 -1
- package/dist/server/create-harness.js +0 -106
- package/dist/server/create-harness.js.map +0 -1
package/docs/models.md
CHANGED
|
@@ -24,7 +24,6 @@ For local models (Ollama, LM Studio, vLLM), only `id` is required per model:
|
|
|
24
24
|
"ollama": {
|
|
25
25
|
"baseUrl": "http://localhost:11434/v1",
|
|
26
26
|
"api": "openai-completions",
|
|
27
|
-
"apiKey": "ollama",
|
|
28
27
|
"models": [
|
|
29
28
|
{ "id": "llama3.1:8b" },
|
|
30
29
|
{ "id": "qwen2.5-coder:7b" }
|
|
@@ -34,7 +33,7 @@ For local models (Ollama, LM Studio, vLLM), only `id` is required per model:
|
|
|
34
33
|
}
|
|
35
34
|
```
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
pi still requires a provider to have credentials before its models appear in `/model`. Keyless local servers such as Ollama (which ignores the key entirely) need any placeholder key saved for the provider with `/login` (for example `ollama`) or `--api-key` at startup for the models to become available.
|
|
38
37
|
|
|
39
38
|
Some OpenAI-compatible servers do not understand the `developer` role used for reasoning-capable models. For those providers, set `compat.supportsDeveloperRole` to `false` so pi sends the system prompt as a `system` message instead. If the server also does not support `reasoning_effort`, set `compat.supportsReasoningEffort` to `false` too.
|
|
40
39
|
|
|
@@ -46,7 +45,6 @@ You can set `compat` at the provider level to apply to all models, or at the mod
|
|
|
46
45
|
"ollama": {
|
|
47
46
|
"baseUrl": "http://localhost:11434/v1",
|
|
48
47
|
"api": "openai-completions",
|
|
49
|
-
"apiKey": "ollama",
|
|
50
48
|
"compat": {
|
|
51
49
|
"supportsDeveloperRole": false,
|
|
52
50
|
"supportsReasoningEffort": false
|
|
@@ -72,7 +70,6 @@ Override defaults when you need specific values:
|
|
|
72
70
|
"ollama": {
|
|
73
71
|
"baseUrl": "http://localhost:11434/v1",
|
|
74
72
|
"api": "openai-completions",
|
|
75
|
-
"apiKey": "ollama",
|
|
76
73
|
"models": [
|
|
77
74
|
{
|
|
78
75
|
"id": "llama3.1:8b",
|
|
@@ -101,7 +98,6 @@ Use `google-generative-ai` with a `baseUrl` to add models from Google AI Studio,
|
|
|
101
98
|
"my-google": {
|
|
102
99
|
"baseUrl": "https://generativelanguage.googleapis.com/v1beta",
|
|
103
100
|
"api": "google-generative-ai",
|
|
104
|
-
"apiKey": "$GEMINI_API_KEY",
|
|
105
101
|
"models": [
|
|
106
102
|
{
|
|
107
103
|
"id": "gemma-4-31b-it",
|
|
@@ -135,41 +131,42 @@ Set `api` at provider level (default for all models) or model level (override pe
|
|
|
135
131
|
|-------|-------------|
|
|
136
132
|
| `baseUrl` | API endpoint URL |
|
|
137
133
|
| `api` | API type (see above) |
|
|
138
|
-
| `apiKey` | Optional API key config (see value resolution below). Omit it when auth is provided by `/login`/`auth.json` or CLI `--api-key`. |
|
|
139
134
|
| `oauth` | Dynamic OAuth provider type. Currently supports `"radius"`; requires the gateway `baseUrl`. |
|
|
140
135
|
| `headers` | Custom headers (see value resolution below) |
|
|
141
|
-
| `authHeader` | Set `true` to add `Authorization: Bearer <
|
|
136
|
+
| `authHeader` | Set `true` to add `Authorization: Bearer <resolved API key>` automatically |
|
|
142
137
|
| `models` | Array of model configurations |
|
|
143
138
|
| `modelOverrides` | Per-model overrides for built-in or models.json-declared models on this provider |
|
|
144
139
|
|
|
145
|
-
For providers with `models`, non-built-in provider configs need `baseUrl` and an `api` value at either provider or model level.
|
|
140
|
+
For providers with `models`, non-built-in provider configs need `baseUrl` and an `api` value at either provider or model level. models.json cannot hold keys: auth is configured through `/login` (stored in `auth.json`) or CLI `--api-key`, and a models.json containing an `apiKey` field is rejected at load with guidance to store the key in `auth.json`. The `key` field in `auth.json` supports the same value syntax as headers below (literal, `$ENV_VAR`, `!command`). If no auth is configured, the models load but stay unavailable in `/model` and `--list-models`.
|
|
146
141
|
|
|
147
142
|
### Value Resolution
|
|
148
143
|
|
|
149
|
-
|
|
144
|
+
Header values in models.json (`headers`) and the `key` field of an `auth.json` credential share one value syntax: command execution, environment interpolation, and literals:
|
|
150
145
|
|
|
151
146
|
- **Shell command:** `"!command"` at the start executes the whole value as a command and uses stdout
|
|
152
147
|
```json
|
|
153
|
-
"
|
|
154
|
-
"
|
|
148
|
+
"key": "!security find-generic-password -ws 'anthropic'"
|
|
149
|
+
"x-secret": "!op read 'op://vault/item/secret'"
|
|
155
150
|
```
|
|
156
151
|
- **Environment interpolation:** `"$ENV_VAR"` or `"${ENV_VAR}"` uses the value of the named variable. Interpolation works inside larger literals.
|
|
157
152
|
```json
|
|
158
|
-
"
|
|
159
|
-
"
|
|
153
|
+
"key": "$MY_API_KEY"
|
|
154
|
+
"x-key": "${KEY_PREFIX}_${KEY_SUFFIX}"
|
|
160
155
|
```
|
|
161
156
|
`$FOO_BAR` is the variable `FOO_BAR`; use `${FOO}_BAR` when `BAR` is literal text. Missing environment variables make the value unresolved.
|
|
162
157
|
- **Escapes:** `"$$"` emits a literal `"$"`; `"$!"` emits a literal `"!"` without triggering command execution.
|
|
163
158
|
```json
|
|
164
|
-
"
|
|
165
|
-
"
|
|
159
|
+
"key": "$$literal-dollar-prefix"
|
|
160
|
+
"x-secret": "$!literal-bang-prefix"
|
|
166
161
|
```
|
|
167
162
|
- **Literal value:** Used directly. Plain uppercase strings such as `MY_API_KEY` are literals; use `$MY_API_KEY` for environment variables.
|
|
168
163
|
```json
|
|
169
|
-
"
|
|
164
|
+
"key": "sk-..."
|
|
170
165
|
```
|
|
171
166
|
|
|
172
|
-
|
|
167
|
+
Command values in models.json `headers` are resolved on every request. pi intentionally does not apply built-in TTL, stale reuse, or recovery logic for arbitrary commands. Different commands need different caching and failure strategies, and pi cannot infer the right one.
|
|
168
|
+
|
|
169
|
+
A command in an `auth.json` credential `key` is resolved when the credential is first read and reused for the process lifetime (failures included); restart the host to pick up a changed value.
|
|
173
170
|
|
|
174
171
|
If your command is slow, expensive, rate-limited, or should keep using a previous value on transient failures, wrap it in your own script or command that implements the caching or TTL behavior you want.
|
|
175
172
|
|
|
@@ -182,7 +179,6 @@ If your command is slow, expensive, rate-limited, or should keep using a previou
|
|
|
182
179
|
"providers": {
|
|
183
180
|
"custom-proxy": {
|
|
184
181
|
"baseUrl": "https://proxy.example.com/v1",
|
|
185
|
-
"apiKey": "$MY_API_KEY",
|
|
186
182
|
"api": "anthropic-messages",
|
|
187
183
|
"headers": {
|
|
188
184
|
"x-portkey-api-key": "$PORTKEY_API_KEY",
|
|
@@ -324,7 +320,6 @@ To merge custom models into a built-in provider, include the `models` array:
|
|
|
324
320
|
"providers": {
|
|
325
321
|
"anthropic": {
|
|
326
322
|
"baseUrl": "https://my-proxy.example.com/v1",
|
|
327
|
-
"apiKey": "$ANTHROPIC_API_KEY",
|
|
328
323
|
"api": "anthropic-messages",
|
|
329
324
|
"models": [...]
|
|
330
325
|
}
|
|
@@ -408,7 +403,6 @@ Built-in Anthropic models enable `supportsStrictTools` in their model metadata.
|
|
|
408
403
|
"anthropic-proxy": {
|
|
409
404
|
"baseUrl": "https://proxy.example.com",
|
|
410
405
|
"api": "anthropic-messages",
|
|
411
|
-
"apiKey": "$ANTHROPIC_PROXY_KEY",
|
|
412
406
|
"compat": {
|
|
413
407
|
"supportsEagerToolInputStreaming": false,
|
|
414
408
|
"supportsLongCacheRetention": true,
|
|
@@ -501,7 +495,6 @@ Example:
|
|
|
501
495
|
"providers": {
|
|
502
496
|
"openrouter": {
|
|
503
497
|
"baseUrl": "https://openrouter.ai/api/v1",
|
|
504
|
-
"apiKey": "$OPENROUTER_API_KEY",
|
|
505
498
|
"api": "openai-completions",
|
|
506
499
|
"models": [
|
|
507
500
|
{
|
|
@@ -551,7 +544,6 @@ Vercel AI Gateway example:
|
|
|
551
544
|
"providers": {
|
|
552
545
|
"vercel-ai-gateway": {
|
|
553
546
|
"baseUrl": "https://ai-gateway.vercel.sh/v1",
|
|
554
|
-
"apiKey": "$AI_GATEWAY_API_KEY",
|
|
555
547
|
"api": "openai-completions",
|
|
556
548
|
"models": [
|
|
557
549
|
{
|
package/docs/providers.md
CHANGED
package/docs/rpc.md
CHANGED
|
@@ -203,7 +203,6 @@ Response:
|
|
|
203
203
|
"sessionFile": "/path/to/session.jsonl",
|
|
204
204
|
"sessionId": "abc123",
|
|
205
205
|
"sessionName": "my-feature-work",
|
|
206
|
-
"autoCompactionEnabled": true,
|
|
207
206
|
"messageCount": 5,
|
|
208
207
|
"pendingMessageCount": 0
|
|
209
208
|
}
|
|
@@ -430,33 +429,9 @@ Response:
|
|
|
430
429
|
|
|
431
430
|
`estimatedTokensAfter` is a heuristic estimate over the runtime message context immediately after compaction, including any `ContextStrategy` replacement projection; it is not a provider-exact token count. `usage` reports the LLM call or calls that generated the summary and may be omitted by custom compaction handlers.
|
|
432
431
|
|
|
433
|
-
#### set_auto_compaction
|
|
434
|
-
|
|
435
|
-
Enable or disable automatic compaction when context is nearly full.
|
|
436
|
-
|
|
437
|
-
```json
|
|
438
|
-
{"type": "set_auto_compaction", "enabled": true}
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
Response:
|
|
442
|
-
```json
|
|
443
|
-
{"type": "response", "command": "set_auto_compaction", "success": true}
|
|
444
|
-
```
|
|
445
|
-
|
|
446
432
|
### Retry
|
|
447
433
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
Enable or disable automatic retry on transient errors (overloaded, rate limit, 5xx).
|
|
451
|
-
|
|
452
|
-
```json
|
|
453
|
-
{"type": "set_auto_retry", "enabled": true}
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
Response:
|
|
457
|
-
```json
|
|
458
|
-
{"type": "response", "command": "set_auto_retry", "success": true}
|
|
459
|
-
```
|
|
434
|
+
Automatic agent-level retry applies unconditionally (the `set_auto_retry` command was removed on 2026-10-03, D-079). To stop a running retry, use `abort_retry`.
|
|
460
435
|
|
|
461
436
|
#### abort_retry
|
|
462
437
|
|
package/docs/sdk.md
CHANGED
|
@@ -426,8 +426,8 @@ for (const diagnostic of diagnostics) {
|
|
|
426
426
|
Authentication resolution priority (handled by `ModelRuntime`):
|
|
427
427
|
1. Runtime overrides (via `setRuntimeApiKey`, not persisted)
|
|
428
428
|
2. Stored credentials in `auth.json` (API keys or OAuth tokens)
|
|
429
|
-
3.
|
|
430
|
-
4.
|
|
429
|
+
3. Provider-registered fallback key (`apiKey` supplied programmatically by the embedding code, e.g. a provider registered with its own key — injected in memory, never persisted; when set it takes precedence over the provider's ambient environment variables)
|
|
430
|
+
4. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
|
|
431
431
|
|
|
432
432
|
```typescript
|
|
433
433
|
import { InMemoryCredentialStore } from "@agent-forge/ai";
|
|
@@ -796,8 +796,7 @@ const { facade } = await createStableAgentSession({
|
|
|
796
796
|
// With overrides
|
|
797
797
|
const settingsManager = SettingsManager.create();
|
|
798
798
|
settingsManager.applyOverrides({
|
|
799
|
-
|
|
800
|
-
retry: { enabled: true, maxRetries: 5 },
|
|
799
|
+
retry: { maxRetries: 5 },
|
|
801
800
|
});
|
|
802
801
|
const { facade } = await createStableAgentSession({ settingsManager });
|
|
803
802
|
|
|
@@ -914,8 +913,7 @@ if (!model) throw new Error("Model not found");
|
|
|
914
913
|
|
|
915
914
|
// In-memory settings with overrides
|
|
916
915
|
const settingsManager = SettingsManager.inMemory({
|
|
917
|
-
|
|
918
|
-
retry: { enabled: true, maxRetries: 2 },
|
|
916
|
+
retry: { maxRetries: 2 },
|
|
919
917
|
});
|
|
920
918
|
|
|
921
919
|
const loader = new DefaultResourceLoader({
|
package/docs/settings.md
CHANGED
|
@@ -47,7 +47,7 @@ Edit directly or use `/settings` for common options. To save startup model defau
|
|
|
47
47
|
| `collapseChangelog` | boolean | `false` | Show condensed changelog after updates |
|
|
48
48
|
| `enableAnalytics` | boolean | `false` | Opt-in analytics data sharing. Currently only asked for during the experimental first-time setup (`PI_EXPERIMENTAL=1`) |
|
|
49
49
|
| `trackingId` | string | - | Analytics tracking identifier, generated when `enableAnalytics` is turned on |
|
|
50
|
-
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape: `"tree"
|
|
50
|
+
| `doubleEscapeAction` | string | `"tree"` | Action for double-escape with an empty editor: `"tree"` or `"none"` (the removed `"fork"` value reads as `"tree"`; use the `/fork` command) |
|
|
51
51
|
| `treeFilterMode` | string | `"default"` | Default filter for `/tree`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, `"all"` |
|
|
52
52
|
| `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
|
|
53
53
|
| `outputPad` | number | `1` | Horizontal padding for user messages, assistant messages, and thinking (0 or 1) |
|
|
@@ -99,16 +99,16 @@ Agent Forge has no default version-check endpoint. A version query runs only whe
|
|
|
99
99
|
|
|
100
100
|
### Compaction
|
|
101
101
|
|
|
102
|
+
Auto-compaction is always on; there is no toggle. The legacy keys `compaction.enabled`, `compaction.microcompactEnabled`, and `compaction.summarySelfCritique` were removed and are silently ignored if present in settings files.
|
|
103
|
+
|
|
102
104
|
| Setting | Type | Default | Description |
|
|
103
105
|
|---------|------|---------|-------------|
|
|
104
|
-
| `compaction.enabled` | boolean | `true` | Enable auto-compaction |
|
|
105
106
|
| `compaction.reserveTokens` | number | `16384` | Tokens reserved for LLM response |
|
|
106
107
|
| `compaction.keepRecentTokens` | number | `20000` | Recent tokens to keep (not summarized) |
|
|
107
108
|
|
|
108
109
|
```json
|
|
109
110
|
{
|
|
110
111
|
"compaction": {
|
|
111
|
-
"enabled": true,
|
|
112
112
|
"reserveTokens": 16384,
|
|
113
113
|
"keepRecentTokens": 20000
|
|
114
114
|
}
|
|
@@ -124,9 +124,10 @@ Agent Forge has no default version-check endpoint. A version query runs only whe
|
|
|
124
124
|
|
|
125
125
|
### Retry
|
|
126
126
|
|
|
127
|
+
Automatic agent-level retry applies unconditionally (the `retry.enabled` switch was removed on 2026-10-03, D-079; stale keys in settings files are silently ignored).
|
|
128
|
+
|
|
127
129
|
| Setting | Type | Default | Description |
|
|
128
130
|
|---------|------|---------|-------------|
|
|
129
|
-
| `retry.enabled` | boolean | `true` | Enable automatic agent-level retry on transient errors |
|
|
130
131
|
| `retry.maxRetries` | number | `5` | Maximum agent-level retry attempts |
|
|
131
132
|
| `retry.baseDelayMs` | number | `2000` | Base delay for agent-level exponential backoff with ±10% jitter (~2s, ~4s, ~8s, ~16s, ~32s) |
|
|
132
133
|
| `retry.maxAgentDelayMs` | number | `60000` | Max agent-level retry delay; the cap applies before jitter (60s) |
|
|
@@ -143,7 +144,6 @@ Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explic
|
|
|
143
144
|
```json
|
|
144
145
|
{
|
|
145
146
|
"retry": {
|
|
146
|
-
"enabled": true,
|
|
147
147
|
"maxRetries": 3,
|
|
148
148
|
"baseDelayMs": 2000,
|
|
149
149
|
"maxAgentDelayMs": 60000,
|
|
@@ -213,7 +213,7 @@ Windows paths in JSON must use forward slashes or escaped backslashes:
|
|
|
213
213
|
|
|
214
214
|
| Setting | Type | Default | Description |
|
|
215
215
|
|---------|------|---------|-------------|
|
|
216
|
-
| `defaultTools` | string[] | - |
|
|
216
|
+
| `defaultTools` | string[] | - | 内置工具初始启用列表;省略时由首方工具插件提供默认集合。裸名列表为白名单替换语义;`+name` 条目在其后按序添加(如 `["read", "+powershell"]`),项目层纯 `+` 列表叠加在用户层之上。`-name` 移除语法已移除,残留条目静默忽略;项目层列表含 `-` 条目时不再与用户层叠加,而是整体替换 |
|
|
217
217
|
|
|
218
218
|
`defaultTools` selects the built-in tools enabled at startup. Plugin and SDK custom tools remain enabled. Available built-ins are `read`, `bash`, `powershell`, `edit`, `write`, `grep`, `find`, and `ls`:
|
|
219
219
|
|
|
@@ -231,7 +231,7 @@ On Windows, select `powershell` instead of `bash`, or include both:
|
|
|
231
231
|
}
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
-
An empty array starts with no built-in tools while preserving plugin and SDK custom tools. `--tools` replaces this behavior with a strict allowlist for all tools, `--no-tools` disables all tools, and `--no-builtin-tools` disables the built-in defaults. `--exclude-tools` filters the resulting list. A project `defaultTools` array replaces the global array.
|
|
234
|
+
An empty array starts with no built-in tools while preserving plugin and SDK custom tools. `--tools` replaces this behavior with a strict allowlist for all tools, `--no-tools` disables all tools, and `--no-builtin-tools` disables the built-in defaults. `--exclude-tools` filters the resulting list. A project `defaultTools` array replaces the global array unless it consists solely of `+name` entries, which layer on top of the resolved global selection.
|
|
235
235
|
|
|
236
236
|
### Sessions
|
|
237
237
|
|
|
@@ -276,7 +276,6 @@ Paths in `~/.agent-forge/agent/settings.json` resolve relative to `~/.agent-forg
|
|
|
276
276
|
| `skills` | string[] | `[]` | Local skill file paths or directories |
|
|
277
277
|
| `prompts` | string[] | `[]` | Local prompt template paths or directories |
|
|
278
278
|
| `themes` | string[] | `[]` | Local theme file paths or directories |
|
|
279
|
-
| `enableSkillCommands` | boolean | `true` | Register skills as `/skill:name` commands |
|
|
280
279
|
|
|
281
280
|
Arrays support glob patterns and exclusions. Use `!pattern` to exclude. Use `+path` to force-include an exact path and `-path` to force-exclude an exact path.
|
|
282
281
|
|
|
@@ -317,12 +316,10 @@ See [packages.md](packages.md) for package management details.
|
|
|
317
316
|
},
|
|
318
317
|
"theme": "dark",
|
|
319
318
|
"compaction": {
|
|
320
|
-
"enabled": true,
|
|
321
319
|
"reserveTokens": 16384,
|
|
322
320
|
"keepRecentTokens": 20000
|
|
323
321
|
},
|
|
324
322
|
"retry": {
|
|
325
|
-
"enabled": true,
|
|
326
323
|
"maxRetries": 3
|
|
327
324
|
},
|
|
328
325
|
"enabledModels": ["claude-*", "gpt-4o"],
|
|
@@ -341,7 +338,7 @@ Project settings (`.agent-forge/settings.json`) override global settings. Nested
|
|
|
341
338
|
// ~/.agent-forge/agent/settings.json (global)
|
|
342
339
|
{
|
|
343
340
|
"theme": "dark",
|
|
344
|
-
"compaction": { "
|
|
341
|
+
"compaction": { "reserveTokens": 16384 }
|
|
345
342
|
}
|
|
346
343
|
|
|
347
344
|
// .agent-forge/settings.json (project)
|
|
@@ -352,6 +349,6 @@ Project settings (`.agent-forge/settings.json`) override global settings. Nested
|
|
|
352
349
|
// Result
|
|
353
350
|
{
|
|
354
351
|
"theme": "dark",
|
|
355
|
-
"compaction": { "
|
|
352
|
+
"compaction": { "reserveTokens": 8192 }
|
|
356
353
|
}
|
|
357
354
|
```
|
package/docs/skills.md
CHANGED
|
@@ -73,7 +73,7 @@ This is progressive disclosure: only descriptions are always in context, full in
|
|
|
73
73
|
|
|
74
74
|
## Skill Commands
|
|
75
75
|
|
|
76
|
-
Skills register as `/skill:name` commands:
|
|
76
|
+
Skills register as `/skill:name` commands unconditionally (the `enableSkillCommands` switch was removed on 2026-10-03, D-079; a stale key in settings files is silently ignored):
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
79
|
/skill:brave-search # Load and execute the skill
|
|
@@ -82,14 +82,6 @@ Skills register as `/skill:name` commands:
|
|
|
82
82
|
|
|
83
83
|
Arguments after the command are appended to the skill content as `User: <args>`.
|
|
84
84
|
|
|
85
|
-
Toggle skill commands via `/settings` in interactive mode or in `settings.json`:
|
|
86
|
-
|
|
87
|
-
```json
|
|
88
|
-
{
|
|
89
|
-
"enableSkillCommands": true
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
85
|
## Skill Structure
|
|
94
86
|
|
|
95
87
|
A skill is a directory with a `SKILL.md` file. Everything else is freeform.
|
package/docs/usage.md
CHANGED
|
@@ -287,6 +287,6 @@ agent-forge --exclude-tools ask_question
|
|
|
287
287
|
|
|
288
288
|
Agent Forge keeps the core small and pushes workflow-specific behavior into plugins, Skills, prompt templates, and external tools.
|
|
289
289
|
|
|
290
|
-
It intentionally does not include MCP, multi-Agent workflows, approval policy, Plan mode, to-dos, or background bash as core business branches.
|
|
290
|
+
It intentionally does not include MCP, multi-Agent workflows, approval policy, Plan mode, to-dos, or background bash as core business branches. MCP servers are configured through `mcp.json` ([MCP](mcp.md)); the rest are added through plugins or external tools such as containers and tmux.
|
|
291
291
|
|
|
292
292
|
For the architectural rationale, see the repository [核心架构宪法](../../../docs/核心架构宪法.md).
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-forge-extension-with-deps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "agent-forge-extension-with-deps",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.87.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
|
@@ -15,8 +15,7 @@ console.log("Current settings:", JSON.stringify(settingsManagerFromDisk.getGloba
|
|
|
15
15
|
// Override specific settings
|
|
16
16
|
const settingsManager = SettingsManager.create(cwd);
|
|
17
17
|
settingsManager.applyOverrides({
|
|
18
|
-
|
|
19
|
-
retry: { enabled: true, maxRetries: 5, baseDelayMs: 1000 },
|
|
18
|
+
retry: { maxRetries: 5, baseDelayMs: 1000 },
|
|
20
19
|
});
|
|
21
20
|
|
|
22
21
|
const { facade: customSettingsFacade } = await createStableAgentSession({
|
|
@@ -41,8 +40,7 @@ if (settingsErrors.length > 0) {
|
|
|
41
40
|
|
|
42
41
|
// For testing without file I/O:
|
|
43
42
|
const inMemorySettings = SettingsManager.inMemory({
|
|
44
|
-
|
|
45
|
-
retry: { enabled: false },
|
|
43
|
+
retry: { maxRetries: 0 },
|
|
46
44
|
});
|
|
47
45
|
|
|
48
46
|
const { facade: testFacade } = await createStableAgentSession({
|
|
@@ -26,8 +26,7 @@ if (!model) throw new Error("Model not found");
|
|
|
26
26
|
|
|
27
27
|
// In-memory settings with overrides
|
|
28
28
|
const settingsManager = SettingsManager.inMemory({
|
|
29
|
-
|
|
30
|
-
retry: { enabled: true, maxRetries: 2 },
|
|
29
|
+
retry: { maxRetries: 2 },
|
|
31
30
|
});
|
|
32
31
|
|
|
33
32
|
const cwd = process.cwd();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-agent-forge/agent-forge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.88.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"agentForgeConfig": {
|
|
@@ -40,9 +40,7 @@
|
|
|
40
40
|
"clean": "shx rm -rf dist",
|
|
41
41
|
"build": "npm run build:unbundled && node ../../scripts/build-agent-forge-bundle.mjs",
|
|
42
42
|
"build:unbundled": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",
|
|
43
|
-
"build:binary": "npm --prefix ../tui run build && npm --prefix ../telemetry run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm --prefix ../protocol run build && npm --prefix ../client run build && npm --prefix ../plugin-sdk run build && npm run build && bun build --compile --no-compile-autoload-bunfig ./src/bun/cli.ts ./src/utils/image-resize-worker.ts --outfile dist/agent-forge && npm run copy-binary-assets",
|
|
44
43
|
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
|
|
45
|
-
"copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
|
|
46
44
|
"test": "vitest --run",
|
|
47
45
|
"shrinkwrap": "node ../../scripts/generate-agent-forge-shrinkwrap.mjs",
|
|
48
46
|
"prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap",
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Microcompact (P1 context hygiene): before spending an LLM summarization
|
|
3
|
-
* call, elide the BULKY OLD tool results from the LLM context — a pure local
|
|
4
|
-
* history transform with zero model cost (the codex/ZCode "microcompact"
|
|
5
|
-
* layer). The session file keeps every byte (append-only marker + logical
|
|
6
|
-
* transform at context build); only the model-visible context shrinks.
|
|
7
|
-
*
|
|
8
|
-
* Contract: the marker is a `custom` session entry (`MICROCOMPACT_CUSTOM_TYPE`)
|
|
9
|
-
* whose data names the elision point. The projection registered by the
|
|
10
|
-
* bootstrap compaction policy plugin (via the PUBLIC
|
|
11
|
-
* `api.registerEntryProjection` channel) replaces eligible tool-result
|
|
12
|
-
* contents older than that point with a fixed placeholder, so reload and
|
|
13
|
-
* token estimation stay consistent with live turns. The kernel applies
|
|
14
|
-
* registered projections verbatim and knows nothing about microcompact.
|
|
15
|
-
*/
|
|
16
|
-
import type { AgentMessage } from "@agent-forge/agent-core";
|
|
17
|
-
import type { SessionEntry } from "../session-manager.ts";
|
|
18
|
-
/** Custom entry type carrying the microcompact elision point. */
|
|
19
|
-
export declare const MICROCOMPACT_CUSTOM_TYPE = "microcompact.v1";
|
|
20
|
-
/** Replacement content for an elided tool result. */
|
|
21
|
-
export declare const MICROCOMPACT_ELIDED_PLACEHOLDER = "[earlier tool result elided by microcompact]";
|
|
22
|
-
/** Tool results shorter than this are not worth eliding (noise, not bulk). */
|
|
23
|
-
export declare const DEFAULT_MIN_ELIDE_CHARS = 200;
|
|
24
|
-
/**
|
|
25
|
-
* Minimum estimated token relief for a microcompact to fire: below this the
|
|
26
|
-
* churn (marker entry + context transition) cannot pay for itself and the
|
|
27
|
-
* caller falls through to full LLM compaction.
|
|
28
|
-
*/
|
|
29
|
-
export declare const MIN_MICROCOMPACT_FREED_TOKENS = 2048;
|
|
30
|
-
/** Data carried by a {@link MICROCOMPACT_CUSTOM_TYPE} custom entry. */
|
|
31
|
-
export interface MicrocompactMarkerData {
|
|
32
|
-
/** Entries strictly BEFORE this entry id (path order) are elision candidates. */
|
|
33
|
-
readonly elideBeforeEntryId: string;
|
|
34
|
-
/** Tool results whose serialized content is shorter are kept verbatim. */
|
|
35
|
-
readonly minChars: number;
|
|
36
|
-
}
|
|
37
|
-
export interface MicrocompactPlan {
|
|
38
|
-
readonly elideBeforeEntryId: string;
|
|
39
|
-
/** Estimated tokens freed by eliding the eligible tool results. */
|
|
40
|
-
readonly freedTokens: number;
|
|
41
|
-
readonly elidedCount: number;
|
|
42
|
-
}
|
|
43
|
-
/** Explicit plan options: `enabled` gates THIS mechanism only (microcompact relief), not compaction. */
|
|
44
|
-
export interface MicrocompactPlanOptions {
|
|
45
|
-
/** Whether microcompact relief may fire at all (settings switch AND session cooldown). */
|
|
46
|
-
enabled: boolean;
|
|
47
|
-
/** Verbatim-protected recent tail budget (estimated tokens). */
|
|
48
|
-
keepRecentTokens: number;
|
|
49
|
-
/** Tool results shorter than this are not worth eliding. */
|
|
50
|
-
minElideChars?: number;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Plan one microcompact over the branch path: protect the most recent
|
|
54
|
-
* `keepRecentTokens` (estimated) as the verbatim tail, and elide the eligible
|
|
55
|
-
* old tool results before that cut. Returns undefined when the caller disabled
|
|
56
|
-
* the mechanism (`options.enabled === false`), when there is nothing to elide,
|
|
57
|
-
* or when the relief cannot reach the minimum. Deliberately does NOT reuse
|
|
58
|
-
* `CompactionSettings`: its `enabled` is the compaction master switch — a
|
|
59
|
-
* different gate.
|
|
60
|
-
*/
|
|
61
|
-
export declare function planMicrocompact(pathEntries: readonly {
|
|
62
|
-
readonly id?: string;
|
|
63
|
-
readonly type: string;
|
|
64
|
-
readonly message?: AgentMessage;
|
|
65
|
-
}[], options: MicrocompactPlanOptions): MicrocompactPlan | undefined;
|
|
66
|
-
/** Resolve the marker data of the LAST microcompact marker in path order. */
|
|
67
|
-
export declare function lastMicrocompactMarker(pathEntries: readonly {
|
|
68
|
-
readonly type: string;
|
|
69
|
-
readonly customType?: string;
|
|
70
|
-
readonly data?: unknown;
|
|
71
|
-
}[]): MicrocompactMarkerData | undefined;
|
|
72
|
-
/**
|
|
73
|
-
* The entry projection backing microcompact: replaces the CONTENT of eligible
|
|
74
|
-
* tool results before the marker's cut point with the fixed placeholder, and
|
|
75
|
-
* keeps everything else verbatim. Registered through the public
|
|
76
|
-
* `api.registerEntryProjection` channel — the same channel any third-party
|
|
77
|
-
* context policy uses; the kernel applies it without interpretation.
|
|
78
|
-
*/
|
|
79
|
-
export declare function applyMicrocompactElision(path: SessionEntry[]): SessionEntry[];
|
|
80
|
-
//# sourceMappingURL=microcompact.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"microcompact.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/microcompact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,oBAAoB,CAAC;AAE1D,qDAAqD;AACrD,eAAO,MAAM,+BAA+B,iDAAiD,CAAC;AAE9F,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD,uEAAuE;AACvE,MAAM,WAAW,sBAAsB;IACtC,iFAAiF;IACjF,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,mEAAmE;IACnE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAgBD,wGAAwG;AACxG,MAAM,WAAW,uBAAuB;IACvC,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC/B,WAAW,EAAE,SAAS;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAA;CAAE,EAAE,EACxG,OAAO,EAAE,uBAAuB,GAC9B,gBAAgB,GAAG,SAAS,CAqC9B;AAED,6EAA6E;AAC7E,wBAAgB,sBAAsB,CACrC,WAAW,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,GACtG,sBAAsB,GAAG,SAAS,CAapC;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CAgB7E","sourcesContent":["/**\n * Microcompact (P1 context hygiene): before spending an LLM summarization\n * call, elide the BULKY OLD tool results from the LLM context — a pure local\n * history transform with zero model cost (the codex/ZCode \"microcompact\"\n * layer). The session file keeps every byte (append-only marker + logical\n * transform at context build); only the model-visible context shrinks.\n *\n * Contract: the marker is a `custom` session entry (`MICROCOMPACT_CUSTOM_TYPE`)\n * whose data names the elision point. The projection registered by the\n * bootstrap compaction policy plugin (via the PUBLIC\n * `api.registerEntryProjection` channel) replaces eligible tool-result\n * contents older than that point with a fixed placeholder, so reload and\n * token estimation stay consistent with live turns. The kernel applies\n * registered projections verbatim and knows nothing about microcompact.\n */\n\nimport type { AgentMessage } from \"@agent-forge/agent-core\";\nimport { contentText } from \"@agent-forge/ai\";\nimport { estimateTokens } from \"../compaction/index.ts\";\nimport type { SessionEntry } from \"../session-manager.ts\";\n\n/** Custom entry type carrying the microcompact elision point. */\nexport const MICROCOMPACT_CUSTOM_TYPE = \"microcompact.v1\";\n\n/** Replacement content for an elided tool result. */\nexport const MICROCOMPACT_ELIDED_PLACEHOLDER = \"[earlier tool result elided by microcompact]\";\n\n/** Tool results shorter than this are not worth eliding (noise, not bulk). */\nexport const DEFAULT_MIN_ELIDE_CHARS = 200;\n\n/**\n * Minimum estimated token relief for a microcompact to fire: below this the\n * churn (marker entry + context transition) cannot pay for itself and the\n * caller falls through to full LLM compaction.\n */\nexport const MIN_MICROCOMPACT_FREED_TOKENS = 2048;\n\n/** Data carried by a {@link MICROCOMPACT_CUSTOM_TYPE} custom entry. */\nexport interface MicrocompactMarkerData {\n\t/** Entries strictly BEFORE this entry id (path order) are elision candidates. */\n\treadonly elideBeforeEntryId: string;\n\t/** Tool results whose serialized content is shorter are kept verbatim. */\n\treadonly minChars: number;\n}\n\nexport interface MicrocompactPlan {\n\treadonly elideBeforeEntryId: string;\n\t/** Estimated tokens freed by eliding the eligible tool results. */\n\treadonly freedTokens: number;\n\treadonly elidedCount: number;\n}\n\nfunction isElidableToolResult(\n\tmessage: AgentMessage,\n\tminChars: number,\n): message is Extract<AgentMessage, { role: \"toolResult\" }> {\n\tif (message.role !== \"toolResult\") return false;\n\t// 媒体保护:含非 text part(image 等)的 tool result 不 elide——占位符替换会\n\t// 把模型可见上下文中的原图数据抹掉(会话文件虽保真,但投影按 marker 重放,\n\t// 图像不会自行回来),保持\"原图常驻直到全量压缩\"的现行裁决。\n\tif (Array.isArray(message.content) && message.content.some((part) => part.type !== \"text\")) {\n\t\treturn false;\n\t}\n\treturn contentText(message.content, \"\").length >= minChars;\n}\n\n/** Explicit plan options: `enabled` gates THIS mechanism only (microcompact relief), not compaction. */\nexport interface MicrocompactPlanOptions {\n\t/** Whether microcompact relief may fire at all (settings switch AND session cooldown). */\n\tenabled: boolean;\n\t/** Verbatim-protected recent tail budget (estimated tokens). */\n\tkeepRecentTokens: number;\n\t/** Tool results shorter than this are not worth eliding. */\n\tminElideChars?: number;\n}\n\n/**\n * Plan one microcompact over the branch path: protect the most recent\n * `keepRecentTokens` (estimated) as the verbatim tail, and elide the eligible\n * old tool results before that cut. Returns undefined when the caller disabled\n * the mechanism (`options.enabled === false`), when there is nothing to elide,\n * or when the relief cannot reach the minimum. Deliberately does NOT reuse\n * `CompactionSettings`: its `enabled` is the compaction master switch — a\n * different gate.\n */\nexport function planMicrocompact(\n\tpathEntries: readonly { readonly id?: string; readonly type: string; readonly message?: AgentMessage }[],\n\toptions: MicrocompactPlanOptions,\n): MicrocompactPlan | undefined {\n\tif (!options.enabled) return undefined;\n\tconst minChars = options.minElideChars ?? DEFAULT_MIN_ELIDE_CHARS;\n\n\t// Protect the recent tail: walk backwards accumulating estimated tokens,\n\t// cutting before the entry that crosses the budget (same walk as\n\t// findCutPoint, but without turn-boundary subtleties — elision never\n\t// removes entries, only replaces contents, so any cut point is safe).\n\tlet accumulated = 0;\n\tlet cutIndex = 0;\n\tfor (let i = pathEntries.length - 1; i >= 0; i--) {\n\t\tconst entry = pathEntries[i];\n\t\tconst messageTokens = entry.message ? estimateTokens(entry.message) : 0;\n\t\tif (messageTokens === 0) continue;\n\t\taccumulated += messageTokens;\n\t\tif (accumulated >= options.keepRecentTokens) {\n\t\t\tcutIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlet freedTokens = 0;\n\tlet elidedCount = 0;\n\tfor (let i = 0; i < cutIndex; i++) {\n\t\tconst message = pathEntries[i].message;\n\t\tif (!message || !isElidableToolResult(message, minChars)) continue;\n\t\tfreedTokens +=\n\t\t\testimateTokens(message) -\n\t\t\testimateTokens({ ...message, content: [{ type: \"text\", text: MICROCOMPACT_ELIDED_PLACEHOLDER }] });\n\t\telidedCount += 1;\n\t}\n\n\tconst elideBefore = pathEntries[cutIndex];\n\tif (elidedCount === 0 || freedTokens < MIN_MICROCOMPACT_FREED_TOKENS) return undefined;\n\tconst elideBeforeEntryId = elideBefore?.id;\n\tif (!elideBeforeEntryId) return undefined;\n\treturn { elideBeforeEntryId, freedTokens, elidedCount };\n}\n\n/** Resolve the marker data of the LAST microcompact marker in path order. */\nexport function lastMicrocompactMarker(\n\tpathEntries: readonly { readonly type: string; readonly customType?: string; readonly data?: unknown }[],\n): MicrocompactMarkerData | undefined {\n\tlet marker: MicrocompactMarkerData | undefined;\n\tfor (const entry of pathEntries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== MICROCOMPACT_CUSTOM_TYPE) continue;\n\t\tconst data = entry.data as Partial<MicrocompactMarkerData> | undefined;\n\t\tif (typeof data?.elideBeforeEntryId === \"string\") {\n\t\t\tmarker = {\n\t\t\t\telideBeforeEntryId: data.elideBeforeEntryId,\n\t\t\t\tminChars: typeof data.minChars === \"number\" && data.minChars >= 0 ? data.minChars : DEFAULT_MIN_ELIDE_CHARS,\n\t\t\t};\n\t\t}\n\t}\n\treturn marker;\n}\n\n/**\n * The entry projection backing microcompact: replaces the CONTENT of eligible\n * tool results before the marker's cut point with the fixed placeholder, and\n * keeps everything else verbatim. Registered through the public\n * `api.registerEntryProjection` channel — the same channel any third-party\n * context policy uses; the kernel applies it without interpretation.\n */\nexport function applyMicrocompactElision(path: SessionEntry[]): SessionEntry[] {\n\tconst marker = lastMicrocompactMarker(path);\n\tif (marker === undefined) return path;\n\tconst cutIndex = path.findIndex((entry) => entry.id === marker.elideBeforeEntryId);\n\tif (cutIndex <= 0) return path;\n\treturn path.map((entry, index) => {\n\t\tif (index >= cutIndex || entry.type !== \"message\") return entry;\n\t\tconst message = entry.message;\n\t\t// 与 planMicrocompact 同一判定(长度阈值 + 媒体保护):投影只 elide 规划\n\t\t// 时认定为可省的 tool result,否则计划与投影对同一历史给出不同结果。\n\t\tif (!isElidableToolResult(message, marker.minChars)) return entry;\n\t\treturn {\n\t\t\t...entry,\n\t\t\tmessage: { ...message, content: [{ type: \"text\", text: MICROCOMPACT_ELIDED_PLACEHOLDER }] },\n\t\t};\n\t});\n}\n"]}
|