@pi-unipi/unipi 2.6.0 → 2.6.2
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 +19 -0
- package/package.json +21 -22
- package/packages/ask-user/package.json +2 -2
- package/packages/ask-user/settings-tui.ts +18 -34
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +2 -15
- package/packages/autocomplete/src/provider.ts +3 -17
- package/packages/btw/extensions/btw.ts +18 -45
- package/packages/btw/package.json +2 -2
- package/packages/compactor/README.md +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/compactor/skills/compactor/SKILL.md +1 -1
- package/packages/compactor/skills/compactor-detail/SKILL.md +3 -5
- package/packages/compactor/skills/compactor-doctor/SKILL.md +1 -1
- package/packages/compactor/skills/compactor-stats/SKILL.md +1 -1
- package/packages/compactor/src/commands/index.ts +47 -78
- package/packages/compactor/src/compaction/brief.ts +161 -90
- package/packages/compactor/src/compaction/build-sections.ts +3 -4
- package/packages/compactor/src/compaction/compact-args.ts +86 -0
- package/packages/compactor/src/compaction/cut.ts +270 -28
- package/packages/compactor/src/compaction/drill-down.ts +261 -0
- package/packages/compactor/src/compaction/format-recall.ts +96 -0
- package/packages/compactor/src/compaction/format.ts +8 -3
- package/packages/compactor/src/compaction/hooks.ts +248 -72
- package/packages/compactor/src/compaction/merge.ts +34 -4
- package/packages/compactor/src/compaction/rank.ts +270 -0
- package/packages/compactor/src/compaction/recall-scope.ts +28 -0
- package/packages/compactor/src/compaction/search-entries.ts +333 -96
- package/packages/compactor/src/compaction/skill-collapse.ts +35 -0
- package/packages/compactor/src/compaction/summarize.ts +37 -6
- package/packages/compactor/src/compaction/token-estimate.ts +104 -0
- package/packages/compactor/src/compaction/touched-files.ts +35 -0
- package/packages/compactor/src/config/manager.ts +2 -27
- package/packages/compactor/src/config/presets.ts +0 -2
- package/packages/compactor/src/config/schema.ts +2 -16
- package/packages/compactor/src/executor/executor.ts +6 -15
- package/packages/compactor/src/executor/runtime.ts +2 -12
- package/packages/compactor/src/index.ts +4 -121
- package/packages/compactor/src/info-screen.ts +3 -10
- package/packages/compactor/src/security/evaluator.ts +0 -53
- package/packages/compactor/src/security/policy.ts +7 -8
- package/packages/compactor/src/session/db.ts +0 -6
- package/packages/compactor/src/tools/ctx-execute-file.ts +0 -5
- package/packages/compactor/src/tools/register.ts +27 -50
- package/packages/compactor/src/tools/vcc-recall.ts +86 -48
- package/packages/compactor/src/tui/settings-overlay.ts +20 -40
- package/packages/compactor/src/types.ts +43 -100
- package/packages/core/constants.ts +1 -28
- package/packages/core/events.ts +1 -83
- package/packages/core/index.ts +1 -0
- package/packages/core/model-cache.ts +18 -12
- package/packages/core/package.json +3 -1
- package/packages/core/tui-overlay.ts +89 -0
- package/packages/core/tui-width.ts +18 -0
- package/packages/core/utils.ts +43 -0
- package/packages/footer/package.json +2 -2
- package/packages/footer/src/help.ts +2 -2
- package/packages/footer/src/index.ts +9 -10
- package/packages/footer/src/presets.ts +1 -27
- package/packages/footer/src/registry/index.ts +0 -23
- package/packages/footer/src/rendering/icons.ts +0 -13
- package/packages/footer/src/rendering/renderer.ts +5 -40
- package/packages/footer/src/rendering/separators.ts +0 -10
- package/packages/footer/src/rendering/theme.ts +1 -15
- package/packages/footer/src/segments/compactor.ts +7 -14
- package/packages/footer/src/segments/core.ts +15 -54
- package/packages/footer/src/segments/kanboard.ts +5 -5
- package/packages/footer/src/segments/mcp.ts +5 -5
- package/packages/footer/src/segments/memory.ts +4 -4
- package/packages/footer/src/segments/notify.ts +2 -2
- package/packages/footer/src/segments/ralph.ts +3 -3
- package/packages/footer/src/segments/status-ext.ts +1 -1
- package/packages/footer/src/segments/workflow.ts +3 -3
- package/packages/footer/src/tps-tracker.ts +0 -8
- package/packages/footer/src/tui/settings-tui.ts +14 -31
- package/packages/footer/src/types.ts +0 -15
- package/packages/image/package.json +2 -2
- package/packages/image/src/register-providers.ts +0 -11
- package/packages/image/src/tools.ts +0 -3
- package/packages/image/src/tui/model-selector.ts +36 -71
- package/packages/info-screen/config.ts +0 -16
- package/packages/info-screen/core-groups.ts +0 -10
- package/packages/info-screen/index.ts +22 -13
- package/packages/info-screen/package.json +2 -2
- package/packages/info-screen/registry.ts +1 -72
- package/packages/info-screen/tui/info-overlay.ts +85 -100
- package/packages/info-screen/usage-parser.ts +12 -47
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/input-shortcuts/src/index.ts +3 -3
- package/packages/input-shortcuts/src/registers.ts +0 -8
- package/packages/input-shortcuts/src/settings-overlay.ts +4 -13
- package/packages/input-shortcuts/src/undo-redo.ts +0 -12
- package/packages/kanboard/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/mcp/src/bridge/client.ts +0 -15
- package/packages/mcp/src/config/manager.ts +0 -38
- package/packages/mcp/src/config/schema.ts +0 -27
- package/packages/mcp/src/config/sync.ts +0 -29
- package/packages/mcp/src/index.ts +1 -1
- package/packages/mcp/src/tui/add-overlay.ts +6 -6
- package/packages/mcp/src/types.ts +0 -6
- package/packages/memory/embedding.ts +0 -31
- package/packages/memory/mempalace.ts +0 -16
- package/packages/memory/package.json +4 -8
- package/packages/memory/storage.ts +56 -753
- package/packages/milestone/hooks.ts +1 -6
- package/packages/milestone/milestone.ts +2 -41
- package/packages/milestone/package.json +2 -2
- package/packages/notify/README.md +1 -0
- package/packages/notify/commands.ts +61 -3
- package/packages/notify/events.ts +0 -10
- package/packages/notify/package.json +2 -2
- package/packages/notify/settings.ts +0 -19
- package/packages/notify/tui/gotify-setup.ts +108 -143
- package/packages/notify/tui/ntfy-setup.ts +137 -172
- package/packages/notify/tui/recap-model-selector.ts +107 -101
- package/packages/notify/tui/settings-overlay.ts +94 -117
- package/packages/notify/tui/telegram-setup.ts +48 -79
- package/packages/notify/types.ts +0 -2
- package/packages/ralph/index.ts +11 -35
- package/packages/ralph/package.json +3 -3
- package/packages/ralph/ralph-loop.ts +3 -44
- package/packages/subagents/package.json +2 -1
- package/packages/subagents/src/agent-manager.ts +0 -51
- package/packages/subagents/src/agent-runner.ts +6 -80
- package/packages/subagents/src/conversation-viewer.ts +1 -19
- package/packages/subagents/src/core-compat.ts +8 -25
- package/packages/subagents/src/custom-agents.ts +0 -1
- package/packages/subagents/src/index.ts +3 -48
- package/packages/subagents/src/types.ts +0 -11
- package/packages/subagents/src/widget.ts +5 -5
- package/packages/unipi/bundled.js +4468 -7769
- package/packages/unipi/index.ts +6 -3
- package/packages/updater/package.json +2 -2
- package/packages/updater/src/changelog.ts +1 -16
- package/packages/updater/src/index.ts +0 -5
- package/packages/updater/src/installer.ts +4 -26
- package/packages/updater/src/markdown.ts +6 -152
- package/packages/updater/src/settings.ts +0 -15
- package/packages/updater/src/tui/changelog-overlay.ts +42 -234
- package/packages/updater/src/tui/list-detail-overlay.ts +260 -0
- package/packages/updater/src/tui/readme-overlay.ts +53 -210
- package/packages/updater/src/tui/settings-overlay.ts +13 -26
- package/packages/updater/src/version.ts +2 -31
- package/packages/utility/package.json +2 -4
- package/packages/utility/src/commands.ts +4 -5
- package/packages/utility/src/herdr-sync.ts +136 -0
- package/packages/utility/src/index.ts +11 -68
- package/packages/utility/src/lifecycle/cleanup.ts +2 -16
- package/packages/utility/src/{diff/settings.ts → settings.ts} +42 -80
- package/packages/utility/src/tools/env.ts +0 -0
- package/packages/utility/src/tui/name-badge-state.ts +18 -2
- package/packages/utility/src/tui/name-badge.ts +5 -4
- package/packages/utility/src/tui/util-settings-tui.ts +41 -224
- package/packages/web-api/package.json +2 -6
- package/packages/web-api/src/engine/dependencies.ts +13 -134
- package/packages/web-api/src/engine/extract.ts +9 -99
- package/packages/web-api/src/engine/format.ts +0 -75
- package/packages/web-api/src/engine/types.ts +0 -39
- package/packages/web-api/src/index.ts +1 -2
- package/packages/web-api/src/providers/base.ts +0 -10
- package/packages/web-api/src/providers/duckduckgo.ts +0 -1
- package/packages/web-api/src/providers/firecrawl.ts +0 -9
- package/packages/web-api/src/providers/jina-reader.ts +0 -9
- package/packages/web-api/src/providers/jina-search.ts +0 -9
- package/packages/web-api/src/providers/perplexity.ts +0 -9
- package/packages/web-api/src/providers/registry.ts +0 -80
- package/packages/web-api/src/providers/serpapi.ts +0 -9
- package/packages/web-api/src/providers/tavily.ts +0 -9
- package/packages/web-api/src/providers/wigolo-client.ts +0 -30
- package/packages/web-api/src/providers/wigolo.ts +0 -1
- package/packages/web-api/src/settings.ts +17 -77
- package/packages/web-api/src/tools.ts +21 -24
- package/packages/web-api/src/tui/settings-dialog.ts +0 -1
- package/packages/workflow/commands.ts +38 -122
- package/packages/workflow/index.ts +1 -5
- package/packages/workflow/package.json +2 -2
- package/packages/cocoindex/README.md +0 -94
- package/packages/cocoindex/bridge.ts +0 -842
- package/packages/cocoindex/commands.ts +0 -175
- package/packages/cocoindex/index.ts +0 -48
- package/packages/cocoindex/installer.ts +0 -397
- package/packages/cocoindex/package.json +0 -49
- package/packages/cocoindex/skills/cocoindex/SKILL.md +0 -88
- package/packages/cocoindex/tools.ts +0 -157
- package/packages/compactor/src/display/diff-renderer.ts +0 -281
- package/packages/compactor/src/display/line-width-safety.ts +0 -28
- package/packages/compactor/src/display/render-utils.ts +0 -52
- package/packages/compactor/src/display/thinking-label.ts +0 -18
- package/packages/compactor/src/display/tool-overrides.ts +0 -136
- package/packages/compactor/src/tools/compact.ts +0 -20
- package/packages/subagents/src/file-lock.ts +0 -102
- package/packages/utility/src/cache/ttl-cache.ts +0 -311
- package/packages/utility/src/diff/highlighter.ts +0 -352
- package/packages/utility/src/diff/parser.ts +0 -191
- package/packages/utility/src/diff/renderer.ts +0 -395
- package/packages/utility/src/diff/theme.ts +0 -316
- package/packages/utility/src/diff/wrapper.ts +0 -339
- package/packages/utility/src/display/capabilities.ts +0 -214
- package/packages/utility/src/display/width.ts +0 -226
- package/packages/utility/src/tools/batch.ts +0 -229
- package/packages/utility/src/tui/badge-settings-tui.ts +0 -388
- package/packages/utility/src/tui/badge-settings.ts +0 -67
- package/packages/utility/src/tui/settings-inspector.ts +0 -303
- package/packages/web-api/src/providers/llm-summarize.ts +0 -71
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool override renderers — mode-aware output for built-in tools
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { OutputMode } from "../types.js";
|
|
6
|
-
import { previewLines, countNonEmptyLines, shortenPath } from "./render-utils.js";
|
|
7
|
-
|
|
8
|
-
export interface ToolOverrideConfig {
|
|
9
|
-
readOutputMode?: OutputMode;
|
|
10
|
-
searchOutputMode?: OutputMode;
|
|
11
|
-
bashOutputMode?: OutputMode;
|
|
12
|
-
previewLines?: number;
|
|
13
|
-
bashCollapsedLines?: number;
|
|
14
|
-
showTruncationHints?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function renderReadResult(
|
|
18
|
-
content: string,
|
|
19
|
-
filePath: string,
|
|
20
|
-
config: ToolOverrideConfig,
|
|
21
|
-
): string {
|
|
22
|
-
switch (config.readOutputMode) {
|
|
23
|
-
case "hidden":
|
|
24
|
-
return `[Read: ${shortenPath(filePath)}]`;
|
|
25
|
-
case "summary":
|
|
26
|
-
return `[Read: ${shortenPath(filePath)} — ${countNonEmptyLines(content)} lines]`;
|
|
27
|
-
case "preview":
|
|
28
|
-
return previewLines(content, config.previewLines ?? 20);
|
|
29
|
-
default:
|
|
30
|
-
return content;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function renderSearchResult(
|
|
35
|
-
results: string,
|
|
36
|
-
config: ToolOverrideConfig,
|
|
37
|
-
): string {
|
|
38
|
-
switch (config.searchOutputMode) {
|
|
39
|
-
case "hidden":
|
|
40
|
-
return `[Search results hidden]`;
|
|
41
|
-
case "count":
|
|
42
|
-
return `[Search: ${countNonEmptyLines(results)} matches]`;
|
|
43
|
-
case "preview":
|
|
44
|
-
return previewLines(results, config.previewLines ?? 20);
|
|
45
|
-
default:
|
|
46
|
-
return results;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function renderBashResult(
|
|
51
|
-
output: string,
|
|
52
|
-
command: string,
|
|
53
|
-
config: ToolOverrideConfig,
|
|
54
|
-
): string {
|
|
55
|
-
switch (config.bashOutputMode) {
|
|
56
|
-
case "hidden":
|
|
57
|
-
return `[Bash: ${command.slice(0, 60)}]`;
|
|
58
|
-
case "summary":
|
|
59
|
-
return `[Bash: ${command.slice(0, 60)} — ${countNonEmptyLines(output)} lines]`;
|
|
60
|
-
case "preview":
|
|
61
|
-
return previewLines(output, config.bashCollapsedLines ?? 5);
|
|
62
|
-
default:
|
|
63
|
-
return output;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Apply display overrides for built-in tool results.
|
|
69
|
-
* Returns modified event result if override applies, undefined otherwise.
|
|
70
|
-
*
|
|
71
|
-
* Call this from the `tool_result` event handler to intercept and transform
|
|
72
|
-
* tool output before it enters the LLM context.
|
|
73
|
-
*/
|
|
74
|
-
export function applyToolDisplayOverride(
|
|
75
|
-
toolName: string,
|
|
76
|
-
event: Record<string, unknown>,
|
|
77
|
-
config: ToolOverrideConfig,
|
|
78
|
-
): { content?: Array<{ type: string; text: string }> } | undefined {
|
|
79
|
-
const content = (event as any).content as Array<{ type: string; text: string }> | undefined;
|
|
80
|
-
if (!content || !Array.isArray(content) || content.length === 0) return undefined;
|
|
81
|
-
|
|
82
|
-
const textContent = content[0]?.text ?? "";
|
|
83
|
-
const previewLinesCount = config.previewLines ?? 20;
|
|
84
|
-
const collapsedLines = config.bashCollapsedLines ?? 5;
|
|
85
|
-
|
|
86
|
-
let overridden: string | undefined;
|
|
87
|
-
|
|
88
|
-
switch (toolName) {
|
|
89
|
-
case "read": {
|
|
90
|
-
const filePath = String((event as any).args?.path ?? "");
|
|
91
|
-
const mode = config.readOutputMode ?? "full";
|
|
92
|
-
if (mode === "hidden") {
|
|
93
|
-
overridden = `[Read: ${shortenPath(filePath)}]`;
|
|
94
|
-
} else if (mode === "summary") {
|
|
95
|
-
overridden = `[Read: ${shortenPath(filePath)} — ${countNonEmptyLines(textContent)} lines]`;
|
|
96
|
-
} else if (mode === "preview") {
|
|
97
|
-
overridden = previewLines(textContent, previewLinesCount);
|
|
98
|
-
}
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
case "grep":
|
|
102
|
-
case "find":
|
|
103
|
-
case "ls": {
|
|
104
|
-
const mode = config.searchOutputMode ?? "full";
|
|
105
|
-
if (mode === "hidden") {
|
|
106
|
-
overridden = `[${toolName} results hidden]`;
|
|
107
|
-
} else if (mode === "count") {
|
|
108
|
-
overridden = `[${toolName}: ${countNonEmptyLines(textContent)} matches]`;
|
|
109
|
-
} else if (mode === "preview") {
|
|
110
|
-
overridden = previewLines(textContent, previewLinesCount);
|
|
111
|
-
}
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
case "bash": {
|
|
115
|
-
const command = String((event as any).args?.command ?? "");
|
|
116
|
-
const mode = config.bashOutputMode ?? "full";
|
|
117
|
-
if (mode === "hidden") {
|
|
118
|
-
overridden = `[Bash: ${command.slice(0, 60)}]`;
|
|
119
|
-
} else if (mode === "summary") {
|
|
120
|
-
overridden = `[Bash: ${command.slice(0, 60)} — ${countNonEmptyLines(textContent)} lines]`;
|
|
121
|
-
} else if (mode === "preview") {
|
|
122
|
-
overridden = previewLines(textContent, collapsedLines);
|
|
123
|
-
}
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
default:
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (overridden !== undefined) {
|
|
131
|
-
return {
|
|
132
|
-
content: [{ type: "text", text: overridden }],
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
return undefined;
|
|
136
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* compact tool — trigger manual compaction with stats
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { CompactionStats } from "../types.js";
|
|
6
|
-
|
|
7
|
-
export interface CompactResult {
|
|
8
|
-
success: boolean;
|
|
9
|
-
stats?: CompactionStats;
|
|
10
|
-
message: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function compactTool(): CompactResult {
|
|
14
|
-
// The actual compaction is handled by the session_before_compact hook.
|
|
15
|
-
// This tool just signals the intent and returns current stats.
|
|
16
|
-
return {
|
|
17
|
-
success: true,
|
|
18
|
-
message: "Compaction triggered. Stats will be available after next compact event.",
|
|
19
|
-
};
|
|
20
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @pi-unipi/subagents — Per-file transparent locking
|
|
3
|
-
*
|
|
4
|
-
* Agents never see lock errors. Write tool queues internally.
|
|
5
|
-
* Per-file granularity: locking src/auth.ts doesn't block src/login.ts.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { FileLockEntry } from "./types.js";
|
|
9
|
-
|
|
10
|
-
export class FileLock {
|
|
11
|
-
/** Active locks by file path. */
|
|
12
|
-
private locks = new Map<string, FileLockEntry>();
|
|
13
|
-
/** Queue of waiting acquires per file path. */
|
|
14
|
-
private queues = new Map<string, Array<() => void>>();
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Acquire a lock on a file. Blocks until available.
|
|
18
|
-
* Returns a release function.
|
|
19
|
-
*
|
|
20
|
-
* @param filePath - Absolute path to the file
|
|
21
|
-
* @param agentId - ID of the agent requesting the lock
|
|
22
|
-
* @returns Release function — call when done writing
|
|
23
|
-
*/
|
|
24
|
-
async acquire(filePath: string, agentId: string): Promise<() => void> {
|
|
25
|
-
// Wait for existing lock
|
|
26
|
-
while (this.locks.has(filePath)) {
|
|
27
|
-
await new Promise<void>((resolve) => {
|
|
28
|
-
const queue = this.queues.get(filePath) ?? [];
|
|
29
|
-
queue.push(resolve);
|
|
30
|
-
this.queues.set(filePath, queue);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Create lock entry
|
|
35
|
-
let releaseFn: () => void;
|
|
36
|
-
const promise = new Promise<void>((resolve) => {
|
|
37
|
-
releaseFn = () => {
|
|
38
|
-
this.locks.delete(filePath);
|
|
39
|
-
resolve();
|
|
40
|
-
// Wake next waiter
|
|
41
|
-
const queue = this.queues.get(filePath);
|
|
42
|
-
if (queue && queue.length > 0) {
|
|
43
|
-
const next = queue.shift()!;
|
|
44
|
-
next();
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const entry: FileLockEntry = {
|
|
50
|
-
agentId,
|
|
51
|
-
filePath,
|
|
52
|
-
promise,
|
|
53
|
-
release: releaseFn!,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
this.locks.set(filePath, entry);
|
|
57
|
-
return releaseFn!;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Check if a file is currently locked.
|
|
62
|
-
*/
|
|
63
|
-
isLocked(filePath: string): boolean {
|
|
64
|
-
return this.locks.has(filePath);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get the agent that holds a lock on a file.
|
|
69
|
-
*/
|
|
70
|
-
getHolder(filePath: string): string | undefined {
|
|
71
|
-
return this.locks.get(filePath)?.agentId;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get count of locked files.
|
|
76
|
-
*/
|
|
77
|
-
get lockCount(): number {
|
|
78
|
-
return this.locks.size;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Release all locks held by an agent (on abort).
|
|
83
|
-
*/
|
|
84
|
-
releaseAll(agentId: string): void {
|
|
85
|
-
for (const [filePath, entry] of this.locks) {
|
|
86
|
-
if (entry.agentId === agentId) {
|
|
87
|
-
entry.release();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Clear all locks (on shutdown).
|
|
94
|
-
*/
|
|
95
|
-
clear(): void {
|
|
96
|
-
for (const entry of this.locks.values()) {
|
|
97
|
-
entry.release();
|
|
98
|
-
}
|
|
99
|
-
this.locks.clear();
|
|
100
|
-
this.queues.clear();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @pi-unipi/utility — TTL Cache
|
|
3
|
-
*
|
|
4
|
-
* General-purpose TTL cache with memory + SQLite backends.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { CacheEntry, CacheBackend, TTLCacheOptions } from "../types.js";
|
|
8
|
-
|
|
9
|
-
// ─── Memory Backend ──────────────────────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
class MemoryBackend<K, V> implements CacheBackend<K, V> {
|
|
12
|
-
private store = new Map<K, CacheEntry<V>>();
|
|
13
|
-
private maxEntries: number;
|
|
14
|
-
|
|
15
|
-
constructor(maxEntries: number = 1000) {
|
|
16
|
-
this.maxEntries = maxEntries;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async get(key: K): Promise<V | undefined> {
|
|
20
|
-
const entry = this.store.get(key);
|
|
21
|
-
if (!entry) return undefined;
|
|
22
|
-
if (Date.now() > entry.expiresAt) {
|
|
23
|
-
this.store.delete(key);
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
return entry.value;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async set(key: K, value: V, ttlMs: number): Promise<void> {
|
|
30
|
-
// Evict oldest if at capacity
|
|
31
|
-
if (this.store.size >= this.maxEntries && !this.store.has(key)) {
|
|
32
|
-
const firstKey = this.store.keys().next().value;
|
|
33
|
-
if (firstKey !== undefined) {
|
|
34
|
-
this.store.delete(firstKey);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const now = Date.now();
|
|
39
|
-
this.store.set(key, {
|
|
40
|
-
value,
|
|
41
|
-
expiresAt: now + ttlMs,
|
|
42
|
-
createdAt: now,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async has(key: K): Promise<boolean> {
|
|
47
|
-
const entry = this.store.get(key);
|
|
48
|
-
if (!entry) return false;
|
|
49
|
-
if (Date.now() > entry.expiresAt) {
|
|
50
|
-
this.store.delete(key);
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async delete(key: K): Promise<boolean> {
|
|
57
|
-
return this.store.delete(key);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async cleanupExpired(): Promise<number> {
|
|
61
|
-
const now = Date.now();
|
|
62
|
-
let count = 0;
|
|
63
|
-
for (const [key, entry] of this.store) {
|
|
64
|
-
if (now > entry.expiresAt) {
|
|
65
|
-
this.store.delete(key);
|
|
66
|
-
count++;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return count;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async clear(): Promise<void> {
|
|
73
|
-
this.store.clear();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ─── SQLite Backend ──────────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
// Minimal sqlite3 type declarations for lazy loading
|
|
80
|
-
interface Sqlite3Db {
|
|
81
|
-
run(sql: string, callback?: (err: Error | null) => void): Sqlite3Db;
|
|
82
|
-
run(sql: string, params: unknown[], callback?: (err: Error | null) => void): Sqlite3Db;
|
|
83
|
-
get(sql: string, params: unknown[], callback: (err: Error | null, row: unknown) => void): Sqlite3Db;
|
|
84
|
-
close(callback?: (err: Error | null) => void): void;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
interface Sqlite3 {
|
|
88
|
-
Database: new (path: string) => Sqlite3Db;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Lazy-load sqlite to avoid hard dependency
|
|
92
|
-
let sqlite3: Sqlite3 | null = null;
|
|
93
|
-
let sqliteLoadAttempted = false;
|
|
94
|
-
|
|
95
|
-
async function loadSqlite(): Promise<Sqlite3 | null> {
|
|
96
|
-
if (sqliteLoadAttempted) return sqlite3;
|
|
97
|
-
sqliteLoadAttempted = true;
|
|
98
|
-
try {
|
|
99
|
-
// Use dynamic import with type assertion to bypass module resolution
|
|
100
|
-
const mod = await eval("import('sqlite3')") as { default?: Sqlite3; Database?: unknown } | Sqlite3;
|
|
101
|
-
if (mod && typeof mod === "object") {
|
|
102
|
-
// Handle both ESM default export and CJS-style export
|
|
103
|
-
sqlite3 = (mod as { default?: Sqlite3 }).default ?? (mod as Sqlite3);
|
|
104
|
-
}
|
|
105
|
-
} catch {
|
|
106
|
-
sqlite3 = null;
|
|
107
|
-
}
|
|
108
|
-
return sqlite3;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
class SQLiteBackend<K, V> implements CacheBackend<K, V> {
|
|
112
|
-
private db: Sqlite3Db | null = null;
|
|
113
|
-
private dbPath: string;
|
|
114
|
-
private ready: Promise<void>;
|
|
115
|
-
|
|
116
|
-
constructor(dbPath: string) {
|
|
117
|
-
this.dbPath = dbPath;
|
|
118
|
-
this.ready = this.init();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private async init(): Promise<void> {
|
|
122
|
-
const sqlite = await loadSqlite();
|
|
123
|
-
if (!sqlite) {
|
|
124
|
-
throw new Error("sqlite3 not available for persistent cache");
|
|
125
|
-
}
|
|
126
|
-
this.db = new sqlite.Database(this.dbPath);
|
|
127
|
-
|
|
128
|
-
await new Promise<void>((resolve, reject) => {
|
|
129
|
-
this.db!.run(
|
|
130
|
-
`CREATE TABLE IF NOT EXISTS cache (
|
|
131
|
-
key TEXT PRIMARY KEY,
|
|
132
|
-
value TEXT NOT NULL,
|
|
133
|
-
expires_at INTEGER NOT NULL,
|
|
134
|
-
created_at INTEGER NOT NULL
|
|
135
|
-
)`,
|
|
136
|
-
(err: Error | null) => (err ? reject(err) : resolve()),
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Create index for fast expiration queries
|
|
141
|
-
await new Promise<void>((resolve, reject) => {
|
|
142
|
-
this.db!.run(
|
|
143
|
-
`CREATE INDEX IF NOT EXISTS idx_expires ON cache(expires_at)`,
|
|
144
|
-
(err: Error | null) => (err ? reject(err) : resolve()),
|
|
145
|
-
);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private async ensureReady(): Promise<void> {
|
|
150
|
-
await this.ready;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async get(key: K): Promise<V | undefined> {
|
|
154
|
-
await this.ensureReady();
|
|
155
|
-
if (!this.db) return undefined;
|
|
156
|
-
|
|
157
|
-
const row = await new Promise<{ value: string; expires_at: number } | undefined>(
|
|
158
|
-
(resolve, reject) => {
|
|
159
|
-
this.db!.get(
|
|
160
|
-
"SELECT value, expires_at FROM cache WHERE key = ?",
|
|
161
|
-
[String(key)],
|
|
162
|
-
(err: Error | null, row: unknown) => {
|
|
163
|
-
if (err) reject(err);
|
|
164
|
-
else resolve(row as { value: string; expires_at: number } | undefined);
|
|
165
|
-
},
|
|
166
|
-
);
|
|
167
|
-
},
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
if (!row) return undefined;
|
|
171
|
-
if (Date.now() > row.expires_at) {
|
|
172
|
-
await this.delete(key);
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
return JSON.parse(row.value) as V;
|
|
178
|
-
} catch {
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async set(key: K, value: V, ttlMs: number): Promise<void> {
|
|
184
|
-
await this.ensureReady();
|
|
185
|
-
if (!this.db) return;
|
|
186
|
-
|
|
187
|
-
const now = Date.now();
|
|
188
|
-
const expiresAt = now + ttlMs;
|
|
189
|
-
const serialized = JSON.stringify(value);
|
|
190
|
-
|
|
191
|
-
await new Promise<void>((resolve, reject) => {
|
|
192
|
-
this.db!.run(
|
|
193
|
-
`INSERT INTO cache (key, value, expires_at, created_at)
|
|
194
|
-
VALUES (?, ?, ?, ?)
|
|
195
|
-
ON CONFLICT(key) DO UPDATE SET
|
|
196
|
-
value = excluded.value,
|
|
197
|
-
expires_at = excluded.expires_at,
|
|
198
|
-
created_at = excluded.created_at`,
|
|
199
|
-
[String(key), serialized, expiresAt, now],
|
|
200
|
-
(err: Error | null) => (err ? reject(err) : resolve()),
|
|
201
|
-
);
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async has(key: K): Promise<boolean> {
|
|
206
|
-
const value = await this.get(key);
|
|
207
|
-
return value !== undefined;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async delete(key: K): Promise<boolean> {
|
|
211
|
-
await this.ensureReady();
|
|
212
|
-
if (!this.db) return false;
|
|
213
|
-
|
|
214
|
-
return new Promise<boolean>((resolve, reject) => {
|
|
215
|
-
this.db!.run(
|
|
216
|
-
"DELETE FROM cache WHERE key = ?",
|
|
217
|
-
[String(key)],
|
|
218
|
-
function (this: { changes: number }, err: Error | null) {
|
|
219
|
-
if (err) reject(err);
|
|
220
|
-
else resolve(this.changes > 0);
|
|
221
|
-
},
|
|
222
|
-
);
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async cleanupExpired(): Promise<number> {
|
|
227
|
-
await this.ensureReady();
|
|
228
|
-
if (!this.db) return 0;
|
|
229
|
-
|
|
230
|
-
return new Promise<number>((resolve, reject) => {
|
|
231
|
-
this.db!.run(
|
|
232
|
-
"DELETE FROM cache WHERE expires_at <= ?",
|
|
233
|
-
[Date.now()],
|
|
234
|
-
function (this: { changes: number }, err: Error | null) {
|
|
235
|
-
if (err) reject(err);
|
|
236
|
-
else resolve(this.changes);
|
|
237
|
-
},
|
|
238
|
-
);
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async clear(): Promise<void> {
|
|
243
|
-
await this.ensureReady();
|
|
244
|
-
if (!this.db) return;
|
|
245
|
-
|
|
246
|
-
await new Promise<void>((resolve, reject) => {
|
|
247
|
-
this.db!.run("DELETE FROM cache", (err: Error | null) => (err ? reject(err) : resolve()));
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// ─── TTL Cache ───────────────────────────────────────────────────────────────
|
|
253
|
-
|
|
254
|
-
/** Default options */
|
|
255
|
-
const DEFAULTS: Required<TTLCacheOptions> = {
|
|
256
|
-
persistent: false,
|
|
257
|
-
dbPath: "",
|
|
258
|
-
defaultTtlMs: 3600000, // 1 hour
|
|
259
|
-
maxMemoryEntries: 1000,
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* General-purpose TTL cache with optional SQLite persistence.
|
|
264
|
-
*/
|
|
265
|
-
export class TTLCache<K = string, V = unknown> {
|
|
266
|
-
private backend: CacheBackend<K, V>;
|
|
267
|
-
private opts: Required<TTLCacheOptions>;
|
|
268
|
-
|
|
269
|
-
constructor(options: TTLCacheOptions = {}) {
|
|
270
|
-
this.opts = { ...DEFAULTS, ...options };
|
|
271
|
-
|
|
272
|
-
if (this.opts.persistent) {
|
|
273
|
-
const dbPath =
|
|
274
|
-
this.opts.dbPath ||
|
|
275
|
-
new URL("~/.unipi/cache/ttl-cache.db", import.meta.url).pathname;
|
|
276
|
-
this.backend = new SQLiteBackend<K, V>(dbPath);
|
|
277
|
-
} else {
|
|
278
|
-
this.backend = new MemoryBackend<K, V>(this.opts.maxMemoryEntries);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/** Get a value by key */
|
|
283
|
-
async get(key: K): Promise<V | undefined> {
|
|
284
|
-
return this.backend.get(key);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/** Set a value with TTL */
|
|
288
|
-
async set(key: K, value: V, ttlMs?: number): Promise<void> {
|
|
289
|
-
return this.backend.set(key, value, ttlMs ?? this.opts.defaultTtlMs);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/** Check if key exists and is not expired */
|
|
293
|
-
async has(key: K): Promise<boolean> {
|
|
294
|
-
return this.backend.has(key);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** Delete a key */
|
|
298
|
-
async delete(key: K): Promise<boolean> {
|
|
299
|
-
return this.backend.delete(key);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/** Clean up all expired entries */
|
|
303
|
-
async cleanupExpired(): Promise<number> {
|
|
304
|
-
return this.backend.cleanupExpired();
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/** Clear all entries */
|
|
308
|
-
async clear(): Promise<void> {
|
|
309
|
-
return this.backend.clear();
|
|
310
|
-
}
|
|
311
|
-
}
|