@docyrus/docyrus 0.0.22 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +12 -0
  2. package/main.js +234 -11
  3. package/main.js.map +4 -4
  4. package/package.json +6 -4
  5. package/resources/pi-agent/assets/docyrus-logo.svg +16 -0
  6. package/resources/pi-agent/extensions/architect.ts +771 -0
  7. package/resources/pi-agent/extensions/notify.ts +57 -55
  8. package/resources/pi-agent/extensions/pi-custom-compaction/CHANGELOG.md +27 -0
  9. package/resources/pi-agent/extensions/pi-custom-compaction/LICENSE +21 -0
  10. package/resources/pi-agent/extensions/pi-custom-compaction/README.md +244 -0
  11. package/resources/pi-agent/extensions/pi-custom-compaction/VENDORED_FROM.md +6 -0
  12. package/resources/pi-agent/extensions/pi-custom-compaction/banner.png +0 -0
  13. package/resources/pi-agent/extensions/pi-custom-compaction/commands/register-commands.ts +63 -0
  14. package/resources/pi-agent/extensions/pi-custom-compaction/events/register-events.ts +229 -0
  15. package/resources/pi-agent/extensions/pi-custom-compaction/index.ts +10 -0
  16. package/resources/pi-agent/extensions/pi-custom-compaction/package.json +57 -0
  17. package/resources/pi-agent/extensions/pi-custom-compaction/paths.ts +13 -0
  18. package/resources/pi-agent/extensions/pi-custom-compaction/policy/config.ts +32 -0
  19. package/resources/pi-agent/extensions/pi-custom-compaction/policy/merge.ts +67 -0
  20. package/resources/pi-agent/extensions/pi-custom-compaction/policy/parse.ts +354 -0
  21. package/resources/pi-agent/extensions/pi-custom-compaction/policy/types.ts +131 -0
  22. package/resources/pi-agent/extensions/pi-custom-compaction/runtime/model-resolution.ts +77 -0
  23. package/resources/pi-agent/extensions/pi-custom-compaction/runtime/pure.ts +56 -0
  24. package/resources/pi-agent/extensions/pi-custom-compaction/runtime/session-state.ts +244 -0
  25. package/resources/pi-agent/extensions/pi-custom-compaction/summary/generate.ts +184 -0
  26. package/resources/pi-agent/extensions/pi-custom-compaction/summary/template.ts +124 -0
  27. package/server-loader.js +4017 -0
  28. package/server-loader.js.map +7 -0
