@dyyz1993/pi-coding-agent 0.74.24 → 0.74.27
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 +9 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +3 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/session-manager.d.ts +5 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +8 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/extensions/agent-permissions/index.ts +235 -0
- package/dist/extensions/ask-tools/index.ts +115 -0
- package/dist/extensions/auto-memory/contract.d.ts +51 -0
- package/dist/extensions/auto-memory/contract.d.ts.map +1 -0
- package/dist/extensions/auto-memory/contract.js +2 -0
- package/dist/extensions/auto-memory/contract.js.map +1 -0
- package/dist/extensions/auto-memory/contract.ts +56 -0
- package/dist/extensions/auto-memory/index.ts +969 -0
- package/dist/extensions/auto-memory/prompts.ts +202 -0
- package/dist/extensions/auto-memory/skip-rules.ts +297 -0
- package/dist/extensions/auto-memory/utils.ts +208 -0
- package/dist/extensions/auto-session-title/index.ts +83 -0
- package/dist/extensions/bash-ext/contract.d.ts +79 -0
- package/dist/extensions/bash-ext/contract.d.ts.map +1 -0
- package/dist/extensions/bash-ext/contract.js +2 -0
- package/dist/extensions/bash-ext/contract.js.map +1 -0
- package/dist/extensions/bash-ext/contract.ts +69 -0
- package/dist/extensions/bash-ext/index.ts +858 -0
- package/dist/extensions/claude-hooks-compat/config-loader.ts +49 -0
- package/dist/extensions/claude-hooks-compat/handler-runner.ts +377 -0
- package/dist/extensions/claude-hooks-compat/if-parser.ts +53 -0
- package/dist/extensions/claude-hooks-compat/index.ts +178 -0
- package/dist/extensions/claude-hooks-compat/matcher.ts +17 -0
- package/dist/extensions/claude-hooks-compat/stdin-builder.ts +27 -0
- package/dist/extensions/claude-hooks-compat/types.ts +77 -0
- package/dist/extensions/compaction-manager/config.ts +47 -0
- package/dist/extensions/compaction-manager/context-fold.ts +63 -0
- package/dist/extensions/compaction-manager/index.ts +151 -0
- package/dist/extensions/compaction-manager/microcompact.ts +49 -0
- package/dist/extensions/compaction-manager/reactive.ts +9 -0
- package/dist/extensions/compaction-manager/session-memory.ts +48 -0
- package/dist/extensions/coordinator/INTEGRATION.md +376 -0
- package/dist/extensions/coordinator/handler.test.ts +277 -0
- package/dist/extensions/coordinator/handler.ts +189 -0
- package/dist/extensions/coordinator/index.ts +261 -0
- package/dist/extensions/coordinator/types.d.ts +100 -0
- package/dist/extensions/coordinator/types.d.ts.map +1 -0
- package/dist/extensions/coordinator/types.js +2 -0
- package/dist/extensions/coordinator/types.js.map +1 -0
- package/dist/extensions/coordinator/types.ts +72 -0
- package/dist/extensions/file-snapshot/index.ts +131 -0
- package/dist/extensions/file-time-guard/README.md +133 -0
- package/dist/extensions/file-time-guard/config.ts +13 -0
- package/dist/extensions/file-time-guard/index.ts +171 -0
- package/dist/extensions/hooks-engine/index.ts +117 -0
- package/dist/extensions/lsp/lsp/client/file-tracker.ts +70 -0
- package/dist/extensions/lsp/lsp/client/registry.ts +305 -0
- package/dist/extensions/lsp/lsp/client/runtime.ts +832 -0
- package/dist/extensions/lsp/lsp/config/resolver.ts +573 -0
- package/dist/extensions/lsp/lsp/contract.d.ts +101 -0
- package/dist/extensions/lsp/lsp/contract.d.ts.map +1 -0
- package/dist/extensions/lsp/lsp/contract.js +2 -0
- package/dist/extensions/lsp/lsp/contract.js.map +1 -0
- package/dist/extensions/lsp/lsp/contract.ts +103 -0
- package/dist/extensions/lsp/lsp/hooks/agent-end.ts +169 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.d.ts +10 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.d.ts.map +1 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js +30 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js.map +1 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.ts +41 -0
- package/dist/extensions/lsp/lsp/hooks/writethrough.ts +342 -0
- package/dist/extensions/lsp/lsp/index.ts +310 -0
- package/dist/extensions/lsp/lsp/lsp.test.ts +684 -0
- package/dist/extensions/lsp/lsp/monitoring/server-metrics.ts +176 -0
- package/dist/extensions/lsp/lsp/tools/lsp-tool.ts +402 -0
- package/dist/extensions/lsp/lsp/utils/dependency-resolver.ts +147 -0
- package/dist/extensions/lsp/lsp/utils/diagnostics-wait.ts +41 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.d.ts +20 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.d.ts.map +1 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.js +64 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.js.map +1 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.ts +76 -0
- package/dist/extensions/message-bridge/GUIDE.md +210 -0
- package/dist/extensions/message-bridge/index.ts +222 -0
- package/dist/extensions/output-guard/index.ts +446 -0
- package/dist/extensions/preview/index.ts +278 -0
- package/dist/extensions/rules-engine/MATCH_HISTORY_RECONCILIATION.md +111 -0
- package/dist/extensions/rules-engine/RULES-ENGINE-GUIDE.md +470 -0
- package/dist/extensions/rules-engine/cache.js +232 -0
- package/dist/extensions/rules-engine/cache.ts +38 -0
- package/dist/extensions/rules-engine/config.js +63 -0
- package/dist/extensions/rules-engine/config.ts +70 -0
- package/dist/extensions/rules-engine/index.js +1530 -0
- package/dist/extensions/rules-engine/index.ts +552 -0
- package/dist/extensions/rules-engine/injector.js +68 -0
- package/dist/extensions/rules-engine/injector.ts +74 -0
- package/dist/extensions/rules-engine/loader.js +179 -0
- package/dist/extensions/rules-engine/loader.ts +205 -0
- package/dist/extensions/rules-engine/matcher.js +534 -0
- package/dist/extensions/rules-engine/matcher.ts +52 -0
- package/dist/extensions/rules-engine/types.d.ts +156 -0
- package/dist/extensions/rules-engine/types.d.ts.map +1 -0
- package/dist/extensions/rules-engine/types.js +2 -0
- package/dist/extensions/rules-engine/types.js.map +1 -0
- package/dist/extensions/rules-engine/types.ts +169 -0
- package/dist/extensions/session-supervisor/checker.ts +116 -0
- package/dist/extensions/session-supervisor/config.ts +45 -0
- package/dist/extensions/session-supervisor/index.ts +726 -0
- package/dist/extensions/session-supervisor/prompts.ts +132 -0
- package/dist/extensions/session-supervisor/scheduler.ts +69 -0
- package/dist/extensions/session-supervisor/types.ts +215 -0
- package/dist/extensions/subagent/README.md +172 -0
- package/dist/extensions/subagent/agents/explorer.md +25 -0
- package/dist/extensions/subagent/agents/guide.md +27 -0
- package/dist/extensions/subagent/agents/planner.md +37 -0
- package/dist/extensions/subagent/agents/reviewer.md +35 -0
- package/dist/extensions/subagent/agents/scout.md +50 -0
- package/dist/extensions/subagent/agents/verification.md +35 -0
- package/dist/extensions/subagent/agents/worker.md +24 -0
- package/dist/extensions/subagent/agents.ts +25 -0
- package/dist/extensions/subagent/index.ts +987 -0
- package/dist/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/dist/extensions/subagent/prompts/implement.md +10 -0
- package/dist/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/dist/extensions/subagent-ext/contract.d.ts +2 -0
- package/dist/extensions/subagent-ext/contract.d.ts.map +1 -0
- package/dist/extensions/subagent-ext/contract.js +2 -0
- package/dist/extensions/subagent-ext/contract.js.map +1 -0
- package/dist/extensions/subagent-ext/contract.ts +1 -0
- package/dist/extensions/subagent-ext/index.ts +347 -0
- package/dist/extensions/subagent-shared/contract.d.ts +25 -0
- package/dist/extensions/subagent-shared/contract.d.ts.map +1 -0
- package/dist/extensions/subagent-shared/contract.js +2 -0
- package/dist/extensions/subagent-shared/contract.js.map +1 -0
- package/dist/extensions/subagent-shared/contract.ts +28 -0
- package/dist/extensions/subagent-shared/index.ts +4 -0
- package/dist/extensions/subagent-shared/render.ts +166 -0
- package/dist/extensions/subagent-shared/types.ts +35 -0
- package/dist/extensions/subagent-shared/utils.ts +112 -0
- package/dist/extensions/subagent-v2/contract.d.ts +2 -0
- package/dist/extensions/subagent-v2/contract.d.ts.map +1 -0
- package/dist/extensions/subagent-v2/contract.js +2 -0
- package/dist/extensions/subagent-v2/contract.js.map +1 -0
- package/dist/extensions/subagent-v2/contract.ts +1 -0
- package/dist/extensions/subagent-v2/index.ts +599 -0
- package/dist/extensions/todo-ext/contract.d.ts +27 -0
- package/dist/extensions/todo-ext/contract.d.ts.map +1 -0
- package/dist/extensions/todo-ext/contract.js +2 -0
- package/dist/extensions/todo-ext/contract.js.map +1 -0
- package/dist/extensions/todo-ext/contract.ts +30 -0
- package/dist/extensions/todo-ext/index.ts +419 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export interface ClaudeHookConfig {
|
|
2
|
+
hooks?: Record<string, MatcherGroup[]>;
|
|
3
|
+
disableAllHooks?: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface MatcherGroup {
|
|
7
|
+
matcher?: string;
|
|
8
|
+
hooks: HookHandler[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface HookHandler {
|
|
12
|
+
type: "command" | "http" | "mcp_tool" | "prompt" | "agent";
|
|
13
|
+
command?: string;
|
|
14
|
+
prompt?: string;
|
|
15
|
+
url?: string;
|
|
16
|
+
server?: string;
|
|
17
|
+
tool?: string;
|
|
18
|
+
input?: Record<string, unknown>;
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
allowedEnvVars?: string[];
|
|
21
|
+
model?: string;
|
|
22
|
+
timeout?: number;
|
|
23
|
+
if?: string;
|
|
24
|
+
async?: boolean;
|
|
25
|
+
asyncRewake?: boolean;
|
|
26
|
+
shell?: "bash" | "powershell";
|
|
27
|
+
statusMessage?: string;
|
|
28
|
+
once?: boolean;
|
|
29
|
+
"x-pi-variables"?: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface HookStdinData {
|
|
33
|
+
session_id: string;
|
|
34
|
+
transcript_path: string;
|
|
35
|
+
cwd: string;
|
|
36
|
+
permission_mode: string;
|
|
37
|
+
hook_event_name: string;
|
|
38
|
+
tool_name?: string;
|
|
39
|
+
tool_input?: Record<string, unknown>;
|
|
40
|
+
tool_use_id?: string;
|
|
41
|
+
tool_output?: string;
|
|
42
|
+
agent_type?: string;
|
|
43
|
+
agent_id?: string;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface HookOutput {
|
|
48
|
+
exitCode: number;
|
|
49
|
+
stdout: string;
|
|
50
|
+
stderr: string;
|
|
51
|
+
parsed?: HookParsedOutput;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface HookParsedOutput {
|
|
55
|
+
decision?: string;
|
|
56
|
+
reason?: string;
|
|
57
|
+
ok?: boolean;
|
|
58
|
+
continue?: boolean;
|
|
59
|
+
stopReason?: string;
|
|
60
|
+
suppressOutput?: boolean;
|
|
61
|
+
systemMessage?: string;
|
|
62
|
+
retry?: boolean;
|
|
63
|
+
hookSpecificOutput?: {
|
|
64
|
+
hookEventName?: string;
|
|
65
|
+
permissionDecision?: "allow" | "deny" | "ask" | "defer";
|
|
66
|
+
permissionDecisionReason?: string;
|
|
67
|
+
updatedInput?: Record<string, unknown>;
|
|
68
|
+
modifiedToolInput?: Record<string, unknown>;
|
|
69
|
+
additionalContext?: string;
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface IfClause {
|
|
75
|
+
tool: string;
|
|
76
|
+
pattern: string;
|
|
77
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface CompactionManagerConfig {
|
|
2
|
+
microcompact: {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
maxAgeMs: number;
|
|
5
|
+
clearableTools: string[];
|
|
6
|
+
};
|
|
7
|
+
sessionMemory: {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
memoryDir: string;
|
|
10
|
+
minContentLength: number;
|
|
11
|
+
};
|
|
12
|
+
reactive: {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
warnPercent: number;
|
|
15
|
+
forceCompactPercent: number;
|
|
16
|
+
};
|
|
17
|
+
contextFold: {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
maxAgeMs: number;
|
|
20
|
+
keepRecentCount: number;
|
|
21
|
+
maxSummaryLength: number;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_CONFIG: CompactionManagerConfig = {
|
|
26
|
+
microcompact: {
|
|
27
|
+
enabled: true,
|
|
28
|
+
maxAgeMs: 60 * 60 * 1000,
|
|
29
|
+
clearableTools: ["read", "bash", "grep", "find", "glob", "webFetch"],
|
|
30
|
+
},
|
|
31
|
+
sessionMemory: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
memoryDir: ".pi/memory",
|
|
34
|
+
minContentLength: 50,
|
|
35
|
+
},
|
|
36
|
+
reactive: {
|
|
37
|
+
enabled: true,
|
|
38
|
+
warnPercent: 75,
|
|
39
|
+
forceCompactPercent: 90,
|
|
40
|
+
},
|
|
41
|
+
contextFold: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
maxAgeMs: 30 * 60 * 1000,
|
|
44
|
+
keepRecentCount: 6,
|
|
45
|
+
maxSummaryLength: 200,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { AgentMessage } from "@dyyz1993/pi-agent-core";
|
|
2
|
+
import type { AssistantMessage, Message, TextContent, ThinkingContent, ToolCall } from "@dyyz1993/pi-ai";
|
|
3
|
+
import type { SessionEntry, SessionMessageEntry } from "@dyyz1993/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
export function findFoldableEntries(
|
|
6
|
+
entries: SessionEntry[],
|
|
7
|
+
foldedIds: Set<string>,
|
|
8
|
+
maxAgeMs: number,
|
|
9
|
+
keepRecentCount: number,
|
|
10
|
+
): SessionMessageEntry[] {
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
const messageEntries = entries.filter(
|
|
13
|
+
(e): e is SessionMessageEntry => e.type === "message" && e.message.role === "assistant",
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
if (messageEntries.length <= keepRecentCount) return [];
|
|
17
|
+
|
|
18
|
+
const candidates = messageEntries.slice(0, -keepRecentCount);
|
|
19
|
+
|
|
20
|
+
return candidates.filter((entry) => {
|
|
21
|
+
if (foldedIds.has(entry.id)) return false;
|
|
22
|
+
const msg = entry.message as AssistantMessage;
|
|
23
|
+
if (!Array.isArray(msg.content)) return false;
|
|
24
|
+
const age = now - msg.timestamp;
|
|
25
|
+
return age >= maxAgeMs;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function extractFoldSummary(message: AssistantMessage, maxLength: number): string {
|
|
30
|
+
if (!Array.isArray(message.content)) return "[folded empty message]";
|
|
31
|
+
|
|
32
|
+
const textParts: string[] = [];
|
|
33
|
+
for (const block of message.content) {
|
|
34
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
35
|
+
textParts.push(block.text);
|
|
36
|
+
} else if (block.type === "toolCall" && typeof block.name === "string") {
|
|
37
|
+
textParts.push(`[called ${block.name}]`);
|
|
38
|
+
} else if (block.type === "thinking") {
|
|
39
|
+
textParts.push("[thinking]");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const full = textParts.join(" ").replace(/\s+/g, " ").trim();
|
|
44
|
+
if (!full) return "[folded empty message]";
|
|
45
|
+
|
|
46
|
+
if (full.length <= maxLength) return full;
|
|
47
|
+
return full.slice(0, maxLength).trim() + "...";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function estimateMessageTokens(message: Message): number {
|
|
51
|
+
if (!Array.isArray(message.content)) return 0;
|
|
52
|
+
let total = 0;
|
|
53
|
+
for (const block of message.content) {
|
|
54
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
55
|
+
total += Math.ceil(block.text.length / 4);
|
|
56
|
+
} else if (block.type === "thinking" && typeof (block as ThinkingContent).thinking === "string") {
|
|
57
|
+
total += Math.ceil((block as ThinkingContent).thinking.length / 4);
|
|
58
|
+
} else {
|
|
59
|
+
total += 50;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return total;
|
|
63
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { ExtensionAPI } from "@dyyz1993/pi-coding-agent";
|
|
4
|
+
import type { AssistantMessage } from "@dyyz1993/pi-ai";
|
|
5
|
+
import { DEFAULT_CONFIG, type CompactionManagerConfig } from "./config.js";
|
|
6
|
+
import { extractFoldSummary, estimateMessageTokens, findFoldableEntries } from "./context-fold.js";
|
|
7
|
+
import { microcompactMessages, stripThinkingBlocks } from "./microcompact.js";
|
|
8
|
+
import { buildMemorySummary, readMemoryFiles } from "./session-memory.js";
|
|
9
|
+
import { shouldWarn, shouldForceCompact } from "./reactive.js";
|
|
10
|
+
|
|
11
|
+
function loadConfig(): CompactionManagerConfig {
|
|
12
|
+
const configPath = join(process.cwd(), ".pi", "compaction.json");
|
|
13
|
+
if (existsSync(configPath)) {
|
|
14
|
+
try {
|
|
15
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
16
|
+
return {
|
|
17
|
+
microcompact: { ...DEFAULT_CONFIG.microcompact, ...raw.microcompact },
|
|
18
|
+
sessionMemory: { ...DEFAULT_CONFIG.sessionMemory, ...raw.sessionMemory },
|
|
19
|
+
reactive: { ...DEFAULT_CONFIG.reactive, ...raw.reactive },
|
|
20
|
+
contextFold: { ...DEFAULT_CONFIG.contextFold, ...raw.contextFold },
|
|
21
|
+
};
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.debug("[compaction-manager] config load failed:", err instanceof Error ? err.message : err);
|
|
24
|
+
return DEFAULT_CONFIG;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return DEFAULT_CONFIG;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function (pi: ExtensionAPI) {
|
|
31
|
+
const config = loadConfig();
|
|
32
|
+
|
|
33
|
+
if (config.microcompact.enabled) {
|
|
34
|
+
pi.on("context", (event, _ctx) => {
|
|
35
|
+
const microResult = microcompactMessages(event.messages, config.microcompact.clearableTools, config.microcompact.maxAgeMs);
|
|
36
|
+
const messages = microResult?.messages ?? event.messages;
|
|
37
|
+
const thinkResult = stripThinkingBlocks(messages);
|
|
38
|
+
return thinkResult ?? microResult;
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
pi.on("context", (event, _ctx) => {
|
|
42
|
+
return stripThinkingBlocks(event.messages);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (config.contextFold.enabled) {
|
|
47
|
+
pi.on("turn_end", (_event, ctx) => {
|
|
48
|
+
const entries = ctx.sessionManager.getBranch();
|
|
49
|
+
|
|
50
|
+
const foldedIds = new Set<string>();
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry.type === "fold") {
|
|
53
|
+
foldedIds.add(entry.targetId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const foldable = findFoldableEntries(
|
|
58
|
+
entries,
|
|
59
|
+
foldedIds,
|
|
60
|
+
config.contextFold.maxAgeMs,
|
|
61
|
+
config.contextFold.keepRecentCount,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (foldable.length === 0) return;
|
|
65
|
+
|
|
66
|
+
for (const entry of foldable) {
|
|
67
|
+
const msg = entry.message as AssistantMessage;
|
|
68
|
+
const summary = extractFoldSummary(msg, config.contextFold.maxSummaryLength);
|
|
69
|
+
const tokens = estimateMessageTokens(msg);
|
|
70
|
+
pi.foldEntry(entry.id, summary, tokens);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
ctx.ui.notify(`Context fold: folded ${foldable.length} old message(s)`, "info");
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (config.sessionMemory.enabled) {
|
|
78
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
79
|
+
const { preparation, signal } = event;
|
|
80
|
+
|
|
81
|
+
const memoryFiles = await readMemoryFiles(ctx.cwd, config.sessionMemory.memoryDir);
|
|
82
|
+
if (memoryFiles.size === 0 || signal.aborted) return;
|
|
83
|
+
|
|
84
|
+
const result = buildMemorySummary(memoryFiles, preparation, config.sessionMemory.minContentLength);
|
|
85
|
+
if (!result) return;
|
|
86
|
+
|
|
87
|
+
ctx.ui.notify(
|
|
88
|
+
`Session Memory Compact: using ${memoryFiles.size} memory files instead of LLM summary`,
|
|
89
|
+
"info",
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return { compaction: result };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (config.reactive.enabled) {
|
|
97
|
+
let warnedThisTurn = false;
|
|
98
|
+
|
|
99
|
+
pi.on("after_provider_response", (event, ctx) => {
|
|
100
|
+
if (event.status === 429) {
|
|
101
|
+
ctx.ui.notify("Rate limited — API is throttling requests", "warning");
|
|
102
|
+
} else if (event.status >= 500) {
|
|
103
|
+
ctx.ui.notify(`API server error (${event.status}) — will retry automatically`, "warning");
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
pi.on("turn_end", (_event, ctx) => {
|
|
108
|
+
const usage = ctx.getContextUsage();
|
|
109
|
+
if (!usage || usage.tokens === null) return;
|
|
110
|
+
|
|
111
|
+
const { tokens, contextWindow, percent } = usage;
|
|
112
|
+
|
|
113
|
+
if (shouldForceCompact(tokens, contextWindow, config.reactive.forceCompactPercent) && !warnedThisTurn) {
|
|
114
|
+
ctx.ui.notify(
|
|
115
|
+
`Context critical: ${percent!.toFixed(0)}% (${tokens!.toLocaleString()} / ${contextWindow.toLocaleString()} tokens). Consider /compact-force.`,
|
|
116
|
+
"warning",
|
|
117
|
+
);
|
|
118
|
+
warnedThisTurn = true;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (shouldWarn(tokens, contextWindow, config.reactive.warnPercent) && !warnedThisTurn) {
|
|
123
|
+
ctx.ui.notify(
|
|
124
|
+
`Context high: ${percent!.toFixed(0)}% (${tokens!.toLocaleString()} / ${contextWindow.toLocaleString()} tokens)`,
|
|
125
|
+
"info",
|
|
126
|
+
);
|
|
127
|
+
warnedThisTurn = true;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
pi.on("agent_start", () => {
|
|
132
|
+
warnedThisTurn = false;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
pi.registerCommand("compact-force", {
|
|
136
|
+
description: "Force compaction immediately with optional custom instructions",
|
|
137
|
+
handler: async (args, ctx) => {
|
|
138
|
+
const instructions = args.trim() || undefined;
|
|
139
|
+
ctx.compact({
|
|
140
|
+
customInstructions: instructions,
|
|
141
|
+
onComplete: (result) => {
|
|
142
|
+
ctx.ui.notify(`Compaction done: ${result.tokensBefore.toLocaleString()} tokens compressed`, "info");
|
|
143
|
+
},
|
|
144
|
+
onError: (error) => {
|
|
145
|
+
ctx.ui.notify(`Compaction failed: ${error.message}`, "error");
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AgentMessage } from "@dyyz1993/pi-agent-core";
|
|
2
|
+
import type { AssistantMessage, TextContent, ToolResultMessage } from "@dyyz1993/pi-ai";
|
|
3
|
+
|
|
4
|
+
export function microcompactMessages(
|
|
5
|
+
messages: AgentMessage[],
|
|
6
|
+
clearableTools: string[],
|
|
7
|
+
maxAgeMs: number,
|
|
8
|
+
): { messages: AgentMessage[] } | undefined {
|
|
9
|
+
const now = Date.now();
|
|
10
|
+
let modified = false;
|
|
11
|
+
|
|
12
|
+
const cleaned = messages.map((msg) => {
|
|
13
|
+
if (msg.role !== "toolResult") return msg;
|
|
14
|
+
const toolMsg = msg as ToolResultMessage;
|
|
15
|
+
if (!clearableTools.includes(toolMsg.toolName)) return msg;
|
|
16
|
+
if (toolMsg.isError) return msg;
|
|
17
|
+
if (now - toolMsg.timestamp < maxAgeMs) return msg;
|
|
18
|
+
|
|
19
|
+
modified = true;
|
|
20
|
+
return {
|
|
21
|
+
...toolMsg,
|
|
22
|
+
content: [{ type: "text" as const, text: `[Old ${toolMsg.toolName} result cleared]` }],
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return modified ? { messages: cleaned } : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function stripThinkingBlocks(messages: AgentMessage[]): { messages: AgentMessage[] } | undefined {
|
|
30
|
+
let modified = false;
|
|
31
|
+
|
|
32
|
+
const cleaned = messages.map((msg) => {
|
|
33
|
+
if (msg.role !== "assistant") return msg;
|
|
34
|
+
const assistant = msg as AssistantMessage;
|
|
35
|
+
if (!Array.isArray(assistant.content)) return msg;
|
|
36
|
+
|
|
37
|
+
const hasThinking = assistant.content.some((block: AssistantMessage["content"][number]) => block.type === "thinking");
|
|
38
|
+
if (!hasThinking) return msg;
|
|
39
|
+
|
|
40
|
+
modified = true;
|
|
41
|
+
const filtered = assistant.content.filter((block: AssistantMessage["content"][number]) => block.type !== "thinking");
|
|
42
|
+
return {
|
|
43
|
+
...assistant,
|
|
44
|
+
content: filtered,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return modified ? { messages: cleaned } : undefined;
|
|
49
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function shouldWarn(tokens: number | null, contextWindow: number, warnPercent: number): boolean {
|
|
2
|
+
if (tokens === null) return false;
|
|
3
|
+
return (tokens / contextWindow) * 100 >= warnPercent;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function shouldForceCompact(tokens: number | null, contextWindow: number, forcePercent: number): boolean {
|
|
7
|
+
if (tokens === null) return false;
|
|
8
|
+
return (tokens / contextWindow) * 100 >= forcePercent;
|
|
9
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { CompactionPreparation, CompactionResult } from "@dyyz1993/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
export type { CompactionPreparation, CompactionResult };
|
|
6
|
+
|
|
7
|
+
export function buildMemorySummary(
|
|
8
|
+
memoryFiles: Map<string, string>,
|
|
9
|
+
preparation: CompactionPreparation,
|
|
10
|
+
minContentLength: number,
|
|
11
|
+
): CompactionResult | undefined {
|
|
12
|
+
if (memoryFiles.size === 0) return undefined;
|
|
13
|
+
|
|
14
|
+
const parts: string[] = [];
|
|
15
|
+
for (const [name, content] of memoryFiles) {
|
|
16
|
+
parts.push(`### ${name}\n${content}`);
|
|
17
|
+
}
|
|
18
|
+
const summary = parts.join("\n\n---\n\n");
|
|
19
|
+
|
|
20
|
+
if (summary.trim().length < minContentLength) return undefined;
|
|
21
|
+
|
|
22
|
+
const estimatedTokens = Math.ceil(summary.length / 4);
|
|
23
|
+
if (estimatedTokens > preparation.settings.reserveTokens) return undefined;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
summary,
|
|
27
|
+
firstKeptEntryId: preparation.firstKeptEntryId,
|
|
28
|
+
tokensBefore: preparation.tokensBefore,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function readMemoryFiles(cwd: string, memoryDir: string): Promise<Map<string, string>> {
|
|
33
|
+
const dir = join(cwd, memoryDir);
|
|
34
|
+
const files = new Map<string, string>();
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const entries = await readdir(dir);
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (!entry.endsWith(".md")) continue;
|
|
40
|
+
const content = await readFile(join(dir, entry), "utf-8");
|
|
41
|
+
files.set(entry, content);
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.debug("[compaction-manager] session memory dir read failed:", err instanceof Error ? err.message : err);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return files;
|
|
48
|
+
}
|