@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.
Files changed (137) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +2 -2
  3. package/dist/bundle/.build-manifest.json +65 -87
  4. package/dist/bundle/chunks/{chunk-BFC2BDVJ.js → chunk-HLJEVRWP.js} +2 -2
  5. package/dist/bundle/chunks/{chunk-MOHOLL3F.js → chunk-YGHQME4V.js} +72 -72
  6. package/dist/bundle/chunks/image-resize-worker.js +2 -2
  7. package/dist/bundle/cli.js +1 -1
  8. package/dist/bundle/client.js +1 -1
  9. package/dist/bundle/index.js +1 -1
  10. package/dist/bundle/rpc-entry.js +1 -1
  11. package/dist/cli/auth-check.d.ts.map +1 -1
  12. package/dist/cli/auth-check.js +14 -2
  13. package/dist/cli/auth-check.js.map +1 -1
  14. package/dist/cli/auth-command.d.ts.map +1 -1
  15. package/dist/cli/auth-command.js +1 -1
  16. package/dist/cli/auth-command.js.map +1 -1
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +3 -2
  19. package/dist/config.js.map +1 -1
  20. package/dist/core/agent-session-compaction.d.ts +6 -10
  21. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  22. package/dist/core/agent-session-compaction.js +7 -111
  23. package/dist/core/agent-session-compaction.js.map +1 -1
  24. package/dist/core/agent-session-internal.d.ts +0 -6
  25. package/dist/core/agent-session-internal.d.ts.map +1 -1
  26. package/dist/core/agent-session-internal.js.map +1 -1
  27. package/dist/core/agent-session-retry.d.ts.map +1 -1
  28. package/dist/core/agent-session-retry.js +0 -1
  29. package/dist/core/agent-session-retry.js.map +1 -1
  30. package/dist/core/agent-session.d.ts +0 -20
  31. package/dist/core/agent-session.d.ts.map +1 -1
  32. package/dist/core/agent-session.js +5 -33
  33. package/dist/core/agent-session.js.map +1 -1
  34. package/dist/core/extensions/bootstrap-compaction-policy.d.ts.map +1 -1
  35. package/dist/core/extensions/bootstrap-compaction-policy.js +0 -34
  36. package/dist/core/extensions/bootstrap-compaction-policy.js.map +1 -1
  37. package/dist/core/extensions/bootstrap-retry-policy.d.ts.map +1 -1
  38. package/dist/core/extensions/bootstrap-retry-policy.js +2 -2
  39. package/dist/core/extensions/bootstrap-retry-policy.js.map +1 -1
  40. package/dist/core/extensions/provider-failover.d.ts +7 -3
  41. package/dist/core/extensions/provider-failover.d.ts.map +1 -1
  42. package/dist/core/extensions/provider-failover.js +9 -8
  43. package/dist/core/extensions/provider-failover.js.map +1 -1
  44. package/dist/core/facade/coding-agent-facade.d.ts +0 -6
  45. package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
  46. package/dist/core/facade/coding-agent-facade.js +0 -3
  47. package/dist/core/facade/coding-agent-facade.js.map +1 -1
  48. package/dist/core/i18n/locales/zh-cn/cli.d.ts.map +1 -1
  49. package/dist/core/i18n/locales/zh-cn/cli.js +3 -1
  50. package/dist/core/i18n/locales/zh-cn/cli.js.map +1 -1
  51. package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -1
  52. package/dist/core/i18n/locales/zh-cn/interactive-mode.js +2 -0
  53. package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -1
  54. package/dist/core/i18n/locales/zh-cn/tui-components-selectors.d.ts.map +1 -1
  55. package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js +0 -2
  56. package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js.map +1 -1
  57. package/dist/core/model-config.d.ts +0 -1
  58. package/dist/core/model-config.d.ts.map +1 -1
  59. package/dist/core/model-config.js +9 -1
  60. package/dist/core/model-config.js.map +1 -1
  61. package/dist/core/model-runtime.d.ts.map +1 -1
  62. package/dist/core/model-runtime.js +3 -3
  63. package/dist/core/model-runtime.js.map +1 -1
  64. package/dist/core/provider-composer.d.ts +2 -2
  65. package/dist/core/provider-composer.d.ts.map +1 -1
  66. package/dist/core/provider-composer.js +7 -8
  67. package/dist/core/provider-composer.js.map +1 -1
  68. package/dist/core/sdk.d.ts +8 -0
  69. package/dist/core/sdk.d.ts.map +1 -1
  70. package/dist/core/sdk.js +6 -1
  71. package/dist/core/sdk.js.map +1 -1
  72. package/dist/core/settings-manager.d.ts +9 -20
  73. package/dist/core/settings-manager.d.ts.map +1 -1
  74. package/dist/core/settings-manager.js +20 -54
  75. package/dist/core/settings-manager.js.map +1 -1
  76. package/dist/main.d.ts.map +1 -1
  77. package/dist/main.js +3 -2
  78. package/dist/main.js.map +1 -1
  79. package/dist/modes/interactive/components/footer.d.ts +0 -2
  80. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  81. package/dist/modes/interactive/components/footer.js +2 -7
  82. package/dist/modes/interactive/components/footer.js.map +1 -1
  83. package/dist/modes/interactive/components/settings-selector.d.ts +2 -6
  84. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/settings-selector.js +6 -28
  86. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  87. package/dist/modes/interactive/interactive-mode.d.ts +0 -1
  88. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  89. package/dist/modes/interactive/interactive-mode.js +23 -36
  90. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  91. package/dist/modes/rpc/rpc-client.d.ts +0 -8
  92. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  93. package/dist/modes/rpc/rpc-client.js +0 -12
  94. package/dist/modes/rpc/rpc-client.js.map +1 -1
  95. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  96. package/dist/modes/rpc/rpc-mode.js +0 -9
  97. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  98. package/dist/modes/rpc/rpc-types.d.ts +0 -19
  99. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  100. package/dist/modes/rpc/rpc-types.js.map +1 -1
  101. package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts +9 -2
  102. package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
  103. package/dist/plugins/ecosystem/node-plugin-bootstrap.js +23 -0
  104. package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
  105. package/dist/plugins/ecosystem/plugin-bootstrap.d.ts +9 -1
  106. package/dist/plugins/ecosystem/plugin-bootstrap.d.ts.map +1 -1
  107. package/dist/plugins/ecosystem/plugin-bootstrap.js +5 -0
  108. package/dist/plugins/ecosystem/plugin-bootstrap.js.map +1 -1
  109. package/dist/utils/photon.d.ts +2 -2
  110. package/dist/utils/photon.d.ts.map +1 -1
  111. package/dist/utils/photon.js +2 -2
  112. package/dist/utils/photon.js.map +1 -1
  113. package/docs/compaction.md +1 -3
  114. package/docs/docs.json +4 -0
  115. package/docs/index.md +1 -0
  116. package/docs/mcp.md +103 -0
  117. package/docs/models.md +14 -22
  118. package/docs/providers.md +0 -1
  119. package/docs/rpc.md +1 -26
  120. package/docs/sdk.md +4 -6
  121. package/docs/settings.md +9 -12
  122. package/docs/skills.md +1 -9
  123. package/docs/usage.md +1 -1
  124. package/examples/extensions/with-deps/package-lock.json +2 -2
  125. package/examples/extensions/with-deps/package.json +1 -1
  126. package/examples/extensions/with-deps/plugin.json +1 -1
  127. package/examples/sdk/10-settings.ts +2 -4
  128. package/examples/sdk/12-full-control.ts +1 -2
  129. package/package.json +1 -3
  130. package/dist/core/extensions/microcompact.d.ts +0 -80
  131. package/dist/core/extensions/microcompact.d.ts.map +0 -1
  132. package/dist/core/extensions/microcompact.js +0 -134
  133. package/dist/core/extensions/microcompact.js.map +0 -1
  134. package/dist/server/create-harness.d.ts +0 -26
  135. package/dist/server/create-harness.d.ts.map +0 -1
  136. package/dist/server/create-harness.js +0 -106
  137. 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