@@ -0,0 +1,229 @@
1
+ import type { Api, Model } from "@mariozechner/pi-ai";
2
+ import { compact, type ExtensionAPI, type ExtensionContext, type SessionBeforeCompactEvent } from "@mariozechner/pi-coding-agent";
3
+ import type { ISummaryPolicy } from "../policy/types.js";
4
+ import {
5
+ computeFileLists,
6
+ formatFileOperations,
7
+ generateTemplateSummary,
8
+ generateTurnPrefixSummary,
9
+ getReserveTokens,
10
+ } from "../summary/generate.js";
11
+ import { buildSummaryPrompt, discoverTemplate, resolveSummarySettings } from "../summary/template.js";
12
+ import { getLastAssistantMessage, resolveSummaryModel } from "../runtime/model-resolution.js";
13
+ import { resolveEffectivePolicy, shouldTriggerProactiveCompact } from "../runtime/pure.js";
14
+ import type { IRuntimeServices } from "../runtime/session-state.js";
15
+
16
+ async function generateCustomCompaction(
17
+ event: SessionBeforeCompactEvent,
18
+ template: string,
19
+ updateTemplate: string | undefined,
20
+ summarySettings: ISummaryPolicy,
21
+ model: Model<Api>,
22
+ apiKey: string,
23
+ ) {
24
+ const reserveTokens = getReserveTokens(event);
25
+ const summaryPrompt = buildSummaryPrompt(
26
+ template,
27
+ updateTemplate,
28
+ event.preparation.previousSummary,
29
+ event.customInstructions,
30
+ summarySettings.preservationInstruction,
31
+ );
32
+ const historySummary =
33
+ event.preparation.messagesToSummarize.length > 0
34
+ ? await generateTemplateSummary(
35
+ event.preparation.messagesToSummarize,
36
+ model,
37
+ apiKey,
38
+ summaryPrompt,
39
+ reserveTokens,
40
+ event.signal,
41
+ summarySettings.thinkingLevel,
42
+ event.preparation.previousSummary,
43
+ )
44
+ : event.preparation.previousSummary ?? "No prior history.";
45
+ if (!historySummary.trim()) {
46
+ throw new Error("Custom template summarization returned empty summary");
47
+ }
48
+
49
+ const turnPrefixSummary =
50
+ event.preparation.isSplitTurn && event.preparation.turnPrefixMessages.length > 0
51
+ ? await generateTurnPrefixSummary(
52
+ event.preparation.turnPrefixMessages,
53
+ model,
54
+ apiKey,
55
+ reserveTokens,
56
+ event.signal,
57
+ summarySettings.thinkingLevel,
58
+ )
59
+ : undefined;
60
+ if (turnPrefixSummary !== undefined && !turnPrefixSummary.trim()) {
61
+ throw new Error("Turn prefix summarization returned empty summary");
62
+ }
63
+
64
+ const mergedSummary = turnPrefixSummary
65
+ ? `${historySummary}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefixSummary}`
66
+ : historySummary;
67
+ const details = computeFileLists(event.preparation.fileOps);
68
+ const summary = `${mergedSummary}${formatFileOperations(details)}`;
69
+ return {
70
+ summary,
71
+ firstKeptEntryId: event.preparation.firstKeptEntryId,
72
+ tokensBefore: event.preparation.tokensBefore,
73
+ details,
74
+ };
75
+ }
76
+
77
+ function initializeSessionStatus(ctx: ExtensionContext, runtime: IRuntimeServices): void {
78
+ const basePolicy = runtime.loadEffectivePolicy(ctx, { warnOnInvalidConfig: false });
79
+ if (!basePolicy.enabled) {return;}
80
+ const { policy, profileName } = resolveEffectivePolicy(ctx, basePolicy);
81
+ runtime.setActiveProfileName(profileName);
82
+ runtime.updateStatus(ctx, policy);
83
+ }
84
+
85
+ export function registerEvents(pi: ExtensionAPI, runtime: IRuntimeServices): void {
86
+ pi.on("agent_end", async(event, ctx) => {
87
+ const basePolicy = runtime.loadEffectivePolicy(ctx, { warnOnInvalidConfig: false });
88
+ if (!basePolicy.enabled) {
89
+ runtime.clearInFlight();
90
+ runtime.setActiveProfileName(undefined);
91
+ runtime.updateStatus(ctx, basePolicy);
92
+ return;
93
+ }
94
+
95
+ const { policy, profileName } = resolveEffectivePolicy(ctx, basePolicy);
96
+ runtime.setActiveProfileName(profileName);
97
+
98
+ const shouldTrigger = shouldTriggerProactiveCompact({
99
+ lastAssistantMessage: getLastAssistantMessage(event.messages),
100
+ usage: ctx.getContextUsage(),
101
+ inFlight: runtime.isInFlight(),
102
+ nowMs: Date.now(),
103
+ lastProactiveAtMs: runtime.getLastProactiveAtMs(),
104
+ policy,
105
+ });
106
+ if (!shouldTrigger) {
107
+ runtime.updateStatus(ctx, policy);
108
+ return;
109
+ }
110
+
111
+ const triggered = runtime.triggerCompaction(ctx, policy, "proactive");
112
+ if (triggered) {
113
+ runtime.setLastProactiveAtMs(Date.now());
114
+ }
115
+ });
116
+
117
+ pi.on("session_before_compact", async(event, ctx) => {
118
+ const basePolicy = runtime.loadEffectivePolicy(ctx, { warnOnInvalidConfig: false });
119
+ if (!basePolicy.enabled) {
120
+ runtime.clearInFlight();
121
+ runtime.setActiveProfileName(undefined);
122
+ runtime.updateStatus(ctx, basePolicy);
123
+ return undefined;
124
+ }
125
+
126
+ const { policy, profileName, profileTemplates } = resolveEffectivePolicy(ctx, basePolicy);
127
+ runtime.setActiveProfileName(profileName);
128
+ runtime.setInFlight("session_before_compact");
129
+ runtime.updateStatus(ctx, policy);
130
+
131
+ const resolved = await resolveSummaryModel(ctx, policy, runtime.notify);
132
+ if (!resolved) {
133
+ runtime.clearInFlight();
134
+ return undefined;
135
+ }
136
+
137
+ const summarySettings = resolveSummarySettings(policy, resolved.entry);
138
+ const templateResolution = discoverTemplate(ctx.cwd, profileName, profileTemplates);
139
+ if (templateResolution.fallbackReason) {
140
+ runtime.notify(
141
+ ctx,
142
+ policy,
143
+ "warning",
144
+ `Could not load summary template ${templateResolution.resolvedPath} (${templateResolution.fallbackReason}). Falling back to built-in compaction format.`,
145
+ { dedupeKey: `template-fallback:${templateResolution.resolvedPath}:${templateResolution.fallbackReason}` },
146
+ );
147
+ }
148
+ if (templateResolution.updateFallbackReason) {
149
+ runtime.notify(
150
+ ctx,
151
+ policy,
152
+ "warning",
153
+ `Could not load update summary template ${templateResolution.updateResolvedPath} (${templateResolution.updateFallbackReason}). Falling back to initial summary template.`,
154
+ {
155
+ dedupeKey: `template-update-fallback:${templateResolution.updateResolvedPath}:${templateResolution.updateFallbackReason}`,
156
+ },
157
+ );
158
+ }
159
+
160
+ try {
161
+ const result = templateResolution.template
162
+ ? await generateCustomCompaction(
163
+ event,
164
+ templateResolution.template,
165
+ templateResolution.updateTemplate,
166
+ summarySettings,
167
+ resolved.model,
168
+ resolved.apiKey,
169
+ )
170
+ : await compact(
171
+ event.preparation,
172
+ resolved.model,
173
+ resolved.apiKey,
174
+ event.customInstructions,
175
+ event.signal,
176
+ );
177
+
178
+ return {
179
+ compaction: {
180
+ summary: result.summary,
181
+ tokensBefore: result.tokensBefore,
182
+ details: result.details,
183
+ firstKeptEntryId: result.firstKeptEntryId,
184
+ },
185
+ };
186
+ } catch (error) {
187
+ runtime.clearInFlight();
188
+ if (event.signal.aborted) {return undefined;}
189
+ const message = error instanceof Error ? error.message : String(error);
190
+ runtime.notify(ctx, policy, "error", `Compaction policy summary failed: ${message}`, {
191
+ critical: true,
192
+ });
193
+ return undefined;
194
+ }
195
+ });
196
+
197
+ pi.on("session_compact", async(_event, ctx) => {
198
+ runtime.clearInFlight();
199
+ runtime.markPostCompact();
200
+ const basePolicy = runtime.loadEffectivePolicy(ctx, { warnOnInvalidConfig: false });
201
+ const { policy, profileName } = resolveEffectivePolicy(ctx, basePolicy);
202
+ runtime.setActiveProfileName(profileName);
203
+ runtime.updateStatus(ctx, policy);
204
+ });
205
+
206
+ pi.on("session_start", async(_event, ctx) => {
207
+ runtime.clearSessionScopedState(ctx);
208
+ initializeSessionStatus(ctx, runtime);
209
+ });
210
+
211
+ pi.on("session_switch", async(_event, ctx) => {
212
+ runtime.clearSessionScopedState(ctx);
213
+ initializeSessionStatus(ctx, runtime);
214
+ });
215
+
216
+ pi.on("session_fork", async(_event, ctx) => {
217
+ runtime.clearSessionScopedState(ctx);
218
+ initializeSessionStatus(ctx, runtime);
219
+ });
220
+
221
+ pi.on("session_tree", async(_event, ctx) => {
222
+ runtime.clearSessionScopedState(ctx);
223
+ initializeSessionStatus(ctx, runtime);
224
+ });
225
+
226
+ pi.on("session_shutdown", async(_event, ctx) => {
227
+ runtime.clearSessionScopedState(ctx);
228
+ });
229
+ }
@@ -0,0 +1,10 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { registerCommands } from "./commands/register-commands.js";
3
+ import { registerEvents } from "./events/register-events.js";
4
+ import { createRuntimeServices } from "./runtime/session-state.js";
5
+
6
+ export default function compactionPolicyExtension(pi: ExtensionAPI) {
7
+ const runtime = createRuntimeServices();
8
+ registerCommands(pi, runtime);
9
+ registerEvents(pi, runtime);
10
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "pi-custom-compaction",
3
+ "version": "0.2.0",
4
+ "description": "Custom compaction for pi — swap the model, template, and trigger for context compaction",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi",
8
+ "pi-coding-agent",
9
+ "compaction",
10
+ "context-management",
11
+ "extension"
12
+ ],
13
+ "homepage": "https://github.com/nicobailon/pi-custom-compaction#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/nicobailon/pi-custom-compaction/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/nicobailon/pi-custom-compaction.git"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Nico Bailon",
23
+ "type": "module",
24
+ "files": [
25
+ "index.ts",
26
+ "commands/register-commands.ts",
27
+ "events/register-events.ts",
28
+ "policy/config.ts",
29
+ "policy/merge.ts",
30
+ "policy/parse.ts",
31
+ "policy/types.ts",
32
+ "runtime/model-resolution.ts",
33
+ "runtime/pure.ts",
34
+ "runtime/session-state.ts",
35
+ "summary/generate.ts",
36
+ "summary/template.ts",
37
+ "banner.png",
38
+ "README.md",
39
+ "CHANGELOG.md",
40
+ "LICENSE"
41
+ ],
42
+ "scripts": {
43
+ "test": "tsx --test test/**/*.test.ts"
44
+ },
45
+ "devDependencies": {
46
+ "@mariozechner/pi-agent-core": "^0.57.1",
47
+ "@mariozechner/pi-ai": "^0.57.1",
48
+ "@mariozechner/pi-coding-agent": "^0.57.1",
49
+ "@mariozechner/pi-tui": "^0.57.1",
50
+ "tsx": "^4.20.5"
51
+ },
52
+ "pi": {
53
+ "extensions": [
54
+ "./index.ts"
55
+ ]
56
+ }
57
+ }
@@ -0,0 +1,13 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+
4
+ const LEGACY_PI_AGENT_ROOT = join(homedir(), ".pi", "agent");
5
+
6
+ export function resolveAgentRootPath(): string {
7
+ const agentDir = process.env.PI_CODING_AGENT_DIR?.trim();
8
+ return agentDir && agentDir.length > 0 ? agentDir : LEGACY_PI_AGENT_ROOT;
9
+ }
10
+
11
+ export function resolveGlobalCompactionPolicyPath(): string {
12
+ return join(resolveAgentRootPath(), "compaction-policy.json");
13
+ }
@@ -0,0 +1,32 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parsePolicyPatch } from "./parse.js";
4
+ import { resolveGlobalCompactionPolicyPath } from "../paths.js";
5
+ import {
6
+ CONFIG_FILE,
7
+ type ICompactionPolicyPatch,
8
+ type IParseResult,
9
+ } from "./types.js";
10
+
11
+ function readConfigFile(configPath: string): IParseResult<ICompactionPolicyPatch> {
12
+ try {
13
+ const raw = readFileSync(configPath, "utf8");
14
+ const json = JSON.parse(raw);
15
+ const parsed = parsePolicyPatch(json);
16
+ if (!parsed.ok) {
17
+ return { ok: false, error: `Invalid ${configPath}: ${parsed.error}` };
18
+ }
19
+ return parsed;
20
+ } catch (error) {
21
+ const message = error instanceof Error ? error.message : String(error);
22
+ return { ok: false, error: `Invalid ${configPath}: ${message}` };
23
+ }
24
+ }
25
+
26
+ export function readProjectPolicyPatch(cwd: string): IParseResult<ICompactionPolicyPatch> {
27
+ const globalConfigPath = resolveGlobalCompactionPolicyPath();
28
+ const projectPath = join(cwd, CONFIG_FILE);
29
+ if (existsSync(projectPath)) {return readConfigFile(projectPath);}
30
+ if (existsSync(globalConfigPath)) {return readConfigFile(globalConfigPath);}
31
+ return { ok: true, value: {} };
32
+ }
@@ -0,0 +1,67 @@
1
+ import type {
2
+ ICompactionPolicy,
3
+ ICompactionPolicyPatch,
4
+ IPolicyKey,
5
+ IProfileOverride,
6
+ ISummaryThinkingLevel,
7
+ } from "./types.js";
8
+
9
+ export function setPatchValue(patch: ICompactionPolicyPatch, key: IPolicyKey, value: unknown): void {
10
+ switch (key) {
11
+ case "trigger.maxTokens":
12
+ patch.trigger = { ...patch.trigger, maxTokens: value as number };
13
+ return;
14
+ case "trigger.minTokens":
15
+ patch.trigger = { ...patch.trigger, minTokens: value as number };
16
+ return;
17
+ case "trigger.cooldownMs":
18
+ patch.trigger = { ...patch.trigger, cooldownMs: value as number };
19
+ return;
20
+ case "trigger.builtinReserveTokens":
21
+ patch.trigger = { ...patch.trigger, builtinReserveTokens: value as number };
22
+ return;
23
+ case "trigger.builtinSkipMarginPercent":
24
+ patch.trigger = { ...patch.trigger, builtinSkipMarginPercent: value as number };
25
+ return;
26
+ case "ui.name":
27
+ patch.ui = { ...patch.ui, name: value as string };
28
+ return;
29
+ case "ui.quiet":
30
+ patch.ui = { ...patch.ui, quiet: value as boolean };
31
+ return;
32
+ case "ui.showStatus":
33
+ patch.ui = { ...patch.ui, showStatus: value as boolean };
34
+ return;
35
+ case "ui.minimalStatus":
36
+ patch.ui = { ...patch.ui, minimalStatus: value as boolean };
37
+ return;
38
+ case "summary.thinkingLevel":
39
+ patch.summary = { ...patch.summary, thinkingLevel: value as ISummaryThinkingLevel };
40
+ return;
41
+ case "summary.preservationInstruction":
42
+ patch.summary = { ...patch.summary, preservationInstruction: value as string };
43
+ return;
44
+ default:
45
+ return;
46
+ }
47
+ }
48
+
49
+ export function mergePolicy(base: ICompactionPolicy, patch: ICompactionPolicyPatch): ICompactionPolicy {
50
+ return {
51
+ enabled: patch.enabled ?? base.enabled,
52
+ trigger: { ...base.trigger, ...(patch.trigger ?? {}) },
53
+ models: patch.models ?? base.models,
54
+ ui: { ...base.ui, ...(patch.ui ?? {}) },
55
+ summary: { ...base.summary, ...(patch.summary ?? {}) },
56
+ profiles: patch.profiles !== undefined ? patch.profiles : base.profiles,
57
+ };
58
+ }
59
+
60
+ export function applyProfileOverrides(policy: ICompactionPolicy, profile: IProfileOverride): ICompactionPolicy {
61
+ return {
62
+ ...policy,
63
+ trigger: { ...policy.trigger, ...(profile.trigger ?? {}) },
64
+ models: profile.models ?? policy.models,
65
+ summary: { ...policy.summary, ...(profile.summary ?? {}) },
66
+ };
67
+ }