@oh-my-pi/pi-coding-agent 15.7.3 → 15.7.6
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 +69 -1
- package/dist/types/config/settings-schema.d.ts +12 -22
- package/dist/types/eval/__tests__/heartbeat.test.d.ts +1 -0
- package/dist/types/eval/heartbeat.d.ts +45 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +24 -5
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/local-protocol.d.ts +19 -9
- package/dist/types/internal-urls/types.d.ts +14 -0
- package/dist/types/lsp/client.d.ts +3 -0
- package/dist/types/lsp/diagnostics-ledger.d.ts +10 -0
- package/dist/types/lsp/index.d.ts +2 -0
- package/dist/types/lsp/utils.d.ts +4 -0
- package/dist/types/mcp/manager.d.ts +14 -5
- package/dist/types/modes/acp/acp-agent.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/custom-editor.d.ts +0 -1
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/controllers/command-controller.d.ts +2 -3
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +10 -7
- package/dist/types/session/shake-types.d.ts +3 -3
- package/dist/types/task/repair-args.d.ts +52 -0
- package/dist/types/tiny/models.d.ts +0 -14
- package/dist/types/tiny/title-client.d.ts +28 -2
- package/dist/types/tiny/title-protocol.d.ts +8 -9
- package/dist/types/tools/ask.d.ts +8 -6
- package/dist/types/tools/eval-backends.d.ts +12 -0
- package/dist/types/tools/eval-render.d.ts +52 -0
- package/dist/types/tools/eval.d.ts +2 -35
- package/dist/types/tools/find.d.ts +1 -1
- package/dist/types/tools/index.d.ts +4 -11
- package/dist/types/tools/path-utils.d.ts +7 -0
- package/dist/types/tui/output-block.d.ts +11 -10
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/thinking-note.ts +13 -0
- package/package.json +9 -9
- package/scripts/build-binary.ts +0 -1
- package/src/cli.ts +59 -0
- package/src/config/model-registry.ts +33 -4
- package/src/config/settings-schema.ts +13 -24
- package/src/config/settings.ts +10 -0
- package/src/discovery/claude.ts +41 -22
- package/src/edit/index.ts +23 -3
- package/src/eval/__tests__/agent-bridge.test.ts +90 -0
- package/src/eval/__tests__/heartbeat.test.ts +84 -0
- package/src/eval/__tests__/llm-bridge.test.ts +30 -0
- package/src/eval/agent-bridge.ts +44 -38
- package/src/eval/heartbeat.ts +74 -0
- package/src/eval/js/executor.ts +13 -9
- package/src/eval/llm-bridge.ts +20 -14
- package/src/eval/py/executor.ts +14 -18
- package/src/exec/bash-executor.ts +31 -5
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +16 -18
- package/src/extensibility/extensions/runner.ts +22 -17
- package/src/extensibility/extensions/types.ts +39 -5
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/internal-urls/local-protocol.ts +23 -11
- package/src/internal-urls/types.ts +15 -0
- package/src/lsp/client.ts +28 -5
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/index.ts +9 -22
- package/src/lsp/utils.ts +21 -0
- package/src/mcp/manager.ts +87 -4
- package/src/modes/acp/acp-agent.ts +8 -4
- package/src/modes/components/assistant-message.ts +28 -1
- package/src/modes/components/custom-editor.ts +9 -7
- package/src/modes/components/hook-selector.ts +159 -32
- package/src/modes/components/tool-execution.ts +20 -4
- package/src/modes/controllers/command-controller.ts +7 -39
- package/src/modes/controllers/event-controller.ts +38 -28
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +0 -15
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +2 -1
- package/src/modes/rpc/rpc-mode.ts +17 -6
- package/src/modes/theme/theme-schema.json +30 -0
- package/src/modes/theme/theme.ts +39 -2
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +5 -2
- package/src/prompts/system/project-prompt.md +3 -2
- package/src/prompts/system/subagent-system-prompt.md +12 -8
- package/src/prompts/system/system-prompt.md +8 -6
- package/src/prompts/tools/ask.md +2 -1
- package/src/prompts/tools/eval.md +1 -1
- package/src/session/agent-session.ts +75 -103
- package/src/session/shake-types.ts +4 -5
- package/src/slash-commands/builtin-registry.ts +2 -4
- package/src/task/executor.ts +14 -4
- package/src/task/index.ts +3 -2
- package/src/task/repair-args.ts +117 -0
- package/src/tiny/models.ts +0 -28
- package/src/tiny/title-client.ts +133 -43
- package/src/tiny/title-protocol.ts +11 -16
- package/src/tiny/worker.ts +6 -61
- package/src/tools/ask.ts +74 -32
- package/src/tools/ast-edit.ts +3 -0
- package/src/tools/ast-grep.ts +3 -0
- package/src/tools/eval-backends.ts +38 -0
- package/src/tools/eval-render.ts +750 -0
- package/src/tools/eval.ts +27 -754
- package/src/tools/find.ts +20 -6
- package/src/tools/gh.ts +1 -0
- package/src/tools/index.ts +7 -37
- package/src/tools/path-utils.ts +13 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/renderers.ts +1 -1
- package/src/tools/search.ts +12 -1
- package/src/tools/write.ts +9 -1
- package/src/tui/output-block.ts +42 -79
- package/src/utils/git.ts +9 -3
|
@@ -404,6 +404,36 @@
|
|
|
404
404
|
"additionalProperties": {
|
|
405
405
|
"type": "string"
|
|
406
406
|
}
|
|
407
|
+
},
|
|
408
|
+
"spinnerFrames": {
|
|
409
|
+
"description": "Override the spinner frames. Use a flat array to set both `status` and `activity`, or an object to override each independently. Frames are advanced ~12.5fps for status spinners and ~60fps for activity spinners.",
|
|
410
|
+
"oneOf": [
|
|
411
|
+
{
|
|
412
|
+
"type": "array",
|
|
413
|
+
"minItems": 1,
|
|
414
|
+
"items": { "type": "string", "minLength": 1 }
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"type": "object",
|
|
418
|
+
"properties": {
|
|
419
|
+
"status": {
|
|
420
|
+
"type": "array",
|
|
421
|
+
"minItems": 1,
|
|
422
|
+
"items": { "type": "string", "minLength": 1 }
|
|
423
|
+
},
|
|
424
|
+
"activity": {
|
|
425
|
+
"type": "array",
|
|
426
|
+
"minItems": 1,
|
|
427
|
+
"items": { "type": "string", "minLength": 1 }
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
"additionalProperties": false,
|
|
431
|
+
"anyOf": [
|
|
432
|
+
{ "required": ["status"] },
|
|
433
|
+
{ "required": ["activity"] }
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
]
|
|
407
437
|
}
|
|
408
438
|
},
|
|
409
439
|
"additionalProperties": false
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -812,6 +812,25 @@ const SPINNER_FRAMES: Record<SymbolPreset, Record<SpinnerType, string[]>> = {
|
|
|
812
812
|
},
|
|
813
813
|
};
|
|
814
814
|
|
|
815
|
+
/**
|
|
816
|
+
* Shape accepted by `themeJson.symbols.spinnerFrames`. A flat array applies to
|
|
817
|
+
* both spinner types; an object lets a theme override `status` and/or
|
|
818
|
+
* `activity` independently. Anything not specified falls back to the symbol
|
|
819
|
+
* preset's default frames.
|
|
820
|
+
*/
|
|
821
|
+
type SpinnerFramesOverride = string[] | { status?: string[]; activity?: string[] };
|
|
822
|
+
|
|
823
|
+
function normalizeSpinnerFramesOverride(
|
|
824
|
+
value: SpinnerFramesOverride | undefined,
|
|
825
|
+
): Partial<Record<SpinnerType, string[]>> {
|
|
826
|
+
if (value === undefined) return {};
|
|
827
|
+
if (Array.isArray(value)) return { status: value, activity: value };
|
|
828
|
+
const result: Partial<Record<SpinnerType, string[]>> = {};
|
|
829
|
+
if (value.status) result.status = value.status;
|
|
830
|
+
if (value.activity) result.activity = value.activity;
|
|
831
|
+
return result;
|
|
832
|
+
}
|
|
833
|
+
|
|
815
834
|
// ============================================================================
|
|
816
835
|
// Types & Schema
|
|
817
836
|
// ============================================================================
|
|
@@ -898,6 +917,19 @@ const themeColorsSchema = z.object(
|
|
|
898
917
|
},
|
|
899
918
|
);
|
|
900
919
|
|
|
920
|
+
const spinnerFramesArraySchema = z.array(z.string().min(1)).min(1);
|
|
921
|
+
const spinnerFramesSchema = z.union([
|
|
922
|
+
spinnerFramesArraySchema,
|
|
923
|
+
z
|
|
924
|
+
.object({
|
|
925
|
+
status: spinnerFramesArraySchema.optional(),
|
|
926
|
+
activity: spinnerFramesArraySchema.optional(),
|
|
927
|
+
})
|
|
928
|
+
.refine(value => value.status !== undefined || value.activity !== undefined, {
|
|
929
|
+
message: "spinnerFrames object must define `status` and/or `activity`",
|
|
930
|
+
}),
|
|
931
|
+
]);
|
|
932
|
+
|
|
901
933
|
const symbolPresetSchema = z.enum(["unicode", "nerd", "ascii"]);
|
|
902
934
|
|
|
903
935
|
const themeJsonSchema = z.object({
|
|
@@ -916,6 +948,7 @@ const themeJsonSchema = z.object({
|
|
|
916
948
|
.object({
|
|
917
949
|
preset: symbolPresetSchema.optional(),
|
|
918
950
|
overrides: z.record(z.string(), z.string()).optional(),
|
|
951
|
+
spinnerFrames: spinnerFramesSchema.optional(),
|
|
919
952
|
})
|
|
920
953
|
.optional(),
|
|
921
954
|
});
|
|
@@ -1243,6 +1276,7 @@ export class Theme {
|
|
|
1243
1276
|
#fgColors: Record<ThemeColor, string>;
|
|
1244
1277
|
#bgColors: Record<ThemeBg, string>;
|
|
1245
1278
|
#symbols: SymbolMap;
|
|
1279
|
+
#spinnerFramesOverrides: Partial<Record<SpinnerType, string[]>>;
|
|
1246
1280
|
|
|
1247
1281
|
constructor(
|
|
1248
1282
|
fgColors: Record<ThemeColor, string | number>,
|
|
@@ -1250,6 +1284,7 @@ export class Theme {
|
|
|
1250
1284
|
private readonly mode: ColorMode,
|
|
1251
1285
|
private readonly symbolPreset: SymbolPreset,
|
|
1252
1286
|
symbolOverrides: Partial<Record<SymbolKey, string>>,
|
|
1287
|
+
spinnerFramesOverrides: Partial<Record<SpinnerType, string[]>> = {},
|
|
1253
1288
|
) {
|
|
1254
1289
|
this.#fgColors = {} as Record<ThemeColor, string>;
|
|
1255
1290
|
for (const [key, value] of Object.entries(fgColors) as [ThemeColor, string | number][]) {
|
|
@@ -1269,6 +1304,7 @@ export class Theme {
|
|
|
1269
1304
|
logger.debug("Invalid symbol key in override", { key, availableKeys: Object.keys(this.#symbols) });
|
|
1270
1305
|
}
|
|
1271
1306
|
}
|
|
1307
|
+
this.#spinnerFramesOverrides = spinnerFramesOverrides;
|
|
1272
1308
|
}
|
|
1273
1309
|
|
|
1274
1310
|
fg(color: ThemeColor, text: string): string {
|
|
@@ -1563,7 +1599,7 @@ export class Theme {
|
|
|
1563
1599
|
* Get spinner frames by type.
|
|
1564
1600
|
*/
|
|
1565
1601
|
getSpinnerFrames(type: SpinnerType = "status"): string[] {
|
|
1566
|
-
return SPINNER_FRAMES[this.symbolPreset][type];
|
|
1602
|
+
return this.#spinnerFramesOverrides[type] ?? SPINNER_FRAMES[this.symbolPreset][type];
|
|
1567
1603
|
}
|
|
1568
1604
|
|
|
1569
1605
|
/**
|
|
@@ -1735,7 +1771,8 @@ function createTheme(themeJson: ThemeJson, options: CreateThemeOptions = {}): Th
|
|
|
1735
1771
|
// Extract symbol configuration - settings override takes precedence over theme
|
|
1736
1772
|
const symbolPreset: SymbolPreset = symbolPresetOverride ?? themeJson.symbols?.preset ?? "unicode";
|
|
1737
1773
|
const symbolOverrides = themeJson.symbols?.overrides ?? {};
|
|
1738
|
-
|
|
1774
|
+
const spinnerFramesOverrides = normalizeSpinnerFramesOverride(themeJson.symbols?.spinnerFrames);
|
|
1775
|
+
return new Theme(fgColors, bgColors, colorMode, symbolPreset, symbolOverrides, spinnerFramesOverrides);
|
|
1739
1776
|
}
|
|
1740
1777
|
|
|
1741
1778
|
async function loadTheme(name: string, options: CreateThemeOptions = {}): Promise<Theme> {
|
package/src/modes/types.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { Settings } from "../config/settings";
|
|
|
7
7
|
import type {
|
|
8
8
|
ExtensionUIContext,
|
|
9
9
|
ExtensionUIDialogOptions,
|
|
10
|
+
ExtensionUISelectItem,
|
|
10
11
|
ExtensionWidgetContent,
|
|
11
12
|
ExtensionWidgetOptions,
|
|
12
13
|
} from "../extensibility/extensions";
|
|
@@ -297,7 +298,7 @@ export interface InteractiveModeContext {
|
|
|
297
298
|
setHookStatus(key: string, text: string | undefined): void;
|
|
298
299
|
showHookSelector(
|
|
299
300
|
title: string,
|
|
300
|
-
options:
|
|
301
|
+
options: ExtensionUISelectItem[],
|
|
301
302
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
302
303
|
): Promise<string | undefined>;
|
|
303
304
|
hideHookSelector(): void;
|
|
@@ -262,8 +262,11 @@ export class UiHelpers {
|
|
|
262
262
|
break;
|
|
263
263
|
}
|
|
264
264
|
case "assistant": {
|
|
265
|
-
const assistantComponent = new AssistantMessageComponent(
|
|
266
|
-
|
|
265
|
+
const assistantComponent = new AssistantMessageComponent(
|
|
266
|
+
message,
|
|
267
|
+
this.ctx.hideThinkingBlock,
|
|
268
|
+
() => this.ctx.ui.requestRender(),
|
|
269
|
+
this.ctx.session.extensionRunner?.getAssistantThinkingRenderers(),
|
|
267
270
|
);
|
|
268
271
|
this.ctx.chatContainer.addChild(assistantComponent);
|
|
269
272
|
break;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
PROJECT
|
|
2
|
+
===================================
|
|
3
|
+
|
|
2
4
|
<workstation>
|
|
3
5
|
{{#list environment prefix="- " join="\n"}}{{label}}: {{value}}{{/list}}
|
|
4
6
|
</workstation>
|
|
@@ -47,4 +49,3 @@ Today is {{date}}, and the current working directory is '{{cwd}}'.
|
|
|
47
49
|
{{#if appendPrompt}}
|
|
48
50
|
{{appendPrompt}}
|
|
49
51
|
{{/if}}
|
|
50
|
-
[/PROJECT]
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
ROLE
|
|
2
|
+
===================================
|
|
3
|
+
|
|
2
4
|
{{agent}}
|
|
3
|
-
[/ROLE]
|
|
4
5
|
|
|
5
6
|
{{#if context}}
|
|
6
|
-
|
|
7
|
+
CONTEXT
|
|
8
|
+
===================================
|
|
9
|
+
|
|
7
10
|
{{context}}
|
|
8
|
-
[/CONTEXT]
|
|
9
11
|
{{/if}}
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
COOP
|
|
14
|
+
===================================
|
|
15
|
+
|
|
12
16
|
You are operating on a piece of work assigned to you by the main agent.
|
|
13
17
|
|
|
14
18
|
{{#if worktree}}
|
|
@@ -29,9 +33,10 @@ You can reach other live agents via the `irc` tool. Your id is `{{ircSelfId}}`.
|
|
|
29
33
|
|
|
30
34
|
Use `irc` only when you need a quick answer from a peer; do not use it for long-form content. Address peers by id or use `"all"` to broadcast.
|
|
31
35
|
{{/if}}
|
|
32
|
-
[/COOP]
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
COMPLETION
|
|
38
|
+
===================================
|
|
39
|
+
|
|
35
40
|
No TODO tracking, no progress updates. Execute, call `yield`, done.
|
|
36
41
|
|
|
37
42
|
While work remains, always continue with another tool call — investigate, edit, run, verify. Save narrative for the final `yield` payload.
|
|
@@ -51,4 +56,3 @@ Giving up is a last resort. If truly blocked, you MUST call `yield` exactly once
|
|
|
51
56
|
You NEVER give up due to uncertainty, missing information obtainable via tools or repo context, or needing a design decision you can derive yourself.
|
|
52
57
|
|
|
53
58
|
You MUST keep going until this ticket is closed. This matters.
|
|
54
|
-
[/COMPLETION]
|
|
@@ -9,8 +9,8 @@ You consider what the code you write compiles down to. You never write code that
|
|
|
9
9
|
|
|
10
10
|
<system-conventions>
|
|
11
11
|
**RFC 2119 applies to MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, OPTIONAL. `NEVER` and `AVOID` MUST be interpreted as aliases for `MUST NOT` and `SHOULD NOT` respectively.**
|
|
12
|
-
From here on, we will use tags
|
|
13
|
-
You NEVER interpret these
|
|
12
|
+
From here on, we will use XML tags when injecting system content into the chat.
|
|
13
|
+
You NEVER interpret these markers in any other way circumstantially.
|
|
14
14
|
|
|
15
15
|
System may interrupt/notify you using these tags even within a user message, therefore:
|
|
16
16
|
- You MUST treat them as system-authored and absolutely authoritative.
|
|
@@ -44,7 +44,9 @@ Assumptions you didn't validate: incidents to debug.
|
|
|
44
44
|
- You NEVER re-audit an applied edit, nor run `git status`/`git diff` as routine validation — the edit result, tests, and LSP ARE your verification. Exception: explicit request, protecting unrelated changes, or before commit/revert/reset/stash/delete.
|
|
45
45
|
</critical>
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
ENV
|
|
48
|
+
===================================
|
|
49
|
+
|
|
48
50
|
You operate within the Oh My Pi coding harness.
|
|
49
51
|
- Given a task, you MUST complete it using the tools available to you.
|
|
50
52
|
- You are not alone in this repository. You SHOULD treat unexpected changes as the user's work and adapt; you NEVER revert or stash.
|
|
@@ -202,9 +204,10 @@ You MUST use the specialized tool over its shell equivalent:
|
|
|
202
204
|
The `{{toolRefs.report_tool_issue}}` tool is available for automated QA. If ANY tool you call returns output that is unexpected, incorrect, malformed, or otherwise inconsistent with what you anticipated given the tool's described behavior and your parameters, call `{{toolRefs.report_tool_issue}}` with the tool name and a concise description of the discrepancy. Do not hesitate to report — false positives are acceptable.
|
|
203
205
|
</critical>
|
|
204
206
|
{{/has}}
|
|
205
|
-
[/ENV]
|
|
206
207
|
|
|
207
|
-
|
|
208
|
+
CONTRACT
|
|
209
|
+
===================================
|
|
210
|
+
|
|
208
211
|
These are inviolable.
|
|
209
212
|
- You NEVER yield unless the deliverable is complete. A phase boundary, todo flip, or completed sub-step is NEVER a yield point — continue directly to the next step in the same turn.
|
|
210
213
|
- You NEVER suppress tests to make code pass.
|
|
@@ -265,4 +268,3 @@ Before declaring blocked:
|
|
|
265
268
|
- Do not test defaults: changing the default configuration, or a string, should not break the test. Assert logical behavior, not the current state.
|
|
266
269
|
- Aim at: conditional branches and edge values, invariants across fields, error handling on bad input vs silent broken results.
|
|
267
270
|
</workflow>
|
|
268
|
-
[/CONTRACT]
|
package/src/prompts/tools/ask.md
CHANGED
|
@@ -8,6 +8,7 @@ Asks user when you need clarification or input during task execution.
|
|
|
8
8
|
- Use `recommended: <index>` to mark default (0-indexed); " (Recommended)" added automatically
|
|
9
9
|
- Use `questions` for multiple related questions instead of asking one at a time
|
|
10
10
|
- Set `multi: true` on question to allow multiple selections
|
|
11
|
+
- Use short option labels; put explanatory tradeoffs in `description` instead of merging them into the label
|
|
11
12
|
</instruction>
|
|
12
13
|
|
|
13
14
|
<caution>
|
|
@@ -22,7 +23,7 @@ Asks user when you need clarification or input during task execution.
|
|
|
22
23
|
|
|
23
24
|
<examples>
|
|
24
25
|
# Single question
|
|
25
|
-
questions: [{"id": "auth_method", "question": "Which authentication method should this API use?", "options": [{"label": "JWT"}, {"label": "OAuth2"}, {"label": "Session cookies"}], "recommended": 0}]
|
|
26
|
+
questions: [{"id": "auth_method", "question": "Which authentication method should this API use?", "options": [{"label": "JWT", "description": "Bearer tokens for stateless API clients."}, {"label": "OAuth2", "description": "Delegated authorization with external identity providers."}, {"label": "Session cookies", "description": "Browser-first authentication backed by server-side sessions."}], "recommended": 0}]
|
|
26
27
|
|
|
27
28
|
# Multiple questions
|
|
28
29
|
questions: [{"id": "storage_type", "question": "Which storage backend?", "options": [{"label": "SQLite"}, {"label": "PostgreSQL"}]}, {"id": "auth_method", "question": "Which auth method?", "options": [{"label": "JWT"}, {"label": "Session cookies"}]}]
|
|
@@ -8,7 +8,7 @@ Cell fields:
|
|
|
8
8
|
- `language` — {{#if py}}`"py"` for the IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` for the persistent JavaScript VM{{/if}}.
|
|
9
9
|
- `code` — cell body, verbatim. Newlines, quotes, and indentation are JSON-encoded; no fences, no headers.
|
|
10
10
|
- `title` (optional) — short label shown in the transcript (e.g. `"imports"`, `"load config"`).
|
|
11
|
-
- `timeout` (optional) — per-cell
|
|
11
|
+
- `timeout` (optional) — per-cell wall-clock budget in seconds (1-600). Default 30. It bounds the cell's **own** work, but is paused while an `agent()`/`parallel()`/`llm()` call is in flight — so a long fanout or a slow completion runs to completion, while the cell itself is still bounded. Compute, `print`/stdout, `log()`/`phase()`, and ordinary tool calls all count against the budget; raise `timeout` for a cell that does heavy local work or long non-agent tool calls.
|
|
12
12
|
- `reset` (optional) — wipe this cell's language kernel before running.{{#ifAll py js}} Reset is per-language: a `py` cell's reset does not touch the JavaScript VM and vice versa.{{/ifAll}}
|
|
13
13
|
|
|
14
14
|
**Work incrementally:**
|
|
@@ -51,11 +51,8 @@ import {
|
|
|
51
51
|
prepareCompaction,
|
|
52
52
|
type ShakeConfig,
|
|
53
53
|
type ShakeRegion,
|
|
54
|
-
type ShakeSummaryComplete,
|
|
55
|
-
type ShakeSummaryItem,
|
|
56
54
|
type SummaryOptions,
|
|
57
55
|
shouldCompact,
|
|
58
|
-
summarizeShakeRegions,
|
|
59
56
|
} from "@oh-my-pi/pi-agent-core/compaction";
|
|
60
57
|
import { DEFAULT_PRUNE_CONFIG, pruneToolOutputs } from "@oh-my-pi/pi-agent-core/compaction/pruning";
|
|
61
58
|
import type {
|
|
@@ -162,6 +159,7 @@ import { containsOrchestrate, ORCHESTRATE_NOTICE } from "../modes/orchestrate";
|
|
|
162
159
|
import { getCurrentThemeName, theme } from "../modes/theme/theme";
|
|
163
160
|
import { parseTurnBudget } from "../modes/turn-budget";
|
|
164
161
|
import { containsUltrathink, ULTRATHINK_NOTICE } from "../modes/ultrathink";
|
|
162
|
+
import { computeNonMessageTokens } from "../modes/utils/context-usage";
|
|
165
163
|
import { containsWorkflow, WORKFLOW_NOTICE } from "../modes/workflow";
|
|
166
164
|
import type { PlanModeState } from "../plan-mode/state";
|
|
167
165
|
import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
|
|
@@ -185,8 +183,7 @@ import {
|
|
|
185
183
|
resolveThinkingLevelForModel,
|
|
186
184
|
toReasoningEffort,
|
|
187
185
|
} from "../thinking";
|
|
188
|
-
import {
|
|
189
|
-
import { shutdownTinyTitleClient, tinyModelClient } from "../tiny/title-client";
|
|
186
|
+
import { shutdownTinyTitleClient } from "../tiny/title-client";
|
|
190
187
|
import {
|
|
191
188
|
buildDiscoverableToolSearchIndex,
|
|
192
189
|
collectDiscoverableTools,
|
|
@@ -241,11 +238,11 @@ export type AgentSessionEvent =
|
|
|
241
238
|
| {
|
|
242
239
|
type: "auto_compaction_start";
|
|
243
240
|
reason: "threshold" | "overflow" | "idle" | "incomplete";
|
|
244
|
-
action: "context-full" | "handoff" | "shake"
|
|
241
|
+
action: "context-full" | "handoff" | "shake";
|
|
245
242
|
}
|
|
246
243
|
| {
|
|
247
244
|
type: "auto_compaction_end";
|
|
248
|
-
action: "context-full" | "handoff" | "shake"
|
|
245
|
+
action: "context-full" | "handoff" | "shake";
|
|
249
246
|
result: CompactionResult | undefined;
|
|
250
247
|
aborted: boolean;
|
|
251
248
|
willRetry: boolean;
|
|
@@ -3621,8 +3618,13 @@ export class AgentSession {
|
|
|
3621
3618
|
/**
|
|
3622
3619
|
* Replace MCP tools in the registry and recompute the visible MCP tool set immediately.
|
|
3623
3620
|
* This allows /mcp add/remove/reauth to take effect without restarting the session.
|
|
3621
|
+
*
|
|
3622
|
+
* @param mcpTools The new MCP tools to register.
|
|
3623
|
+
* @param options.activateAll When true, force-activates every newly registered MCP tool
|
|
3624
|
+
* regardless of prior selection state. Used when an ACP client provisions MCP servers
|
|
3625
|
+
* for a session where MCP discovery is disabled.
|
|
3624
3626
|
*/
|
|
3625
|
-
async refreshMCPTools(mcpTools: CustomTool[]): Promise<void> {
|
|
3627
|
+
async refreshMCPTools(mcpTools: CustomTool[], options?: { activateAll?: boolean }): Promise<void> {
|
|
3626
3628
|
const previousSelectedMCPToolNames = this.getSelectedMCPToolNames();
|
|
3627
3629
|
const existingNames = Array.from(this.#toolRegistry.keys());
|
|
3628
3630
|
for (const name of existingNames) {
|
|
@@ -3663,6 +3665,18 @@ export class AgentSession {
|
|
|
3663
3665
|
this.#getConfiguredDefaultSelectedMCPToolNames(),
|
|
3664
3666
|
);
|
|
3665
3667
|
|
|
3668
|
+
if (options?.activateAll) {
|
|
3669
|
+
// Force-activate every newly registered MCP tool. This path is used
|
|
3670
|
+
// when an ACP client provisions MCP servers for a session where MCP
|
|
3671
|
+
// discovery is disabled — without it, getSelectedMCPToolNames()
|
|
3672
|
+
// returns only already-active tools (circular deadlock: tools can
|
|
3673
|
+
// only become active if they're already active).
|
|
3674
|
+
const newMcpNames = mcpTools.map(t => t.name);
|
|
3675
|
+
const nextActive = [...new Set([...this.#getActiveNonMCPToolNames(), ...newMcpNames])];
|
|
3676
|
+
await this.#applyActiveToolsByName(nextActive, { previousSelectedMCPToolNames });
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3666
3680
|
const nextActive = [...this.#getActiveNonMCPToolNames(), ...this.getSelectedMCPToolNames()];
|
|
3667
3681
|
await this.#applyActiveToolsByName(nextActive, { previousSelectedMCPToolNames });
|
|
3668
3682
|
}
|
|
@@ -4283,7 +4297,7 @@ export class AgentSession {
|
|
|
4283
4297
|
// next microtask alongside the new turn.
|
|
4284
4298
|
const lastAssistant = this.#findLastAssistantMessage();
|
|
4285
4299
|
if (lastAssistant && !options?.skipCompactionCheck) {
|
|
4286
|
-
await this.#checkCompaction(lastAssistant, false, false);
|
|
4300
|
+
await this.#checkCompaction(lastAssistant, false, false, false);
|
|
4287
4301
|
}
|
|
4288
4302
|
|
|
4289
4303
|
// Build messages array (session context, eager todo prelude, then active prompt message)
|
|
@@ -4385,6 +4399,11 @@ export class AgentSession {
|
|
|
4385
4399
|
}
|
|
4386
4400
|
}
|
|
4387
4401
|
|
|
4402
|
+
await this.#runPrePromptCompactionIfNeeded(messages);
|
|
4403
|
+
if (this.#promptGeneration !== generation) {
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
|
|
4388
4407
|
const agentPromptOptions = options?.toolChoice ? { toolChoice: options.toolChoice } : undefined;
|
|
4389
4408
|
await this.#promptAgentWithIdleRetry(messages, agentPromptOptions);
|
|
4390
4409
|
if (!options?.skipPostPromptRecoveryWait) {
|
|
@@ -5659,10 +5678,6 @@ export class AgentSession {
|
|
|
5659
5678
|
* - `images` delegates to {@link dropImages}.
|
|
5660
5679
|
* - `elide` replaces whole tool-call results and large fenced/XML blocks
|
|
5661
5680
|
* with short placeholders that embed an `artifact://` recovery link.
|
|
5662
|
-
* - `summary` extractively compresses the same regions with the configured
|
|
5663
|
-
* local on-device model (`providers.shakeSummaryModel`), falling back to
|
|
5664
|
-
* the elide placeholder per region (or wholesale when the local model is
|
|
5665
|
-
* unavailable). Never calls a remote/cloud LLM.
|
|
5666
5681
|
*
|
|
5667
5682
|
* Mutates the branch in place, persists via `rewriteEntries`, replays the
|
|
5668
5683
|
* rebuilt context through the agent, and tears down provider sessions that
|
|
@@ -5683,29 +5698,7 @@ export class AgentSession {
|
|
|
5683
5698
|
}
|
|
5684
5699
|
|
|
5685
5700
|
const artifactId = await this.#saveShakeArtifact(regions);
|
|
5686
|
-
|
|
5687
|
-
if (mode === "summary") {
|
|
5688
|
-
// Manual `/shake summary` installs the compaction controller so Esc /
|
|
5689
|
-
// `abortCompaction()` can cancel the local-model pass; the auto-shake path
|
|
5690
|
-
// passes its own signal and manages `#autoCompactionAbortController`.
|
|
5691
|
-
let controller: AbortController | undefined;
|
|
5692
|
-
let signal = opts.signal;
|
|
5693
|
-
if (!signal) {
|
|
5694
|
-
if (this.#compactionAbortController) throw new Error("Compaction already in progress");
|
|
5695
|
-
controller = new AbortController();
|
|
5696
|
-
this.#compactionAbortController = controller;
|
|
5697
|
-
signal = controller.signal;
|
|
5698
|
-
}
|
|
5699
|
-
try {
|
|
5700
|
-
replacements = await this.#buildShakeSummaryReplacements(regions, artifactId, signal);
|
|
5701
|
-
} finally {
|
|
5702
|
-
if (controller && this.#compactionAbortController === controller) {
|
|
5703
|
-
this.#compactionAbortController = undefined;
|
|
5704
|
-
}
|
|
5705
|
-
}
|
|
5706
|
-
} else {
|
|
5707
|
-
replacements = regions.map((region, index) => this.#shakeElidePlaceholder(region, index, artifactId));
|
|
5708
|
-
}
|
|
5701
|
+
const replacements = regions.map((region, index) => this.#shakeElidePlaceholder(region, index, artifactId));
|
|
5709
5702
|
|
|
5710
5703
|
let toolResultsDropped = 0;
|
|
5711
5704
|
let blocksDropped = 0;
|
|
@@ -5762,56 +5755,6 @@ export class AgentSession {
|
|
|
5762
5755
|
}
|
|
5763
5756
|
}
|
|
5764
5757
|
|
|
5765
|
-
/**
|
|
5766
|
-
* Build per-region replacements for summary mode using the configured local
|
|
5767
|
-
* on-device model (`providers.shakeSummaryModel`) via {@link tinyModelClient}.
|
|
5768
|
-
* Shake summary never calls a remote/cloud LLM. When the configured model is
|
|
5769
|
-
* not a known local key, every region falls back to the elide placeholder.
|
|
5770
|
-
* Otherwise compresses via {@link summarizeShakeRegions}; per region, uses
|
|
5771
|
-
* the parsed summary (with a recovery footer) or the elide placeholder when
|
|
5772
|
-
* the local model omitted it / was unavailable. Any thrown failure degrades
|
|
5773
|
-
* the whole batch to elide so the reduction still happens.
|
|
5774
|
-
*/
|
|
5775
|
-
async #buildShakeSummaryReplacements(
|
|
5776
|
-
regions: ShakeRegion[],
|
|
5777
|
-
artifactId: string | undefined,
|
|
5778
|
-
signal: AbortSignal | undefined,
|
|
5779
|
-
): Promise<string[]> {
|
|
5780
|
-
const elide = (): string[] =>
|
|
5781
|
-
regions.map((region, index) => this.#shakeElidePlaceholder(region, index, artifactId));
|
|
5782
|
-
|
|
5783
|
-
const modelKey = this.settings.get("providers.shakeSummaryModel");
|
|
5784
|
-
if (!isTinyMemoryLocalModelKey(modelKey)) return elide();
|
|
5785
|
-
|
|
5786
|
-
const items: ShakeSummaryItem[] = regions.map((region, index) => ({
|
|
5787
|
-
index,
|
|
5788
|
-
label: region.label,
|
|
5789
|
-
text: region.originalText,
|
|
5790
|
-
}));
|
|
5791
|
-
|
|
5792
|
-
const complete: ShakeSummaryComplete = (promptText, opts) =>
|
|
5793
|
-
tinyModelClient.complete(modelKey, promptText, { maxTokens: opts.maxTokens, signal: opts.signal });
|
|
5794
|
-
|
|
5795
|
-
let summaries: Map<number, string>;
|
|
5796
|
-
try {
|
|
5797
|
-
summaries = await summarizeShakeRegions(items, complete, { signal });
|
|
5798
|
-
} catch (error) {
|
|
5799
|
-
logger.warn("Shake summary compression failed; falling back to elide", {
|
|
5800
|
-
error: error instanceof Error ? error.message : String(error),
|
|
5801
|
-
});
|
|
5802
|
-
return elide();
|
|
5803
|
-
}
|
|
5804
|
-
|
|
5805
|
-
return regions.map((region, index) => {
|
|
5806
|
-
const summary = summaries.get(index);
|
|
5807
|
-
if (!summary) return this.#shakeElidePlaceholder(region, index, artifactId);
|
|
5808
|
-
if (artifactId) {
|
|
5809
|
-
return `${summary}\n\n[recover full: artifact://${artifactId} (region ${index + 1})]`;
|
|
5810
|
-
}
|
|
5811
|
-
return summary;
|
|
5812
|
-
});
|
|
5813
|
-
}
|
|
5814
|
-
|
|
5815
5758
|
/**
|
|
5816
5759
|
* Manually compact the session context.
|
|
5817
5760
|
* Aborts current agent operation first.
|
|
@@ -6171,6 +6114,34 @@ export class AgentSession {
|
|
|
6171
6114
|
}
|
|
6172
6115
|
}
|
|
6173
6116
|
|
|
6117
|
+
#estimatePendingPromptTokens(messages: AgentMessage[]): number {
|
|
6118
|
+
let tokens = computeNonMessageTokens(this);
|
|
6119
|
+
for (const message of this.messages) {
|
|
6120
|
+
tokens += estimateTokens(message);
|
|
6121
|
+
}
|
|
6122
|
+
for (const message of messages) {
|
|
6123
|
+
tokens += estimateTokens(message);
|
|
6124
|
+
}
|
|
6125
|
+
return tokens;
|
|
6126
|
+
}
|
|
6127
|
+
|
|
6128
|
+
async #runPrePromptCompactionIfNeeded(messages: AgentMessage[]): Promise<void> {
|
|
6129
|
+
const model = this.model;
|
|
6130
|
+
if (!model) return;
|
|
6131
|
+
const contextWindow = model.contextWindow ?? 0;
|
|
6132
|
+
if (contextWindow <= 0) return;
|
|
6133
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
6134
|
+
const contextTokens = this.#estimatePendingPromptTokens(messages);
|
|
6135
|
+
if (!shouldCompact(contextTokens, contextWindow, compactionSettings)) return;
|
|
6136
|
+
|
|
6137
|
+
logger.debug("Pre-prompt context maintenance triggered by pending prompt size", {
|
|
6138
|
+
contextTokens,
|
|
6139
|
+
contextWindow,
|
|
6140
|
+
model: `${model.provider}/${model.id}`,
|
|
6141
|
+
});
|
|
6142
|
+
await this.#runAutoCompaction("threshold", false, false, false, { autoContinue: false });
|
|
6143
|
+
}
|
|
6144
|
+
|
|
6174
6145
|
/**
|
|
6175
6146
|
* Check if context maintenance or promotion is needed and run it.
|
|
6176
6147
|
* Called after agent_end and before prompt submission.
|
|
@@ -6191,6 +6162,7 @@ export class AgentSession {
|
|
|
6191
6162
|
* `agent_end` handler set this to true so `session.prompt()` resolves cleanly; callers
|
|
6192
6163
|
* on the pre-prompt path (where the next agent turn is about to start) set it to false
|
|
6193
6164
|
* to avoid racing the deferred handoff against the new turn.
|
|
6165
|
+
* @param autoContinue Whether maintenance may schedule the agent-authored continuation prompt.
|
|
6194
6166
|
* @returns true when a deferred handoff was scheduled. Callers MUST then skip any
|
|
6195
6167
|
* subsequent `#scheduleAgentContinue` / reminder appends for this turn — the
|
|
6196
6168
|
* handoff will replace session state and a concurrent `agent.continue()` would
|
|
@@ -6200,6 +6172,7 @@ export class AgentSession {
|
|
|
6200
6172
|
assistantMessage: AssistantMessage,
|
|
6201
6173
|
skipAbortedCheck = true,
|
|
6202
6174
|
allowDefer = true,
|
|
6175
|
+
autoContinue = true,
|
|
6203
6176
|
): Promise<boolean> {
|
|
6204
6177
|
// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
|
|
6205
6178
|
if (skipAbortedCheck && assistantMessage.stopReason === "aborted") return false;
|
|
@@ -6237,7 +6210,7 @@ export class AgentSession {
|
|
|
6237
6210
|
// No promotion target available fall through to compaction
|
|
6238
6211
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
6239
6212
|
if (compactionSettings.enabled && compactionSettings.strategy !== "off") {
|
|
6240
|
-
await this.#runAutoCompaction("overflow", true, false, allowDefer);
|
|
6213
|
+
await this.#runAutoCompaction("overflow", true, false, allowDefer, { autoContinue });
|
|
6241
6214
|
}
|
|
6242
6215
|
return false;
|
|
6243
6216
|
}
|
|
@@ -6269,7 +6242,7 @@ export class AgentSession {
|
|
|
6269
6242
|
model: `${assistantMessage.provider}/${assistantMessage.model}`,
|
|
6270
6243
|
strategy: incompleteCompactionSettings.strategy,
|
|
6271
6244
|
});
|
|
6272
|
-
await this.#runAutoCompaction("incomplete", true, false, allowDefer);
|
|
6245
|
+
await this.#runAutoCompaction("incomplete", true, false, allowDefer, { autoContinue });
|
|
6273
6246
|
} else {
|
|
6274
6247
|
// Neither promotion nor compaction is available — surface the dead-end so
|
|
6275
6248
|
// the user understands why the turn yielded with nothing.
|
|
@@ -6295,7 +6268,7 @@ export class AgentSession {
|
|
|
6295
6268
|
// Try promotion first — if a larger model is available, switch instead of compacting
|
|
6296
6269
|
const promoted = await this.#tryContextPromotion(assistantMessage);
|
|
6297
6270
|
if (!promoted) {
|
|
6298
|
-
return await this.#runAutoCompaction("threshold", false, false, allowDefer);
|
|
6271
|
+
return await this.#runAutoCompaction("threshold", false, false, allowDefer, { autoContinue });
|
|
6299
6272
|
}
|
|
6300
6273
|
}
|
|
6301
6274
|
return false;
|
|
@@ -7019,17 +6992,18 @@ export class AgentSession {
|
|
|
7019
6992
|
willRetry: boolean,
|
|
7020
6993
|
deferred = false,
|
|
7021
6994
|
allowDefer = true,
|
|
6995
|
+
options: { autoContinue?: boolean } = {},
|
|
7022
6996
|
): Promise<boolean> {
|
|
7023
6997
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
7024
6998
|
if (compactionSettings.strategy === "off") return false;
|
|
7025
6999
|
if (reason !== "idle" && !compactionSettings.enabled) return false;
|
|
7026
7000
|
const generation = this.#promptGeneration;
|
|
7027
|
-
|
|
7028
|
-
// Shake
|
|
7029
|
-
//
|
|
7030
|
-
//
|
|
7031
|
-
if (compactionSettings.strategy === "shake"
|
|
7032
|
-
const outcome = await this.#runAutoShake(reason,
|
|
7001
|
+
const shouldAutoContinue = options.autoContinue !== false && compactionSettings.autoContinue !== false;
|
|
7002
|
+
// Shake runs inline (cheap, no remote LLM). On overflow recovery, if shake
|
|
7003
|
+
// reclaims nothing we fall through to the summary-compaction body below so
|
|
7004
|
+
// the oversized input still gets resolved.
|
|
7005
|
+
if (compactionSettings.strategy === "shake") {
|
|
7006
|
+
const outcome = await this.#runAutoShake(reason, willRetry, generation, shouldAutoContinue);
|
|
7033
7007
|
if (outcome !== "fallback") return false;
|
|
7034
7008
|
}
|
|
7035
7009
|
// "overflow" and "incomplete" force inline execution because they are recovery
|
|
@@ -7098,7 +7072,7 @@ export class AgentSession {
|
|
|
7098
7072
|
aborted: false,
|
|
7099
7073
|
willRetry: false,
|
|
7100
7074
|
});
|
|
7101
|
-
if (!autoCompactionSignal.aborted && reason !== "idle" &&
|
|
7075
|
+
if (!autoCompactionSignal.aborted && reason !== "idle" && shouldAutoContinue) {
|
|
7102
7076
|
this.#scheduleAutoContinuePrompt(generation);
|
|
7103
7077
|
}
|
|
7104
7078
|
return false;
|
|
@@ -7350,7 +7324,7 @@ export class AgentSession {
|
|
|
7350
7324
|
};
|
|
7351
7325
|
await this.#emitSessionEvent({ type: "auto_compaction_end", action, result, aborted: false, willRetry });
|
|
7352
7326
|
|
|
7353
|
-
if (!willRetry && reason !== "idle" &&
|
|
7327
|
+
if (!willRetry && reason !== "idle" && shouldAutoContinue) {
|
|
7354
7328
|
this.#scheduleAutoContinuePrompt(generation);
|
|
7355
7329
|
}
|
|
7356
7330
|
|
|
@@ -7426,20 +7400,18 @@ export class AgentSession {
|
|
|
7426
7400
|
*/
|
|
7427
7401
|
async #runAutoShake(
|
|
7428
7402
|
reason: "overflow" | "threshold" | "idle" | "incomplete",
|
|
7429
|
-
strategy: "shake" | "shake-summary",
|
|
7430
7403
|
willRetry: boolean,
|
|
7431
7404
|
generation: number,
|
|
7405
|
+
autoContinue: boolean,
|
|
7432
7406
|
): Promise<"handled" | "fallback"> {
|
|
7433
|
-
const action =
|
|
7434
|
-
const mode = strategy === "shake-summary" ? "summary" : "elide";
|
|
7407
|
+
const action = "shake";
|
|
7435
7408
|
await this.#emitSessionEvent({ type: "auto_compaction_start", reason, action });
|
|
7436
7409
|
this.#autoCompactionAbortController?.abort();
|
|
7437
7410
|
const controller = new AbortController();
|
|
7438
7411
|
this.#autoCompactionAbortController = controller;
|
|
7439
7412
|
const signal = controller.signal;
|
|
7440
|
-
const compactionSettings = this.settings.getGroup("compaction");
|
|
7441
7413
|
try {
|
|
7442
|
-
const result = await this.shake(
|
|
7414
|
+
const result = await this.shake("elide", { config: DEFAULT_SHAKE_CONFIG, signal });
|
|
7443
7415
|
if (signal.aborted) {
|
|
7444
7416
|
await this.#emitSessionEvent({
|
|
7445
7417
|
type: "auto_compaction_end",
|
|
@@ -7473,7 +7445,7 @@ export class AgentSession {
|
|
|
7473
7445
|
skipped: !reclaimed,
|
|
7474
7446
|
});
|
|
7475
7447
|
|
|
7476
|
-
if (!willRetry && reason !== "idle" &&
|
|
7448
|
+
if (!willRetry && reason !== "idle" && autoContinue) {
|
|
7477
7449
|
this.#scheduleAutoContinuePrompt(generation);
|
|
7478
7450
|
}
|
|
7479
7451
|
if (willRetry) {
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/** Mode selector for `AgentSession.shake`. */
|
|
9
|
-
export type ShakeMode = "elide" | "
|
|
9
|
+
export type ShakeMode = "elide" | "images";
|
|
10
10
|
|
|
11
11
|
/** Outcome of an `AgentSession.shake` run. */
|
|
12
12
|
export interface ShakeResult {
|
|
13
13
|
mode: ShakeMode;
|
|
14
|
-
/** Whole tool-call results dropped
|
|
14
|
+
/** Whole tool-call results dropped. */
|
|
15
15
|
toolResultsDropped: number;
|
|
16
|
-
/** Large fenced/XML blocks dropped
|
|
16
|
+
/** Large fenced/XML blocks dropped. */
|
|
17
17
|
blocksDropped: number;
|
|
18
18
|
/** Image blocks removed (images mode only). */
|
|
19
19
|
imagesDropped?: number;
|
|
@@ -39,6 +39,5 @@ export function formatShakeSummary(result: ShakeResult): string {
|
|
|
39
39
|
parts.push(`${result.blocksDropped} block${result.blocksDropped === 1 ? "" : "s"}`);
|
|
40
40
|
}
|
|
41
41
|
if (parts.length === 0) return "Nothing to shake.";
|
|
42
|
-
|
|
43
|
-
return `${verb} ${parts.join(" + ")} (~${result.tokensFreed} tokens freed).`;
|
|
42
|
+
return `Shook ${parts.join(" + ")} (~${result.tokensFreed} tokens freed).`;
|
|
44
43
|
}
|