pi-agent-flow 1.8.40 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -37
- package/agents/audit.md +21 -22
- package/agents/build.md +23 -22
- package/agents/craft.md +23 -27
- package/agents/debug.md +24 -28
- package/agents/ideas.md +21 -101
- package/agents/scout.md +20 -19
- package/dist/batch/batch-bash.d.ts +2 -2
- package/dist/batch/batch-bash.d.ts.map +1 -1
- package/dist/batch/batch-bash.js +30 -7
- package/dist/batch/batch-bash.js.map +1 -1
- package/dist/batch/constants.d.ts +31 -5
- package/dist/batch/constants.d.ts.map +1 -1
- package/dist/batch/constants.js +50 -3
- package/dist/batch/constants.js.map +1 -1
- package/dist/batch/execute.d.ts +0 -1
- package/dist/batch/execute.d.ts.map +1 -1
- package/dist/batch/execute.js +210 -6
- package/dist/batch/execute.js.map +1 -1
- package/dist/batch/fuzzy-edit.d.ts +0 -6
- package/dist/batch/fuzzy-edit.d.ts.map +1 -1
- package/dist/batch/fuzzy-edit.js +1 -1
- package/dist/batch/fuzzy-edit.js.map +1 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +87 -16
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/render.d.ts +0 -1
- package/dist/batch/render.d.ts.map +1 -1
- package/dist/batch/render.js +7 -101
- package/dist/batch/render.js.map +1 -1
- package/dist/batch/shell-compress.d.ts +25 -0
- package/dist/batch/shell-compress.d.ts.map +1 -0
- package/dist/batch/shell-compress.js +602 -0
- package/dist/batch/shell-compress.js.map +1 -0
- package/dist/batch/summary.d.ts +5 -0
- package/dist/batch/summary.d.ts.map +1 -0
- package/dist/batch/summary.js +101 -0
- package/dist/batch/summary.js.map +1 -0
- package/dist/batch/symbols.d.ts.map +1 -1
- package/dist/batch/symbols.js +12 -7
- package/dist/batch/symbols.js.map +1 -1
- package/dist/{config.d.ts → config/config.d.ts} +39 -2
- package/dist/config/config.d.ts.map +1 -0
- package/dist/{config.js → config/config.js} +220 -9
- package/dist/config/config.js.map +1 -0
- package/dist/config/log.d.ts +27 -0
- package/dist/config/log.d.ts.map +1 -0
- package/dist/config/log.js +104 -0
- package/dist/config/log.js.map +1 -0
- package/dist/config/models.d.ts +2 -0
- package/dist/config/models.d.ts.map +1 -0
- package/dist/config/models.js +49 -0
- package/dist/config/models.js.map +1 -0
- package/dist/{settings-resolver.d.ts → config/settings-resolver.d.ts} +9 -2
- package/dist/config/settings-resolver.d.ts.map +1 -0
- package/dist/config/settings-resolver.js +275 -0
- package/dist/config/settings-resolver.js.map +1 -0
- package/dist/core/agents.d.ts.map +1 -0
- package/dist/{agents.js → core/agents.js} +13 -12
- package/dist/core/agents.js.map +1 -0
- package/dist/core/delegation.d.ts +24 -0
- package/dist/core/delegation.d.ts.map +1 -0
- package/dist/core/delegation.js +48 -0
- package/dist/core/delegation.js.map +1 -0
- package/dist/core/depth.d.ts.map +1 -0
- package/dist/{depth.js → core/depth.js} +9 -8
- package/dist/core/depth.js.map +1 -0
- package/dist/{executor.d.ts → core/executor.d.ts} +18 -3
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/{executor.js → core/executor.js} +53 -14
- package/dist/core/executor.js.map +1 -0
- package/dist/{flow.d.ts → core/flow.d.ts} +13 -1
- package/dist/core/flow.d.ts.map +1 -0
- package/dist/{flow.js → core/flow.js} +125 -64
- package/dist/core/flow.js.map +1 -0
- package/dist/{session-mode.d.ts → core/session-mode.d.ts} +2 -1
- package/dist/core/session-mode.d.ts.map +1 -0
- package/dist/{session-mode.js → core/session-mode.js} +2 -1
- package/dist/core/session-mode.js.map +1 -0
- package/dist/core/session-registry.d.ts +16 -0
- package/dist/core/session-registry.d.ts.map +1 -0
- package/dist/core/session-registry.js +30 -0
- package/dist/core/session-registry.js.map +1 -0
- package/dist/core/transitions.d.ts.map +1 -0
- package/dist/{transitions.js → core/transitions.js} +1 -1
- package/dist/core/transitions.js.map +1 -0
- package/dist/flow/auto-warp.d.ts +12 -0
- package/dist/flow/auto-warp.d.ts.map +1 -0
- package/dist/flow/auto-warp.js +29 -0
- package/dist/flow/auto-warp.js.map +1 -0
- package/dist/flow/command.d.ts +8 -0
- package/dist/flow/command.d.ts.map +1 -0
- package/dist/flow/command.js +194 -0
- package/dist/flow/command.js.map +1 -0
- package/dist/flow/continuation.d.ts +16 -0
- package/dist/flow/continuation.d.ts.map +1 -0
- package/dist/flow/continuation.js +188 -0
- package/dist/flow/continuation.js.map +1 -0
- package/dist/flow/index.d.ts +18 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/flow/index.js +25 -0
- package/dist/flow/index.js.map +1 -0
- package/dist/flow/loop-command.d.ts +8 -0
- package/dist/flow/loop-command.d.ts.map +1 -0
- package/dist/flow/loop-command.js +99 -0
- package/dist/flow/loop-command.js.map +1 -0
- package/dist/flow/loop-templates.d.ts +7 -0
- package/dist/flow/loop-templates.d.ts.map +1 -0
- package/dist/flow/loop-templates.js +38 -0
- package/dist/flow/loop-templates.js.map +1 -0
- package/dist/flow/loop.d.ts +19 -0
- package/dist/flow/loop.d.ts.map +1 -0
- package/dist/flow/loop.js +95 -0
- package/dist/flow/loop.js.map +1 -0
- package/dist/flow/perform-warp.d.ts +28 -0
- package/dist/flow/perform-warp.d.ts.map +1 -0
- package/dist/flow/perform-warp.js +127 -0
- package/dist/flow/perform-warp.js.map +1 -0
- package/dist/flow/settings-command.d.ts +51 -0
- package/dist/flow/settings-command.d.ts.map +1 -0
- package/dist/flow/settings-command.js +937 -0
- package/dist/flow/settings-command.js.map +1 -0
- package/dist/flow/store.d.ts +26 -0
- package/dist/flow/store.d.ts.map +1 -0
- package/dist/flow/store.js +166 -0
- package/dist/flow/store.js.map +1 -0
- package/dist/flow/template-shared.d.ts +9 -0
- package/dist/flow/template-shared.d.ts.map +1 -0
- package/dist/flow/template-shared.js +13 -0
- package/dist/flow/template-shared.js.map +1 -0
- package/dist/flow/template-strings.d.ts +8 -0
- package/dist/flow/template-strings.d.ts.map +1 -0
- package/dist/flow/template-strings.js +36 -0
- package/dist/flow/template-strings.js.map +1 -0
- package/dist/flow/types.d.ts +61 -0
- package/dist/flow/types.d.ts.map +1 -0
- package/dist/flow/types.js +5 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/flow/warp-command.d.ts +8 -0
- package/dist/flow/warp-command.d.ts.map +1 -0
- package/dist/flow/warp-command.js +144 -0
- package/dist/flow/warp-command.js.map +1 -0
- package/dist/flow/warp-utils.d.ts +11 -0
- package/dist/flow/warp-utils.d.ts.map +1 -0
- package/dist/flow/warp-utils.js +187 -0
- package/dist/flow/warp-utils.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +115 -31
- package/dist/index.js.map +1 -1
- package/dist/{notify-state.d.ts → notify/notify-state.d.ts} +2 -1
- package/dist/notify/notify-state.d.ts.map +1 -0
- package/dist/notify/notify-state.js.map +1 -0
- package/dist/notify/notify.d.ts.map +1 -0
- package/dist/{notify.js → notify/notify.js} +3 -2
- package/dist/notify/notify.js.map +1 -0
- package/dist/{cli-args.d.ts → snapshot/cli-args.d.ts} +4 -2
- package/dist/snapshot/cli-args.d.ts.map +1 -0
- package/dist/{cli-args.js → snapshot/cli-args.js} +13 -1
- package/dist/snapshot/cli-args.js.map +1 -0
- package/dist/snapshot/index.d.ts +2 -0
- package/dist/snapshot/index.d.ts.map +1 -0
- package/dist/snapshot/index.js +2 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/{reasoning-strip.d.ts → snapshot/reasoning-strip.d.ts} +0 -4
- package/dist/snapshot/reasoning-strip.d.ts.map +1 -0
- package/dist/{reasoning-strip.js → snapshot/reasoning-strip.js} +2 -2
- package/dist/snapshot/reasoning-strip.js.map +1 -0
- package/dist/snapshot/runner-events.d.ts.map +1 -0
- package/dist/{runner-events.js → snapshot/runner-events.js} +1 -1
- package/dist/snapshot/runner-events.js.map +1 -0
- package/dist/{snapshot.d.ts → snapshot/snapshot.d.ts} +24 -18
- package/dist/snapshot/snapshot.d.ts.map +1 -0
- package/dist/snapshot/snapshot.js +1791 -0
- package/dist/snapshot/snapshot.js.map +1 -0
- package/dist/{structured-output.d.ts → snapshot/structured-output.d.ts} +1 -1
- package/dist/snapshot/structured-output.d.ts.map +1 -0
- package/dist/snapshot/structured-output.js.map +1 -0
- package/dist/{flow-prompt.d.ts → steering/flow-prompt.d.ts} +2 -2
- package/dist/steering/flow-prompt.d.ts.map +1 -0
- package/dist/{flow-prompt.js → steering/flow-prompt.js} +1 -1
- package/dist/steering/flow-prompt.js.map +1 -0
- package/dist/{sliding-prompt.d.ts → steering/sliding-prompt.d.ts} +8 -7
- package/dist/steering/sliding-prompt.d.ts.map +1 -0
- package/dist/{sliding-prompt.js → steering/sliding-prompt.js} +18 -64
- package/dist/steering/sliding-prompt.js.map +1 -0
- package/dist/{tool-utils.d.ts → steering/tool-utils.d.ts} +1 -0
- package/dist/steering/tool-utils.d.ts.map +1 -0
- package/dist/{tool-utils.js → steering/tool-utils.js} +10 -3
- package/dist/steering/tool-utils.js.map +1 -0
- package/dist/{ask-user.d.ts → tools/ask-user.d.ts} +3 -15
- package/dist/tools/ask-user.d.ts.map +1 -0
- package/dist/tools/ask-user.js +778 -0
- package/dist/tools/ask-user.js.map +1 -0
- package/dist/{timed-bash.d.ts → tools/timed-bash.d.ts} +2 -7
- package/dist/tools/timed-bash.d.ts.map +1 -0
- package/dist/{timed-bash.js → tools/timed-bash.js} +11 -2
- package/dist/tools/timed-bash.js.map +1 -0
- package/dist/{web-tool.d.ts → tools/web-tool.d.ts} +1 -1
- package/dist/tools/web-tool.d.ts.map +1 -0
- package/dist/{web-tool.js → tools/web-tool.js} +8 -7
- package/dist/tools/web-tool.js.map +1 -0
- package/dist/tui/flow-colors.d.ts +55 -0
- package/dist/tui/flow-colors.d.ts.map +1 -0
- package/dist/tui/flow-colors.js +22 -0
- package/dist/tui/flow-colors.js.map +1 -0
- package/dist/{render-utils.d.ts → tui/render-utils.d.ts} +6 -2
- package/dist/tui/render-utils.d.ts.map +1 -0
- package/dist/{render-utils.js → tui/render-utils.js} +40 -12
- package/dist/tui/render-utils.js.map +1 -0
- package/dist/tui/render.d.ts +21 -0
- package/dist/tui/render.d.ts.map +1 -0
- package/dist/tui/render.js +786 -0
- package/dist/tui/render.js.map +1 -0
- package/dist/tui/scramble/algorithm.d.ts +7 -0
- package/dist/tui/scramble/algorithm.d.ts.map +1 -0
- package/dist/tui/scramble/algorithm.js +227 -0
- package/dist/tui/scramble/algorithm.js.map +1 -0
- package/dist/tui/scramble/constants.d.ts +99 -0
- package/dist/tui/scramble/constants.d.ts.map +1 -0
- package/dist/tui/scramble/constants.js +101 -0
- package/dist/tui/scramble/constants.js.map +1 -0
- package/dist/tui/scramble/index.d.ts +6 -0
- package/dist/tui/scramble/index.d.ts.map +1 -0
- package/dist/tui/scramble/index.js +6 -0
- package/dist/tui/scramble/index.js.map +1 -0
- package/dist/tui/scramble/manager.d.ts +44 -0
- package/dist/tui/scramble/manager.d.ts.map +1 -0
- package/dist/tui/scramble/manager.js +899 -0
- package/dist/tui/scramble/manager.js.map +1 -0
- package/dist/tui/scramble/utils.d.ts +18 -0
- package/dist/tui/scramble/utils.d.ts.map +1 -0
- package/dist/tui/scramble/utils.js +145 -0
- package/dist/tui/scramble/utils.js.map +1 -0
- package/dist/tui/single-select-layout.d.ts +17 -0
- package/dist/tui/single-select-layout.d.ts.map +1 -0
- package/dist/{single-select-layout.js → tui/single-select-layout.js} +8 -25
- package/dist/tui/single-select-layout.js.map +1 -0
- package/dist/types/flow.d.ts +112 -0
- package/dist/types/flow.d.ts.map +1 -0
- package/dist/{types.js → types/flow.js} +3 -54
- package/dist/types/flow.js.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/output.d.ts +110 -0
- package/dist/types/output.d.ts.map +1 -0
- package/dist/types/output.js +5 -0
- package/dist/types/output.js.map +1 -0
- package/dist/types/ui.d.ts +24 -0
- package/dist/types/ui.d.ts.map +1 -0
- package/dist/types/ui.js +55 -0
- package/dist/types/ui.js.map +1 -0
- package/package.json +1 -1
- package/dist/agents.d.ts.map +0 -1
- package/dist/agents.js.map +0 -1
- package/dist/ask-user.d.ts.map +0 -1
- package/dist/ask-user.js +0 -1405
- package/dist/ask-user.js.map +0 -1
- package/dist/batch.d.ts +0 -12
- package/dist/batch.d.ts.map +0 -1
- package/dist/batch.js +0 -11
- package/dist/batch.js.map +0 -1
- package/dist/cli-args.d.ts.map +0 -1
- package/dist/cli-args.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/depth.d.ts.map +0 -1
- package/dist/depth.js.map +0 -1
- package/dist/executor.d.ts.map +0 -1
- package/dist/executor.js.map +0 -1
- package/dist/flow-prompt.d.ts.map +0 -1
- package/dist/flow-prompt.js.map +0 -1
- package/dist/flow.d.ts.map +0 -1
- package/dist/flow.js.map +0 -1
- package/dist/notify-state.d.ts.map +0 -1
- package/dist/notify-state.js.map +0 -1
- package/dist/notify.d.ts.map +0 -1
- package/dist/notify.js.map +0 -1
- package/dist/reasoning-strip.d.ts.map +0 -1
- package/dist/reasoning-strip.js.map +0 -1
- package/dist/render-utils.d.ts.map +0 -1
- package/dist/render-utils.js.map +0 -1
- package/dist/render.d.ts +0 -24
- package/dist/render.d.ts.map +0 -1
- package/dist/render.js +0 -592
- package/dist/render.js.map +0 -1
- package/dist/runner-events.d.ts.map +0 -1
- package/dist/runner-events.js.map +0 -1
- package/dist/scramble.d.ts +0 -183
- package/dist/scramble.d.ts.map +0 -1
- package/dist/scramble.js +0 -2478
- package/dist/scramble.js.map +0 -1
- package/dist/session-mode.d.ts.map +0 -1
- package/dist/session-mode.js.map +0 -1
- package/dist/settings-resolver.d.ts.map +0 -1
- package/dist/settings-resolver.js +0 -148
- package/dist/settings-resolver.js.map +0 -1
- package/dist/single-select-layout.d.ts +0 -20
- package/dist/single-select-layout.d.ts.map +0 -1
- package/dist/single-select-layout.js.map +0 -1
- package/dist/sliding-prompt.d.ts.map +0 -1
- package/dist/sliding-prompt.js.map +0 -1
- package/dist/snapshot.d.ts.map +0 -1
- package/dist/snapshot.js +0 -797
- package/dist/snapshot.js.map +0 -1
- package/dist/spec-mode.d.ts +0 -13
- package/dist/spec-mode.d.ts.map +0 -1
- package/dist/spec-mode.js +0 -90
- package/dist/spec-mode.js.map +0 -1
- package/dist/structured-output.d.ts.map +0 -1
- package/dist/structured-output.js.map +0 -1
- package/dist/timed-bash.d.ts.map +0 -1
- package/dist/timed-bash.js.map +0 -1
- package/dist/tool-utils.d.ts.map +0 -1
- package/dist/tool-utils.js.map +0 -1
- package/dist/transitions.d.ts.map +0 -1
- package/dist/transitions.js.map +0 -1
- package/dist/types.d.ts +0 -224
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/web-tool.d.ts.map +0 -1
- package/dist/web-tool.js.map +0 -1
- /package/dist/{agents.d.ts → core/agents.d.ts} +0 -0
- /package/dist/{depth.d.ts → core/depth.d.ts} +0 -0
- /package/dist/{transitions.d.ts → core/transitions.d.ts} +0 -0
- /package/dist/{notify-state.js → notify/notify-state.js} +0 -0
- /package/dist/{notify.d.ts → notify/notify.d.ts} +0 -0
- /package/dist/{runner-events.d.ts → snapshot/runner-events.d.ts} +0 -0
- /package/dist/{structured-output.js → snapshot/structured-output.js} +0 -0
package/dist/snapshot.js
DELETED
|
@@ -1,797 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Two JSONL protocols are used in this codebase:
|
|
3
|
-
*
|
|
4
|
-
* 1. Fork Snapshot Protocol (snapshot.ts):
|
|
5
|
-
* Types: session, model_change, thinking_level_change, system, message,
|
|
6
|
-
* compression-stats
|
|
7
|
-
* Purpose: Serialized session state passed to child flows via --session.
|
|
8
|
-
* Emitted by buildForkSessionSnapshotJsonl() and consumed by
|
|
9
|
-
* sanitizeForkSnapshot() before forking.
|
|
10
|
-
*
|
|
11
|
-
* 2. Streaming Stdout Protocol (runner-events.ts):
|
|
12
|
-
* Types: session, agent_start, turn_start, message_start, message_end,
|
|
13
|
-
* message_update
|
|
14
|
-
* Sub-events under message_update: thinking_start, thinking_delta, text_delta
|
|
15
|
-
* Purpose: Real-time events emitted by the pi process stdout during flow
|
|
16
|
-
* execution. Parsed by processFlowJsonLine().
|
|
17
|
-
*/
|
|
18
|
-
import { stripReasoningFromAssistantMessage } from "./reasoning-strip.js";
|
|
19
|
-
import { stripSteeringHintFromContent, contentContainsSteeringHintTag, isJsonEqual, } from "./sliding-prompt.js";
|
|
20
|
-
import { stripStrategicHintsFromContent } from "./tool-utils.js";
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Session snapshot serialization
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
export function buildForkSessionSnapshotJsonl(sessionManager) {
|
|
25
|
-
const header = sessionManager.getHeader();
|
|
26
|
-
if (!header || typeof header !== "object")
|
|
27
|
-
return null;
|
|
28
|
-
const branchEntries = sessionManager.getBranch();
|
|
29
|
-
const lines = [];
|
|
30
|
-
// Emit session header once, unless getBranch() already includes it as the
|
|
31
|
-
// first entry (some session managers include the header in the branch).
|
|
32
|
-
const firstBranch = branchEntries[0];
|
|
33
|
-
const headerId = header?.id;
|
|
34
|
-
const firstId = firstBranch && typeof firstBranch === "object" ? firstBranch?.id : undefined;
|
|
35
|
-
const firstType = firstBranch && typeof firstBranch === "object" ? firstBranch?.type : undefined;
|
|
36
|
-
if (!firstBranch ||
|
|
37
|
-
typeof firstBranch !== "object" ||
|
|
38
|
-
(firstType !== "session" && firstType !== "header") ||
|
|
39
|
-
firstId !== headerId) {
|
|
40
|
-
lines.push(JSON.stringify(header));
|
|
41
|
-
}
|
|
42
|
-
// Emit system event so the JSONL is self-contained — parsers can reconstruct
|
|
43
|
-
// full context without needing the markdown section.
|
|
44
|
-
const systemPrompt = header.systemPrompt;
|
|
45
|
-
if (typeof systemPrompt === "string" && systemPrompt) {
|
|
46
|
-
lines.push(JSON.stringify({ type: "system", content: systemPrompt }));
|
|
47
|
-
}
|
|
48
|
-
for (const entry of branchEntries)
|
|
49
|
-
lines.push(JSON.stringify(entry));
|
|
50
|
-
return `${lines.join("\n")}\n`;
|
|
51
|
-
}
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// Flow result compression
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
/**
|
|
56
|
-
* Render a compressed flow result as compact text for child context.
|
|
57
|
-
*/
|
|
58
|
-
export function renderCompressedFlowResult(r) {
|
|
59
|
-
const parts = [`[Flow: ${r.type} ${r.status}]`];
|
|
60
|
-
if (r.intent)
|
|
61
|
-
parts.push(`Intent: ${r.intent}`);
|
|
62
|
-
if (r.aim)
|
|
63
|
-
parts.push(`Aim: ${r.aim}`);
|
|
64
|
-
if (r.summary)
|
|
65
|
-
parts.push(`Summary: ${r.summary}`);
|
|
66
|
-
if (r.files?.length) {
|
|
67
|
-
const fileLines = r.files
|
|
68
|
-
.map((f) => {
|
|
69
|
-
if (!f.path)
|
|
70
|
-
return undefined;
|
|
71
|
-
const role = f.role ? ` (${f.role})` : "";
|
|
72
|
-
const desc = f.description ? ` — ${f.description}` : "";
|
|
73
|
-
return ` ${f.path}${role}${desc}`;
|
|
74
|
-
})
|
|
75
|
-
.filter((line) => line !== undefined);
|
|
76
|
-
// Safety net: if >50% of file entries were invalid (no path), compression is
|
|
77
|
-
// producing garbage. Return undefined so caller falls back to truncated raw.
|
|
78
|
-
if (fileLines.length === 0 || fileLines.length < r.files.length / 2) {
|
|
79
|
-
return undefined;
|
|
80
|
-
}
|
|
81
|
-
parts.push(`Files:\n${fileLines.join("\n")}`);
|
|
82
|
-
}
|
|
83
|
-
if (r.actions?.length) {
|
|
84
|
-
const actionLines = r.actions.map((a) => {
|
|
85
|
-
const result = a.result ? ` → ${a.result}` : "";
|
|
86
|
-
const target = a.target ? ` (${a.target})` : "";
|
|
87
|
-
return ` [${a.type}] ${a.description}${target}${result}`;
|
|
88
|
-
});
|
|
89
|
-
parts.push(`Actions:\n${actionLines.join("\n")}`);
|
|
90
|
-
}
|
|
91
|
-
if (r.commands?.length) {
|
|
92
|
-
const cmdLines = r.commands.map((c) => ` ${c.tool ?? "cmd"}: ${c.command}`);
|
|
93
|
-
parts.push(`Commands:\n${cmdLines.join("\n")}`);
|
|
94
|
-
}
|
|
95
|
-
if (r.notDone?.length) {
|
|
96
|
-
const ndLines = r.notDone.map((n) => {
|
|
97
|
-
const reason = n.reason ? ` — ${n.reason}` : "";
|
|
98
|
-
return ` ${n.item}${reason}`;
|
|
99
|
-
});
|
|
100
|
-
parts.push(`Not done:\n${ndLines.join("\n")}`);
|
|
101
|
-
}
|
|
102
|
-
if (r.nextSteps?.length) {
|
|
103
|
-
parts.push(`Next steps:\n${r.nextSteps.map((s) => ` ${s}`).join("\n")}`);
|
|
104
|
-
}
|
|
105
|
-
if (r.reasoning?.length) {
|
|
106
|
-
parts.push(`Reasoning:\n${r.reasoning.map((s) => ` ${s}`).join("\n")}`);
|
|
107
|
-
}
|
|
108
|
-
if (r.notes?.length) {
|
|
109
|
-
parts.push(`Notes:\n${r.notes.map((s) => ` ${s}`).join("\n")}`);
|
|
110
|
-
}
|
|
111
|
-
if (r.error)
|
|
112
|
-
parts.push(`Error: ${r.error}`);
|
|
113
|
-
const text = parts.join("\n");
|
|
114
|
-
if (text.includes("undefined"))
|
|
115
|
-
return undefined;
|
|
116
|
-
return text;
|
|
117
|
-
}
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
// batch_read result compression
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
/**
|
|
122
|
-
* Extract file paths from a batch_read tool call's arguments.
|
|
123
|
-
* Handles both { o: [...] } and bare array argument formats.
|
|
124
|
-
*/
|
|
125
|
-
function extractBatchReadPaths(args) {
|
|
126
|
-
if (!args || typeof args !== "object")
|
|
127
|
-
return [];
|
|
128
|
-
let ops;
|
|
129
|
-
if (Array.isArray(args)) {
|
|
130
|
-
ops = args;
|
|
131
|
-
}
|
|
132
|
-
else if (Array.isArray(args.o)) {
|
|
133
|
-
ops = args.o;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
return [];
|
|
137
|
-
}
|
|
138
|
-
const paths = [];
|
|
139
|
-
for (const op of ops) {
|
|
140
|
-
if (!op || typeof op !== "object")
|
|
141
|
-
continue;
|
|
142
|
-
const p = op.p;
|
|
143
|
-
if (typeof p === "string" && p)
|
|
144
|
-
paths.push(p);
|
|
145
|
-
}
|
|
146
|
-
return paths;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Render a compressed batch_read result as compact metadata for child context.
|
|
150
|
-
* Format: [batch_read] N ops → paths: file1.ts, file2.ts, …
|
|
151
|
-
*/
|
|
152
|
-
function renderCompressedBatchReadResult(paths) {
|
|
153
|
-
const MAX_PATHS_DISPLAY = 10;
|
|
154
|
-
const display = paths.slice(0, MAX_PATHS_DISPLAY);
|
|
155
|
-
const suffix = paths.length > MAX_PATHS_DISPLAY ? `, … +${paths.length - MAX_PATHS_DISPLAY} more` : "";
|
|
156
|
-
return `[batch_read] ${paths.length} ops → paths: ${display.join(", ")}${suffix}`;
|
|
157
|
-
}
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
159
|
-
// Additional tool result compressors
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
const DEBUG_CONTEXT = typeof process !== "undefined" && process.env.PI_FLOW_DEBUG_CONTEXT === "1";
|
|
162
|
-
function logCompress(toolName, before, after) {
|
|
163
|
-
if (!DEBUG_CONTEXT)
|
|
164
|
-
return;
|
|
165
|
-
const reduction = before > 0 ? ((1 - after / before) * 100).toFixed(0) : "0";
|
|
166
|
-
console.error(`[context-compress] ${toolName}: ${before} → ${after} bytes (${reduction}% reduction)`);
|
|
167
|
-
}
|
|
168
|
-
const KNOWN_SECTION_HEADERS = [
|
|
169
|
-
/^--- (.+) \((\d+) lines\) ---$/,
|
|
170
|
-
/^--- (.+) (context map|file summary) ---$/,
|
|
171
|
-
/^--- bash \[.+\] exit (\d+) ---$/,
|
|
172
|
-
/^--- edit: .+ ---$/,
|
|
173
|
-
/^--- write: .+ ---$/,
|
|
174
|
-
/^--- delete: .+ ---$/,
|
|
175
|
-
/^--- read: .+ ---$/,
|
|
176
|
-
/^--- (?!bash \[|edit:|write:|delete:|read:)(.+) ---$/,
|
|
177
|
-
];
|
|
178
|
-
function isKnownSectionHeader(line) {
|
|
179
|
-
return KNOWN_SECTION_HEADERS.some((re) => re.test(line));
|
|
180
|
-
}
|
|
181
|
-
/** Compress batch tool result: keep bash sections verbatim, truncate read content. */
|
|
182
|
-
function compressBatchResult(text) {
|
|
183
|
-
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
184
|
-
const out = [];
|
|
185
|
-
let i = 0;
|
|
186
|
-
while (i < lines.length) {
|
|
187
|
-
const line = lines[i];
|
|
188
|
-
// File read section with content — truncate
|
|
189
|
-
const readMatch = line.match(/^--- (.+) \((\d+) lines\) ---$/);
|
|
190
|
-
if (readMatch) {
|
|
191
|
-
out.push(`--- ${readMatch[1]} (${readMatch[2]} lines, content truncated) ---`);
|
|
192
|
-
i++;
|
|
193
|
-
while (i < lines.length && !isKnownSectionHeader(lines[i])) {
|
|
194
|
-
i++;
|
|
195
|
-
}
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
// Context map / file summary section — truncate
|
|
199
|
-
const ctxMapMatch = line.match(/^--- (.+) (context map|file summary) ---$/);
|
|
200
|
-
if (ctxMapMatch) {
|
|
201
|
-
out.push(`--- ${ctxMapMatch[1]} (${ctxMapMatch[2]}, truncated) ---`);
|
|
202
|
-
i++;
|
|
203
|
-
while (i < lines.length && !isKnownSectionHeader(lines[i])) {
|
|
204
|
-
i++;
|
|
205
|
-
}
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
// File read without line count — truncate
|
|
209
|
-
// Negative lookahead excludes bash/edit/write/delete/read-error sections that should be kept verbatim
|
|
210
|
-
const fallbackReadMatch = line.match(/^--- (?!bash \[|edit:|write:|delete:|read:)(.+) ---$/);
|
|
211
|
-
if (fallbackReadMatch) {
|
|
212
|
-
out.push(`--- ${fallbackReadMatch[1]} (content truncated) ---`);
|
|
213
|
-
i++;
|
|
214
|
-
while (i < lines.length && !isKnownSectionHeader(lines[i])) {
|
|
215
|
-
i++;
|
|
216
|
-
}
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
// Everything else (bash, edit, write, delete, error, summary) — keep as-is
|
|
220
|
-
out.push(line);
|
|
221
|
-
i++;
|
|
222
|
-
}
|
|
223
|
-
return out.join("\n");
|
|
224
|
-
}
|
|
225
|
-
/** Compress web tool result into compact metadata. */
|
|
226
|
-
function compressWebResult(text, args) {
|
|
227
|
-
// Try to extract query/url from args
|
|
228
|
-
let query;
|
|
229
|
-
let url;
|
|
230
|
-
if (args && typeof args === "object") {
|
|
231
|
-
const a = args;
|
|
232
|
-
const ops = Array.isArray(a.o) ? a.o : Array.isArray(a.op) ? a.op : undefined;
|
|
233
|
-
if (ops && ops.length > 0) {
|
|
234
|
-
const firstOp = ops[0];
|
|
235
|
-
query = typeof firstOp.q === "string" ? firstOp.q : undefined;
|
|
236
|
-
url = typeof firstOp.u === "string" ? firstOp.u : undefined;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
// Search result format: numbered list
|
|
240
|
-
if (text.match(/^\d+\. .+\n https?:\/\//m)) {
|
|
241
|
-
const lines = text.split("\n\n");
|
|
242
|
-
const count = lines.length;
|
|
243
|
-
const firstTitle = lines[0]?.match(/^\d+\. (.+)\n/)?.[1] ?? "unknown";
|
|
244
|
-
const q = query ? ` "${query}"` : "";
|
|
245
|
-
return `[web:search]${q} · ${count} results · first: ${firstTitle}`;
|
|
246
|
-
}
|
|
247
|
-
// Fetch result format: File/Title/Content length/Preview
|
|
248
|
-
const fileMatch = text.match(/^File: (.+)\n/m);
|
|
249
|
-
const titleMatch = text.match(/^Title: (.+)\n/m);
|
|
250
|
-
const lengthMatch = text.match(/^Content length: (\d+) chars\n/m);
|
|
251
|
-
if (fileMatch || titleMatch || lengthMatch || url) {
|
|
252
|
-
const file = url ?? fileMatch?.[1] ?? "";
|
|
253
|
-
const title = titleMatch?.[1] ?? "";
|
|
254
|
-
const length = lengthMatch?.[1] ?? "0";
|
|
255
|
-
return `[web:fetch] ${file} · "${title}" · ${length} chars`;
|
|
256
|
-
}
|
|
257
|
-
return `[web] result truncated (${text.length} chars)`;
|
|
258
|
-
}
|
|
259
|
-
/** Compress ask_user tool result into compact metadata. */
|
|
260
|
-
function compressAskUserResult(text, args) {
|
|
261
|
-
let question = "";
|
|
262
|
-
if (args && typeof args === "object") {
|
|
263
|
-
const q = args.question;
|
|
264
|
-
if (typeof q === "string") {
|
|
265
|
-
question = q.length > 80 ? q.slice(0, 77) + "..." : q;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
const answeredMatch = text.match(/^User answered: (.+)$/ms);
|
|
269
|
-
if (answeredMatch) {
|
|
270
|
-
const q = question ? ` "${question}"` : "";
|
|
271
|
-
return `[ask_user]${q} → "${answeredMatch[1]}"`;
|
|
272
|
-
}
|
|
273
|
-
if (text.match(/^User cancelled/m)) {
|
|
274
|
-
const q = question ? ` "${question}"` : "";
|
|
275
|
-
return `[ask_user]${q} → cancelled`;
|
|
276
|
-
}
|
|
277
|
-
return `[ask_user] · ${text.length} chars`;
|
|
278
|
-
}
|
|
279
|
-
// ---------------------------------------------------------------------------
|
|
280
|
-
// Shared: toolCallId → toolName mapping
|
|
281
|
-
// ---------------------------------------------------------------------------
|
|
282
|
-
/**
|
|
283
|
-
* Build a map from toolCallId → toolName by scanning assistant messages.
|
|
284
|
-
*/
|
|
285
|
-
function buildToolCallIdToNameMap(lines) {
|
|
286
|
-
const map = new Map();
|
|
287
|
-
for (const line of lines) {
|
|
288
|
-
let entry;
|
|
289
|
-
try {
|
|
290
|
-
entry = JSON.parse(line);
|
|
291
|
-
}
|
|
292
|
-
catch {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
if (entry?.type !== "message" || entry.message?.role !== "assistant")
|
|
296
|
-
continue;
|
|
297
|
-
const content = entry.message.content;
|
|
298
|
-
if (!Array.isArray(content))
|
|
299
|
-
continue;
|
|
300
|
-
for (const part of content) {
|
|
301
|
-
if (part.type === "toolCall" && part.name) {
|
|
302
|
-
const tcId = part.id ?? part.toolCallId;
|
|
303
|
-
if (typeof tcId === "string" && tcId.trim()) {
|
|
304
|
-
map.set(tcId, part.name);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return map;
|
|
310
|
-
}
|
|
311
|
-
// ---------------------------------------------------------------------------
|
|
312
|
-
// Tool result compression (flow + batch_read)
|
|
313
|
-
// ---------------------------------------------------------------------------
|
|
314
|
-
/**
|
|
315
|
-
* Compress tool results in a sanitized session snapshot.
|
|
316
|
-
*
|
|
317
|
-
* Handles two tool types:
|
|
318
|
-
* - `flow` results: replaced with compact CompressedFlowResult output from cache.
|
|
319
|
-
* - `batch_read` results: replaced with compact metadata (paths + op count)
|
|
320
|
-
* since children have `batch` and can re-read files themselves.
|
|
321
|
-
*/
|
|
322
|
-
export function compressToolResults(snapshot, cache) {
|
|
323
|
-
const lines = snapshot.trimEnd().split("\n");
|
|
324
|
-
// Quick check: if there are no flow cache entries and no compressible tool calls,
|
|
325
|
-
// nothing to compress — return early.
|
|
326
|
-
if (cache.size === 0) {
|
|
327
|
-
const hasCompressible = lines.some((line) => {
|
|
328
|
-
try {
|
|
329
|
-
const entry = JSON.parse(line);
|
|
330
|
-
return entry?.type === "message" && entry.message?.role === "assistant" &&
|
|
331
|
-
Array.isArray(entry.message.content) &&
|
|
332
|
-
entry.message.content.some((p) => p.type === "toolCall" &&
|
|
333
|
-
["batch_read", "batch", "web", "ask_user"].includes(p.name));
|
|
334
|
-
}
|
|
335
|
-
catch {
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
const hasToolResultMessages = lines.some((line) => {
|
|
340
|
-
try {
|
|
341
|
-
const entry = JSON.parse(line);
|
|
342
|
-
return entry?.type === "message" &&
|
|
343
|
-
(entry.message?.role === "tool" || entry.message?.role === "toolResult");
|
|
344
|
-
}
|
|
345
|
-
catch {
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
// Must run the pass whenever tool results exist: we drop empty/whitespace
|
|
350
|
-
// toolCallIds and pass through bash/flow/etc. even when the cache is empty.
|
|
351
|
-
if (!hasCompressible && !hasToolResultMessages)
|
|
352
|
-
return snapshot;
|
|
353
|
-
}
|
|
354
|
-
// Build toolCallId → toolName mapping
|
|
355
|
-
const toolCallIdToName = buildToolCallIdToNameMap(lines);
|
|
356
|
-
// Build toolCallId → arguments mapping for all tools (needed for batch/web/ask_user metadata)
|
|
357
|
-
const toolCallIdToArgs = new Map();
|
|
358
|
-
for (const line of lines) {
|
|
359
|
-
let entry;
|
|
360
|
-
try {
|
|
361
|
-
entry = JSON.parse(line);
|
|
362
|
-
}
|
|
363
|
-
catch {
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
if (entry?.type !== "message" || entry.message?.role !== "assistant")
|
|
367
|
-
continue;
|
|
368
|
-
const content = entry.message.content;
|
|
369
|
-
if (!Array.isArray(content))
|
|
370
|
-
continue;
|
|
371
|
-
for (const part of content) {
|
|
372
|
-
if (part.type === "toolCall" && (part.id || part.toolCallId) && part.arguments) {
|
|
373
|
-
toolCallIdToArgs.set(part.id ?? part.toolCallId, part.arguments);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
const result = [];
|
|
378
|
-
// Second pass: compress matching tool results
|
|
379
|
-
for (const line of lines) {
|
|
380
|
-
let entry;
|
|
381
|
-
try {
|
|
382
|
-
entry = JSON.parse(line);
|
|
383
|
-
}
|
|
384
|
-
catch {
|
|
385
|
-
result.push(line);
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
if (entry?.type !== "message" || (entry.message?.role !== "tool" && entry.message?.role !== "toolResult")) {
|
|
389
|
-
result.push(line);
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
// Extract toolCallId — message-level or content-level toolResult.
|
|
393
|
-
// Drop only *explicit* empty/whitespace IDs (APIs reject those). Missing
|
|
394
|
-
// toolCallId is treated as legacy shape and passes through unchanged.
|
|
395
|
-
let toolCallId;
|
|
396
|
-
let invalidEmptyId = false;
|
|
397
|
-
if (typeof entry.message.toolCallId === "string") {
|
|
398
|
-
const v = entry.message.toolCallId;
|
|
399
|
-
if (!v.trim())
|
|
400
|
-
invalidEmptyId = true;
|
|
401
|
-
else
|
|
402
|
-
toolCallId = v;
|
|
403
|
-
}
|
|
404
|
-
else if (Array.isArray(entry.message.content)) {
|
|
405
|
-
for (const part of entry.message.content) {
|
|
406
|
-
if (part.type === "toolResult" && typeof part.toolCallId === "string") {
|
|
407
|
-
if (!part.toolCallId.trim()) {
|
|
408
|
-
invalidEmptyId = true;
|
|
409
|
-
break;
|
|
410
|
-
}
|
|
411
|
-
toolCallId = part.toolCallId;
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (invalidEmptyId)
|
|
417
|
-
continue;
|
|
418
|
-
if (!toolCallId) {
|
|
419
|
-
result.push(line);
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
const toolName = toolCallIdToName.get(toolCallId);
|
|
423
|
-
let rendered;
|
|
424
|
-
let originalText = "";
|
|
425
|
-
// --- Compress flow tool results ---
|
|
426
|
-
if (toolName === "flow") {
|
|
427
|
-
const compressed = cache.get(toolCallId);
|
|
428
|
-
if (!compressed || compressed.length === 0) {
|
|
429
|
-
// Cache miss (never populated or evicted) — do NOT pass megabytes of raw
|
|
430
|
-
// flow output verbatim into child context. Render a minimal placeholder.
|
|
431
|
-
originalText = extractToolResultText(entry) ?? "";
|
|
432
|
-
const rawContent = entry.message?.content;
|
|
433
|
-
const contentSize = rawContent
|
|
434
|
-
? (typeof rawContent === "string" ? rawContent.length : JSON.stringify(rawContent).length)
|
|
435
|
-
: 0;
|
|
436
|
-
const size = originalText.length || contentSize || line.length;
|
|
437
|
-
rendered = `[flow] prior result · ${size} chars (not cached or evicted)`;
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
const renderResults = compressed.map(renderCompressedFlowResult);
|
|
441
|
-
const hasAnyUndefined = renderResults.some(r => r === undefined);
|
|
442
|
-
if (hasAnyUndefined) {
|
|
443
|
-
// Safety net: compression produced garbage, fall back to truncated raw.
|
|
444
|
-
originalText = extractToolResultText(entry) ?? "";
|
|
445
|
-
const size = originalText.length;
|
|
446
|
-
rendered = size > 2000
|
|
447
|
-
? originalText.slice(0, 2000) + "\n[truncated]"
|
|
448
|
-
: originalText;
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
rendered = renderResults.filter((r) => r !== undefined).join("\n\n");
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
// Note: batch_read tool results are now compressed in stripBatchReadToolCalls
|
|
456
|
-
// before compressToolResults runs, so this branch is no longer needed.
|
|
457
|
-
// Kept as a no-op safety net for any edge cases.
|
|
458
|
-
else if (toolName === "batch_read") {
|
|
459
|
-
rendered = undefined; // handled upstream
|
|
460
|
-
}
|
|
461
|
-
// --- Compress batch tool results (selective: keep bash, truncate reads) ---
|
|
462
|
-
else if (toolName === "batch") {
|
|
463
|
-
originalText = extractToolResultText(entry) ?? "";
|
|
464
|
-
rendered = compressBatchResult(originalText);
|
|
465
|
-
}
|
|
466
|
-
// --- Compress web tool results ---
|
|
467
|
-
else if (toolName === "web") {
|
|
468
|
-
originalText = extractToolResultText(entry) ?? "";
|
|
469
|
-
const args = toolCallIdToArgs.get(toolCallId);
|
|
470
|
-
rendered = compressWebResult(originalText, args);
|
|
471
|
-
}
|
|
472
|
-
// --- Compress ask_user tool results ---
|
|
473
|
-
else if (toolName === "ask_user") {
|
|
474
|
-
originalText = extractToolResultText(entry) ?? "";
|
|
475
|
-
const args = toolCallIdToArgs.get(toolCallId);
|
|
476
|
-
rendered = compressAskUserResult(originalText, args);
|
|
477
|
-
}
|
|
478
|
-
if (rendered !== undefined) {
|
|
479
|
-
logCompress(toolName ?? "unknown", originalText.length || line.length, rendered.length);
|
|
480
|
-
// Strip the 'details' field which carries UI metadata that children don't need.
|
|
481
|
-
// This eliminates ~98% of payload bloat from flow tool results.
|
|
482
|
-
const { details, ...messageWithoutDetails } = entry.message;
|
|
483
|
-
if (typeof entry.message.toolCallId === "string") {
|
|
484
|
-
entry = {
|
|
485
|
-
...entry,
|
|
486
|
-
message: {
|
|
487
|
-
...messageWithoutDetails,
|
|
488
|
-
content: [{ type: "text", text: rendered }],
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
entry = {
|
|
494
|
-
...entry,
|
|
495
|
-
message: {
|
|
496
|
-
...messageWithoutDetails,
|
|
497
|
-
content: entry.message.content.map((part) => part.type === "toolResult" && part.toolCallId === toolCallId
|
|
498
|
-
? { ...part, content: rendered }
|
|
499
|
-
: part),
|
|
500
|
-
},
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
result.push(JSON.stringify(entry));
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
// Other tool results pass through unchanged
|
|
507
|
-
result.push(line);
|
|
508
|
-
}
|
|
509
|
-
return `${result.join("\n")}\n`;
|
|
510
|
-
}
|
|
511
|
-
/** Extract text content from a tool result entry for compression analysis. */
|
|
512
|
-
function extractToolResultText(entry) {
|
|
513
|
-
if (typeof entry.message?.content === "string") {
|
|
514
|
-
return entry.message.content;
|
|
515
|
-
}
|
|
516
|
-
if (Array.isArray(entry.message?.content)) {
|
|
517
|
-
for (const part of entry.message.content) {
|
|
518
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
519
|
-
return part.text;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
return undefined;
|
|
524
|
-
}
|
|
525
|
-
/**
|
|
526
|
-
* Backward-compatible alias for compressToolResults.
|
|
527
|
-
* @deprecated Use compressToolResults instead.
|
|
528
|
-
*/
|
|
529
|
-
export function compressFlowToolResults(snapshot, cache) {
|
|
530
|
-
return compressToolResults(snapshot, cache);
|
|
531
|
-
}
|
|
532
|
-
// ---------------------------------------------------------------------------
|
|
533
|
-
// batch_read tool call stripping
|
|
534
|
-
// ---------------------------------------------------------------------------
|
|
535
|
-
/**
|
|
536
|
-
* Strip batch_read tool calls from assistant messages in a session snapshot.
|
|
537
|
-
*
|
|
538
|
-
* Children don't have batch_read in their active tools, so seeing calls to it
|
|
539
|
-
* could confuse the model. This removes toolCall parts where name === "batch_read"
|
|
540
|
-
* from assistant messages AND drops the corresponding toolResult messages
|
|
541
|
-
* whose toolCallId references a stripped batch_read call. Keeping orphaned tool
|
|
542
|
-
* results causes strict API providers (e.g. kimi-coding, DeepSeek) to reject
|
|
543
|
-
* the request with `tool_call_id is not found`.
|
|
544
|
-
*/
|
|
545
|
-
export function stripBatchReadToolCalls(snapshot) {
|
|
546
|
-
const lines = snapshot.trimEnd().split("\n");
|
|
547
|
-
// Pass 1: Collect all batch_read toolCallIds from assistant messages.
|
|
548
|
-
const batchReadToolCallIds = new Set();
|
|
549
|
-
for (const line of lines) {
|
|
550
|
-
let entry;
|
|
551
|
-
try {
|
|
552
|
-
entry = JSON.parse(line);
|
|
553
|
-
}
|
|
554
|
-
catch {
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
557
|
-
if (entry?.type !== "message" || entry.message?.role !== "assistant")
|
|
558
|
-
continue;
|
|
559
|
-
const content = entry.message.content;
|
|
560
|
-
if (!Array.isArray(content))
|
|
561
|
-
continue;
|
|
562
|
-
for (const part of content) {
|
|
563
|
-
if (part.type === "toolCall" && part.name === "batch_read" && (part.id || part.toolCallId)) {
|
|
564
|
-
batchReadToolCallIds.add(part.id ?? part.toolCallId);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
// Pass 2: Strip batch_read toolCall parts from assistant messages,
|
|
569
|
-
// and remove orphaned tool result messages.
|
|
570
|
-
const result = [];
|
|
571
|
-
for (const line of lines) {
|
|
572
|
-
let entry;
|
|
573
|
-
try {
|
|
574
|
-
entry = JSON.parse(line);
|
|
575
|
-
}
|
|
576
|
-
catch {
|
|
577
|
-
result.push(line);
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
if (entry?.type !== "message") {
|
|
581
|
-
result.push(line);
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
// Tool result message — skip if it's a batch_read result
|
|
585
|
-
if (entry.message.role === "tool" || entry.message.role === "toolResult") {
|
|
586
|
-
const toolCallId = entry.message.toolCallId ??
|
|
587
|
-
(Array.isArray(entry.message.content) ? entry.message.content.find((p) => p.type === "toolResult")?.toolCallId : undefined);
|
|
588
|
-
if (toolCallId && batchReadToolCallIds.has(toolCallId))
|
|
589
|
-
continue;
|
|
590
|
-
result.push(line);
|
|
591
|
-
continue;
|
|
592
|
-
}
|
|
593
|
-
if (entry.message.role !== "assistant") {
|
|
594
|
-
result.push(line);
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
const content = entry.message.content;
|
|
598
|
-
if (!Array.isArray(content)) {
|
|
599
|
-
result.push(line);
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
const hasBatchReadCall = content.some((part) => part.type === "toolCall" && part.name === "batch_read");
|
|
603
|
-
if (!hasBatchReadCall) {
|
|
604
|
-
result.push(line);
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
const filteredContent = content.filter((part) => !(part.type === "toolCall" && part.name === "batch_read"));
|
|
608
|
-
if (filteredContent.length === 0) {
|
|
609
|
-
filteredContent.push({ type: "text", text: "" });
|
|
610
|
-
}
|
|
611
|
-
result.push(JSON.stringify({
|
|
612
|
-
...entry,
|
|
613
|
-
message: {
|
|
614
|
-
...entry.message,
|
|
615
|
-
content: filteredContent,
|
|
616
|
-
},
|
|
617
|
-
}));
|
|
618
|
-
}
|
|
619
|
-
return `${result.join("\n")}\n`;
|
|
620
|
-
}
|
|
621
|
-
export function sanitizeForkSnapshot(snapshot, cache = new Map(), options) {
|
|
622
|
-
if (!snapshot)
|
|
623
|
-
return snapshot;
|
|
624
|
-
const preBytes = snapshot.length;
|
|
625
|
-
const lines = snapshot.trimEnd().split("\n");
|
|
626
|
-
const sanitizedLines = [];
|
|
627
|
-
for (let i = 0; i < lines.length; i++) {
|
|
628
|
-
const line = lines[i];
|
|
629
|
-
let entry;
|
|
630
|
-
try {
|
|
631
|
-
entry = JSON.parse(line);
|
|
632
|
-
}
|
|
633
|
-
catch {
|
|
634
|
-
sanitizedLines.push(line);
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
let changed = false;
|
|
638
|
-
// Header (first line): merge fork metadata and replace parent system prompt.
|
|
639
|
-
if (i === 0 && entry && typeof entry === "object") {
|
|
640
|
-
// Inject fork metadata so children know their lineage.
|
|
641
|
-
if (options && (options.forkedFrom || options.forkedAt || options.parentFlow || options.depth !== undefined)) {
|
|
642
|
-
entry = {
|
|
643
|
-
...entry,
|
|
644
|
-
...(options.forkedFrom !== undefined ? { forkedFrom: options.forkedFrom } : {}),
|
|
645
|
-
...(options.forkedAt !== undefined ? { forkedAt: options.forkedAt } : {}),
|
|
646
|
-
...(options.parentFlow !== undefined ? { parentFlow: options.parentFlow } : {}),
|
|
647
|
-
...(options.depth !== undefined ? { depth: options.depth } : {}),
|
|
648
|
-
};
|
|
649
|
-
changed = true;
|
|
650
|
-
}
|
|
651
|
-
// Replace the parent orchestrator system prompt with a brief note.
|
|
652
|
-
// Children receive their own directive in the <activation> block.
|
|
653
|
-
if (entry.systemPrompt && typeof entry.systemPrompt === "string") {
|
|
654
|
-
entry = { ...entry, systemPrompt: "[parent orchestrator system prompt stripped — child receives its own directive]" };
|
|
655
|
-
changed = true;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
// Drop sliding system prompt messages entirely.
|
|
659
|
-
if (entry?.type === "message" &&
|
|
660
|
-
entry.message?.role === "system" &&
|
|
661
|
-
contentContainsSteeringHintTag(entry.message?.content)) {
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
if (entry?.type === "message" && entry.message) {
|
|
665
|
-
let message = entry.message;
|
|
666
|
-
// Normalize internal "toolResult" role to "tool" for API compatibility.
|
|
667
|
-
if (message.role === "toolResult") {
|
|
668
|
-
message = { ...message, role: "tool" };
|
|
669
|
-
changed = true;
|
|
670
|
-
}
|
|
671
|
-
// Strip reasoning/thinking from assistant messages.
|
|
672
|
-
// (Reasoning typically only appears in assistant messages, but we
|
|
673
|
-
// also check system/tool roles as a safety net for provider-specific
|
|
674
|
-
// formats. stripReasoningFromAssistantMessage is a no-op on non-assistant
|
|
675
|
-
// shapes, so calling it universally is safe.)
|
|
676
|
-
if (message.role === "assistant" || message.role === "system" || message.role === "tool") {
|
|
677
|
-
const stripped = stripReasoningFromAssistantMessage(message);
|
|
678
|
-
message = stripped.message;
|
|
679
|
-
changed ||= stripped.changed;
|
|
680
|
-
}
|
|
681
|
-
// Strip inner `message.timestamp` — the outer event-level timestamp (ISO string)
|
|
682
|
-
// is sufficient for ordering. The inner epoch-ms timestamp is redundant.
|
|
683
|
-
if ("timestamp" in message) {
|
|
684
|
-
const { timestamp, ...restMessage } = message;
|
|
685
|
-
message = restMessage;
|
|
686
|
-
changed = true;
|
|
687
|
-
}
|
|
688
|
-
// Strip API metadata fields that children don't need (~5-7 KB per assistant message).
|
|
689
|
-
// IMPORTANT: keep `usage` (including `totalTokens`). The child `pi` process replays
|
|
690
|
-
// this JSONL and core/session code reads `message.usage.totalTokens`; stripping
|
|
691
|
-
// `usage` causes: Cannot read properties of undefined (reading 'totalTokens').
|
|
692
|
-
// Strip `cost` from `usage` — it's always zeros in forked context and children never need it.
|
|
693
|
-
if (message.role === "assistant") {
|
|
694
|
-
const { api, provider, model, stopReason, responseId, responseModel, usage, ...rest } = message;
|
|
695
|
-
let stripped = false;
|
|
696
|
-
if (api !== undefined || provider !== undefined || model !== undefined ||
|
|
697
|
-
stopReason !== undefined || responseId !== undefined || responseModel !== undefined) {
|
|
698
|
-
stripped = true;
|
|
699
|
-
}
|
|
700
|
-
// Strip cost sub-object from usage while preserving totalTokens and other fields.
|
|
701
|
-
let cleanedUsage = usage;
|
|
702
|
-
if (usage && typeof usage === "object" && "cost" in usage) {
|
|
703
|
-
const { cost, ...usageWithoutCost } = usage;
|
|
704
|
-
cleanedUsage = usageWithoutCost;
|
|
705
|
-
stripped = true;
|
|
706
|
-
}
|
|
707
|
-
if (stripped) {
|
|
708
|
-
message = { ...rest, ...(cleanedUsage !== undefined ? { usage: cleanedUsage } : {}) };
|
|
709
|
-
changed = true;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
// Strip `details` from tool/toolResult messages — carries FlowDetails UI metadata
|
|
713
|
-
// (mode, flowStyle, projectAgentsDir, results) that children never need.
|
|
714
|
-
if (message.role === "tool" || message.role === "toolResult") {
|
|
715
|
-
if ("details" in message) {
|
|
716
|
-
const { details, ...restMessage } = message;
|
|
717
|
-
message = restMessage;
|
|
718
|
-
changed = true;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
if ("content" in message) {
|
|
722
|
-
let modifiedContent = message.content;
|
|
723
|
-
// Strip sliding prompts
|
|
724
|
-
const afterSliding = stripSteeringHintFromContent(modifiedContent);
|
|
725
|
-
if (!isJsonEqual(afterSliding, modifiedContent)) {
|
|
726
|
-
modifiedContent = afterSliding;
|
|
727
|
-
changed = true;
|
|
728
|
-
}
|
|
729
|
-
// Strip strategic hints from tool results
|
|
730
|
-
if (message.role === "tool" || message.role === "toolResult") {
|
|
731
|
-
const afterHints = stripStrategicHintsFromContent(modifiedContent);
|
|
732
|
-
if (!isJsonEqual(afterHints, modifiedContent)) {
|
|
733
|
-
modifiedContent = afterHints;
|
|
734
|
-
changed = true;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
if (changed) {
|
|
738
|
-
message = { ...message, content: modifiedContent };
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
if (changed) {
|
|
742
|
-
entry = { ...entry, message };
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
const outLine = changed ? JSON.stringify(entry) : line;
|
|
746
|
-
sanitizedLines.push(outLine);
|
|
747
|
-
}
|
|
748
|
-
// Reparent orphaned parentIds after steering-hint messages were dropped.
|
|
749
|
-
// Build a set of surviving message IDs, then fix any parentId that points
|
|
750
|
-
// to a removed message.
|
|
751
|
-
const survivingIds = new Set();
|
|
752
|
-
for (const line of sanitizedLines) {
|
|
753
|
-
try {
|
|
754
|
-
const entry = JSON.parse(line);
|
|
755
|
-
const id = entry?.message?.id ?? entry?.message?.messageId ?? entry?.id;
|
|
756
|
-
if (typeof id === "string" && id)
|
|
757
|
-
survivingIds.add(id);
|
|
758
|
-
}
|
|
759
|
-
catch { /* ignore */ }
|
|
760
|
-
}
|
|
761
|
-
for (let i = 0; i < sanitizedLines.length; i++) {
|
|
762
|
-
try {
|
|
763
|
-
const entry = JSON.parse(sanitizedLines[i]);
|
|
764
|
-
if (!entry?.message)
|
|
765
|
-
continue;
|
|
766
|
-
const parentId = entry.message.parentId ?? entry.message.parentMessageId;
|
|
767
|
-
if (typeof parentId === "string" && parentId && !survivingIds.has(parentId)) {
|
|
768
|
-
const { parentId: _pid, parentMessageId: _pmid, ...restMessage } = entry.message;
|
|
769
|
-
entry.message = restMessage;
|
|
770
|
-
sanitizedLines[i] = JSON.stringify(entry);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
catch { /* ignore */ }
|
|
774
|
-
}
|
|
775
|
-
let sanitized = `${sanitizedLines.join("\n")}\n`;
|
|
776
|
-
// Strip batch_read tool calls from assistant messages.
|
|
777
|
-
// Children don't have batch_read in their active tools.
|
|
778
|
-
sanitized = stripBatchReadToolCalls(sanitized);
|
|
779
|
-
// Compress tool results (flow, batch, web, ask_user).
|
|
780
|
-
sanitized = compressToolResults(sanitized, cache);
|
|
781
|
-
// Telemetry: measure total delta across sanitization, stripping, and compression.
|
|
782
|
-
const postBytes = sanitized.length;
|
|
783
|
-
const reduction = preBytes > 0 ? ((1 - postBytes / preBytes) * 100).toFixed(0) : "0";
|
|
784
|
-
if (DEBUG_CONTEXT) {
|
|
785
|
-
console.error(`[context-snapshot] pre: ${preBytes} → post: ${postBytes} bytes (${reduction}% reduction)`);
|
|
786
|
-
}
|
|
787
|
-
// Always emit compression-stats as a trailing metadata entry so the dump contains
|
|
788
|
-
// observability data regardless of DEBUG_CONTEXT setting.
|
|
789
|
-
sanitized = sanitized.trimEnd() + "\n" + JSON.stringify({
|
|
790
|
-
type: "compression-stats",
|
|
791
|
-
preBytes,
|
|
792
|
-
postBytes,
|
|
793
|
-
reductionPercent: Number(reduction),
|
|
794
|
-
}) + "\n";
|
|
795
|
-
return sanitized;
|
|
796
|
-
}
|
|
797
|
-
//# sourceMappingURL=snapshot.js.map
|