@aexol/spectral 0.7.8 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agents.js +4 -4
- package/dist/agent/index.js +8 -8
- package/dist/cli.js +1 -1
- package/dist/commands/serve.js +1 -1
- package/dist/extensions/kanban-bridge.js +668 -0
- package/dist/extensions/spectral-vision-fallback.js +84 -46
- package/dist/mcp/agent-dir.js +1 -1
- package/dist/mcp/config.js +3 -3
- package/dist/mcp/init.js +1 -9
- package/dist/mcp/sampling-handler.js +1 -1
- package/dist/mcp/server-manager.js +5 -1
- package/dist/memory/commands/status.js +1 -1
- package/dist/memory/compaction.js +2 -2
- package/dist/memory/config.js +3 -3
- package/dist/memory/debug-log.js +1 -1
- package/dist/memory/index.js +2 -0
- package/dist/memory/observer.js +2 -2
- package/dist/memory/tokens.js +1 -1
- package/dist/memory/tools/read-project-observations.js +2 -2
- package/dist/memory/tools/recall-observation.js +2 -2
- package/dist/memory/tools/write-project-observation.js +60 -0
- package/dist/relay/auto-research.js +57 -23
- package/dist/relay/dispatcher.js +28 -2
- package/dist/relay/models-fetch.js +2 -2
- package/dist/{pi → sdk}/ai/env-api-keys.js +9 -49
- package/dist/{pi → sdk}/ai/utils/oauth/anthropic.js +1 -1
- package/dist/{pi → sdk}/ai/utils/oauth/openai-codex.js +1 -1
- package/dist/{pi → sdk}/coding-agent/config.js +11 -78
- package/dist/{pi → sdk}/coding-agent/core/agent-session.js +2 -0
- package/dist/{pi → sdk}/coding-agent/core/compaction/compaction.js +161 -5
- package/dist/{pi → sdk}/coding-agent/core/extensions/loader.js +2 -35
- package/dist/{pi → sdk}/coding-agent/core/extensions/runner.js +1 -2
- package/dist/{pi → sdk}/coding-agent/core/model-registry.js +11 -4
- package/dist/sdk/coding-agent/core/model-resolver-utils.js +8 -0
- package/dist/{pi → sdk}/coding-agent/core/model-resolver.js +1 -1
- package/dist/{pi → sdk}/coding-agent/core/package-manager.js +5 -5
- package/dist/{pi → sdk}/coding-agent/core/resource-loader.js +1 -1
- 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 +1 -170
- package/dist/{pi → sdk}/coding-agent/core/system-prompt.js +3 -1
- package/dist/{pi → sdk}/coding-agent/core/telemetry.js +1 -1
- package/dist/sdk/coding-agent/core/theme.js +202 -0
- package/dist/{pi → sdk}/coding-agent/core/tools/bash.js +17 -18
- package/dist/{pi → sdk}/coding-agent/core/tools/edit.js +7 -8
- package/dist/{pi → sdk}/coding-agent/core/tools/find.js +9 -13
- package/dist/{pi → sdk}/coding-agent/core/tools/grep.js +10 -14
- package/dist/{pi → sdk}/coding-agent/core/tools/ls.js +9 -10
- package/dist/{pi → sdk}/coding-agent/core/tools/read.js +15 -25
- package/dist/{pi/coding-agent/modes/interactive/components/diff.js → sdk/coding-agent/core/tools/render-diff.js} +18 -31
- package/dist/{pi → sdk}/coding-agent/core/tools/write.js +10 -11
- package/dist/{pi → sdk}/coding-agent/index.js +7 -5
- package/dist/{pi → sdk}/coding-agent/migrations.js +3 -3
- package/dist/{pi → sdk}/coding-agent/modes/index.js +0 -1
- package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-mode.js +2 -2
- package/dist/{pi → sdk}/coding-agent/utils/photon.js +2 -10
- package/dist/sdk/coding-agent/utils/pi-user-agent.js +3 -0
- 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} +114 -97
- package/dist/server/handlers/sessions.js +21 -0
- package/dist/server/session-stream.js +5 -5
- package/package.json +6 -3
- package/dist/pi/coding-agent/bun/cli.js +0 -7
- package/dist/pi/coding-agent/bun/restore-sandbox-env.js +0 -31
- package/dist/pi/coding-agent/cli/args.js +0 -340
- package/dist/pi/coding-agent/cli/file-processor.js +0 -82
- package/dist/pi/coding-agent/cli/initial-message.js +0 -21
- package/dist/pi/coding-agent/core/footer-data-provider.js +0 -309
- package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +0 -35
- package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +0 -26
- package/dist/pi/coding-agent/modes/interactive/interactive-mode.js +0 -3
- package/dist/pi/coding-agent/modes/interactive/theme/theme.js +0 -1022
- package/dist/pi/coding-agent/utils/pi-user-agent.js +0 -4
- /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/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/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/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/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/http-dispatcher.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/keybindings.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/messages.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/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/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/render-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/main.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-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/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
|
@@ -1,55 +1,87 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Spectral Vision Extension
|
|
3
3
|
*
|
|
4
|
-
* Automatically
|
|
5
|
-
*
|
|
6
|
-
* and
|
|
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.
|
|
7
8
|
*
|
|
8
9
|
* Logic:
|
|
9
10
|
* - ALWAYS intercepts images (even when main model supports vision),
|
|
10
|
-
* replacing them with text descriptions
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
|
15
17
|
* vision-capable model by provider priority.
|
|
16
18
|
*
|
|
17
19
|
* Hooks into the `context` event to intercept ALL images before they reach
|
|
18
20
|
* the LLM (covers user-attached images and tool-result images alike).
|
|
19
21
|
*/
|
|
20
|
-
import { streamSimple } from "../
|
|
22
|
+
import { streamSimple } from "../sdk/ai/index.js";
|
|
21
23
|
// ---------------------------------------------------------------------------
|
|
22
24
|
// Helpers
|
|
23
25
|
// ---------------------------------------------------------------------------
|
|
24
|
-
/**
|
|
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;
|
|
26
38
|
const allModels = ctx.modelRegistry.getAll();
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const visionModels = availableModels.filter((m) => m.input.includes("image"));
|
|
30
|
-
if (visionModels.length === 0)
|
|
39
|
+
const match = allModels.find((m) => m.provider === defaultVisionProvider && m.id === defaultVisionModel);
|
|
40
|
+
if (!match || !match.input.includes("image"))
|
|
31
41
|
return undefined;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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;
|
|
44
61
|
}
|
|
45
|
-
// 2.
|
|
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;
|
|
46
73
|
const providerPriority = ["anthropic", "openai", "google", "openrouter"];
|
|
47
74
|
for (const provider of providerPriority) {
|
|
48
75
|
const match = visionModels.find((m) => m.provider === provider);
|
|
49
|
-
if (match)
|
|
76
|
+
if (match) {
|
|
77
|
+
process.stderr.write(`[spectral-vision] Using fallback vision model: ${match.provider}/${match.id}\n`);
|
|
50
78
|
return match;
|
|
79
|
+
}
|
|
51
80
|
}
|
|
52
|
-
|
|
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;
|
|
53
85
|
}
|
|
54
86
|
/** Check if any content block in an array is an image */
|
|
55
87
|
function hasImageContent(content) {
|
|
@@ -111,6 +143,13 @@ async function describeImages(visionModel, content, contextText, ctx) {
|
|
|
111
143
|
const textNote = description.trim()
|
|
112
144
|
? `[Image description from ${visionModel.provider}/${visionModel.id} (${imageCount} image(s)):\n${description}\n]`
|
|
113
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
|
+
}
|
|
114
153
|
return [
|
|
115
154
|
...textPrefixBlocks,
|
|
116
155
|
{ type: "text", text: textNote },
|
|
@@ -119,6 +158,13 @@ async function describeImages(visionModel, content, contextText, ctx) {
|
|
|
119
158
|
catch (err) {
|
|
120
159
|
const msg = err instanceof Error ? err.message : String(err);
|
|
121
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
|
+
}
|
|
122
168
|
return [
|
|
123
169
|
...textPrefixBlocks,
|
|
124
170
|
{
|
|
@@ -134,7 +180,7 @@ async function describeImages(visionModel, content, contextText, ctx) {
|
|
|
134
180
|
export default function spectralVisionExtension(pi) {
|
|
135
181
|
let visionModel;
|
|
136
182
|
pi.on("session_start", (_event, ctx) => {
|
|
137
|
-
visionModel =
|
|
183
|
+
visionModel = resolveVisionModel(ctx);
|
|
138
184
|
if (visionModel) {
|
|
139
185
|
process.stderr.write(`[spectral-vision] Ready — using ${visionModel.provider}/${visionModel.id} for image descriptions.\n`);
|
|
140
186
|
}
|
|
@@ -151,27 +197,19 @@ export default function spectralVisionExtension(pi) {
|
|
|
151
197
|
}
|
|
152
198
|
if (totalImages === 0)
|
|
153
199
|
return;
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
visionModel = currentModel;
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
// Refresh vision model
|
|
162
|
-
if (!visionModel ||
|
|
163
|
-
!ctx.modelRegistry.hasConfiguredAuth(visionModel)) {
|
|
164
|
-
visionModel = findVisionModel(ctx);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
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);
|
|
167
204
|
if (!visionModel) {
|
|
168
205
|
process.stderr.write("[spectral-vision] No vision model available, images will be omitted\n");
|
|
169
206
|
return;
|
|
170
207
|
}
|
|
171
208
|
process.stderr.write(`[spectral-vision] Describing ${totalImages} image(s) with ${visionModel.provider}/${visionModel.id}...\n`);
|
|
172
|
-
// Process each message
|
|
209
|
+
// Process each message — intercept both user messages and tool results
|
|
210
|
+
// that contain images (e.g. read tool returning a screenshot).
|
|
173
211
|
const processed = await Promise.all(messages.map(async (msg) => {
|
|
174
|
-
if (msg.role !== "user" || !Array.isArray(msg.content) || !hasImageContent(msg.content)) {
|
|
212
|
+
if ((msg.role !== "user" && msg.role !== "toolResult") || !Array.isArray(msg.content) || !hasImageContent(msg.content)) {
|
|
175
213
|
return msg;
|
|
176
214
|
}
|
|
177
215
|
// Extract context from preceding text blocks
|
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);
|
package/dist/mcp/init.js
CHANGED
|
@@ -208,15 +208,7 @@ export function flushMetadataCache(state) {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
|
-
function safeFg(
|
|
212
|
-
try {
|
|
213
|
-
const styled = ui?.theme?.fg?.(color, text);
|
|
214
|
-
if (styled)
|
|
215
|
-
return styled;
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
// fall through to plain text
|
|
219
|
-
}
|
|
211
|
+
function safeFg(_ui, _color, text) {
|
|
220
212
|
return text;
|
|
221
213
|
}
|
|
222
214
|
export function updateStatusBar(state) {
|
|
@@ -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";
|
|
@@ -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";
|
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/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { registerObserverTrigger } from "./hooks/observer-trigger.js";
|
|
|
6
6
|
import { Runtime } from "./runtime.js";
|
|
7
7
|
import { registerRecallTool } from "./tools/recall-observation.js";
|
|
8
8
|
import { registerReadProjectObservationsTool } from "./tools/read-project-observations.js";
|
|
9
|
+
import { registerWriteProjectObservationTool } from "./tools/write-project-observation.js";
|
|
9
10
|
export default function observationalMemory(pi) {
|
|
10
11
|
const runtime = new Runtime();
|
|
11
12
|
// Log extension load so we can confirm it's running in serve mode.
|
|
@@ -17,4 +18,5 @@ export default function observationalMemory(pi) {
|
|
|
17
18
|
registerViewCommand(pi, runtime);
|
|
18
19
|
registerRecallTool(pi);
|
|
19
20
|
registerReadProjectObservationsTool(pi);
|
|
21
|
+
registerWriteProjectObservationTool(pi);
|
|
20
22
|
}
|
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/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";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Type } from "../../sdk/ai/index.js";
|
|
2
|
+
import { defineTool } from "../../sdk/coding-agent/index.js";
|
|
3
|
+
import { getProjectObsStore } from "../project-observations-store.js";
|
|
4
|
+
import { hashId } from "../ids.js";
|
|
5
|
+
export const WRITE_PROJECT_OBSERVATION_TOOL_NAME = "write_project_observation";
|
|
6
|
+
export const writeProjectObservationTool = defineTool({
|
|
7
|
+
name: WRITE_PROJECT_OBSERVATION_TOOL_NAME,
|
|
8
|
+
label: "Write project observation",
|
|
9
|
+
description: "Write a durable, cross-session observation about this project. " +
|
|
10
|
+
"Use this to persist discovered conventions, architectural rules, " +
|
|
11
|
+
"gotchas, and directory purposes. Observations are deduplicated by " +
|
|
12
|
+
"content hash — writing the same fact twice is harmless (idempotent). " +
|
|
13
|
+
"Future sessions can discover these via read_project_observations.",
|
|
14
|
+
promptSnippet: "Use write_project_observation(content, relevance) to persist project-level knowledge.",
|
|
15
|
+
promptGuidelines: [
|
|
16
|
+
"Use for STABLE facts: conventions, architecture decisions, gotchas, file roles.",
|
|
17
|
+
"Use relevance='critical' for NEVER-EDIT rules (auto-generated files, invariant constraints).",
|
|
18
|
+
"Use relevance='high' for important conventions the agent must follow.",
|
|
19
|
+
"Use relevance='medium' for useful patterns and directory purposes.",
|
|
20
|
+
"Content must be a single, self-contained sentence — no markdown, no lists.",
|
|
21
|
+
"Deduplication is automatic — same content = same ID → INSERT OR REPLACE.",
|
|
22
|
+
],
|
|
23
|
+
parameters: Type.Object({
|
|
24
|
+
content: Type.String({
|
|
25
|
+
minLength: 1,
|
|
26
|
+
description: "A single self-contained sentence describing the fact. No markdown, no lists.",
|
|
27
|
+
}),
|
|
28
|
+
relevance: Type.Union([
|
|
29
|
+
Type.Literal("low"),
|
|
30
|
+
Type.Literal("medium"),
|
|
31
|
+
Type.Literal("high"),
|
|
32
|
+
Type.Literal("critical"),
|
|
33
|
+
], { description: "Importance: critical > high > medium > low" }),
|
|
34
|
+
}),
|
|
35
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
36
|
+
const store = getProjectObsStore();
|
|
37
|
+
if (!store) {
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: "text", text: "Project observations store is not available." }],
|
|
40
|
+
details: { status: "store_unavailable" },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const projectId = store.getProjectByCwd(ctx.cwd);
|
|
44
|
+
if (!projectId) {
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: `No project found for current working directory: ${ctx.cwd}` }],
|
|
47
|
+
details: { status: "no_project" },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const id = hashId(params.content);
|
|
51
|
+
store.insertProjectObservations(projectId, "auto-research", [{ id, content: params.content, relevance: params.relevance }], Date.now());
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: `Observation [${id}] recorded (relevance: ${params.relevance}).` }],
|
|
54
|
+
details: { id, status: "ok" },
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
export function registerWriteProjectObservationTool(pi) {
|
|
59
|
+
pi.registerTool(writeProjectObservationTool);
|
|
60
|
+
}
|