@aexol/spectral 0.7.8 → 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 +8 -8
- package/dist/cli.js +1 -1
- package/dist/commands/serve.js +1 -1
- package/dist/extensions/spectral-vision-fallback.js +81 -44
- 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 +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/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/relay/auto-research.js +23 -23
- package/dist/relay/dispatcher.js +28 -2
- package/dist/relay/models-fetch.js +2 -2
- package/dist/{pi → sdk}/coding-agent/cli/args.js +4 -4
- package/dist/{pi → sdk}/coding-agent/config.js +9 -9
- 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/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/telemetry.js +1 -1
- package/dist/{pi → sdk}/coding-agent/migrations.js +3 -3
- 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} +113 -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 → 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/loader.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/extensions/runner.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/keybindings.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/settings-manager.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/bash.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/edit-diff.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/edit.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/find.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/grep.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/ls.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/read.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/core/tools/write.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/components/keybinding-hints.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/components/visual-truncate.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/interactive-mode.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/theme/theme.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
package/dist/agent/agents.js
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* System prompt for the agent goes here.
|
|
13
13
|
*
|
|
14
14
|
* Locations:
|
|
15
|
-
* ~/.
|
|
16
|
-
* .
|
|
15
|
+
* ~/.spectral/agent/agents/*.md — User-level (always loaded)
|
|
16
|
+
* .spectral/agents/*.md — Project-level (opt-in via agentScope)
|
|
17
17
|
*/
|
|
18
18
|
import * as fs from "node:fs";
|
|
19
19
|
import * as path from "node:path";
|
|
20
|
-
import { getAgentDir, parseFrontmatter } from "../
|
|
20
|
+
import { getAgentDir, parseFrontmatter } from "../sdk/coding-agent/index.js";
|
|
21
21
|
function loadAgentsFromDir(dir, source) {
|
|
22
22
|
const agents = [];
|
|
23
23
|
if (!fs.existsSync(dir)) {
|
|
@@ -74,7 +74,7 @@ function isDirectory(p) {
|
|
|
74
74
|
function findNearestProjectAgentsDir(cwd) {
|
|
75
75
|
let currentDir = cwd;
|
|
76
76
|
while (true) {
|
|
77
|
-
const candidate = path.join(currentDir, ".
|
|
77
|
+
const candidate = path.join(currentDir, ".spectral", "agents");
|
|
78
78
|
if (isDirectory(candidate))
|
|
79
79
|
return candidate;
|
|
80
80
|
const parentDir = path.dirname(currentDir);
|
package/dist/agent/index.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* - Chain: { chain: [{ agent, task: "... {previous} ..." }, ...] }
|
|
14
14
|
*
|
|
15
15
|
* Agent definitions live as markdown files with YAML frontmatter:
|
|
16
|
-
* - ~/.
|
|
17
|
-
* - .
|
|
16
|
+
* - ~/.spectral/agent/agents/*.md (user-level, always loaded)
|
|
17
|
+
* - .spectral/agents/*.md (project-level, opt-in via agentScope)
|
|
18
18
|
*
|
|
19
19
|
* Subagents use an in-process `agentLoop()` for:
|
|
20
20
|
* - Clean context isolation (fresh messages array)
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
* - Signal propagation (abort passthrough)
|
|
25
25
|
* - Access to observational memory via the recall tool
|
|
26
26
|
*/
|
|
27
|
-
import { agentLoop } from "../
|
|
28
|
-
import { StringEnum } from "../
|
|
27
|
+
import { agentLoop } from "../sdk/agent-core/index.js";
|
|
28
|
+
import { StringEnum } from "../sdk/ai/index.js";
|
|
29
29
|
import { Type } from "typebox";
|
|
30
30
|
import { discoverAgents } from "./agents.js";
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
// Built-in tool resolution (no spawn needed — tools are registered in-process)
|
|
33
33
|
// ---------------------------------------------------------------------------
|
|
34
|
-
import { createTool, allToolNames } from "../
|
|
35
|
-
import { wrapToolDefinition } from "../
|
|
34
|
+
import { createTool, allToolNames } from "../sdk/coding-agent/core/tools/index.js";
|
|
35
|
+
import { wrapToolDefinition } from "../sdk/coding-agent/core/tools/tool-definition-wrapper.js";
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
37
37
|
// Constants
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
@@ -386,8 +386,8 @@ export default function subagentExtension(pi) {
|
|
|
386
386
|
description: [
|
|
387
387
|
"Delegate tasks to specialized subagents with isolated context.",
|
|
388
388
|
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
|
|
389
|
-
'Default agent scope is "user" (from ~/.
|
|
390
|
-
'To enable project-local agents in .
|
|
389
|
+
'Default agent scope is "user" (from ~/.spectral/agent/agents).',
|
|
390
|
+
'To enable project-local agents in .spectral/agents, set agentScope: "both" (or "project").',
|
|
391
391
|
].join(" "),
|
|
392
392
|
promptSnippet: "Run a subagent to investigate, plan, review, or implement code changes",
|
|
393
393
|
promptGuidelines: [
|
package/dist/cli.js
CHANGED
|
@@ -46,7 +46,7 @@ function printHeader() {
|
|
|
46
46
|
].join("\n"));
|
|
47
47
|
}
|
|
48
48
|
// ---- Disable interactive git editors ----------------------------------------
|
|
49
|
-
// Set at process level for serve mode (
|
|
49
|
+
// Set at process level for serve mode (AgentBridge in-process → getShellEnv()
|
|
50
50
|
// returns process.env). Without this, `git rebase --continue`, `git commit`
|
|
51
51
|
// (without -m), `git merge`, etc. open an editor that hangs forever — the
|
|
52
52
|
// agent's bash tool runs with `stdio: ["ignore", ...]`, so there is no TTY
|
package/dist/commands/serve.js
CHANGED
|
@@ -146,7 +146,7 @@ export async function runServe(opts = {}) {
|
|
|
146
146
|
const backendUrl = opts.backendUrlOverride ?? process.env.SPECTRAL_BACKEND_URL ?? DEFAULT_BACKEND_URL;
|
|
147
147
|
const relayUrl = opts.relayUrlOverride ?? process.env.SPECTRAL_RELAY_URL ?? deriveRelayUrl(backendUrl);
|
|
148
148
|
// Register BEFORE constructing the SessionStreamManager. The manager
|
|
149
|
-
// (and every
|
|
149
|
+
// (and every AgentBridge it spawns) needs the machine JWT to authenticate
|
|
150
150
|
// backend-proxied inference calls, so registration must succeed first.
|
|
151
151
|
// Fail-fast on error — a clear message beats a hang.
|
|
152
152
|
let registration;
|
|
@@ -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,19 +197,10 @@ 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;
|
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";
|
|
@@ -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/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";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-research handler — sends an auto-research task through the existing
|
|
3
|
-
*
|
|
3
|
+
* AgentBridge (backend proxy) instead of spawning a separate pi process.
|
|
4
4
|
*
|
|
5
5
|
* This ensures auto-research uses the same model and API keys as the active
|
|
6
6
|
* session — no separate subprocess, no missing API key errors.
|
|
@@ -47,12 +47,12 @@ function makeRelaySubscriber(sessionId, relay) {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Scan the project's .
|
|
50
|
+
* Scan the project's .spectral/extensions/auto-research/ directory for generated
|
|
51
51
|
* extension directories. Each subdirectory with .ts files counts as one
|
|
52
52
|
* extension.
|
|
53
53
|
*/
|
|
54
54
|
function scanGeneratedExtensions(projectPath) {
|
|
55
|
-
const arDir = path.join(projectPath, ".
|
|
55
|
+
const arDir = path.join(projectPath, ".spectral", "extensions", "auto-research");
|
|
56
56
|
const extensions = [];
|
|
57
57
|
try {
|
|
58
58
|
if (!fs.existsSync(arDir))
|
|
@@ -105,7 +105,7 @@ function scanGeneratedExtensions(projectPath) {
|
|
|
105
105
|
}
|
|
106
106
|
extensions.push({
|
|
107
107
|
name: entry.name,
|
|
108
|
-
path: `.
|
|
108
|
+
path: `.spectral/extensions/auto-research/${entry.name}`,
|
|
109
109
|
description,
|
|
110
110
|
usesLLM,
|
|
111
111
|
fileCount,
|
|
@@ -137,7 +137,7 @@ function hasAgentsMdUpdate(projectPath) {
|
|
|
137
137
|
*/
|
|
138
138
|
function readManifest(projectPath) {
|
|
139
139
|
try {
|
|
140
|
-
const mPath = path.join(projectPath, ".
|
|
140
|
+
const mPath = path.join(projectPath, ".spectral", "extensions", "auto-research", "manifest.json");
|
|
141
141
|
if (!fs.existsSync(mPath))
|
|
142
142
|
return null;
|
|
143
143
|
const raw = fs.readFileSync(mPath, "utf-8");
|
|
@@ -164,7 +164,7 @@ function gatherPreRunContext(projectPath) {
|
|
|
164
164
|
const isIncremental = manifest !== null;
|
|
165
165
|
const existingExtensions = [];
|
|
166
166
|
try {
|
|
167
|
-
const arDir = path.join(projectPath, ".
|
|
167
|
+
const arDir = path.join(projectPath, ".spectral", "extensions", "auto-research");
|
|
168
168
|
if (fs.existsSync(arDir)) {
|
|
169
169
|
for (const entry of fs.readdirSync(arDir, { withFileTypes: true })) {
|
|
170
170
|
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
@@ -209,7 +209,7 @@ function buildIncrementalSection(ctx) {
|
|
|
209
209
|
if (ctx.existingExtensions.length > 0) {
|
|
210
210
|
lines.push("### Existing extensions to review:");
|
|
211
211
|
for (const name of ctx.existingExtensions) {
|
|
212
|
-
lines.push(` - \`.
|
|
212
|
+
lines.push(` - \`.spectral/extensions/auto-research/${name}/\``);
|
|
213
213
|
}
|
|
214
214
|
lines.push("");
|
|
215
215
|
}
|
|
@@ -224,7 +224,7 @@ function buildIncrementalSection(ctx) {
|
|
|
224
224
|
lines.push(" was removed from the project, delete the extension directory entirely.", "");
|
|
225
225
|
lines.push("4. **Update AGENTS.md** — The AUTO-RESEARCH section should reflect the CURRENT");
|
|
226
226
|
lines.push(" set of extensions (remove stale entries, add new ones).", "");
|
|
227
|
-
lines.push("5. **Save manifest.json** — Write/update .
|
|
227
|
+
lines.push("5. **Save manifest.json** — Write/update .spectral/extensions/auto-research/manifest.json");
|
|
228
228
|
lines.push(` with: lastRun (ISO), lastCommit (git HEAD), runCount (${ctx.manifest ? ctx.manifest.runCount + 1 : 1}),`);
|
|
229
229
|
lines.push(" and extensions array with name/path/category for each generated extension.", "");
|
|
230
230
|
return lines.join("\n");
|
|
@@ -234,7 +234,7 @@ function buildIncrementalSection(ctx) {
|
|
|
234
234
|
*/
|
|
235
235
|
function writeManifest(projectPath, extensions) {
|
|
236
236
|
try {
|
|
237
|
-
const arDir = path.join(projectPath, ".
|
|
237
|
+
const arDir = path.join(projectPath, ".spectral", "extensions", "auto-research");
|
|
238
238
|
if (!fs.existsSync(arDir))
|
|
239
239
|
fs.mkdirSync(arDir, { recursive: true });
|
|
240
240
|
let currentCommit = "unknown";
|
|
@@ -257,7 +257,7 @@ function writeManifest(projectPath, extensions) {
|
|
|
257
257
|
}
|
|
258
258
|
/**
|
|
259
259
|
* Build the auto-research task prompt. This is sent as a user message
|
|
260
|
-
* through the existing
|
|
260
|
+
* through the existing AgentBridge, so the agent uses the session's model
|
|
261
261
|
* and backend proxy.
|
|
262
262
|
*
|
|
263
263
|
* @param projectPath Absolute path to the project root
|
|
@@ -297,8 +297,8 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
297
297
|
"}",
|
|
298
298
|
"```",
|
|
299
299
|
"",
|
|
300
|
-
"Extensions live in: `.
|
|
301
|
-
"Auto-research generates extensions into `.
|
|
300
|
+
"Extensions live in: `.spectral/extensions/` (project-local) or `~/.spectral/agent/extensions/` (user-global).",
|
|
301
|
+
"Auto-research generates extensions into `.spectral/extensions/auto-research/<name>/`.",
|
|
302
302
|
"",
|
|
303
303
|
"### Tools (pi.registerTool)",
|
|
304
304
|
"",
|
|
@@ -400,10 +400,10 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
400
400
|
"",
|
|
401
401
|
"### Extension File Structure",
|
|
402
402
|
"",
|
|
403
|
-
"Each extension is a directory under `.
|
|
403
|
+
"Each extension is a directory under `.spectral/extensions/auto-research/`:",
|
|
404
404
|
"",
|
|
405
405
|
"```",
|
|
406
|
-
".
|
|
406
|
+
".spectral/extensions/auto-research/",
|
|
407
407
|
" <extension-name>/",
|
|
408
408
|
" index.ts # Entry point — default export activate(pi)",
|
|
409
409
|
" utils.ts # [optional] Helper functions",
|
|
@@ -440,7 +440,7 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
440
440
|
"",
|
|
441
441
|
"1. **Context Collection** — Explore the project structure:",
|
|
442
442
|
" - Read package.json, tsconfig.json, deno.json (if present)",
|
|
443
|
-
" - Check existing extensions under .
|
|
443
|
+
" - Check existing extensions under .spectral/extensions/",
|
|
444
444
|
" - Review key source files to understand architecture",
|
|
445
445
|
" - Check git log for recent changes and patterns",
|
|
446
446
|
"",
|
|
@@ -454,9 +454,9 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
454
454
|
" - Use `pi.registerTool()` for simple tools with TypeBox validation",
|
|
455
455
|
" - Use `pi.registerCommand()` for custom slash commands",
|
|
456
456
|
" - For LLM-powered extensions, use `ctx.modelRegistry` to call models",
|
|
457
|
-
" - Create files under `.
|
|
457
|
+
" - Create files under `.spectral/extensions/auto-research/<name>/`",
|
|
458
458
|
" - Each extension needs an `index.ts` that registers its tools/commands",
|
|
459
|
-
" - Read `.
|
|
459
|
+
" - Read `.spectral/agents/auto-research-templates.md` for proven extension templates",
|
|
460
460
|
"",
|
|
461
461
|
"4. **Validation** — Verify generated extensions:",
|
|
462
462
|
" - Ensure all imports resolve to available packages (see reference above)",
|
|
@@ -478,7 +478,7 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
478
478
|
"",
|
|
479
479
|
"### Important Rules",
|
|
480
480
|
"",
|
|
481
|
-
"- Write each extension as TypeScript files under `.
|
|
481
|
+
"- Write each extension as TypeScript files under `.spectral/extensions/auto-research/<name>/`",
|
|
482
482
|
"- Every extension directory MUST have an `index.ts` entry point",
|
|
483
483
|
"- Use proper TypeScript with type annotations and error handling",
|
|
484
484
|
"- Extensions must handle errors gracefully (never crash the agent)",
|
|
@@ -491,7 +491,7 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
491
491
|
"agent sessions automatically know about the new capabilities.",
|
|
492
492
|
"",
|
|
493
493
|
"**How AGENTS.md works:**",
|
|
494
|
-
"-
|
|
494
|
+
"- Spectral loads AGENTS.md at startup from the project root and parent directories",
|
|
495
495
|
"- All found AGENTS.md files are concatenated and injected into the system prompt",
|
|
496
496
|
"- This means documented extensions get discovered by the agent automatically",
|
|
497
497
|
"",
|
|
@@ -502,7 +502,7 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
502
502
|
"## Auto-Generated Extensions",
|
|
503
503
|
"",
|
|
504
504
|
"These extensions were generated by auto-research. They are available",
|
|
505
|
-
"in every session.
|
|
505
|
+
"in every session. Spectral loads them automatically from `.spectral/extensions/`.",
|
|
506
506
|
"",
|
|
507
507
|
"### `<extension-name>`",
|
|
508
508
|
"",
|
|
@@ -539,7 +539,7 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
539
539
|
// ---------------------------------------------------------------------------
|
|
540
540
|
const AR_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes — generous for LLM-based analysis
|
|
541
541
|
/**
|
|
542
|
-
* Execute auto-research for a project through the existing
|
|
542
|
+
* Execute auto-research for a project through the existing AgentBridge.
|
|
543
543
|
*
|
|
544
544
|
* Caller (dispatcher) is fire-and-forget — this function is `void` and
|
|
545
545
|
* all errors are surfaced as `auto_research_error` events on the wire
|
|
@@ -547,7 +547,7 @@ const AR_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes — generous for LLM-based a
|
|
|
547
547
|
*
|
|
548
548
|
* This replaces the old subprocess-spawning approach. Instead of launching
|
|
549
549
|
* a separate pi process (which lacks backend proxy credentials), we send
|
|
550
|
-
* the auto-research task through the session's existing
|
|
550
|
+
* the auto-research task through the session's existing AgentBridge. The agent
|
|
551
551
|
* uses the session's model, backend proxy, and all available tools.
|
|
552
552
|
*/
|
|
553
553
|
export function handleAutoResearch(input, deps) {
|
|
@@ -700,7 +700,7 @@ export function handleAutoResearch(input, deps) {
|
|
|
700
700
|
// then timeout fires and calls finalize() again — but finalize() is
|
|
701
701
|
// gated by watcherFired, so it's a no-op. The only issue is we don't
|
|
702
702
|
// clearTimeout on watcher fire — but that's fine, the timer is harmless.
|
|
703
|
-
// --- Send the prompt through the existing
|
|
703
|
+
// --- Send the prompt through the existing AgentBridge (backend proxy) ---
|
|
704
704
|
manager.prompt(sessionId, taskContent, storedModelId).catch((err) => {
|
|
705
705
|
if (watcherFired)
|
|
706
706
|
return; // already handled by watcher
|