omp-vcc 0.1.2 → 0.1.5
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 +67 -59
- package/extensions/main.ts +20 -8
- package/extensions/vcc-core/core/migrate-stale.ts +162 -0
- package/extensions/vcc-core/core/settings.ts +11 -0
- package/extensions/vcc-core/details.ts +10 -1
- package/extensions/vcc-core/hook.ts +336 -30
- package/package.json +14 -10
- package/scripts/e2e.ts +131 -0
- package/scripts/smoke.ts +21 -1
- package/scripts/uninstall-reset.js +251 -22
- package/skills/omp-vcc/SKILL.md +101 -20
- package/commands/omp-vcc.md +0 -19
- package/commands/vcc-recall.md +0 -21
package/README.md
CHANGED
|
@@ -1,105 +1,113 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Algorithmic VCC Compaction for oh-my-pi
|
|
2
2
|
|
|
3
|
-
> Fast, deterministic, lossless compaction
|
|
3
|
+
> Fast, deterministic, lossless compaction. No LLM calls. Port of [`sting8k/pi-vcc@0.7.0`](https://github.com/sting8k/pi-vcc) into [oh-my-pi](https://github.com/can1357/oh-my-pi). Paper [`arxiv:2603.29678`](https://arxiv.org/pdf/2603.29678) (Zhang & Agrawala).
|
|
4
4
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
|
7
|
-
> **New here?** → [`docs/setup.md`](docs/setup.md) — 5-minute install with prerequisites, 4 install options (link / npm / git), verify, first `/omp-vcc`, recall, config, and troubleshooting. The commands below are the tldr.
|
|
8
|
-
|
|
9
7
|
```sh
|
|
10
|
-
|
|
11
|
-
omp plugin
|
|
8
|
+
omp plugin link /Users/zhu/code/projects/omp-vcc # local
|
|
9
|
+
omp plugin install omp-vcc # npmjs, no auth
|
|
10
|
+
omp plugin install @zhulinchng/omp-vcc # GitHub Packages, needs PAT
|
|
11
|
+
omp plugin install github:zhulinchng/omp-vcc # git, latest main
|
|
12
12
|
omp plugin list --json | jq '.[] | select(.name|contains("omp-vcc"))'
|
|
13
|
-
|
|
14
|
-
# from npm (unscoped)
|
|
15
|
-
omp plugin install omp-vcc
|
|
16
|
-
|
|
17
|
-
# from GitHub Packages (scoped, requires auth — see below)
|
|
18
|
-
omp plugin install @zhulinchng/omp-vcc
|
|
19
|
-
|
|
20
|
-
# from git
|
|
21
|
-
omp plugin install github:zhulinchng/omp-vcc
|
|
22
13
|
```
|
|
23
14
|
|
|
24
|
-
|
|
15
|
+
New here → [`docs/setup.md`](docs/setup.md).
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
- **Manual** `/omp-vcc [keep:N] [focus]` (and `/pi-vcc` alias) — e.g. `/omp-vcc keep:2 fix auth` keeps last 2 user turns.
|
|
28
|
-
- **Recall** `vcc_recall({query:"redis cache", scope:"all", page:1})` or `/vcc-recall hook|inject scope:all page:2` — ranked regex → TF-IDF OR, 5/page, `mode:'touched'` and `#N:path` drill-down.
|
|
17
|
+
## What you get
|
|
29
18
|
|
|
19
|
+
- **Manual** `/omp-vcc [keep:N] [focus]` (`/pi-vcc` alias) — compact only, single-line toast `90k→22k` — e.g. `/omp-vcc keep:2 fix auth` (detailed savings via `/vcc-stats`).
|
|
20
|
+
- **Recall** `vcc_recall({query, scope, page, mode, expand})` + `/vcc-recall` — regex → TF-IDF OR, 5/page, `mode:'touched'`, `#N:path` drill-down.
|
|
21
|
+
- **Savings** toast `90k→22k (76% saved)` single line (inline `Last compaction` detail removed; use `/vcc-stats`), divider, `/vcc-stats` + `vcc_stats` tool (50-capped history, per-pi), `details.savings` persisted, `/tmp/omp-vcc-debug.json`.
|
|
30
22
|
## Commands
|
|
31
23
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
| `/
|
|
35
|
-
| `/pi-vcc` |
|
|
36
|
-
| `/vcc-
|
|
37
|
-
| `/pi-vcc-recall` | Alias |
|
|
24
|
+
| `/omp-vcc [keep:N] [focus]` | Compact only. `keep:0` = all. Smart-keep boosts `keep:1` (5k→25k) unless explicit. Toast single line; detail via `/vcc-stats`. |
|
|
25
|
+
| `/pi-vcc` | Legacy alias for `/omp-vcc` |
|
|
26
|
+
| `/vcc-recall [query] [scope:all] [page:N] [mode:touched]` | Search compacted history |
|
|
27
|
+
| `/pi-vcc-recall` | Legacy alias for `/vcc-recall` |
|
|
28
|
+
| `/vcc-stats [history\|all]` | Last savings + history table (single, no alias) |
|
|
38
29
|
|
|
39
|
-
|
|
30
|
+
Tools: `vcc_recall`, `vcc_stats` (approval `read`). Extension-only — no `commands/*.md` file slash commands (avoids duplicate `/omp-vcc`).
|
|
40
31
|
|
|
41
32
|
## Configuration
|
|
42
33
|
|
|
43
|
-
File `~/.omp/omp-vcc/config.json`
|
|
34
|
+
File `~/.omp/omp-vcc/config.json` — XDG: `$OMP_VCC_CONFIG_PATH` > `$PI_VCC_CONFIG_PATH` > `$OMP_DIR`/`$PI_CODING_AGENT_DIR` > `~/.omp/omp-vcc/config.json` (migrates `~/.pi/agent/pi-vcc-config.json` once).
|
|
44
35
|
|
|
45
36
|
```json
|
|
46
37
|
{
|
|
38
|
+
"vccEnabled": true,
|
|
47
39
|
"overrideDefaultCompaction": true,
|
|
48
40
|
"smartKeepTail": true,
|
|
49
41
|
"continueAfterThresholdCompact": true,
|
|
50
|
-
"debug": false
|
|
42
|
+
"debug": false,
|
|
43
|
+
"chainShakeHint": false
|
|
51
44
|
}
|
|
52
45
|
```
|
|
53
46
|
|
|
47
|
+
| Flag | Default | Effect |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `vccEnabled` | `true` | Master switch. `false` = no interception. |
|
|
50
|
+
| `overrideDefaultCompaction` | `true` | `true` = VCC handles threshold/overflow. `false` = host `methodOrder` handles them; `/omp-vcc` still works. |
|
|
51
|
+
| `smartKeepTail` | `true` | Grow `keep:1` when tail <5k (cap 25k). Explicit `keep:N` never boosted. |
|
|
52
|
+
| `continueAfterThresholdCompact` | `true` | Invisible-continue (`omp-vcc-auto-continue`) after threshold compact. |
|
|
53
|
+
| `debug` | `false` | Write `/tmp/omp-vcc-debug.json` per compaction. |
|
|
54
|
+
| `chainShakeHint` | `false` | Eager post-VCC `shake` chain. Host rescue already auto-shakes on dead-end; this forces it. |
|
|
55
|
+
|
|
56
|
+
Toggle live: `omp config set plugins."@zhulinchng/omp-vcc".debug true` or `/settings` → `@zhulinchng/omp-vcc`. File is source of truth; `ctx.settings` overlays at runtime.
|
|
57
|
+
|
|
54
58
|
## How it works
|
|
55
59
|
|
|
56
|
-
VCC compiles
|
|
60
|
+
VCC compiles JSONL trace via **lex → parse IR → monotonic line assignment → view lowering** into `V_full` (identity), `V_ui` (5 sections + ranked brief), `V_adapt(b,ρ)` (recall). Pointer invariant `V_ui → V_full[s:e]` via `(#N)` refs.
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
```
|
|
63
|
+
Calibrate cpt → SmartKeep → BuildOwnCut → Normalize → FilterNoise → BuildSections → Brief (1100→2000 tok, 120 lines) → Merge
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
See [`docs/harness.md`](docs/harness.md) for host impact and [`docs/architecture.md`](docs/architecture.md) for pipeline.
|
|
67
|
+
|
|
68
|
+
## Combining with shake and snapcompact
|
|
61
69
|
|
|
62
|
-
`
|
|
70
|
+
VCC summarizes **history**; `shake` elides `artifact://` blocks in **kept tail** (disjoint); `snapcompact` is an alternative history archiver (vision bitmaps, needs `model.input includes "image"`). One `CompactionEntry` per trigger — combos are additive or sequential, never double-summarization.
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
| Trigger | `override` | Result |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| threshold, `override:true` (default) | VCC handles; host auto-shakes if still over band (`#rescueCompactionDeadEnd`) | VCC + shake if dead-end |
|
|
75
|
+
| threshold, `override:false` | Host walks `methodOrder` (`remote→snapcompact→handoff→shake→soft`) | VCC → snapcompact/shake fallback |
|
|
76
|
+
| `/omp-vcc keep:2` | Always VCC (sentinel `__omp_vcc__`) | VCC |
|
|
77
|
+
| `/omp-vcc` then `/compact snapcompact` | Sequential | VCC entry, then snapcompact entry |
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
- **VCC** — Python `VCC.py` adaptive/transposed views, `SEP`, `match_lines`, `_tokenize`, `_trunc`, projection model
|
|
68
|
-
- **Paper** — AppWorld evaluation: +1.1–4.2 task_goal points, ½–⅔ token halving, smaller memory
|
|
79
|
+
Additive VCC+shake is automatic. Eager chain: `chainShakeHint:true`. Explicit mode (`snapcompact`/`shake`/`soft`/…) bypasses VCC even when `override:true` (`hook.ts:733`). Full table: [`docs/setup.md`](docs/setup.md) + [`docs/harness.md §8`](docs/harness.md#8-working-with-existing-compaction-strategies).
|
|
69
80
|
|
|
70
|
-
##
|
|
81
|
+
## Development
|
|
71
82
|
|
|
72
|
-
```
|
|
73
|
-
omp plugin link .
|
|
74
|
-
omp plugin doctor
|
|
83
|
+
```sh
|
|
75
84
|
bunx tsc --noEmit
|
|
76
|
-
bun
|
|
77
|
-
bun test
|
|
85
|
+
bun test # 515 tests, 48 files, 1443 expects, 0 fail
|
|
86
|
+
bun test tests/e2e --timeout 120000 # 111 E2E
|
|
87
|
+
bun run smoke # 9 checks: 3 hooks + 4 cmds + 2 tools + pipeline
|
|
88
|
+
omp plugin link . && omp plugin doctor
|
|
78
89
|
```
|
|
79
90
|
|
|
80
|
-
|
|
91
|
+
`type:module`, `allowImportingTsExtensions`, `// @ts-nocheck` on vendored `extensions/vcc-core/**`, zero `dist/`. `prepublishOnly` runs `tsc && test && smoke`.
|
|
81
92
|
|
|
82
93
|
## Verification
|
|
83
94
|
|
|
84
95
|
```sh
|
|
85
|
-
bunx tsc --noEmit
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
bunx tsc --noEmit && bun test && bun run smoke
|
|
97
|
+
omp -e @zhulinchng/omp-vcc
|
|
98
|
+
/omp-vcc keep:1 # expect [Session Goal] + toast omp-vcc: kept 1/2 turns
|
|
99
|
+
/vcc-stats # table + history
|
|
100
|
+
cat /tmp/omp-vcc-debug.json # when debug:true
|
|
89
101
|
```
|
|
90
102
|
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
Full matrix: [`docs/verification.md`](docs/verification.md). Harness map: [`docs/harness.md §9`](docs/harness.md#9-verification-map-claim--evidence).
|
|
104
|
+
|
|
105
|
+
## Docs
|
|
106
|
+
|
|
107
|
+
[`setup`](docs/setup.md) · [`configuration`](docs/configuration.md) · [`architecture`](docs/architecture.md) · [`harness`](docs/harness.md) · [`verification`](docs/verification.md) · [`omp-compaction`](docs/omp-compaction.md) · [`omp-snapcompact`](docs/omp-snapcompact.md) · [`paper-notes`](docs/paper-notes.md) · [`PUBLISHING`](docs/PUBLISHING.md)
|
|
93
108
|
|
|
94
|
-
|
|
95
|
-
- GitHub Packages (scoped): `npm publish --registry https://npm.pkg.github.com` (package `@zhulinchng/omp-vcc`, needs `GITHUB_TOKEN` with `read:packages, write:packages`)
|
|
96
|
-
- Consumer GPR auth: add to `~/.npmrc`:
|
|
97
|
-
```
|
|
98
|
-
@zhulinchng:registry=https://npm.pkg.github.com
|
|
99
|
-
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT
|
|
100
|
-
```
|
|
101
|
-
- Marketplace: add an entry to `.omp-plugin/marketplace.json` (see `plugin-skill/assets/templates/marketplace-entry.json.template`).
|
|
109
|
+
Publishing: `npm publish --access public` (unscoped `omp-vcc`), `gh release create vX.Y.Z` → GPR (`@zhulinchng/omp-vcc`). Consumer GPR auth: `@zhulinchng:registry=https://npm.pkg.github.com`.
|
|
102
110
|
|
|
103
111
|
## License
|
|
104
112
|
|
|
105
|
-
MIT
|
|
113
|
+
MIT
|
package/extensions/main.ts
CHANGED
|
@@ -6,14 +6,20 @@
|
|
|
6
6
|
|
|
7
7
|
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
8
8
|
import { scaffoldSettings } from "./vcc-core/core/settings";
|
|
9
|
+
import { migrateStalePluginEntries } from "./vcc-core/core/migrate-stale";
|
|
10
|
+
import { loadAllMessages } from "./vcc-core/core/load-messages";
|
|
9
11
|
import {
|
|
10
12
|
registerBeforeCompactHook,
|
|
11
13
|
PI_VCC_COMPACT_INSTRUCTION,
|
|
12
14
|
OMP_VCC_COMPACT_INSTRUCTION,
|
|
13
15
|
getLastCompactionStats,
|
|
16
|
+
getCompactionHistory,
|
|
17
|
+
formatLastStatsDetail,
|
|
18
|
+
formatStatsTable,
|
|
14
19
|
scheduleCompactionStatsNotify,
|
|
20
|
+
registerVccStatsTool as registerVccStatsToolHook,
|
|
21
|
+
registerVccStatsCommand as registerVccStatsCommandHook,
|
|
15
22
|
} from "./vcc-core/hook";
|
|
16
|
-
import { loadAllMessages } from "./vcc-core/core/load-messages";
|
|
17
23
|
import { searchEntriesDetailed, getTouchedFiles } from "./vcc-core/core/search-entries";
|
|
18
24
|
import { formatRecallOutput, formatTouchedOutput } from "./vcc-core/core/format-recall";
|
|
19
25
|
import { getActiveLineageEntryIds } from "./vcc-core/core/lineage";
|
|
@@ -40,9 +46,11 @@ const parseRecallCommandArgs = (
|
|
|
40
46
|
|
|
41
47
|
const DEFAULT_RECENT = 25;
|
|
42
48
|
const PAGE_SIZE = 5;
|
|
43
|
-
|
|
44
49
|
export default function (pi: ExtensionAPI): void {
|
|
45
50
|
scaffoldSettings();
|
|
51
|
+
try {
|
|
52
|
+
migrateStalePluginEntries();
|
|
53
|
+
} catch {}
|
|
46
54
|
registerBeforeCompactHook(pi);
|
|
47
55
|
|
|
48
56
|
// ── vcc_recall tool — implements VCC V_adapt via rho predicate (paper §2.1 eq.2) ──
|
|
@@ -152,27 +160,27 @@ export default function (pi: ExtensionAPI): void {
|
|
|
152
160
|
return { content: [{ type: "text", text: output }], details: undefined };
|
|
153
161
|
},
|
|
154
162
|
} as unknown as Parameters<ExtensionAPI["registerTool"]>[0]);
|
|
163
|
+
// ── vcc_stats tool — stats surface for savings (paper § verification) ──
|
|
164
|
+
registerVccStatsToolHook(pi);
|
|
155
165
|
|
|
156
|
-
// ── /omp-vcc command — manual algorithmic compaction (V_ui) ──
|
|
157
166
|
pi.registerCommand("omp-vcc", {
|
|
158
167
|
description: "Compact conversation with omp-vcc structured summary (keep:N + optional focus)",
|
|
159
168
|
handler: async (args: string, ctx: unknown) => {
|
|
160
169
|
const c = ctx as {
|
|
161
170
|
compact: (instructions?: string) => Promise<void>;
|
|
162
171
|
ui: { notify: (msg: string, level?: string) => void };
|
|
172
|
+
sessionManager?: { getSessionFile?: () => string | undefined };
|
|
163
173
|
};
|
|
164
174
|
const parsed = parseKeepAndPrompt(args);
|
|
165
175
|
const keep = parsed.keepUserTurns;
|
|
166
176
|
const followUpPrompt = parsed.followUpPrompt;
|
|
167
177
|
const customInstructions = buildOmpCustomInstructions(keep);
|
|
168
|
-
// Also accept pi sentinel for legacy: map keep via buildPiVcc if needed? Use omp.
|
|
169
|
-
// Notify before compact for UX parity with pi-vcc
|
|
170
178
|
try {
|
|
171
179
|
c.ui.notify(`omp-vcc: compacting with keep:${keep ?? 1}${followUpPrompt ? ` + focus` : ""}...`, "info");
|
|
172
180
|
} catch {}
|
|
173
181
|
try {
|
|
174
182
|
await c.compact(customInstructions);
|
|
175
|
-
const stats = getLastCompactionStats();
|
|
183
|
+
const stats = getLastCompactionStats(pi);
|
|
176
184
|
if (stats) {
|
|
177
185
|
scheduleCompactionStatsNotify(c as unknown as { ui: { notify: (msg: string, level?: string) => void } }, stats);
|
|
178
186
|
} else {
|
|
@@ -209,7 +217,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
209
217
|
const customInstructions = buildPiVccCustomInstructions(keep);
|
|
210
218
|
try {
|
|
211
219
|
await c.compact(customInstructions);
|
|
212
|
-
const stats = getLastCompactionStats();
|
|
220
|
+
const stats = getLastCompactionStats(pi);
|
|
213
221
|
if (stats) scheduleCompactionStatsNotify(c as unknown as { ui: { notify: (msg: string, level?: string) => void } }, stats);
|
|
214
222
|
else try { c.ui.notify("Compacted with pi-vcc (via omp-vcc)", "info"); } catch {}
|
|
215
223
|
if (followUpPrompt) {
|
|
@@ -309,11 +317,15 @@ export default function (pi: ExtensionAPI): void {
|
|
|
309
317
|
const header = totalPages > 1 ? `Page ${page}/${totalPages} (${hits.length} total matches${scopeSuffix}${truncationNote})` : `${hits.length} matches${scopeSuffix}${truncationNote}`;
|
|
310
318
|
const footer = page < totalPages ? `\n--- /pi-vcc-recall ${query}${scopeArg} page:${page + 1} ---` : "";
|
|
311
319
|
const output = formatRecallOutput(pageResults, query, header) + footer;
|
|
320
|
+
|
|
312
321
|
try { piAny.sendMessage?.({ customType: "vcc-recall", content: output, display: true }, { triggerTurn: false }); } catch {}
|
|
313
322
|
},
|
|
314
323
|
});
|
|
324
|
+
// ── /vcc-stats commands — show savings table (PR3) ──
|
|
325
|
+
registerVccStatsCommandHook(pi);
|
|
326
|
+
|
|
315
327
|
}
|
|
316
328
|
// ── Re-exports for pi-vcc test compatibility (not dead: tests import via hook directly,
|
|
317
329
|
// but external consumers and the `vcc-recall` shim may import via main) ──
|
|
318
|
-
export { registerBeforeCompactHook, PI_VCC_COMPACT_INSTRUCTION, OMP_VCC_COMPACT_INSTRUCTION, getLastCompactionStats,
|
|
330
|
+
export { registerBeforeCompactHook, PI_VCC_COMPACT_INSTRUCTION, OMP_VCC_COMPACT_INSTRUCTION, getLastCompactionStats, getCompactionHistory, formatCompactionStats, formatStatsTable, formatLastStatsDetail, scheduleCompactionStatsNotify, AUTO_CONTINUE_CUSTOM_TYPE, LEGACY_AUTO_CONTINUE_CUSTOM_TYPE, invalidExpandIndices, registerRecallTool, registerVccRecallCommand, registerPiVccCommand, registerVccStatsTool, registerVccStatsCommand, clearCompactionHistoryForTests } from "./vcc-core/hook";
|
|
319
331
|
export { buildPiVccCustomInstructions, parseKeepAndPrompt } from "./vcc-core/core/compact-args";
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// One-time migration: remove stale @zhu/omp-vcc lock+symlink left by renames.
|
|
3
|
+
// correctly installed `omp-vcc` or npm `dependencies` entries (real dirs).
|
|
4
|
+
// Mirrors scripts/uninstall-reset.js:cleanupStalePluginEntries.
|
|
5
|
+
|
|
6
|
+
import * as fsSync from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
const HISTORIC = ["@zhu/omp-vcc", "@zhulinchng/omp-vcc"];
|
|
11
|
+
const CURRENT = "omp-vcc";
|
|
12
|
+
|
|
13
|
+
function readJson(p: string): unknown | null {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(fsSync.readFileSync(p, "utf8"));
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isSymlink(p: string): boolean {
|
|
22
|
+
try {
|
|
23
|
+
return fsSync.lstatSync(p).isSymbolicLink();
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function migrateStalePluginEntries(home?: string): string {
|
|
30
|
+
const baseHome = home || homedir();
|
|
31
|
+
const pluginsDir = join(baseHome, ".omp", "plugins");
|
|
32
|
+
const lockPath = join(pluginsDir, "omp-plugins.lock.json");
|
|
33
|
+
const pkgPath = join(pluginsDir, "package.json");
|
|
34
|
+
const nm = join(pluginsDir, "node_modules");
|
|
35
|
+
|
|
36
|
+
const lockRaw = readJson(lockPath) as { plugins?: Record<string, unknown>; settings?: Record<string, unknown> } | null;
|
|
37
|
+
if (!lockRaw || typeof lockRaw !== "object") return "no-lock";
|
|
38
|
+
|
|
39
|
+
const pkgRaw = readJson(pkgPath) as { dependencies?: Record<string, string> } | null;
|
|
40
|
+
const deps: Record<string, string> = (pkgRaw && typeof pkgRaw.dependencies === "object" && pkgRaw.dependencies) || {};
|
|
41
|
+
|
|
42
|
+
const pluginKeys: string[] = lockRaw.plugins ? Object.keys(lockRaw.plugins) : [];
|
|
43
|
+
const ompKeys = pluginKeys.filter((k) => k.includes("omp-vcc"));
|
|
44
|
+
const candidates = new Set([...ompKeys, ...HISTORIC, CURRENT]);
|
|
45
|
+
|
|
46
|
+
let removedLocks: string[] = [];
|
|
47
|
+
let removedLinks: string[] = [];
|
|
48
|
+
|
|
49
|
+
const getPkgName = (name: string): string | null => {
|
|
50
|
+
const j = readJson(join(nm, name, "package.json")) as { name?: string } | null;
|
|
51
|
+
return j && typeof j.name === "string" ? j.name : null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const getReal = (name: string): string | null => {
|
|
55
|
+
const p = join(nm, name);
|
|
56
|
+
if (!isSymlink(p)) return null;
|
|
57
|
+
try {
|
|
58
|
+
return fsSync.realpathSync(p);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const realToKeys = new Map<string, string[]>();
|
|
65
|
+
for (const k of ompKeys) {
|
|
66
|
+
const rp = getReal(k);
|
|
67
|
+
if (!rp) continue;
|
|
68
|
+
const list = realToKeys.get(rp) || [];
|
|
69
|
+
list.push(k);
|
|
70
|
+
realToKeys.set(rp, list);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const k of [...ompKeys]) {
|
|
74
|
+
if (k in deps) continue;
|
|
75
|
+
const pkgName = getPkgName(k);
|
|
76
|
+
if (pkgName && pkgName !== k) {
|
|
77
|
+
const isHistoric = HISTORIC.includes(k) || k !== CURRENT;
|
|
78
|
+
if (isHistoric) {
|
|
79
|
+
const p = join(nm, k);
|
|
80
|
+
if (isSymlink(p)) {
|
|
81
|
+
try {
|
|
82
|
+
fsSync.rmSync(p, { force: true });
|
|
83
|
+
removedLinks.push(k);
|
|
84
|
+
if (k.startsWith("@")) {
|
|
85
|
+
const scopeDir = join(nm, k.split("/")[0]!);
|
|
86
|
+
try {
|
|
87
|
+
if (fsSync.readdirSync(scopeDir).length === 0) fsSync.rmSync(scopeDir, { force: true });
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
} catch {}
|
|
91
|
+
}
|
|
92
|
+
if (lockRaw.plugins && (lockRaw.plugins as Record<string, unknown>)[k]) {
|
|
93
|
+
delete (lockRaw.plugins as Record<string, unknown>)[k];
|
|
94
|
+
removedLocks.push(k);
|
|
95
|
+
}
|
|
96
|
+
if (lockRaw.settings && (lockRaw.settings as Record<string, unknown>)[k]) delete (lockRaw.settings as Record<string, unknown>)[k];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const [, keys] of realToKeys) {
|
|
102
|
+
if (keys.length <= 1) continue;
|
|
103
|
+
let keeper: string | null = null;
|
|
104
|
+
for (const k of keys) {
|
|
105
|
+
if (getPkgName(k) === k) {
|
|
106
|
+
keeper = k;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!keeper) keeper = keys.includes(CURRENT) ? CURRENT : keys[0]!;
|
|
111
|
+
for (const k of keys) {
|
|
112
|
+
if (k === keeper) continue;
|
|
113
|
+
if (k in deps) continue;
|
|
114
|
+
const p = join(nm, k);
|
|
115
|
+
if (isSymlink(p)) {
|
|
116
|
+
try {
|
|
117
|
+
fsSync.rmSync(p, { force: true });
|
|
118
|
+
if (!removedLinks.includes(k)) removedLinks.push(k);
|
|
119
|
+
if (k.startsWith("@")) {
|
|
120
|
+
const scopeDir = join(nm, k.split("/")[0]!);
|
|
121
|
+
try {
|
|
122
|
+
if (fsSync.readdirSync(scopeDir).length === 0) fsSync.rmSync(scopeDir, { force: true });
|
|
123
|
+
} catch {}
|
|
124
|
+
}
|
|
125
|
+
} catch {}
|
|
126
|
+
}
|
|
127
|
+
if (lockRaw.plugins && (lockRaw.plugins as Record<string, unknown>)[k]) {
|
|
128
|
+
delete (lockRaw.plugins as Record<string, unknown>)[k];
|
|
129
|
+
if (!removedLocks.includes(k)) removedLocks.push(k);
|
|
130
|
+
}
|
|
131
|
+
if (lockRaw.settings && (lockRaw.settings as Record<string, unknown>)[k]) delete (lockRaw.settings as Record<string, unknown>)[k];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const cand of candidates) {
|
|
136
|
+
const p = join(nm, cand);
|
|
137
|
+
if (!isSymlink(p)) continue;
|
|
138
|
+
const hasLock = !!(lockRaw.plugins && (lockRaw.plugins as Record<string, unknown>)[cand]);
|
|
139
|
+
if (hasLock) continue;
|
|
140
|
+
const pkgName = getPkgName(cand);
|
|
141
|
+
if (pkgName === CURRENT || pkgName === "@zhu/omp-vcc" || pkgName === "@zhulinchng/omp-vcc") {
|
|
142
|
+
try {
|
|
143
|
+
fsSync.rmSync(p, { force: true });
|
|
144
|
+
if (!removedLinks.includes(cand)) removedLinks.push(cand);
|
|
145
|
+
if (cand.startsWith("@")) {
|
|
146
|
+
const scopeDir = join(nm, cand.split("/")[0]!);
|
|
147
|
+
try {
|
|
148
|
+
if (fsSync.readdirSync(scopeDir).length === 0) fsSync.rmSync(scopeDir, { force: true });
|
|
149
|
+
} catch {}
|
|
150
|
+
}
|
|
151
|
+
} catch {}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (removedLocks.length > 0 || removedLinks.length > 0) {
|
|
156
|
+
try {
|
|
157
|
+
fsSync.writeFileSync(lockPath, JSON.stringify(lockRaw, null, 2));
|
|
158
|
+
} catch {}
|
|
159
|
+
return `migrated locks:${removedLocks.join(",") || "none"} links:${removedLinks.join(",") || "none"}`;
|
|
160
|
+
}
|
|
161
|
+
return "no-stale";
|
|
162
|
+
}
|
|
@@ -56,6 +56,16 @@ export interface PiVccSettings {
|
|
|
56
56
|
continueAfterThresholdCompact: boolean;
|
|
57
57
|
/** Write debug snapshot to /tmp/omp-vcc-debug.json on each compaction. */
|
|
58
58
|
debug: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* When true, after a successful VCC threshold/overflow compaction, eagerly
|
|
61
|
+
* trigger a follow-up shake via ctx.compact when the host rescue would not.
|
|
62
|
+
* Default false: host's #rescueCompactionDeadEnd already runs shake elide
|
|
63
|
+
* automatically when VCC didn't create enough headroom, and leaving shake in
|
|
64
|
+
* methodOrder covers that case without a second entry. Set true only if you
|
|
65
|
+
* want a chained shake even when VCC already made headroom (costs a second
|
|
66
|
+
* CompactionEntry).
|
|
67
|
+
*/
|
|
68
|
+
chainShakeHint: boolean;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
export const DEFAULT_SETTINGS: PiVccSettings = {
|
|
@@ -64,6 +74,7 @@ export const DEFAULT_SETTINGS: PiVccSettings = {
|
|
|
64
74
|
smartKeepTail: true,
|
|
65
75
|
continueAfterThresholdCompact: true,
|
|
66
76
|
debug: false,
|
|
77
|
+
chainShakeHint: false,
|
|
67
78
|
};
|
|
68
79
|
|
|
69
80
|
const readJson = (path: string): Record<string, unknown> | null => {
|
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
import type { CompactionReason } from "./types";
|
|
3
3
|
|
|
4
4
|
export interface PiVccCompactionDetails {
|
|
5
|
-
compactor: "pi-vcc";
|
|
5
|
+
compactor: "pi-vcc" | "omp-vcc";
|
|
6
6
|
version: number;
|
|
7
7
|
sections: string[];
|
|
8
8
|
sourceMessageCount: number;
|
|
9
9
|
previousSummaryUsed: boolean;
|
|
10
10
|
reason?: CompactionReason;
|
|
11
11
|
willRetry?: boolean;
|
|
12
|
+
savings?: {
|
|
13
|
+
tokensBefore: number;
|
|
14
|
+
summaryChars: number;
|
|
15
|
+
summaryTokensEst: number;
|
|
16
|
+
keptTokensEst: number;
|
|
17
|
+
tokensAfterEst: number;
|
|
18
|
+
tokensSavedEst: number;
|
|
19
|
+
savedPercentEst: number;
|
|
20
|
+
};
|
|
12
21
|
}
|