- The `apiKey` value is a placeholder because Ollama ignores it. pi still treats models as requiring auth before they appear in `/model`, so keyless local servers should keep a dummy value, save a key for that provider with `/login`, or pass `--api-key` when selecting the model.
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 <apiKey>` automatically |
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. `apiKey` is not required to load the file: models become available when auth is configured through `/login`/`auth.json`, CLI `--api-key`, or provider `apiKey`. If no auth is configured, the models load but stay unavailable in `/model` and `--list-models`.
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
- The `apiKey` and `headers` fields support command execution, environment interpolation, and literals:
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
- "apiKey": "!security find-generic-password -ws 'anthropic'"
154
- "apiKey": "!op read 'op://vault/item/credential'"
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
- "apiKey": "$MY_API_KEY"
159
- "apiKey": "${KEY_PREFIX}_${KEY_SUFFIX}"
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
- "apiKey": "$$literal-dollar-prefix"
165
- "apiKey": "$!literal-bang-prefix"
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
- "apiKey": "sk-..."
164
+ "key": "sk-..."
170
165
  ```
171
166
 
172
- For `models.json`, shell commands are resolved at request time. 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.
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
@@ -314,4 +314,3 @@ When resolving credentials for a provider:
314
314
  1. CLI `--api-key` flag
315
315
  2. `auth.json` entry (API key or OAuth token)
316
316
  3. Environment variable
317
- 4. Custom provider keys from `models.json`
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
- #### set_auto_retry
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. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
430
- 4. Fallback resolver (for custom provider keys from `models.json`)
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
- compaction: { enabled: false },
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
- compaction: { enabled: false },
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"`, `"fork"`, or `"none"` |
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[] | - | 内置工具初始启用列表;省略时由首方工具插件提供默认集合。裸名为替换语义;`+name`/`-name` 在其后按序增删(如 `["-bash", "+powershell"]`),项目层 `+/-` 条目叠加在用户层之上 |
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": { "enabled": true, "reserveTokens": 16384 }
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": { "enabled": true, "reserveTokens": 8192 }
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. Add them through plugins or use external tools such as containers and tmux.
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.86.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.86.0",
9
+ "version": "0.87.0",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-forge-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.86.0",
4
+ "version": "0.87.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "agent-forge.example.with-deps",
3
- "version": "0.86.0",
3
+ "version": "0.87.0",
4
4
  "hostVersion": ">=0.84.0",
5
5
  "apiVersion": "1-draft",
6
6
  "entry": "./index.ts",
@@ -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
- compaction: { enabled: false },
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
- compaction: { enabled: false },
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
- compaction: { enabled: false },
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.87.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"]}