@daniel156161/prism 0.2.78 → 0.2.82
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/dist/pi/pi-env.d.ts +1 -1
- package/dist/pi/pi-env.js +1 -1
- package/dist/pi/pi-patch-interactive.js +8 -5
- package/dist/pi/pi-patch-interactive.js.map +1 -1
- package/dist/prism-extensions/integrations/ai-memory-errors.d.ts +29 -0
- package/dist/prism-extensions/integrations/ai-memory-errors.js +106 -0
- package/dist/prism-extensions/integrations/ai-memory-errors.js.map +1 -0
- package/dist/prism-extensions/integrations/ai-memory-http.d.ts +28 -0
- package/dist/prism-extensions/integrations/ai-memory-http.js +92 -0
- package/dist/prism-extensions/integrations/ai-memory-http.js.map +1 -0
- package/dist/prism-extensions/integrations/ai-memory-system.d.ts +5 -0
- package/dist/prism-extensions/integrations/ai-memory-system.js +205 -123
- package/dist/prism-extensions/integrations/ai-memory-system.js.map +1 -1
- package/dist/prism-extensions/integrations/ai-memory-write-preview.d.ts +36 -0
- package/dist/prism-extensions/integrations/ai-memory-write-preview.js +67 -0
- package/dist/prism-extensions/integrations/ai-memory-write-preview.js.map +1 -0
- package/dist/prism-extensions/integrations/research-tools.js +3 -3
- package/dist/prism-extensions/integrations/research-tools.js.map +1 -1
- package/dist/prism-extensions/integrations/youtube.d.ts +2 -2
- package/dist/prism-extensions/integrations/youtube.js +6 -7
- package/dist/prism-extensions/integrations/youtube.js.map +1 -1
- package/dist/prism-extensions/ui/collapsed-text-rendering.d.ts +4 -2
- package/dist/prism-extensions/ui/collapsed-text-rendering.js +12 -7
- package/dist/prism-extensions/ui/collapsed-text-rendering.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/README.md +4 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +5 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +3 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +6 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/README.md +8 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-individual.json +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.d.ts +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.js +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.js +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-client/CHANGELOG.md +1 -1
- package/node_modules/@earendil-works/pi-client/package.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/CHANGELOG.md +31 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +3 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.d.ts +19 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.js +50 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.d.ts +24 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.js +102 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.d.ts +4 -14
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.js +38 -79
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +11 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +0 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +79 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +1 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts +5 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.d.ts +8 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.d.ts +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.js +3 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +37 -39
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +16 -20
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +6 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.d.ts +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +11 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.d.ts +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js +5 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.d.ts +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js +5 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.d.ts +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.d.ts +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +6 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.d.ts +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +6 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +57 -21
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/session-selector.js +0 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +48 -233
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +11 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.d.ts +26 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.js +104 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md +3 -2
- package/node_modules/@earendil-works/pi-coding-agent/docs/keybindings.md +9 -5
- package/node_modules/@earendil-works/pi-coding-agent/docs/providers.md +8 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package-lock.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package-lock.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package-lock.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
- package/node_modules/@earendil-works/pi-protocol/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts +10 -0
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.js +8 -0
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/latex.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/latex.js +188 -48
- package/node_modules/@earendil-works/pi-tui/dist/latex.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts +12 -0
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js +146 -12
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/child_process.d.ts +0 -1
- package/node_modules/@types/node/crypto.d.ts +6 -6
- package/node_modules/@types/node/dns/promises.d.ts +5 -5
- package/node_modules/@types/node/dns.d.ts +5 -5
- package/node_modules/@types/node/fs.d.ts +7 -0
- package/node_modules/@types/node/http.d.ts +30 -0
- package/node_modules/@types/node/http2.d.ts +4 -4
- package/node_modules/@types/node/os.d.ts +30 -30
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/@types/node/perf_hooks.d.ts +2 -1
- package/node_modules/@types/node/process.d.ts +42 -43
- package/node_modules/@types/node/quic.d.ts +1049 -84
- package/node_modules/@types/node/sqlite.d.ts +14 -1
- package/node_modules/@types/node/stream/iter.d.ts +3 -2
- package/node_modules/@types/node/test.d.ts +61 -0
- package/node_modules/@types/node/v8.d.ts +20 -23
- package/package.json +6 -6
- package/src/prism-extensions/integrations/ai-memory-errors.ts +112 -0
- package/src/prism-extensions/integrations/ai-memory-http.ts +114 -0
- package/src/prism-extensions/integrations/ai-memory-system.ts +221 -132
- package/src/prism-extensions/integrations/ai-memory-write-preview.ts +83 -0
- package/src/prism-extensions/integrations/research-tools.ts +3 -3
- package/src/prism-extensions/integrations/youtube.ts +6 -6
- package/src/prism-extensions/ui/collapsed-text-rendering.ts +14 -8
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/prism-session-db.js +0 -128
|
@@ -2,24 +2,39 @@ type ExtensionAPI = any
|
|
|
2
2
|
|
|
3
3
|
import { Type } from "typebox"
|
|
4
4
|
import { readSettings } from "../core/shared-config.js"
|
|
5
|
+
import { formatDegradedNotice } from "./ai-memory-errors.js"
|
|
6
|
+
import {
|
|
7
|
+
aiMemoryBaseUrl,
|
|
8
|
+
aiMemoryInjectCacheTtlMs,
|
|
9
|
+
aiMemoryInjectTimeoutMs,
|
|
10
|
+
aiMemoryRequestTimeoutMs,
|
|
11
|
+
aiMemoryStatusTimeoutMs,
|
|
12
|
+
deleteJson,
|
|
13
|
+
getJson,
|
|
14
|
+
methodJson,
|
|
15
|
+
postJson,
|
|
16
|
+
putJson,
|
|
17
|
+
} from "./ai-memory-http.js"
|
|
18
|
+
import { isAiMemoryWriteTool, renderAiMemoryWriteResult } from "./ai-memory-write-preview.js"
|
|
5
19
|
import { renderCollapsibleTextResult } from "../ui/collapsed-text-rendering.js"
|
|
6
|
-
import {
|
|
20
|
+
import { obsidianOpenUrl } from "./obsidian-memory.js"
|
|
21
|
+
import { ansiHyperlink, formatBracketedToolCall, renderSingleLineToolCall, type ThemeLike } from "../ui/tool-call-rendering.js"
|
|
7
22
|
|
|
8
|
-
const DEFAULT_BASE_URL = "http://127.0.0.1:8765"
|
|
9
23
|
const DEFAULT_LIMIT = 8
|
|
10
24
|
const DEFAULT_CONTEXT_LIMIT = 5
|
|
11
25
|
const DEFAULT_CONTEXT_SCORE_THRESHOLD = 0.15
|
|
12
|
-
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000
|
|
13
|
-
const DEFAULT_STATUS_TIMEOUT_MS = 1_500
|
|
14
|
-
const DEFAULT_INJECT_TIMEOUT_MS = 1_200
|
|
15
|
-
const DEFAULT_INJECT_CACHE_TTL_MS = 60_000
|
|
16
26
|
const DEFAULT_ALWAYS_CONTEXT_CACHE_TTL_MS = 60_000
|
|
17
27
|
|
|
18
28
|
const injectCache = new Map<string, { expiresAt: number; results: any[] }>()
|
|
19
29
|
const alwaysContextCache = { expiresAt: 0, content: "" }
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
const reportedBackgroundFailures = new Set<string>()
|
|
31
|
+
|
|
32
|
+
/** Test hook: drop injection caches and one-shot failure notices. */
|
|
33
|
+
export function resetAiMemoryCaches(): void {
|
|
34
|
+
injectCache.clear()
|
|
35
|
+
alwaysContextCache.expiresAt = 0
|
|
36
|
+
alwaysContextCache.content = ""
|
|
37
|
+
reportedBackgroundFailures.clear()
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
function clampLimit(value: unknown, fallback = DEFAULT_LIMIT): number {
|
|
@@ -68,28 +83,6 @@ function aiMemoryContextScoreThreshold(env: NodeJS.ProcessEnv = process.env): nu
|
|
|
68
83
|
return Number.isFinite(parsed) ? Math.max(0, parsed) : DEFAULT_CONTEXT_SCORE_THRESHOLD
|
|
69
84
|
}
|
|
70
85
|
|
|
71
|
-
function positiveIntegerSetting(value: unknown, fallback: number): number {
|
|
72
|
-
const parsed = Number(value)
|
|
73
|
-
if (!Number.isFinite(parsed)) return fallback
|
|
74
|
-
return Math.max(1, Math.trunc(parsed))
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function aiMemoryRequestTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {
|
|
78
|
-
return positiveIntegerSetting(env.PRISM_AI_MEMORY_TIMEOUT_MS ?? env.PI_AI_MEMORY_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function aiMemoryStatusTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {
|
|
82
|
-
return positiveIntegerSetting(env.PRISM_AI_MEMORY_STATUS_TIMEOUT_MS ?? env.PI_AI_MEMORY_STATUS_TIMEOUT_MS, DEFAULT_STATUS_TIMEOUT_MS)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function aiMemoryInjectTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {
|
|
86
|
-
return positiveIntegerSetting(env.PRISM_AI_MEMORY_INJECT_TIMEOUT_MS ?? env.PI_AI_MEMORY_INJECT_TIMEOUT_MS, DEFAULT_INJECT_TIMEOUT_MS)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function aiMemoryInjectCacheTtlMs(env: NodeJS.ProcessEnv = process.env): number {
|
|
90
|
-
return positiveIntegerSetting(env.PRISM_AI_MEMORY_INJECT_CACHE_TTL_MS ?? env.PI_AI_MEMORY_INJECT_CACHE_TTL_MS, DEFAULT_INJECT_CACHE_TTL_MS)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
86
|
function cleanFilters(params: any): Record<string, string> {
|
|
94
87
|
const filters: Record<string, string> = {}
|
|
95
88
|
for (const key of ["source", "project", "type", "status", "tag", "path"]) {
|
|
@@ -99,74 +92,6 @@ function cleanFilters(params: any): Record<string, string> {
|
|
|
99
92
|
return filters
|
|
100
93
|
}
|
|
101
94
|
|
|
102
|
-
async function postJson<T>(path: string, body: unknown, timeoutMs = aiMemoryRequestTimeoutMs()): Promise<T> {
|
|
103
|
-
const baseUrl = aiMemoryBaseUrl()
|
|
104
|
-
const response = await fetch(`${baseUrl}${path}`, {
|
|
105
|
-
method: "POST",
|
|
106
|
-
headers: { "content-type": "application/json; charset=utf-8", accept: "application/json" },
|
|
107
|
-
body: JSON.stringify(body),
|
|
108
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
109
|
-
})
|
|
110
|
-
const text = await response.text()
|
|
111
|
-
if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
|
|
112
|
-
return JSON.parse(text) as T
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function putJson<T>(path: string, body: unknown, timeoutMs = aiMemoryRequestTimeoutMs()): Promise<T> {
|
|
116
|
-
const baseUrl = aiMemoryBaseUrl()
|
|
117
|
-
const response = await fetch(`${baseUrl}${path}`, {
|
|
118
|
-
method: "PUT",
|
|
119
|
-
headers: { "content-type": "application/json; charset=utf-8", accept: "application/json" },
|
|
120
|
-
body: JSON.stringify(body),
|
|
121
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
122
|
-
})
|
|
123
|
-
const text = await response.text()
|
|
124
|
-
if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
|
|
125
|
-
return JSON.parse(text) as T
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function deleteJson<T>(path: string, body: unknown, timeoutMs = aiMemoryRequestTimeoutMs()): Promise<T> {
|
|
129
|
-
const baseUrl = aiMemoryBaseUrl()
|
|
130
|
-
const response = await fetch(`${baseUrl}${path}`, {
|
|
131
|
-
method: "DELETE",
|
|
132
|
-
headers: { "content-type": "application/json; charset=utf-8", accept: "application/json" },
|
|
133
|
-
body: JSON.stringify(body),
|
|
134
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
135
|
-
})
|
|
136
|
-
const text = await response.text()
|
|
137
|
-
if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
|
|
138
|
-
return JSON.parse(text) as T
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function methodJson<T>(
|
|
142
|
-
method: string,
|
|
143
|
-
path: string,
|
|
144
|
-
timeoutMs = aiMemoryRequestTimeoutMs(),
|
|
145
|
-
body?: unknown,
|
|
146
|
-
): Promise<T> {
|
|
147
|
-
const baseUrl = aiMemoryBaseUrl()
|
|
148
|
-
const response = await fetch(`${baseUrl}${path}`, {
|
|
149
|
-
method,
|
|
150
|
-
headers: { accept: "application/json", ...(body === undefined ? {} : { "content-type": "application/json; charset=utf-8" }) },
|
|
151
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
152
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
153
|
-
})
|
|
154
|
-
const text = await response.text()
|
|
155
|
-
if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
|
|
156
|
-
return JSON.parse(text) as T
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function getJson<T>(path: string, timeoutMs = aiMemoryStatusTimeoutMs()): Promise<T> {
|
|
160
|
-
const baseUrl = aiMemoryBaseUrl()
|
|
161
|
-
const response = await fetch(`${baseUrl}${path}`, {
|
|
162
|
-
headers: { accept: "application/json" },
|
|
163
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
164
|
-
})
|
|
165
|
-
const text = await response.text()
|
|
166
|
-
if (!response.ok) throw new Error(`AI Memory request failed: HTTP ${response.status} ${text.slice(0, 500)}`)
|
|
167
|
-
return JSON.parse(text) as T
|
|
168
|
-
}
|
|
169
|
-
|
|
170
95
|
function formatSearchResults(results: any[]): string {
|
|
171
96
|
if (!Array.isArray(results) || results.length === 0) return "No AI Memory results found. Is the index built?"
|
|
172
97
|
return results.map((row, index) => {
|
|
@@ -176,11 +101,19 @@ function formatSearchResults(results: any[]): string {
|
|
|
176
101
|
const lexical = Number(row.lexical_score ?? 0).toFixed(4)
|
|
177
102
|
const graph = Number(row.graph_score ?? 0).toFixed(1)
|
|
178
103
|
const fact = Number(row.fact_score ?? 0).toFixed(1)
|
|
104
|
+
const feedback = Number(row.feedback_score ?? 0).toFixed(2)
|
|
105
|
+
const stale = Number(row.stale_score ?? 0).toFixed(2)
|
|
179
106
|
const excerpt = String(row.text ?? "").replace(/\s+/g, " ").trim().slice(0, 700)
|
|
180
|
-
return `${index + 1}. score=${score} vector=${vector} keyword=${keyword} lexical=${lexical} graph=${graph} fact=${fact}\nsource=${row.source}:${row.path} > ${row.heading}\n${excerpt}`
|
|
107
|
+
return `${index + 1}. score=${score} vector=${vector} keyword=${keyword} lexical=${lexical} graph=${graph} fact=${fact} feedback=${feedback} stale=${stale}\nsource=${row.source}:${row.path} > ${row.heading}\n${excerpt}`
|
|
181
108
|
}).join("\n\n")
|
|
182
109
|
}
|
|
183
110
|
|
|
111
|
+
export function formatSearchResponse(data: any): string {
|
|
112
|
+
const results = Array.isArray(data?.results) ? data.results : []
|
|
113
|
+
const notices = [formatDegradedNotice(data?.degraded), String(data?.hint ?? "").trim()].filter(Boolean)
|
|
114
|
+
return [...notices, formatSearchResults(results)].join("\n\n")
|
|
115
|
+
}
|
|
116
|
+
|
|
184
117
|
function formatReadResult(data: any): string {
|
|
185
118
|
const source = String(data?.source ?? "unknown")
|
|
186
119
|
const path = String(data?.path ?? "unknown")
|
|
@@ -277,9 +210,37 @@ function renderAiMemoryResult(result: any, options: { expanded?: boolean; isPart
|
|
|
277
210
|
})
|
|
278
211
|
}
|
|
279
212
|
|
|
280
|
-
function formatAiMemoryToolCall(toolName: string, args: any, theme: ThemeLike): string {
|
|
213
|
+
export function formatAiMemoryToolCall(toolName: string, args: any, theme: ThemeLike): string {
|
|
281
214
|
const label = toolName.replace(/^ai_memory_/, "ai memory ").replace(/_/g, " ")
|
|
282
|
-
|
|
215
|
+
const rawValue = args?.query || args?.id || args?.path || args?.session || ""
|
|
216
|
+
const value = typeof rawValue === "string" && rawValue.trim() ? rawValue.trim() : "..."
|
|
217
|
+
const linksToVaultNote = isAiMemoryWriteTool(toolName) || toolName === "ai_memory_vault_delete"
|
|
218
|
+
if (linksToVaultNote && value !== "...") {
|
|
219
|
+
return `${theme.fg("toolTitle", theme.bold(label))} ${theme.fg("accent", ansiHyperlink(obsidianOpenUrl(value), value))}`
|
|
220
|
+
}
|
|
221
|
+
return formatBracketedToolCall(label, value, theme)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Surface background (non-tool) failures once per distinct reason. The dedupe key
|
|
226
|
+
* ignores the per-turn query so a broken API does not warn on every prompt.
|
|
227
|
+
*/
|
|
228
|
+
function reportBackgroundFailure(ctx: any, label: string, error: unknown): void {
|
|
229
|
+
const message = `AI Memory ${label} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
230
|
+
const key = `${label}|${message.split(" | query=")[0]}`
|
|
231
|
+
if (reportedBackgroundFailures.has(key)) return
|
|
232
|
+
reportedBackgroundFailures.add(key)
|
|
233
|
+
ctx?.ui?.notify?.(message, "warning")
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function formatVaultWriteResult(action: "Wrote" | "Edited", params: any, responsePath: unknown): string {
|
|
237
|
+
const path = String(responsePath ?? params.path ?? "")
|
|
238
|
+
const frontmatter = params.frontmatter === undefined ? "" : String(params.frontmatter).trim()
|
|
239
|
+
const heading = action === "Edited" ? `# ${String(params.heading ?? "").trim()}` : ""
|
|
240
|
+
const body = String(params.body ?? "")
|
|
241
|
+
const writtenContent = [frontmatter, heading, body].filter(Boolean).join("\n\n") || "(empty)"
|
|
242
|
+
const status = action === "Wrote" ? `Wrote: ${path}\n\n` : ""
|
|
243
|
+
return `${status}Write content:\n${writtenContent}`
|
|
283
244
|
}
|
|
284
245
|
|
|
285
246
|
export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
|
|
@@ -305,12 +266,12 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
|
|
|
305
266
|
path: Type.Optional(Type.String({ description: "Path substring filter" })),
|
|
306
267
|
}),
|
|
307
268
|
async execute(_id: string, params: any) {
|
|
308
|
-
const data = await postJson<{ results: any[] }>("/search", {
|
|
269
|
+
const data = await postJson<{ results: any[]; degraded?: any; hint?: string }>("/search", {
|
|
309
270
|
query: String(params.query ?? ""),
|
|
310
271
|
limit: clampLimit(params.limit),
|
|
311
272
|
filters: cleanFilters(params),
|
|
312
273
|
})
|
|
313
|
-
return { content: [{ type: "text", text:
|
|
274
|
+
return { content: [{ type: "text", text: formatSearchResponse(data) }], details: { count: data.results?.length ?? 0, results: data.results, degraded: data.degraded, hint: data.hint } }
|
|
314
275
|
},
|
|
315
276
|
renderCall(args: any, theme: ThemeLike, context: any) {
|
|
316
277
|
return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_search", args, theme), context)
|
|
@@ -369,6 +330,130 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
|
|
|
369
330
|
},
|
|
370
331
|
})
|
|
371
332
|
|
|
333
|
+
pi.registerTool({
|
|
334
|
+
name: "ai_memory_rate_result",
|
|
335
|
+
label: "AI Memory Rate Result",
|
|
336
|
+
description: "Rate one retrieved memory result so future ranking learns from it.",
|
|
337
|
+
promptSnippet: "Use ai_memory_rate_result when a retrieved memory was clearly helpful, irrelevant, outdated, or wrong.",
|
|
338
|
+
promptGuidelines: [
|
|
339
|
+
"Rate the path exactly as returned by ai_memory_search.",
|
|
340
|
+
"Use 'outdated' or 'wrong' when the user corrects information that came from memory.",
|
|
341
|
+
"This changes ranking only; it never writes or deletes a note.",
|
|
342
|
+
],
|
|
343
|
+
parameters: Type.Object({
|
|
344
|
+
path: Type.String({ description: "Path of the rated result, e.g. 02 Projekte/Memory.md" }),
|
|
345
|
+
rating: Type.String({ description: "helpful, irrelevant, outdated, or wrong" }),
|
|
346
|
+
query: Type.Optional(Type.String({ description: "Search query that produced the result" })),
|
|
347
|
+
reason: Type.Optional(Type.String({ description: "Short reason in German" })),
|
|
348
|
+
source: Type.Optional(Type.String({ description: "obsidian or prism_session" })),
|
|
349
|
+
chunk_id: Type.Optional(Type.String({ description: "Exact chunk id if known" })),
|
|
350
|
+
session_id: Type.Optional(Type.String({ description: "Originating session id" })),
|
|
351
|
+
}),
|
|
352
|
+
async execute(_id: string, params: any) {
|
|
353
|
+
const data = await postJson<any>("/retrieval-feedback", {
|
|
354
|
+
path: String(params.path ?? ""),
|
|
355
|
+
rating: String(params.rating ?? ""),
|
|
356
|
+
query: String(params.query ?? ""),
|
|
357
|
+
reason: String(params.reason ?? ""),
|
|
358
|
+
source: String(params.source ?? ""),
|
|
359
|
+
chunk_id: String(params.chunk_id ?? ""),
|
|
360
|
+
session_id: String(params.session_id ?? ""),
|
|
361
|
+
})
|
|
362
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], details: data }
|
|
363
|
+
},
|
|
364
|
+
renderCall(args: any, theme: ThemeLike, context: any) {
|
|
365
|
+
return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_rate_result", args, theme), context)
|
|
366
|
+
},
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
pi.registerTool({
|
|
370
|
+
name: "ai_memory_session_review",
|
|
371
|
+
label: "AI Memory Session Review",
|
|
372
|
+
description: "Review a Prism session for outcome evidence and human-approved durable lesson proposals.",
|
|
373
|
+
promptSnippet: "Review a completed session and return pending lessons; this does not approve them.",
|
|
374
|
+
parameters: Type.Object({
|
|
375
|
+
session: Type.Optional(Type.String({ description: "Session path/id or latest" })),
|
|
376
|
+
force: Type.Optional(Type.Boolean({ description: "Review an unchanged session again" })),
|
|
377
|
+
}),
|
|
378
|
+
async execute(_id: string, params: any) {
|
|
379
|
+
const data = await postJson<any>("/session-review", {
|
|
380
|
+
session: typeof params.session === "string" ? params.session : "latest",
|
|
381
|
+
force: params.force === true,
|
|
382
|
+
})
|
|
383
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], details: data }
|
|
384
|
+
},
|
|
385
|
+
renderCall(args: any, theme: ThemeLike, context: any) {
|
|
386
|
+
return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_session_review", args, theme), context)
|
|
387
|
+
},
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
pi.registerTool({
|
|
391
|
+
name: "ai_memory_propose_lesson",
|
|
392
|
+
label: "AI Memory Propose Lesson",
|
|
393
|
+
description: "Create a pending durable lesson proposal. User approval is still required before writing memory.",
|
|
394
|
+
promptSnippet: "Propose a lesson from explicit user feedback without promoting it automatically.",
|
|
395
|
+
parameters: Type.Object({
|
|
396
|
+
category: Type.String({ description: "correction, decision, preference, workflow, or quality" }),
|
|
397
|
+
title: Type.String({ description: "Short German title" }),
|
|
398
|
+
rule: Type.String({ description: "Proposed durable rule in German" }),
|
|
399
|
+
rationale: Type.Optional(Type.String({ description: "Why the rule matters" })),
|
|
400
|
+
evidence: Type.String({ description: "Exact user feedback supporting the proposal" }),
|
|
401
|
+
session_id: Type.Optional(Type.String({ description: "Originating session id" })),
|
|
402
|
+
confidence: Type.Optional(Type.Number({ description: "Confidence from 0 to 1" })),
|
|
403
|
+
}),
|
|
404
|
+
async execute(_id: string, params: any) {
|
|
405
|
+
const data = await postJson<any>("/lessons", {
|
|
406
|
+
category: String(params.category ?? ""),
|
|
407
|
+
title: String(params.title ?? ""),
|
|
408
|
+
rule: String(params.rule ?? ""),
|
|
409
|
+
rationale: String(params.rationale ?? ""),
|
|
410
|
+
evidence: String(params.evidence ?? ""),
|
|
411
|
+
session_id: String(params.session_id ?? "manual"),
|
|
412
|
+
confidence: params.confidence === undefined ? 0.8 : Number(params.confidence),
|
|
413
|
+
})
|
|
414
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], details: data }
|
|
415
|
+
},
|
|
416
|
+
renderCall(args: any, theme: ThemeLike, context: any) {
|
|
417
|
+
return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_propose_lesson", args, theme), context)
|
|
418
|
+
},
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
pi.registerTool({
|
|
422
|
+
name: "ai_memory_feedback",
|
|
423
|
+
label: "AI Memory Feedback",
|
|
424
|
+
description: "List pending lessons or approve/reject one proposal. Approval writes durable memory.",
|
|
425
|
+
promptSnippet: "Use only after explicit user approval to approve a proposed durable lesson.",
|
|
426
|
+
promptGuidelines: ["Never approve a lesson without explicit user approval in the current conversation."],
|
|
427
|
+
parameters: Type.Object({
|
|
428
|
+
action: Type.Optional(Type.String({ description: "list (default), approve, or reject" })),
|
|
429
|
+
proposal_id: Type.Optional(Type.String({ description: "Required for approve/reject" })),
|
|
430
|
+
status: Type.Optional(Type.String({ description: "List filter: pending, approved, or rejected" })),
|
|
431
|
+
session_id: Type.Optional(Type.String({ description: "Optional session filter for listing" })),
|
|
432
|
+
}),
|
|
433
|
+
async execute(_id: string, params: any) {
|
|
434
|
+
const action = String(params.action ?? "list")
|
|
435
|
+
let data: any
|
|
436
|
+
if (action === "list") {
|
|
437
|
+
const query = new URLSearchParams()
|
|
438
|
+
if (params.status) query.set("status", String(params.status))
|
|
439
|
+
if (params.session_id) query.set("session_id", String(params.session_id))
|
|
440
|
+
data = await getJson<any>(`/lessons${query.size ? `?${query}` : ""}`, aiMemoryRequestTimeoutMs())
|
|
441
|
+
} else {
|
|
442
|
+
if (!["approve", "reject"].includes(action) || !params.proposal_id) {
|
|
443
|
+
throw new Error("action must be list, approve, or reject; proposal_id is required for decisions")
|
|
444
|
+
}
|
|
445
|
+
data = await methodJson<any>("DECISION", "/lessons", aiMemoryRequestTimeoutMs(), {
|
|
446
|
+
proposal_id: String(params.proposal_id),
|
|
447
|
+
decision: action,
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], details: data }
|
|
451
|
+
},
|
|
452
|
+
renderCall(args: any, theme: ThemeLike, context: any) {
|
|
453
|
+
return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_feedback", args, theme), context)
|
|
454
|
+
},
|
|
455
|
+
})
|
|
456
|
+
|
|
372
457
|
// ── File-backed vault ops (write/edit/list/delete) ──
|
|
373
458
|
|
|
374
459
|
pi.registerTool({
|
|
@@ -396,7 +481,7 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
|
|
|
396
481
|
name: "ai_memory_vault_write",
|
|
397
482
|
label: "AI Memory Vault Write",
|
|
398
483
|
description: "Create/overwrite a Markdown file in the configured Obsidian vault.",
|
|
399
|
-
promptSnippet: "Write vault file [path
|
|
484
|
+
promptSnippet: "Write vault file [show path and written body/frontmatter]",
|
|
400
485
|
parameters: Type.Object({
|
|
401
486
|
path: Type.String({ description: "Vault-relative path, e.g. 02 Projekte/Test.md" }),
|
|
402
487
|
frontmatter: Type.Optional(Type.String({ description: "Optional YAML frontmatter including leading/trailing --- lines" })),
|
|
@@ -408,18 +493,21 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
|
|
|
408
493
|
frontmatter: params.frontmatter !== undefined ? String(params.frontmatter) : null,
|
|
409
494
|
body: String(params.body ?? ""),
|
|
410
495
|
})
|
|
411
|
-
return { content: [{ type: "text", text:
|
|
496
|
+
return { content: [{ type: "text", text: formatVaultWriteResult("Wrote", params, data.path) }], details: data }
|
|
412
497
|
},
|
|
413
498
|
renderCall(args: any, theme: ThemeLike, context: any) {
|
|
414
499
|
return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_vault_write", args, theme), context)
|
|
415
500
|
},
|
|
501
|
+
renderResult(result: any, options: any, theme: ThemeLike, context: any) {
|
|
502
|
+
return renderAiMemoryWriteResult("ai_memory_vault_write", result, options, theme, context)
|
|
503
|
+
},
|
|
416
504
|
})
|
|
417
505
|
|
|
418
506
|
pi.registerTool({
|
|
419
507
|
name: "ai_memory_vault_edit",
|
|
420
508
|
label: "AI Memory Vault Edit",
|
|
421
509
|
description: "Replace the content block under a given heading in a Markdown file (or append if missing).",
|
|
422
|
-
promptSnippet: "Edit vault file section
|
|
510
|
+
promptSnippet: "Edit vault file section [show path, heading, and written body/frontmatter]",
|
|
423
511
|
parameters: Type.Object({
|
|
424
512
|
path: Type.String({ description: "Vault-relative path" }),
|
|
425
513
|
heading: Type.String({ description: "Heading text without leading #, e.g. 'Update 2026-01-01 — Thema'" }),
|
|
@@ -433,11 +521,14 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
|
|
|
433
521
|
frontmatter: params.frontmatter !== undefined ? String(params.frontmatter) : null,
|
|
434
522
|
body: String(params.body ?? ""),
|
|
435
523
|
})
|
|
436
|
-
return { content: [{ type: "text", text:
|
|
524
|
+
return { content: [{ type: "text", text: formatVaultWriteResult("Edited", params, data.path) }], details: data }
|
|
437
525
|
},
|
|
438
526
|
renderCall(args: any, theme: ThemeLike, context: any) {
|
|
439
527
|
return renderSingleLineToolCall(formatAiMemoryToolCall("ai_memory_vault_edit", args, theme), context)
|
|
440
528
|
},
|
|
529
|
+
renderResult(result: any, options: any, theme: ThemeLike, context: any) {
|
|
530
|
+
return renderAiMemoryWriteResult("ai_memory_vault_edit", result, options, theme, context)
|
|
531
|
+
},
|
|
441
532
|
})
|
|
442
533
|
|
|
443
534
|
pi.registerTool({
|
|
@@ -482,36 +573,34 @@ export default function aiMemorySystemExtension(pi: ExtensionAPI): void {
|
|
|
482
573
|
.catch(() => ctx.ui.setStatus?.("ai-memory", undefined))
|
|
483
574
|
})
|
|
484
575
|
|
|
485
|
-
pi.on?.("before_agent_start", async (event: any) => {
|
|
576
|
+
pi.on?.("before_agent_start", async (event: any, ctx: any) => {
|
|
577
|
+
const patch: { systemPrompt?: string; message?: any } = {}
|
|
578
|
+
|
|
486
579
|
// Always-loaded durable context (mapped from 00 Kontext) is injected as a
|
|
487
580
|
// system-prompt chunk so it remains in the cached prefix.
|
|
488
|
-
let alwaysBlock: string | undefined
|
|
489
581
|
try {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const block = alwaysBlock.trim()
|
|
500
|
-
return { systemPrompt: `${prev}${prev ? "\n\n" : ""}${block}` }
|
|
582
|
+
const alwaysBlock = buildAlwaysContextMessage(await loadAlwaysContext())?.content?.trim()
|
|
583
|
+
if (alwaysBlock) {
|
|
584
|
+
const prev = String(event?.systemPrompt ?? "")
|
|
585
|
+
patch.systemPrompt = `${prev}${prev ? "\n\n" : ""}${alwaysBlock}`
|
|
586
|
+
}
|
|
587
|
+
} catch (error) {
|
|
588
|
+
// Best-effort, but never silent: a broken API would otherwise drop durable
|
|
589
|
+
// context without anyone noticing.
|
|
590
|
+
reportBackgroundFailure(ctx, "always-context injection", error)
|
|
501
591
|
}
|
|
502
592
|
|
|
503
|
-
// Query-specific candidates
|
|
504
|
-
if (!shouldInjectAiMemoryCandidates()) return
|
|
593
|
+
// Query-specific candidates (independent of the always-context block).
|
|
505
594
|
const query = String(event?.prompt ?? "").trim()
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
// remains available and will surface API errors when the model calls it.
|
|
514
|
-
return undefined
|
|
595
|
+
if (shouldInjectAiMemoryCandidates() && query) {
|
|
596
|
+
try {
|
|
597
|
+
const message = buildAiMemoryContextMessage(await loadAiMemoryInjectResults(query))
|
|
598
|
+
if (message) patch.message = message
|
|
599
|
+
} catch (error) {
|
|
600
|
+
reportBackgroundFailure(ctx, "candidate injection", error)
|
|
601
|
+
}
|
|
515
602
|
}
|
|
603
|
+
|
|
604
|
+
return patch.systemPrompt === undefined && patch.message === undefined ? undefined : patch
|
|
516
605
|
})
|
|
517
606
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live preview and rendering of memory writes.
|
|
3
|
+
*
|
|
4
|
+
* Vault writes used to be invisible until the tool finished: the call row only
|
|
5
|
+
* showed the path and the result slot said "Searching AI Memory...". This module
|
|
6
|
+
* formats the *partially streamed* arguments so the note content is readable
|
|
7
|
+
* while the model is still producing it, and keeps the row expandable with
|
|
8
|
+
* Ctrl+O / Strg+O (`app.tools.expand`) in both states.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { renderCollapsibleText, renderCollapsibleTextResult } from "../ui/collapsed-text-rendering.js"
|
|
12
|
+
import { truncateToWidth, type ThemeLike } from "../ui/tool-call-rendering.js"
|
|
13
|
+
|
|
14
|
+
/** Collapsed height while arguments stream in; the newest lines stay visible. */
|
|
15
|
+
export const WRITE_STREAM_COLLAPSED_LINES = 16
|
|
16
|
+
/** Collapsed height of a finished write result. */
|
|
17
|
+
export const WRITE_RESULT_COLLAPSED_LINES = 24
|
|
18
|
+
|
|
19
|
+
const WRITE_PREVIEW_TOOLS = new Set(["ai_memory_vault_write", "ai_memory_vault_edit"])
|
|
20
|
+
|
|
21
|
+
export function isAiMemoryWriteTool(toolName: string): boolean {
|
|
22
|
+
return WRITE_PREVIEW_TOOLS.has(toolName)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function text(value: unknown): string {
|
|
26
|
+
return value === undefined || value === null ? "" : String(value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function formatAiMemoryWriteTarget(toolName: string, args: any): string {
|
|
30
|
+
const path = text(args?.path).trim() || "(path pending)"
|
|
31
|
+
const heading = text(args?.heading).trim()
|
|
32
|
+
return toolName === "ai_memory_vault_edit" && heading ? `${path} > # ${heading}` : path
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param args partially streamed tool arguments (may miss fields entirely)
|
|
37
|
+
* @param streaming true while arguments are still arriving
|
|
38
|
+
*/
|
|
39
|
+
export function buildAiMemoryWritePreview(toolName: string, args: any, streaming = true): { header: string; content: string } {
|
|
40
|
+
const label = toolName === "ai_memory_vault_edit" ? "Editing" : "Writing"
|
|
41
|
+
const frontmatter = text(args?.frontmatter).trim()
|
|
42
|
+
const heading = text(args?.heading).trim()
|
|
43
|
+
const headingLine = toolName === "ai_memory_vault_edit" && heading ? `# ${heading}` : ""
|
|
44
|
+
const body = text(args?.body)
|
|
45
|
+
const written = [frontmatter, headingLine, body.trimEnd()].filter(Boolean).join("\n\n")
|
|
46
|
+
|
|
47
|
+
const header = `${label}: ${formatAiMemoryWriteTarget(toolName, args)}${streaming ? " … streaming" : ""}`
|
|
48
|
+
const content = written || (streaming ? "(waiting for content…)" : "(empty)")
|
|
49
|
+
return { header, content }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function formatAiMemoryWritePreview(toolName: string, args: any, streaming = true): string {
|
|
53
|
+
const { header, content } = buildAiMemoryWritePreview(toolName, args, streaming)
|
|
54
|
+
return `${header}\n\n${content}`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Result slot for vault writes: streams the note content while the model writes,
|
|
59
|
+
* shows the confirmed content afterwards. Both states honour `expanded`, so
|
|
60
|
+
* Ctrl+O / Strg+O toggles between the collapsed excerpt and the full text.
|
|
61
|
+
*/
|
|
62
|
+
export function renderAiMemoryWriteResult(
|
|
63
|
+
toolName: string,
|
|
64
|
+
result: any,
|
|
65
|
+
options: { expanded?: boolean; isPartial?: boolean } = {},
|
|
66
|
+
theme: ThemeLike,
|
|
67
|
+
context?: any,
|
|
68
|
+
): { render(width: number): string[] } {
|
|
69
|
+
if (!options.isPartial) {
|
|
70
|
+
return renderCollapsibleTextResult(result, options, theme, {
|
|
71
|
+
partialLabel: "Writing AI Memory...",
|
|
72
|
+
emptyLabel: "No AI Memory write result",
|
|
73
|
+
maxLines: WRITE_RESULT_COLLAPSED_LINES,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { header, content } = buildAiMemoryWritePreview(toolName, context?.args ?? {}, context?.argsComplete !== true)
|
|
78
|
+
// Keep the target pinned above the streamed tail so it never scrolls away.
|
|
79
|
+
const body = renderCollapsibleText(content, options, theme, { maxLines: WRITE_STREAM_COLLAPSED_LINES, keep: "tail" })
|
|
80
|
+
return {
|
|
81
|
+
render: (width: number) => [theme.fg("accent", truncateToWidth(header, Math.max(10, width - 2))), ...body.render(width)],
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -332,7 +332,7 @@ export async function webSearch(query: string, limit = 10): Promise<string> {
|
|
|
332
332
|
export async function webFetch(url: string, maxChars = 20_000): Promise<string> {
|
|
333
333
|
const safeUrl = validateHttpUrl(url)
|
|
334
334
|
const safeMaxChars = clampLimit(maxChars, 20_000, 1_000, 100_000)
|
|
335
|
-
if (isYouTubeUrl(safeUrl)) return fetchYouTubeContent(safeUrl
|
|
335
|
+
if (isYouTubeUrl(safeUrl)) return fetchYouTubeContent(safeUrl)
|
|
336
336
|
const response = await fetch(safeUrl, { headers: { "User-Agent": "Mozilla/5.0 (compatible; prism-research/1.0)" }, signal: AbortSignal.timeout(15_000) })
|
|
337
337
|
const text = await response.text()
|
|
338
338
|
if (!response.ok) throw new Error(`Fetch failed for ${safeUrl}: HTTP ${response.status}`)
|
|
@@ -392,8 +392,8 @@ export default function researchToolsExtension(pi: ExtensionAPI): void {
|
|
|
392
392
|
pi.registerTool({
|
|
393
393
|
name: "web_fetch",
|
|
394
394
|
label: "web fetch",
|
|
395
|
-
description: "Fetch readable text from a web page for citation-backed research. For YouTube URLs, extracts structured video metadata
|
|
396
|
-
promptSnippet: "Fetch readable text from a web page. For YouTube URLs, extracts structured video metadata
|
|
395
|
+
description: "Fetch readable text from a web page for citation-backed research. For YouTube URLs, extracts structured video metadata and the complete transcript without applying maxChars truncation.",
|
|
396
|
+
promptSnippet: "Fetch readable text from a web page. For YouTube URLs, extracts structured video metadata and the complete transcript without truncation.",
|
|
397
397
|
parameters: Type.Object({ url: Type.String(), maxChars: Type.Optional(Type.Number()) }),
|
|
398
398
|
async execute(_id: string, params: any) {
|
|
399
399
|
return { content: [{ type: "text", text: await webFetch(params.url, params.maxChars || 20_000) }], details: { url: params.url } }
|
|
@@ -215,7 +215,7 @@ export function formatDuration(seconds: number): string {
|
|
|
215
215
|
// Format the final YouTube result
|
|
216
216
|
// ---------------------------------------------------------------------------
|
|
217
217
|
|
|
218
|
-
export function formatYouTubeResult(meta: YouTubeMetadata, transcript: string,
|
|
218
|
+
export function formatYouTubeResult(meta: YouTubeMetadata, transcript: string, _maxChars?: number): string {
|
|
219
219
|
const lines: string[] = []
|
|
220
220
|
|
|
221
221
|
lines.push(`# ${meta.title}`)
|
|
@@ -240,16 +240,16 @@ export function formatYouTubeResult(meta: YouTubeMetadata, transcript: string, m
|
|
|
240
240
|
lines.push(transcript)
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return
|
|
243
|
+
// YouTube transcripts must stay complete. The generic web_fetch maxChars
|
|
244
|
+
// limit is intentionally ignored here so long videos are not cut off.
|
|
245
|
+
return lines.join("\n")
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
// ---------------------------------------------------------------------------
|
|
249
249
|
// Main entry point for web_fetch YouTube handling
|
|
250
250
|
// ---------------------------------------------------------------------------
|
|
251
251
|
|
|
252
|
-
export async function fetchYouTubeContent(url: string,
|
|
252
|
+
export async function fetchYouTubeContent(url: string, _maxChars?: number): Promise<string> {
|
|
253
253
|
const videoId = extractVideoId(url)
|
|
254
254
|
if (!videoId) throw new Error(`Could not extract video ID from URL: ${url}`)
|
|
255
255
|
|
|
@@ -288,5 +288,5 @@ export async function fetchYouTubeContent(url: string, maxChars = 20_000): Promi
|
|
|
288
288
|
transcript = await fetchTranscriptYtDlp(videoId, "en")
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
return formatYouTubeResult(meta, transcript
|
|
291
|
+
return formatYouTubeResult(meta, transcript)
|
|
292
292
|
}
|
|
@@ -9,6 +9,8 @@ export type CollapsibleTextRenderConfig = {
|
|
|
9
9
|
partialLabel: string
|
|
10
10
|
emptyLabel: string
|
|
11
11
|
maxLines: number
|
|
12
|
+
/** "head" keeps the first lines (default), "tail" keeps the newest lines. */
|
|
13
|
+
keep?: "head" | "tail"
|
|
12
14
|
lines?: (text: string) => string[]
|
|
13
15
|
strip?: (text: string) => string
|
|
14
16
|
}
|
|
@@ -31,26 +33,30 @@ export function renderCollapsibleText(
|
|
|
31
33
|
text: string,
|
|
32
34
|
options: CollapsibleTextRenderOptions = {},
|
|
33
35
|
theme: ThemeLike,
|
|
34
|
-
config: Pick<CollapsibleTextRenderConfig, "maxLines" | "lines" | "strip">,
|
|
36
|
+
config: Pick<CollapsibleTextRenderConfig, "maxLines" | "keep" | "lines" | "strip">,
|
|
35
37
|
): { render(width: number): string[] } {
|
|
36
38
|
const body = config.strip ? config.strip(text) : text
|
|
37
39
|
const lines = config.lines ? config.lines(body) : body.split(/\r?\n/)
|
|
38
|
-
const
|
|
40
|
+
const keepTail = config.keep === "tail"
|
|
41
|
+
const shown = options.expanded ? lines : keepTail ? lines.slice(-config.maxLines) : lines.slice(0, config.maxLines)
|
|
42
|
+
const hidden = lines.length - shown.length
|
|
39
43
|
|
|
40
44
|
return {
|
|
41
45
|
render: (width: number) => {
|
|
42
46
|
const maxWidth = Math.max(10, width - 2)
|
|
47
|
+
const hint = hidden > 0 && !options.expanded
|
|
48
|
+
? theme.fg("muted", truncateToWidth(collapseHint(hidden, keepTail ? "above" : "below"), maxWidth))
|
|
49
|
+
: undefined
|
|
43
50
|
const rendered = shown.map((line) => theme.fg("dim", truncateToWidth(line, maxWidth)))
|
|
44
|
-
if (!
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
return rendered
|
|
51
|
+
if (!hint) return rendered
|
|
52
|
+
return keepTail ? [hint, ...rendered] : [...rendered, hint]
|
|
48
53
|
},
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
export function collapseHint(hiddenLineCount: number): string {
|
|
53
|
-
|
|
57
|
+
export function collapseHint(hiddenLineCount: number, position: "below" | "above" = "below"): string {
|
|
58
|
+
const what = position === "above" ? "earlier lines" : "more lines"
|
|
59
|
+
return `... ${hiddenLineCount} ${what} (Ctrl+O / Strg+O to expand)`
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
export function stripLeadingPathHeader(text: string, relPath: string): string {
|