@aexol/spectral 0.7.7 → 0.8.0
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/agent/agents.js +4 -4
- package/dist/agent/index.js +24 -148
- package/dist/cli.js +25 -220
- package/dist/commands/serve.js +1 -1
- package/dist/extensions/spectral-vision-fallback.js +225 -0
- package/dist/mcp/agent-dir.js +1 -1
- package/dist/mcp/config.js +3 -3
- package/dist/mcp/sampling-handler.js +1 -1
- package/dist/mcp/server-manager.js +5 -1
- package/dist/memory/commands/status.js +6 -6
- package/dist/memory/commands/view.js +16 -14
- package/dist/memory/compaction.js +33 -5
- package/dist/memory/config.js +3 -3
- package/dist/memory/debug-log.js +1 -1
- package/dist/memory/observer.js +2 -2
- package/dist/memory/prompts.js +5 -5
- package/dist/memory/tokens.js +1 -1
- package/dist/memory/tools/read-project-observations.js +2 -2
- package/dist/memory/tools/recall-observation.js +4 -4
- package/dist/relay/auto-research.js +23 -23
- package/dist/relay/dispatcher.js +28 -2
- package/dist/relay/models-fetch.js +15 -3
- package/dist/{pi → sdk}/coding-agent/cli/args.js +4 -4
- package/dist/{pi → sdk}/coding-agent/config.js +9 -20
- package/dist/{pi → sdk}/coding-agent/core/agent-session.js +5 -17
- package/dist/{pi → sdk}/coding-agent/core/compaction/compaction.js +161 -5
- package/dist/{pi → sdk}/coding-agent/core/extensions/loader.js +0 -6
- package/dist/{pi → sdk}/coding-agent/core/extensions/runner.js +7 -1
- package/dist/{pi → sdk}/coding-agent/core/keybindings.js +129 -2
- package/dist/{pi → sdk}/coding-agent/core/model-registry.js +11 -4
- package/dist/{pi → sdk}/coding-agent/core/package-manager.js +5 -5
- package/dist/{pi → sdk}/coding-agent/core/sdk.js +1 -1
- package/dist/{pi → sdk}/coding-agent/core/session-manager.js +4 -4
- package/dist/{pi → sdk}/coding-agent/core/settings-manager.js +20 -0
- package/dist/{pi → sdk}/coding-agent/core/telemetry.js +1 -1
- package/dist/{pi → sdk}/coding-agent/core/tools/bash.js +17 -63
- package/dist/{pi → sdk}/coding-agent/core/tools/edit.js +4 -141
- package/dist/{pi → sdk}/coding-agent/core/tools/find.js +0 -11
- package/dist/{pi → sdk}/coding-agent/core/tools/grep.js +0 -11
- package/dist/{pi → sdk}/coding-agent/core/tools/ls.js +0 -11
- package/dist/{pi → sdk}/coding-agent/core/tools/read.js +0 -12
- package/dist/{pi → sdk}/coding-agent/core/tools/render-utils.js +1 -14
- package/dist/{pi → sdk}/coding-agent/core/tools/write.js +2 -97
- package/dist/{pi → sdk}/coding-agent/migrations.js +3 -3
- package/dist/{pi → sdk}/coding-agent/modes/interactive/components/keybinding-hints.js +1 -1
- package/dist/sdk/coding-agent/modes/interactive/components/visual-truncate.js +26 -0
- package/dist/{pi → sdk}/coding-agent/modes/interactive/theme/theme.js +1 -2
- package/dist/{pi → sdk}/coding-agent/utils/tools-manager.js +1 -1
- package/dist/{pi → sdk}/coding-agent/utils/version-check.js +2 -2
- package/dist/{pi → sdk}/coding-agent/utils/windows-self-update.js +1 -1
- package/dist/server/{pi-bridge.js → agent-bridge.js} +158 -89
- package/dist/server/handlers/sessions.js +21 -0
- package/dist/server/session-stream.js +12 -6
- package/package.json +6 -3
- package/dist/pi/coding-agent/core/export-html/ansi-to-html.js +0 -248
- package/dist/pi/coding-agent/core/export-html/index.js +0 -225
- package/dist/pi/coding-agent/core/export-html/tool-renderer.js +0 -107
- package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +0 -32
- package/dist/pi/tui/autocomplete.js +0 -631
- package/dist/pi/tui/components/box.js +0 -103
- package/dist/pi/tui/components/cancellable-loader.js +0 -34
- package/dist/pi/tui/components/editor.js +0 -1915
- package/dist/pi/tui/components/image.js +0 -88
- package/dist/pi/tui/components/input.js +0 -425
- package/dist/pi/tui/components/loader.js +0 -68
- package/dist/pi/tui/components/markdown.js +0 -633
- package/dist/pi/tui/components/select-list.js +0 -158
- package/dist/pi/tui/components/settings-list.js +0 -184
- package/dist/pi/tui/components/spacer.js +0 -22
- package/dist/pi/tui/components/text.js +0 -88
- package/dist/pi/tui/components/truncated-text.js +0 -50
- package/dist/pi/tui/editor-component.js +0 -1
- package/dist/pi/tui/fuzzy.js +0 -109
- package/dist/pi/tui/index.js +0 -31
- package/dist/pi/tui/keybindings.js +0 -173
- package/dist/pi/tui/keys.js +0 -1172
- package/dist/pi/tui/kill-ring.js +0 -43
- package/dist/pi/tui/stdin-buffer.js +0 -360
- package/dist/pi/tui/terminal-image.js +0 -335
- package/dist/pi/tui/terminal.js +0 -324
- package/dist/pi/tui/tui.js +0 -1076
- package/dist/pi/tui/undo-stack.js +0 -24
- package/dist/pi/tui/utils.js +0 -1016
- /package/dist/{pi → sdk}/agent-core/agent-loop.js +0 -0
- /package/dist/{pi → sdk}/agent-core/agent.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/agent-harness.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/compaction/branch-summarization.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/compaction/compaction.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/compaction/utils.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/env/nodejs.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/messages.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/prompt-templates.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/jsonl-repo.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/jsonl-storage.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/memory-repo.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/memory-storage.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/repo-utils.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/session.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/uuid.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/skills.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/system-prompt.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/types.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/utils/shell-output.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/utils/truncate.js +0 -0
- /package/dist/{pi → sdk}/agent-core/index.js +0 -0
- /package/dist/{pi → sdk}/agent-core/node.js +0 -0
- /package/dist/{pi → sdk}/agent-core/proxy.js +0 -0
- /package/dist/{pi → sdk}/agent-core/types.js +0 -0
- /package/dist/{pi → sdk}/ai/api-registry.js +0 -0
- /package/dist/{pi → sdk}/ai/cli.js +0 -0
- /package/dist/{pi → sdk}/ai/env-api-keys.js +0 -0
- /package/dist/{pi → sdk}/ai/image-models.generated.js +0 -0
- /package/dist/{pi → sdk}/ai/image-models.js +0 -0
- /package/dist/{pi → sdk}/ai/images-api-registry.js +0 -0
- /package/dist/{pi → sdk}/ai/images.js +0 -0
- /package/dist/{pi → sdk}/ai/index.js +0 -0
- /package/dist/{pi → sdk}/ai/models.generated.js +0 -0
- /package/dist/{pi → sdk}/ai/models.js +0 -0
- /package/dist/{pi → sdk}/ai/oauth.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/anthropic.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/faux.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/github-copilot-headers.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/openai-completions.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/openai-prompt-cache.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/register-builtins.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/simple-options.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/transform-messages.js +0 -0
- /package/dist/{pi → sdk}/ai/session-resources.js +0 -0
- /package/dist/{pi → sdk}/ai/stream.js +0 -0
- /package/dist/{pi → sdk}/ai/types.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/diagnostics.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/event-stream.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/hash.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/headers.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/json-parse.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/node-http-proxy.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/anthropic.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/device-code.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/github-copilot.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/index.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/oauth-page.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/openai-codex.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/pkce.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/types.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/overflow.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/sanitize-unicode.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/typebox-helpers.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/validation.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/bun/cli.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/bun/restore-sandbox-env.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/cli/file-processor.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/cli/initial-message.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/cli.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/agent-session-runtime.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/agent-session-services.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/auth-guidance.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/auth-storage.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/bash-executor.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/compaction/branch-summarization.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/compaction/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/compaction/utils.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/defaults.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/diagnostics.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/event-bus.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/exec.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/extensions/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/extensions/types.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/extensions/wrapper.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/footer-data-provider.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/http-dispatcher.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/messages.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/model-resolver.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/output-guard.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/prompt-templates.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/provider-display-names.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/resolve-config-value.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/resource-loader.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/session-cwd.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/skills.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/slash-commands.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/source-info.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/system-prompt.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/timings.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/edit-diff.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/file-mutation-queue.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/output-accumulator.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/path-utils.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/tool-definition-wrapper.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/truncate.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/main.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/components/diff.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/interactive-mode.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/print-mode.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/rpc/jsonl.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-client.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-mode.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-types.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/ansi.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/changelog.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/child-process.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/clipboard-image.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/clipboard-native.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/clipboard.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/exif-orientation.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/frontmatter.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/fs-watch.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/git.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/html.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/image-convert.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/image-resize.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/mime.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/paths.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/photon.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/pi-user-agent.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/shell.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/sleep.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/syntax-highlight.js +0 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spectral Vision Extension
|
|
3
|
+
*
|
|
4
|
+
* Automatically switches to a vision-capable model for image processing
|
|
5
|
+
* when images are attached. This allows non-vision models to "see" images
|
|
6
|
+
* and saves main agent context by using a potentially smaller/cheaper
|
|
7
|
+
* vision model for image understanding.
|
|
8
|
+
*
|
|
9
|
+
* Logic:
|
|
10
|
+
* - ALWAYS intercepts images (even when main model supports vision),
|
|
11
|
+
* replacing them with text descriptions.
|
|
12
|
+
* - Prefers the admin-configured default vision model (isVisionDefault
|
|
13
|
+
* flag from backend via SettingsManager) for image descriptions.
|
|
14
|
+
* - Falls back to the main model for vision if no admin default is
|
|
15
|
+
* configured and the main model supports images.
|
|
16
|
+
* - If neither is available, falls back to the first available
|
|
17
|
+
* vision-capable model by provider priority.
|
|
18
|
+
*
|
|
19
|
+
* Hooks into the `context` event to intercept ALL images before they reach
|
|
20
|
+
* the LLM (covers user-attached images and tool-result images alike).
|
|
21
|
+
*/
|
|
22
|
+
import { streamSimple } from "../sdk/ai/index.js";
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* Check if the SettingsManager has an admin-configured default vision model
|
|
28
|
+
* with working auth. Returns the model if found and auth-ready, or undefined.
|
|
29
|
+
*/
|
|
30
|
+
function getAdminVisionModel(ctx) {
|
|
31
|
+
const settings = ctx.settingsManager;
|
|
32
|
+
if (!settings)
|
|
33
|
+
return undefined;
|
|
34
|
+
const defaultVisionProvider = settings.getDefaultVisionProvider();
|
|
35
|
+
const defaultVisionModel = settings.getDefaultVisionModel();
|
|
36
|
+
if (!defaultVisionProvider || !defaultVisionModel)
|
|
37
|
+
return undefined;
|
|
38
|
+
const allModels = ctx.modelRegistry.getAll();
|
|
39
|
+
const match = allModels.find((m) => m.provider === defaultVisionProvider && m.id === defaultVisionModel);
|
|
40
|
+
if (!match || !match.input.includes("image"))
|
|
41
|
+
return undefined;
|
|
42
|
+
if (!ctx.modelRegistry.hasConfiguredAuth(match))
|
|
43
|
+
return undefined;
|
|
44
|
+
return match;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the best vision model to use for image descriptions.
|
|
48
|
+
*
|
|
49
|
+
* Priority:
|
|
50
|
+
* 1. Admin-configured default vision model (isVisionDefault from backend)
|
|
51
|
+
* 2. Current main model if it supports images
|
|
52
|
+
* 3. Best available vision model by provider priority
|
|
53
|
+
* 4. First available vision model
|
|
54
|
+
*/
|
|
55
|
+
function resolveVisionModel(ctx) {
|
|
56
|
+
// 1. Admin-configured default vision model (always preferred)
|
|
57
|
+
const adminModel = getAdminVisionModel(ctx);
|
|
58
|
+
if (adminModel) {
|
|
59
|
+
process.stderr.write(`[spectral-vision] Using admin-configured default: ${adminModel.provider}/${adminModel.id}\n`);
|
|
60
|
+
return adminModel;
|
|
61
|
+
}
|
|
62
|
+
// 2. Current main model if it supports images
|
|
63
|
+
const currentModel = ctx.model;
|
|
64
|
+
if (currentModel?.input.includes("image") && ctx.modelRegistry.hasConfiguredAuth(currentModel)) {
|
|
65
|
+
process.stderr.write(`[spectral-vision] Using main model for vision: ${currentModel.provider}/${currentModel.id}\n`);
|
|
66
|
+
return currentModel;
|
|
67
|
+
}
|
|
68
|
+
// 3. Fall back to best available vision model by provider priority
|
|
69
|
+
const allModels = ctx.modelRegistry.getAll();
|
|
70
|
+
const visionModels = allModels.filter((m) => m.input.includes("image") && ctx.modelRegistry.hasConfiguredAuth(m));
|
|
71
|
+
if (visionModels.length === 0)
|
|
72
|
+
return undefined;
|
|
73
|
+
const providerPriority = ["anthropic", "openai", "google", "openrouter"];
|
|
74
|
+
for (const provider of providerPriority) {
|
|
75
|
+
const match = visionModels.find((m) => m.provider === provider);
|
|
76
|
+
if (match) {
|
|
77
|
+
process.stderr.write(`[spectral-vision] Using fallback vision model: ${match.provider}/${match.id}\n`);
|
|
78
|
+
return match;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 4. First available vision model
|
|
82
|
+
const fallback = visionModels[0];
|
|
83
|
+
process.stderr.write(`[spectral-vision] Using first available vision model: ${fallback.provider}/${fallback.id}\n`);
|
|
84
|
+
return fallback;
|
|
85
|
+
}
|
|
86
|
+
/** Check if any content block in an array is an image */
|
|
87
|
+
function hasImageContent(content) {
|
|
88
|
+
return Array.isArray(content) && content.some((c) => c?.type === "image");
|
|
89
|
+
}
|
|
90
|
+
/** Count images in a message */
|
|
91
|
+
function countImages(msg) {
|
|
92
|
+
if (!Array.isArray(msg.content))
|
|
93
|
+
return 0;
|
|
94
|
+
return msg.content.filter((c) => c.type === "image").length;
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Core: call vision model to describe images
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
async function describeImages(visionModel, content, contextText, ctx) {
|
|
100
|
+
const userContent = [
|
|
101
|
+
{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: `Please describe the following image(s). ` +
|
|
104
|
+
(contextText ? `Context: ${contextText}. ` : "") +
|
|
105
|
+
"Focus on what is visually visible — text content, UI elements, diagrams, " +
|
|
106
|
+
"code structure, layout, colors, etc. Be concise but thorough. " +
|
|
107
|
+
"If multiple images are provided, describe each one separately with a heading.",
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
const textPrefixBlocks = [];
|
|
111
|
+
let imageCount = 0;
|
|
112
|
+
for (const block of content) {
|
|
113
|
+
if (block.type === "image") {
|
|
114
|
+
userContent.push(block);
|
|
115
|
+
imageCount++;
|
|
116
|
+
}
|
|
117
|
+
else if (block.type === "text") {
|
|
118
|
+
textPrefixBlocks.push(block);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (imageCount === 0)
|
|
122
|
+
return content;
|
|
123
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(visionModel);
|
|
124
|
+
if (!auth.ok) {
|
|
125
|
+
process.stderr.write(`[spectral-vision] No API key for vision model ${visionModel.provider}/${visionModel.id}\n`);
|
|
126
|
+
return content;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const result = await streamSimple(visionModel, {
|
|
130
|
+
systemPrompt: "You are an image description assistant. Describe images accurately and concisely.",
|
|
131
|
+
messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
|
|
132
|
+
tools: [],
|
|
133
|
+
}, {
|
|
134
|
+
apiKey: auth.apiKey,
|
|
135
|
+
headers: auth.headers,
|
|
136
|
+
});
|
|
137
|
+
let description = "";
|
|
138
|
+
for await (const event of result) {
|
|
139
|
+
if (event.type === "text_delta") {
|
|
140
|
+
description += event.delta;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const textNote = description.trim()
|
|
144
|
+
? `[Image description from ${visionModel.provider}/${visionModel.id} (${imageCount} image(s)):\n${description}\n]`
|
|
145
|
+
: `[${imageCount} image(s) — vision model returned empty description]`;
|
|
146
|
+
// Notify the frontend that the vision extension analyzed the image(s)
|
|
147
|
+
try {
|
|
148
|
+
ctx.ui.notify(`[spectral-vision] Analyzed ${imageCount} image(s) with ${visionModel.provider}/${visionModel.id}`, "info");
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// UI context may not be available in headless modes
|
|
152
|
+
}
|
|
153
|
+
return [
|
|
154
|
+
...textPrefixBlocks,
|
|
155
|
+
{ type: "text", text: textNote },
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
160
|
+
process.stderr.write(`[spectral-vision] Vision call failed: ${msg}\n`);
|
|
161
|
+
// Notify frontend about the failure
|
|
162
|
+
try {
|
|
163
|
+
ctx.ui.notify(`[spectral-vision] Failed to analyze ${imageCount} image(s): ${msg}`, "warning");
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// UI context may not be available in headless modes
|
|
167
|
+
}
|
|
168
|
+
return [
|
|
169
|
+
...textPrefixBlocks,
|
|
170
|
+
{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: `[${imageCount} image(s) omitted — vision fallback failed: ${msg}]`,
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Extension entry point
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
export default function spectralVisionExtension(pi) {
|
|
181
|
+
let visionModel;
|
|
182
|
+
pi.on("session_start", (_event, ctx) => {
|
|
183
|
+
visionModel = resolveVisionModel(ctx);
|
|
184
|
+
if (visionModel) {
|
|
185
|
+
process.stderr.write(`[spectral-vision] Ready — using ${visionModel.provider}/${visionModel.id} for image descriptions.\n`);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
process.stderr.write("[spectral-vision] No vision model with auth configured. Image description disabled.\n");
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
pi.on("context", async (event, ctx) => {
|
|
192
|
+
const messages = event.messages;
|
|
193
|
+
// Check if any message contains images
|
|
194
|
+
let totalImages = 0;
|
|
195
|
+
for (const msg of messages) {
|
|
196
|
+
totalImages += countImages(msg);
|
|
197
|
+
}
|
|
198
|
+
if (totalImages === 0)
|
|
199
|
+
return;
|
|
200
|
+
// Re-resolve vision model each time images are encountered.
|
|
201
|
+
// This ensures automatic switching to the admin-configured default
|
|
202
|
+
// vision model even when the main model supports vision.
|
|
203
|
+
visionModel = resolveVisionModel(ctx);
|
|
204
|
+
if (!visionModel) {
|
|
205
|
+
process.stderr.write("[spectral-vision] No vision model available, images will be omitted\n");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
process.stderr.write(`[spectral-vision] Describing ${totalImages} image(s) with ${visionModel.provider}/${visionModel.id}...\n`);
|
|
209
|
+
// Process each message
|
|
210
|
+
const processed = await Promise.all(messages.map(async (msg) => {
|
|
211
|
+
if (msg.role !== "user" || !Array.isArray(msg.content) || !hasImageContent(msg.content)) {
|
|
212
|
+
return msg;
|
|
213
|
+
}
|
|
214
|
+
// Extract context from preceding text blocks
|
|
215
|
+
const textBlocks = msg.content
|
|
216
|
+
.filter((c) => c.type === "text")
|
|
217
|
+
.map((c) => c.text)
|
|
218
|
+
.join(" ");
|
|
219
|
+
const contextText = textBlocks.substring(0, 500); // limit context
|
|
220
|
+
const described = await describeImages(visionModel, msg.content, contextText, ctx);
|
|
221
|
+
return { ...msg, content: described };
|
|
222
|
+
}));
|
|
223
|
+
return { messages: processed };
|
|
224
|
+
});
|
|
225
|
+
}
|
package/dist/mcp/agent-dir.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join, resolve } from "node:path";
|
|
|
3
3
|
export function getAgentDir() {
|
|
4
4
|
const configured = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
5
5
|
if (!configured) {
|
|
6
|
-
return join(homedir(), ".
|
|
6
|
+
return join(homedir(), ".spectral", "agent");
|
|
7
7
|
}
|
|
8
8
|
if (configured === "~") {
|
|
9
9
|
return homedir();
|
package/dist/mcp/config.js
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
5
5
|
import { getAgentPath } from "./agent-dir.js";
|
|
6
6
|
const GENERIC_GLOBAL_CONFIG_PATH = join(homedir(), ".config", "mcp", "mcp.json");
|
|
7
7
|
const PROJECT_CONFIG_NAME = ".mcp.json";
|
|
8
|
-
const
|
|
8
|
+
const PROJECT_MCP_CONFIG_NAME = ".spectral/mcp.json";
|
|
9
9
|
const REPOPROMPT_BINARY_CANDIDATES = [
|
|
10
10
|
join(homedir(), "RepoPrompt", "repoprompt_cli"),
|
|
11
11
|
"/Applications/Repo Prompt.app/Contents/MacOS/repoprompt-mcp",
|
|
@@ -32,7 +32,7 @@ export function getProjectConfigPath(cwd = process.cwd()) {
|
|
|
32
32
|
return resolve(cwd, PROJECT_CONFIG_NAME);
|
|
33
33
|
}
|
|
34
34
|
export function getProjectPiConfigPath(cwd = process.cwd()) {
|
|
35
|
-
return resolve(cwd,
|
|
35
|
+
return resolve(cwd, PROJECT_MCP_CONFIG_NAME);
|
|
36
36
|
}
|
|
37
37
|
export function getConfigDiscoveryPaths(overridePath) {
|
|
38
38
|
return getConfigSources(overridePath).map((source) => ({
|
|
@@ -368,7 +368,7 @@ function findProjectRoot(cwd = process.cwd()) {
|
|
|
368
368
|
if (existsSync(join(current, ".git"))
|
|
369
369
|
|| existsSync(join(current, "package.json"))
|
|
370
370
|
|| existsSync(join(current, PROJECT_CONFIG_NAME))
|
|
371
|
-
|| existsSync(join(current, ".
|
|
371
|
+
|| existsSync(join(current, ".spectral"))) {
|
|
372
372
|
return current;
|
|
373
373
|
}
|
|
374
374
|
const parent = dirname(current);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { complete } from "../
|
|
1
|
+
import { complete } from "../sdk/ai/index.js";
|
|
2
2
|
import { truncateAtWord } from "./utils.js";
|
|
3
3
|
import { CreateMessageRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
export function registerSamplingHandler(client, options) {
|
|
@@ -96,6 +96,8 @@ export class McpServerManager {
|
|
|
96
96
|
}
|
|
97
97
|
// Check for UnauthorizedError — server requires OAuth.
|
|
98
98
|
// This can fire from createHttpTransport probe or client.connect.
|
|
99
|
+
// transport may be undefined if createHttpTransport threw during the probe
|
|
100
|
+
// before returning a transport; close() handles this safely.
|
|
99
101
|
if (error instanceof UnauthorizedError && supportsOAuth(definition)) {
|
|
100
102
|
return {
|
|
101
103
|
client,
|
|
@@ -239,7 +241,9 @@ export class McpServerManager {
|
|
|
239
241
|
connection.status = "closed";
|
|
240
242
|
this.connections.delete(name);
|
|
241
243
|
await connection.client.close().catch(() => { });
|
|
242
|
-
|
|
244
|
+
if (connection.transport) {
|
|
245
|
+
await connection.transport.close().catch(() => { });
|
|
246
|
+
}
|
|
243
247
|
}
|
|
244
248
|
async closeAll() {
|
|
245
249
|
const names = [...this.connections.keys()];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SettingsManager } from "../../
|
|
1
|
+
import { SettingsManager } from "../../sdk/coding-agent/index.js";
|
|
2
2
|
import { getMemoryState, rawTokensSinceLastBound, rawTokensSinceLastCompaction, } from "../branch.js";
|
|
3
3
|
import { observationPoolTokens as estimateObservationPoolTokens } from "../compaction.js";
|
|
4
4
|
import { countByRelevance, formatRelevanceHistogram } from "../relevance.js";
|
|
@@ -39,14 +39,14 @@ export function registerStatusCommand(pi, runtime) {
|
|
|
39
39
|
const pObsLabel = pendingObsCount === 1 ? "observation" : "observations";
|
|
40
40
|
const passiveLines = runtime.config.passive === true
|
|
41
41
|
? [
|
|
42
|
-
"
|
|
42
|
+
"### Mode",
|
|
43
43
|
"Passive: proactive observation and compaction triggers disabled; compaction hook remains active",
|
|
44
44
|
"",
|
|
45
45
|
]
|
|
46
46
|
: [];
|
|
47
47
|
const activityLines = runtime.config.passive === true
|
|
48
48
|
? [
|
|
49
|
-
"
|
|
49
|
+
"### Activity",
|
|
50
50
|
`Observation trigger: passive (~${sinceBound.toLocaleString()} / ${obsThreshold.toLocaleString()} tokens, ${obsPct}%)`,
|
|
51
51
|
" → proactive observation is disabled; manual/Pi compaction can still run sync catch-up observation",
|
|
52
52
|
`Compaction trigger: passive (~${sinceCompaction.toLocaleString()} / ${compThreshold.toLocaleString()} tokens, ${compPct}%)`,
|
|
@@ -56,7 +56,7 @@ export function registerStatusCommand(pi, runtime) {
|
|
|
56
56
|
` distilled from them and redundant observations are pruned away`,
|
|
57
57
|
]
|
|
58
58
|
: [
|
|
59
|
-
"
|
|
59
|
+
"### Activity",
|
|
60
60
|
`Next observation: ~${sinceBound.toLocaleString()} / ${obsThreshold.toLocaleString()} tokens (${obsPct}%)`,
|
|
61
61
|
` → at ${obsThreshold.toLocaleString()} tokens, recent conversation is compressed into new observations`,
|
|
62
62
|
`Next compaction: ~${sinceCompaction.toLocaleString()} / ${compThreshold.toLocaleString()} tokens (${compPct}%)`,
|
|
@@ -68,7 +68,7 @@ export function registerStatusCommand(pi, runtime) {
|
|
|
68
68
|
];
|
|
69
69
|
const lines = [
|
|
70
70
|
...passiveLines,
|
|
71
|
-
"
|
|
71
|
+
"### Memory",
|
|
72
72
|
`Reflections: ~${committedRefsTokens.toLocaleString()} tokens (${committedRefsCount} ${refLabel}) — durable insights`,
|
|
73
73
|
`Observations:`,
|
|
74
74
|
` committed ~${committedObsTokens.toLocaleString()} tokens (${committedObsCount} ${cObsLabel}) — folded into last compaction`,
|
|
@@ -79,7 +79,7 @@ export function registerStatusCommand(pi, runtime) {
|
|
|
79
79
|
];
|
|
80
80
|
if (runtime.observerInFlight || runtime.compactInFlight) {
|
|
81
81
|
lines.push("");
|
|
82
|
-
lines.push("
|
|
82
|
+
lines.push("### In Flight");
|
|
83
83
|
if (runtime.observerInFlight)
|
|
84
84
|
lines.push("Observer: running");
|
|
85
85
|
if (runtime.compactInFlight)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getMemoryState } from "../branch.js";
|
|
2
|
-
import { observationPoolTokens as estimateObservationPoolTokens } from "../compaction.js";
|
|
2
|
+
import { observationPoolTokens as estimateObservationPoolTokens, observationsToMarkdownList, reflectionsToMarkdownList, } from "../compaction.js";
|
|
3
3
|
import { countByRelevance, formatRelevanceHistogram } from "../relevance.js";
|
|
4
4
|
import { estimateStringTokens } from "../tokens.js";
|
|
5
|
-
import { reflectionContent
|
|
5
|
+
import { reflectionContent } from "../types.js";
|
|
6
6
|
export function registerViewCommand(pi, runtime) {
|
|
7
7
|
pi.registerCommand("om-view", {
|
|
8
8
|
description: "Print observational memory details (reflections + observations)",
|
|
@@ -22,39 +22,41 @@ export function registerViewCommand(pi, runtime) {
|
|
|
22
22
|
const totalTokens = committedRefTokens + totalObsTokens;
|
|
23
23
|
const relevanceHistogram = countByRelevance([...committedObs, ...pendingObs]);
|
|
24
24
|
const plural = (n, singular, plural) => (n === 1 ? singular : plural);
|
|
25
|
-
const renderObs = (r) => `[${r.id}] ${r.timestamp} [${r.relevance}] ${r.content}`;
|
|
26
25
|
const sections = [];
|
|
27
|
-
sections.push(
|
|
26
|
+
sections.push(`## Memory Overview\n\n` +
|
|
27
|
+
`${committedRefCount} ${plural(committedRefCount, "reflection", "reflections")} · ` +
|
|
28
28
|
`${totalObsCount} ${plural(totalObsCount, "observation", "observations")} ` +
|
|
29
29
|
`(${committedObsCount} committed, ${pendingObsCount} pending) · ` +
|
|
30
30
|
`~${totalTokens.toLocaleString()} tokens · ` +
|
|
31
31
|
`relevance ${formatRelevanceHistogram(relevanceHistogram)}`);
|
|
32
|
+
sections.push(`## Reflections (${committedRefCount} ${plural(committedRefCount, "entry", "entries")}, ~${committedRefTokens.toLocaleString()} tokens)`);
|
|
32
33
|
sections.push("");
|
|
33
|
-
sections.push(`── Reflections (${committedRefCount} ${plural(committedRefCount, "entry", "entries")}, ~${committedRefTokens.toLocaleString()} tokens) ──`);
|
|
34
34
|
if (committedRefItems.length > 0) {
|
|
35
|
-
sections.push(committedRefItems
|
|
35
|
+
sections.push(reflectionsToMarkdownList(committedRefItems));
|
|
36
36
|
}
|
|
37
37
|
else {
|
|
38
|
-
sections.push("(none)");
|
|
38
|
+
sections.push("*(none)*");
|
|
39
39
|
}
|
|
40
40
|
sections.push("");
|
|
41
|
-
sections.push(
|
|
41
|
+
sections.push(`## Observations (committed: ${committedObsCount} ${plural(committedObsCount, "observation", "observations")}, ~${committedObsTokens.toLocaleString()} tokens)`);
|
|
42
|
+
sections.push("");
|
|
42
43
|
if (committedObs.length > 0) {
|
|
43
|
-
sections.push(committedObs
|
|
44
|
+
sections.push(observationsToMarkdownList(committedObs));
|
|
44
45
|
}
|
|
45
46
|
else {
|
|
46
|
-
sections.push("(none)");
|
|
47
|
+
sections.push("*(none)*");
|
|
47
48
|
}
|
|
48
49
|
sections.push("");
|
|
49
|
-
sections.push(
|
|
50
|
+
sections.push(`## Observations (pending: ${pendingObsCount} ${plural(pendingObsCount, "observation", "observations")}, ~${pendingObsTokens.toLocaleString()} tokens)`);
|
|
51
|
+
sections.push("");
|
|
50
52
|
if (pendingObs.length > 0) {
|
|
51
|
-
sections.push(pendingObs
|
|
53
|
+
sections.push(observationsToMarkdownList(pendingObs));
|
|
52
54
|
}
|
|
53
55
|
else {
|
|
54
|
-
sections.push("(none)");
|
|
56
|
+
sections.push("*(none)*");
|
|
55
57
|
}
|
|
56
58
|
sections.push("");
|
|
57
|
-
sections.push("Tip: use /tree to browse the raw messages still live in the session
|
|
59
|
+
sections.push("*Tip: use /tree to browse the raw messages still live in the session.*");
|
|
58
60
|
ctx.ui.notify(sections.join("\n"), "info");
|
|
59
61
|
},
|
|
60
62
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { agentLoop } from "../
|
|
2
|
-
import { Type } from "../
|
|
1
|
+
import { agentLoop } from "../sdk/agent-core/index.js";
|
|
2
|
+
import { Type } from "../sdk/ai/index.js";
|
|
3
3
|
import { debugLog, isDebugLogEnabled } from "./debug-log.js";
|
|
4
4
|
import { hashId } from "./ids.js";
|
|
5
5
|
import { AGENT_LOOP_MAX_TOKENS, boundedMaxTokens } from "./model-budget.js";
|
|
@@ -798,16 +798,44 @@ export async function runPruner(args, reflections, observations, budgetTokens, o
|
|
|
798
798
|
});
|
|
799
799
|
return result;
|
|
800
800
|
}
|
|
801
|
+
/**
|
|
802
|
+
* Format an observation for Markdown-safe display.
|
|
803
|
+
* Wraps the ID in backticks so it renders as inline code in Markdown
|
|
804
|
+
* rather than being misinterpreted as a link reference.
|
|
805
|
+
*/
|
|
806
|
+
export function observationToMarkdown(record) {
|
|
807
|
+
return `[\`${record.id}\`] ${record.timestamp} [${record.relevance}] ${record.content}`;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Format a reflection for Markdown-safe display.
|
|
811
|
+
* Wraps the ID in backticks so it renders as inline code in Markdown.
|
|
812
|
+
*/
|
|
813
|
+
export function reflectionToMarkdown(reflection) {
|
|
814
|
+
if (typeof reflection === "string")
|
|
815
|
+
return reflection;
|
|
816
|
+
return `[\`${reflection.id}\`] ${reflection.content}`;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Format observations as a Markdown bullet list for display.
|
|
820
|
+
*/
|
|
821
|
+
export function observationsToMarkdownList(observations) {
|
|
822
|
+
return observations.map((o) => `- ${observationToMarkdown(o)}`).join("\n");
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Format reflections as a Markdown bullet list for display.
|
|
826
|
+
*/
|
|
827
|
+
export function reflectionsToMarkdownList(reflections) {
|
|
828
|
+
return reflections.map((r) => `- ${reflectionToMarkdown(r)}`).join("\n");
|
|
829
|
+
}
|
|
801
830
|
export function renderSummary(reflections, observations) {
|
|
802
831
|
if (reflections.length === 0 && observations.length === 0)
|
|
803
832
|
return "";
|
|
804
833
|
const parts = [CONTEXT_USAGE_INSTRUCTIONS];
|
|
805
834
|
if (reflections.length > 0) {
|
|
806
|
-
parts.push(`## Reflections\n${reflections
|
|
835
|
+
parts.push(`## Reflections\n${reflectionsToMarkdownList(reflections)}`);
|
|
807
836
|
}
|
|
808
837
|
if (observations.length > 0) {
|
|
809
|
-
|
|
810
|
-
parts.push(`## Observations\n${body}`);
|
|
838
|
+
parts.push(`## Observations\n${observationsToMarkdownList(observations)}`);
|
|
811
839
|
}
|
|
812
840
|
return parts.join("\n\n");
|
|
813
841
|
}
|
package/dist/memory/config.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { getAgentDir } from "../
|
|
3
|
+
import { getAgentDir } from "../sdk/coding-agent/index.js";
|
|
4
4
|
export const DEFAULTS = {
|
|
5
5
|
observationThresholdTokens: 1_000,
|
|
6
6
|
compactionThresholdTokens: 50_000,
|
|
7
|
-
reflectionThresholdTokens:
|
|
7
|
+
reflectionThresholdTokens: 10_000,
|
|
8
8
|
passive: false,
|
|
9
9
|
debugLog: false,
|
|
10
10
|
observerMaxChunkTokens: 30_000,
|
|
@@ -72,7 +72,7 @@ function readNamespacedConfig(path) {
|
|
|
72
72
|
}
|
|
73
73
|
export function loadConfig(cwd, env = process.env) {
|
|
74
74
|
const globalPath = join(getAgentDir(), "settings.json");
|
|
75
|
-
const projectPath = join(cwd, ".
|
|
75
|
+
const projectPath = join(cwd, ".spectral", "settings.json");
|
|
76
76
|
return {
|
|
77
77
|
...DEFAULTS,
|
|
78
78
|
...readNamespacedConfig(globalPath),
|
package/dist/memory/debug-log.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { existsSync, mkdirSync, renameSync, statSync, unlinkSync, appendFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { getAgentDir } from "../
|
|
4
|
+
import { getAgentDir } from "../sdk/coding-agent/index.js";
|
|
5
5
|
export const DEBUG_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
6
6
|
export const DEBUG_LOG_RELATIVE_PATH = join("observational-memory", "debug.ndjson");
|
|
7
7
|
const storage = new AsyncLocalStorage();
|
package/dist/memory/observer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { agentLoop } from "../
|
|
2
|
-
import { Type } from "../
|
|
1
|
+
import { agentLoop } from "../sdk/agent-core/index.js";
|
|
2
|
+
import { Type } from "../sdk/ai/index.js";
|
|
3
3
|
import { hashId } from "./ids.js";
|
|
4
4
|
import { AGENT_LOOP_MAX_TOKENS, boundedMaxTokens } from "./model-budget.js";
|
|
5
5
|
import { OBSERVER_SYSTEM } from "./prompts.js";
|
package/dist/memory/prompts.js
CHANGED
|
@@ -92,7 +92,7 @@ Your job is to compress a chunk of recent conversation into timestamped, rated o
|
|
|
92
92
|
|
|
93
93
|
You receive:
|
|
94
94
|
- Current reflections (long-lived facts already crystallized).
|
|
95
|
-
- Current observations (already-recorded observations, each shown as "[id] YYYY-MM-DD HH:MM [relevance] content").
|
|
95
|
+
- Current observations (already-recorded observations, each shown as "[\`id\`] YYYY-MM-DD HH:MM [relevance] content").
|
|
96
96
|
- A new chunk of conversation with source entry labels and inline message timestamps. Each source block starts with "[Source entry id: <id>]" followed by content formatted as "[User @ YYYY-MM-DD HH:MM]:", "[Assistant @ ...]:", "[Tool result for <name> @ ...]:", custom messages, or branch summaries.
|
|
97
97
|
- A current local time fallback for observations that have no obvious message timestamp.
|
|
98
98
|
|
|
@@ -141,7 +141,7 @@ Your task is different from the observer's: you are not recording events, you ar
|
|
|
141
141
|
|
|
142
142
|
You receive:
|
|
143
143
|
- Current reflections (already-crystallized long-lived facts, one per line). Newer reflections may begin with a bracketed id handle; treat that id as recall metadata, not as part of the reflection prose.
|
|
144
|
-
- Current observations (timestamped, relevance-tagged events accumulated over many turns). Each is shown as "[id] YYYY-MM-DD HH:MM [relevance] content".
|
|
144
|
+
- Current observations (timestamped, relevance-tagged events accumulated over many turns). Each is shown as "[\`id\`] YYYY-MM-DD HH:MM [relevance] content".
|
|
145
145
|
|
|
146
146
|
How you work:
|
|
147
147
|
1. Read current reflections and observations to understand what is already crystallized and what new signal exists in the pool.
|
|
@@ -198,7 +198,7 @@ ${RELEVANCE_RUBRIC}
|
|
|
198
198
|
|
|
199
199
|
You receive:
|
|
200
200
|
- Current reflections (long-lived facts; they survive regardless — treat them as already captured). Newer reflections may begin with a bracketed id handle; treat that id as recall metadata, not as part of the reflection prose.
|
|
201
|
-
- Current observations (timestamped, relevance-tagged events to prune). Each is shown as "[id] YYYY-MM-DD HH:MM [relevance] [coverage: tag] content", where id is the 12-character hex handle you reference when dropping.
|
|
201
|
+
- Current observations (timestamped, relevance-tagged events to prune). Each is shown as "[\`id\`] YYYY-MM-DD HH:MM [relevance] [coverage: tag] content", where id is the 12-character hex handle you reference when dropping.
|
|
202
202
|
- A pressure line stating pool size, target, tokens still to cut, and the current pass strategy.
|
|
203
203
|
|
|
204
204
|
Coverage tags are pruning signals derived from current provenance-backed reflection support ids. They are strong evidence, not blind commands:
|
|
@@ -279,8 +279,8 @@ export function buildPrunerPassGuidance(pass, maxPasses) {
|
|
|
279
279
|
}
|
|
280
280
|
export const CONTEXT_USAGE_INSTRUCTIONS = `These are condensed memories from earlier in this session.
|
|
281
281
|
|
|
282
|
-
- Reflections: stable, long-lived facts about the user, project, decisions, and constraints. New reflection lines may include ids in brackets.
|
|
283
|
-
- Observations: timestamped events from the conversation history, in chronological order. Observation lines include ids in brackets.
|
|
282
|
+
- Reflections: stable, long-lived facts about the user, project, decisions, and constraints. New reflection lines may include ids in brackets wrapped in backticks.
|
|
283
|
+
- Observations: timestamped events from the conversation history, in chronological order. Observation lines include ids in brackets wrapped in backticks.
|
|
284
284
|
|
|
285
285
|
Treat these as past records. When entries conflict, the most recent observation reflects the latest known state. Work that prior observations describe as completed should not be redone unless the user explicitly asks to revisit it.
|
|
286
286
|
|
package/dist/memory/tokens.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Type } from "../../
|
|
2
|
-
import { defineTool } from "../../
|
|
1
|
+
import { Type } from "../../sdk/ai/index.js";
|
|
2
|
+
import { defineTool } from "../../sdk/coding-agent/index.js";
|
|
3
3
|
import { getProjectObsStore } from "../project-observations-store.js";
|
|
4
4
|
export const READ_PROJECT_OBSERVATIONS_TOOL_NAME = "read_project_observations";
|
|
5
5
|
export const readProjectObservationsTool = defineTool({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Type } from "../../
|
|
2
|
-
import { defineTool } from "../../
|
|
1
|
+
import { Type } from "../../sdk/ai/index.js";
|
|
2
|
+
import { defineTool } from "../../sdk/coding-agent/index.js";
|
|
3
3
|
import { recallMemorySources, } from "../branch.js";
|
|
4
4
|
import { renderRecallSourceEntries, renderRecallSourceEntry } from "../serialize.js";
|
|
5
5
|
import { estimateEntryTokens } from "../tokens.js";
|
|
@@ -181,10 +181,10 @@ function friendlySourceUnavailableMessage(match) {
|
|
|
181
181
|
return `Observation ${match.observation.id} has source entries associated, but some are unavailable on the current branch or are not source-renderable.${missing}${nonSource}`;
|
|
182
182
|
}
|
|
183
183
|
function reflectionLineText(reflection) {
|
|
184
|
-
return `[
|
|
184
|
+
return `[\`${reflection.id}\`] ${reflection.content}`;
|
|
185
185
|
}
|
|
186
186
|
function observationLineText(observation) {
|
|
187
|
-
return `[
|
|
187
|
+
return `[\`${observation.id}\`] ${observation.timestamp} [${observation.relevance}] ${observation.content}`;
|
|
188
188
|
}
|
|
189
189
|
function renderObservationOnlyTextFromResult(result) {
|
|
190
190
|
const sections = [];
|