@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-connection pi SDK lifecycle.
|
|
3
3
|
*
|
|
4
|
-
* One `
|
|
4
|
+
* One `AgentBridge` instance per active WebSocket connection. Wraps:
|
|
5
5
|
* - `createAgentSession` (in-memory session manager — we own persistence in
|
|
6
6
|
* SQLite; pi doesn't need to write its own JSONL files).
|
|
7
7
|
* - `subscribe` listener that translates pi `AgentSessionEvent`s into our
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* History rehydration:
|
|
42
42
|
* On first attach to a previously-created session (e.g. after a server
|
|
43
43
|
* restart), the SessionStreamManager passes the full SQLite transcript
|
|
44
|
-
* to the
|
|
44
|
+
* to the AgentBridge via `AgentBridgeOptions.history`. Before
|
|
45
45
|
* `createAgentSession` is called, each message is appended to the
|
|
46
46
|
* in-memory SessionManager so the LLM sees the full conversation
|
|
47
47
|
* context from the very first prompt. Multi-turn conversations within
|
|
@@ -49,12 +49,13 @@
|
|
|
49
49
|
* instance is reused across `prompt()` calls).
|
|
50
50
|
*/
|
|
51
51
|
import { createJiti } from "@mariozechner/jiti";
|
|
52
|
-
import { AuthStorage, createAgentSession, DefaultResourceLoader, ModelRegistry, SessionManager, SettingsManager, } from "../
|
|
52
|
+
import { AuthStorage, createAgentSession, DefaultResourceLoader, ModelRegistry, SessionManager, SettingsManager, } from "../sdk/coding-agent/index.js";
|
|
53
53
|
import { randomUUID } from "node:crypto";
|
|
54
54
|
import { existsSync, statSync } from "node:fs";
|
|
55
55
|
import { dirname, join, resolve } from "node:path";
|
|
56
56
|
import { fileURLToPath } from "node:url";
|
|
57
57
|
import aexolMcpExtension from "../extensions/aexol-mcp.js";
|
|
58
|
+
import spectralVisionExtension from "../extensions/spectral-vision-fallback.js";
|
|
58
59
|
import subagentExt from "../agent/index.js";
|
|
59
60
|
import designerExtension from "../designer/index.js";
|
|
60
61
|
import observationalMemory from "../memory/index.js";
|
|
@@ -133,7 +134,7 @@ function extractTextFromContent(content) {
|
|
|
133
134
|
*/
|
|
134
135
|
function resolveMcpAdapterEntry() {
|
|
135
136
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
136
|
-
//
|
|
137
|
+
// agent-bridge.ts compiles to dist/server/agent-bridge.js;
|
|
137
138
|
// the bundled MCP adapter sits next door at dist/mcp/index.js.
|
|
138
139
|
const bundledIndex = resolve(__dirname, "..", "mcp", "index.js");
|
|
139
140
|
if (existsSync(bundledIndex))
|
|
@@ -195,6 +196,27 @@ function bareModelId(modelId) {
|
|
|
195
196
|
const idx = modelId.lastIndexOf("/");
|
|
196
197
|
return idx === -1 ? modelId : modelId.slice(idx + 1);
|
|
197
198
|
}
|
|
199
|
+
/** Get model cost from backend credit rates, falling back to hardcoded pricing table. */
|
|
200
|
+
function getModelCost(m) {
|
|
201
|
+
// Prefer backend credit rates when configured
|
|
202
|
+
const hasCredits = m.creditInputPer1M != null ||
|
|
203
|
+
m.creditOutputPer1M != null ||
|
|
204
|
+
m.creditCacheReadPer1M != null ||
|
|
205
|
+
m.creditCacheWritePer1M != null;
|
|
206
|
+
if (hasCredits) {
|
|
207
|
+
return {
|
|
208
|
+
input: m.creditInputPer1M ?? 0,
|
|
209
|
+
output: m.creditOutputPer1M ?? 0,
|
|
210
|
+
cacheRead: m.creditCacheReadPer1M ?? 0,
|
|
211
|
+
cacheWrite: m.creditCacheWritePer1M ?? 0,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Fall back to hardcoded pricing table when credits aren't configured
|
|
215
|
+
const pricing = lookupPricing(m.modelId);
|
|
216
|
+
return pricing
|
|
217
|
+
? { input: pricing.input, output: pricing.output, cacheRead: pricing.cacheRead, cacheWrite: pricing.cacheWrite }
|
|
218
|
+
: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
219
|
+
}
|
|
198
220
|
/** Look up pricing for a modelId. Returns null when unknown. */
|
|
199
221
|
function lookupPricing(modelId) {
|
|
200
222
|
const bare = bareModelId(modelId);
|
|
@@ -210,23 +232,6 @@ function lookupPricing(modelId) {
|
|
|
210
232
|
}
|
|
211
233
|
return null;
|
|
212
234
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Model prefixes known to support reasoning/thinking.
|
|
215
|
-
* Mirrors pi-ai's supportsXhigh() + additional models.
|
|
216
|
-
*/
|
|
217
|
-
const REASONING_SUPPORT_PREFIXES = [
|
|
218
|
-
"gpt-5.2", "gpt-5.3", "gpt-5.4", "gpt-5.5",
|
|
219
|
-
"claude-opus-4",
|
|
220
|
-
"o3", "o4",
|
|
221
|
-
"deepseek-r1",
|
|
222
|
-
"deepseek-v4",
|
|
223
|
-
"gemini-2.5",
|
|
224
|
-
];
|
|
225
|
-
/** Check if a modelId prefix indicates reasoning/thinking support. */
|
|
226
|
-
function supportsReasoning(modelId) {
|
|
227
|
-
const bare = bareModelId(modelId);
|
|
228
|
-
return REASONING_SUPPORT_PREFIXES.some((p) => modelId.startsWith(p) || bare.startsWith(p));
|
|
229
|
-
}
|
|
230
235
|
function inferSyntheticOpenAICompat(model) {
|
|
231
236
|
const bare = bareModelId(model.modelId);
|
|
232
237
|
const isDeepSeek = model.provider === "deepseek" ||
|
|
@@ -372,7 +377,7 @@ function toRestoredMemorySnapshot(snapshot) {
|
|
|
372
377
|
details: normalizeSnapshotDetails(snapshot.details),
|
|
373
378
|
};
|
|
374
379
|
}
|
|
375
|
-
export class
|
|
380
|
+
export class AgentBridge {
|
|
376
381
|
session;
|
|
377
382
|
sessionManager;
|
|
378
383
|
unsubscribe;
|
|
@@ -412,8 +417,8 @@ export class PiBridge {
|
|
|
412
417
|
*/
|
|
413
418
|
async start() {
|
|
414
419
|
if (this.disposed)
|
|
415
|
-
throw new Error("
|
|
416
|
-
const extensionFactories = [aexolMcpExtension, async (pi) => { subagentExt(pi); }, async (pi) => { designerExtension(pi); }, async (pi) => { observationalMemory(pi); }];
|
|
420
|
+
throw new Error("AgentBridge already disposed");
|
|
421
|
+
const extensionFactories = [aexolMcpExtension, async (pi) => { spectralVisionExtension(pi); }, async (pi) => { subagentExt(pi); }, async (pi) => { designerExtension(pi); }, async (pi) => { observationalMemory(pi); }];
|
|
417
422
|
// Load pi-mcp-adapter via jiti so tsc never crawls its .ts files in
|
|
418
423
|
// node_modules. The static `import` was causing tsc to type-check
|
|
419
424
|
// pi-mcp-adapter's source and fail the build on its type errors.
|
|
@@ -428,25 +433,25 @@ export class PiBridge {
|
|
|
428
433
|
}
|
|
429
434
|
}
|
|
430
435
|
catch {
|
|
431
|
-
console.info("[
|
|
436
|
+
console.info("[AgentBridge] pi-mcp-adapter not found; standard MCP servers disabled.");
|
|
432
437
|
}
|
|
433
438
|
}
|
|
434
439
|
else {
|
|
435
|
-
console.info("[
|
|
440
|
+
console.info("[AgentBridge] pi-mcp-adapter not found; standard MCP servers disabled.");
|
|
436
441
|
}
|
|
437
442
|
// ResourceLoader with extensions wired in via factories.
|
|
438
443
|
// Each factory's signature `(pi: ExtensionAPI) => Promise<void>` matches
|
|
439
444
|
// the ExtensionFactory type exactly, so we can pass them directly.
|
|
440
445
|
//
|
|
441
|
-
// Skill discovery: pi's defaults scan ~/.
|
|
442
|
-
// .
|
|
446
|
+
// Skill discovery: pi's defaults scan ~/.spectral/agent/skills/ (user),
|
|
447
|
+
// .spectral/skills/ (project via CONFIG_DIR_NAME), and .agents/skills/
|
|
443
448
|
// (ancestor-walked). We additionally walk ancestors for
|
|
444
449
|
// .opencode/skills and .aexol/skills so OpenCode/Codex/Aexol skills
|
|
445
450
|
// work out of the box.
|
|
446
451
|
const extraSkillPaths = collectAncestorSkillDirs(this.opts.cwd, [".opencode/skills", ".aexol/skills"]);
|
|
447
452
|
const resourceLoader = new DefaultResourceLoader({
|
|
448
453
|
cwd: this.opts.cwd,
|
|
449
|
-
agentDir: this.opts.agentDir ?? `${process.env.HOME ?? ""}/.
|
|
454
|
+
agentDir: this.opts.agentDir ?? `${process.env.HOME ?? ""}/.spectral/agent`,
|
|
450
455
|
extensionFactories,
|
|
451
456
|
noExtensions: false,
|
|
452
457
|
noSkills: false,
|
|
@@ -552,7 +557,7 @@ export class PiBridge {
|
|
|
552
557
|
}
|
|
553
558
|
// system messages are informational only; skip for LLM context
|
|
554
559
|
}
|
|
555
|
-
console.info(`[
|
|
560
|
+
console.info(`[AgentBridge] Rehydrated ${this.opts.history.length} history message(s) into session context`);
|
|
556
561
|
}
|
|
557
562
|
if (this.opts.memorySnapshot) {
|
|
558
563
|
const restoredSnapshot = toRestoredMemorySnapshot(this.opts.memorySnapshot);
|
|
@@ -568,10 +573,10 @@ export class PiBridge {
|
|
|
568
573
|
coveredSourceCount: restoredSnapshot.coveredSourceCount,
|
|
569
574
|
});
|
|
570
575
|
}
|
|
571
|
-
console.info(`[
|
|
576
|
+
console.info(`[AgentBridge] Restored observational memory snapshot covering ${restoredSnapshot.coveredSourceCount} source entr${restoredSnapshot.coveredSourceCount === 1 ? "y" : "ies"}`);
|
|
572
577
|
}
|
|
573
|
-
// Build a model registry that does NOT touch ~/.
|
|
574
|
-
// ~/.
|
|
578
|
+
// Build a model registry that does NOT touch ~/.spectral/agent/auth.json or
|
|
579
|
+
// ~/.spectral/agent/models.json — the backend is now the only source of
|
|
575
580
|
// provider credentials and the only allowed inference target. We then
|
|
576
581
|
// register synthetic providers (`spectral-proxy-anthropic` /
|
|
577
582
|
// `spectral-proxy-openai`) whose `baseUrl` points at the backend's
|
|
@@ -584,39 +589,29 @@ export class PiBridge {
|
|
|
584
589
|
// disk-based auth: this is the single path for `spectral serve`.
|
|
585
590
|
const authStorage = AuthStorage.inMemory();
|
|
586
591
|
this.modelRegistry = ModelRegistry.inMemory(authStorage);
|
|
587
|
-
const fetchModels = this.opts.fetchAllowedModels ?? defaultFetchAllowedModels;
|
|
588
592
|
let allowedModels;
|
|
589
593
|
try {
|
|
590
|
-
allowedModels = await
|
|
591
|
-
backendUrl: this.opts.backendUrl,
|
|
592
|
-
machineJwt: this.opts.machineJwt,
|
|
593
|
-
});
|
|
594
|
+
allowedModels = await this.refreshAllowedModels();
|
|
594
595
|
}
|
|
595
596
|
catch (err) {
|
|
596
597
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
597
598
|
throw new Error(`Failed to fetch allowed models from backend; check SPECTRAL_BACKEND_URL ` +
|
|
598
599
|
`and machine JWT. Underlying error: ${e.message}`);
|
|
599
600
|
}
|
|
600
|
-
this.allowedModels = allowedModels;
|
|
601
|
-
this.registerSyntheticProviders(allowedModels);
|
|
602
601
|
// Build an in-memory SettingsManager seeded with admin-configured
|
|
603
602
|
// defaults from the backend. findInitialModel() will pick up the
|
|
604
603
|
// isDefault model; the vision extension can query isVisionDefault.
|
|
605
604
|
const settingsOverrides = {};
|
|
606
605
|
const defaultModel = allowedModels.find((m) => m.isDefault);
|
|
607
606
|
if (defaultModel) {
|
|
608
|
-
const proxyProvider = defaultModel
|
|
609
|
-
? SPECTRAL_PROXY_ANTHROPIC
|
|
610
|
-
: SPECTRAL_PROXY_OPENAI;
|
|
607
|
+
const proxyProvider = this.proxyProviderForAllowedModel(defaultModel);
|
|
611
608
|
settingsOverrides.defaultProvider = proxyProvider;
|
|
612
609
|
settingsOverrides.defaultModel = defaultModel.modelId;
|
|
613
610
|
console.info(`✓ Default model from backend: ${proxyProvider}/${defaultModel.modelId}`);
|
|
614
611
|
}
|
|
615
612
|
const defaultVisionModel = allowedModels.find((m) => m.isVisionDefault);
|
|
616
613
|
if (defaultVisionModel) {
|
|
617
|
-
const proxyProvider = defaultVisionModel
|
|
618
|
-
? SPECTRAL_PROXY_ANTHROPIC
|
|
619
|
-
: SPECTRAL_PROXY_OPENAI;
|
|
614
|
+
const proxyProvider = this.proxyProviderForAllowedModel(defaultVisionModel);
|
|
620
615
|
settingsOverrides.defaultVisionProvider = proxyProvider;
|
|
621
616
|
settingsOverrides.defaultVisionModel = defaultVisionModel.modelId;
|
|
622
617
|
console.info(`✓ Default vision model from backend: ${proxyProvider}/${defaultVisionModel.modelId}`);
|
|
@@ -649,15 +644,38 @@ export class PiBridge {
|
|
|
649
644
|
// registration.
|
|
650
645
|
try {
|
|
651
646
|
await this.session.bindExtensions({ uiContext });
|
|
652
|
-
console.info("[
|
|
647
|
+
console.info("[AgentBridge] session_start emitted; extensions initialized.");
|
|
653
648
|
}
|
|
654
649
|
catch (err) {
|
|
655
650
|
const msg = err instanceof Error ? err.message : String(err);
|
|
656
|
-
console.warn(`[
|
|
651
|
+
console.warn(`[AgentBridge] session_start failed (extension init error): ${msg}`);
|
|
657
652
|
}
|
|
658
653
|
// Subscribe BEFORE any prompt fires.
|
|
659
654
|
this.unsubscribe = this.session.subscribe((ev) => this.handleEvent(ev));
|
|
660
655
|
}
|
|
656
|
+
proxyProviderForAllowedModel(model) {
|
|
657
|
+
if (model.provider === "anthropic") {
|
|
658
|
+
return SPECTRAL_PROXY_ANTHROPIC;
|
|
659
|
+
}
|
|
660
|
+
if (model.provider === "built-in") {
|
|
661
|
+
return SPECTRAL_PROXY_USER_MODEL;
|
|
662
|
+
}
|
|
663
|
+
return SPECTRAL_PROXY_OPENAI;
|
|
664
|
+
}
|
|
665
|
+
async refreshAllowedModels(opts) {
|
|
666
|
+
if (!this.modelRegistry) {
|
|
667
|
+
throw new Error("AgentBridge model registry unavailable");
|
|
668
|
+
}
|
|
669
|
+
const fetchModels = this.opts.fetchAllowedModels ?? defaultFetchAllowedModels;
|
|
670
|
+
const allowedModels = await fetchModels({
|
|
671
|
+
backendUrl: this.opts.backendUrl,
|
|
672
|
+
machineJwt: this.opts.machineJwt,
|
|
673
|
+
bypassCache: opts?.bypassCache,
|
|
674
|
+
});
|
|
675
|
+
this.allowedModels = allowedModels;
|
|
676
|
+
this.registerSyntheticProviders(allowedModels);
|
|
677
|
+
return allowedModels;
|
|
678
|
+
}
|
|
661
679
|
/**
|
|
662
680
|
* Register one synthetic provider per upstream API shape. Anthropic models
|
|
663
681
|
* go to `${backendUrl}/v1/messages` (Messages API); everything else (OpenAI,
|
|
@@ -684,7 +702,6 @@ export class PiBridge {
|
|
|
684
702
|
authHeader: true,
|
|
685
703
|
api: "anthropic-messages",
|
|
686
704
|
models: anthropicModels.map((m) => {
|
|
687
|
-
const pricing = lookupPricing(m.modelId);
|
|
688
705
|
return {
|
|
689
706
|
id: m.modelId,
|
|
690
707
|
name: m.displayName,
|
|
@@ -697,12 +714,9 @@ export class PiBridge {
|
|
|
697
714
|
// at our synthetic proxy provider so auth resolves to the machine JWT.
|
|
698
715
|
provider: SPECTRAL_PROXY_ANTHROPIC,
|
|
699
716
|
baseUrl,
|
|
700
|
-
reasoning: m.supportsReasoning ??
|
|
717
|
+
reasoning: m.supportsReasoning ?? false,
|
|
701
718
|
input: m.supportsImages !== false ? ["text", "image"] : ["text"],
|
|
702
|
-
|
|
703
|
-
cost: pricing
|
|
704
|
-
? { input: pricing.input, output: pricing.output, cacheRead: pricing.cacheRead, cacheWrite: pricing.cacheWrite }
|
|
705
|
-
: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
719
|
+
cost: getModelCost(m),
|
|
706
720
|
contextWindow: m.contextWindow ?? 0,
|
|
707
721
|
maxTokens: 0,
|
|
708
722
|
};
|
|
@@ -716,7 +730,6 @@ export class PiBridge {
|
|
|
716
730
|
authHeader: true,
|
|
717
731
|
api: "openai-completions",
|
|
718
732
|
models: openaiCompatModels.map((m) => {
|
|
719
|
-
const pricing = lookupPricing(m.modelId);
|
|
720
733
|
return {
|
|
721
734
|
id: m.modelId,
|
|
722
735
|
name: m.displayName,
|
|
@@ -727,12 +740,9 @@ export class PiBridge {
|
|
|
727
740
|
// breaking auth lookup against our synthetic proxy provider.
|
|
728
741
|
provider: SPECTRAL_PROXY_OPENAI,
|
|
729
742
|
baseUrl,
|
|
730
|
-
reasoning: m.supportsReasoning ??
|
|
743
|
+
reasoning: m.supportsReasoning ?? false,
|
|
731
744
|
input: m.supportsImages !== false ? ["text", "image"] : ["text"],
|
|
732
|
-
|
|
733
|
-
cost: pricing
|
|
734
|
-
? { input: pricing.input, output: pricing.output, cacheRead: pricing.cacheRead, cacheWrite: pricing.cacheWrite }
|
|
735
|
-
: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
745
|
+
cost: getModelCost(m),
|
|
736
746
|
contextWindow: m.contextWindow ?? 0,
|
|
737
747
|
maxTokens: 0,
|
|
738
748
|
compat: inferSyntheticOpenAICompat(m),
|
|
@@ -752,18 +762,15 @@ export class PiBridge {
|
|
|
752
762
|
authHeader: true,
|
|
753
763
|
api: "openai-completions",
|
|
754
764
|
models: userModelEntries.map((m) => {
|
|
755
|
-
const pricing = lookupPricing(m.modelId);
|
|
756
765
|
return {
|
|
757
766
|
id: m.modelId,
|
|
758
767
|
name: m.displayName,
|
|
759
768
|
api: "openai-completions",
|
|
760
769
|
provider: SPECTRAL_PROXY_USER_MODEL,
|
|
761
770
|
baseUrl,
|
|
762
|
-
reasoning: m.supportsReasoning ??
|
|
771
|
+
reasoning: m.supportsReasoning ?? false,
|
|
763
772
|
input: m.supportsImages !== false ? ["text", "image"] : ["text"],
|
|
764
|
-
cost:
|
|
765
|
-
? { input: pricing.input, output: pricing.output, cacheRead: pricing.cacheRead, cacheWrite: pricing.cacheWrite }
|
|
766
|
-
: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
773
|
+
cost: getModelCost(m),
|
|
767
774
|
contextWindow: m.contextWindow ?? 0,
|
|
768
775
|
maxTokens: 0,
|
|
769
776
|
compat: inferSyntheticOpenAICompat(m),
|
|
@@ -844,7 +851,7 @@ export class PiBridge {
|
|
|
844
851
|
if (!modelId)
|
|
845
852
|
return true; // nothing to apply — pi keeps its current model
|
|
846
853
|
if (!this.session)
|
|
847
|
-
throw new Error("
|
|
854
|
+
throw new Error("AgentBridge.start() not called");
|
|
848
855
|
if (this.lastAppliedModelId === modelId)
|
|
849
856
|
return true; // idempotent: same model already in effect
|
|
850
857
|
if (!this.modelRegistry) {
|
|
@@ -857,13 +864,26 @@ export class PiBridge {
|
|
|
857
864
|
});
|
|
858
865
|
return false;
|
|
859
866
|
}
|
|
860
|
-
|
|
867
|
+
let model = this.modelRegistry
|
|
861
868
|
.getAvailable()
|
|
862
869
|
.find((m) => m.id === modelId);
|
|
870
|
+
let refreshError;
|
|
871
|
+
if (!model) {
|
|
872
|
+
try {
|
|
873
|
+
await this.refreshAllowedModels({ bypassCache: true });
|
|
874
|
+
model = this.modelRegistry
|
|
875
|
+
.getAvailable()
|
|
876
|
+
.find((m) => m.id === modelId);
|
|
877
|
+
}
|
|
878
|
+
catch (err) {
|
|
879
|
+
refreshError = err instanceof Error ? err : new Error(String(err));
|
|
880
|
+
this.opts.onError?.(refreshError);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
863
883
|
if (!model) {
|
|
864
884
|
this.opts.emit({
|
|
865
885
|
type: "error",
|
|
866
|
-
message: `Unknown modelId "${modelId}" — not found in pi model registry`,
|
|
886
|
+
message: `Unknown modelId "${modelId}" — not found in pi model registry${refreshError ? ` after refresh: ${refreshError.message}` : ""}`,
|
|
867
887
|
});
|
|
868
888
|
return false;
|
|
869
889
|
}
|
|
@@ -965,16 +985,14 @@ export class PiBridge {
|
|
|
965
985
|
*/
|
|
966
986
|
async prompt(text, images) {
|
|
967
987
|
if (!this.session)
|
|
968
|
-
throw new Error("
|
|
969
|
-
//
|
|
970
|
-
//
|
|
971
|
-
// and
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
: undefined;
|
|
975
|
-
const modelSupportsImages = currentModel?.supportsImages === true;
|
|
988
|
+
throw new Error("AgentBridge.start() not called");
|
|
989
|
+
// Always pass images through to the session.
|
|
990
|
+
// The spectral-vision-fallback extension intercepts images via the
|
|
991
|
+
// `context` event and replaces them with text descriptions using a
|
|
992
|
+
// vision-capable model BEFORE they reach the LLM. This means images
|
|
993
|
+
// work regardless of whether the main model supports them.
|
|
976
994
|
try {
|
|
977
|
-
if (images && images.length > 0
|
|
995
|
+
if (images && images.length > 0) {
|
|
978
996
|
const imageContents = images.map((img) => ({
|
|
979
997
|
type: "image",
|
|
980
998
|
data: img.data,
|
|
@@ -982,20 +1000,6 @@ export class PiBridge {
|
|
|
982
1000
|
}));
|
|
983
1001
|
await this.session.prompt(text, { images: imageContents });
|
|
984
1002
|
}
|
|
985
|
-
else if (images && images.length > 0 && !modelSupportsImages) {
|
|
986
|
-
// Model doesn't support images — convert them to text descriptions
|
|
987
|
-
// so the conversation can continue instead of hanging.
|
|
988
|
-
const imageDescriptions = images
|
|
989
|
-
.map((img, i) => `[Image ${i + 1}: ${img.mimeType}, ${img.data.length.toLocaleString()} bytes base64]`)
|
|
990
|
-
.join("\n");
|
|
991
|
-
const augmentedText = `${text}\n\n---\nThe following image(s) were attached but the current model does not support image input:\n${imageDescriptions}\n(Describe what you see or ask the user to switch to a model that supports images.)`;
|
|
992
|
-
this.opts.emit({
|
|
993
|
-
type: "agent_notification",
|
|
994
|
-
message: `The current model does not support image input. ${images.length} image(s) were converted to text descriptions.`,
|
|
995
|
-
level: "warning",
|
|
996
|
-
});
|
|
997
|
-
await this.session.prompt(augmentedText);
|
|
998
|
-
}
|
|
999
1003
|
else {
|
|
1000
1004
|
await this.session.prompt(text);
|
|
1001
1005
|
}
|
|
@@ -1017,7 +1021,7 @@ export class PiBridge {
|
|
|
1017
1021
|
*/
|
|
1018
1022
|
async compact(customInstructions) {
|
|
1019
1023
|
if (!this.session)
|
|
1020
|
-
throw new Error("
|
|
1024
|
+
throw new Error("AgentBridge.start() not called");
|
|
1021
1025
|
this.memoryPhase = "compacting";
|
|
1022
1026
|
try {
|
|
1023
1027
|
await this.session.compact(customInstructions);
|
|
@@ -1106,13 +1110,13 @@ export class PiBridge {
|
|
|
1106
1110
|
e.type === "tool_call" ||
|
|
1107
1111
|
e.type === "tool_result");
|
|
1108
1112
|
if (!finalContent && !hasMeaningfulEvent) {
|
|
1109
|
-
console.debug("[
|
|
1113
|
+
console.debug("[agent-bridge] skipping empty intermediate message");
|
|
1110
1114
|
try {
|
|
1111
1115
|
this.opts.onAssistantMessageSkipped?.(messageId);
|
|
1112
1116
|
}
|
|
1113
1117
|
catch (err) {
|
|
1114
1118
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
1115
|
-
console.error(`[
|
|
1119
|
+
console.error(`[agent-bridge] onAssistantMessageSkipped failed: ${e.message}`);
|
|
1116
1120
|
}
|
|
1117
1121
|
return;
|
|
1118
1122
|
}
|
|
@@ -1440,6 +1444,15 @@ function detectMemorySystem(message) {
|
|
|
1440
1444
|
// Keep generic observational-memory messages visible in the landing badge.
|
|
1441
1445
|
return "memory_observer";
|
|
1442
1446
|
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Detect subsystem from notification messages by known prefix.
|
|
1449
|
+
* Returns undefined for generic extension messages without a matching prefix.
|
|
1450
|
+
*/
|
|
1451
|
+
function detectNotifSystem(message) {
|
|
1452
|
+
if (message.startsWith("[spectral-vision]"))
|
|
1453
|
+
return "vision";
|
|
1454
|
+
return detectMemorySystem(message);
|
|
1455
|
+
}
|
|
1443
1456
|
/**
|
|
1444
1457
|
* Create a minimal ExtensionUIContext that forwards `notify()` calls as
|
|
1445
1458
|
* `agent_notification` wire events. All other UI methods are no-ops —
|
|
@@ -1450,15 +1463,18 @@ function detectMemorySystem(message) {
|
|
|
1450
1463
|
function createHeadlessUIContext(emit) {
|
|
1451
1464
|
// Defer to a Proxy so we don't need to stub every method.
|
|
1452
1465
|
// `notify` is the only method called by extensions in serve mode
|
|
1453
|
-
// (observational memory, MCP status bar updates).
|
|
1466
|
+
// (observational memory, MCP status bar updates, spectral-vision).
|
|
1454
1467
|
const handler = {
|
|
1455
1468
|
get(_target, prop) {
|
|
1456
1469
|
if (prop === "notify") {
|
|
1457
1470
|
return (message, type) => {
|
|
1458
1471
|
const level = type ?? "info";
|
|
1459
|
-
const system =
|
|
1472
|
+
const system = detectNotifSystem(message);
|
|
1460
1473
|
if (system?.startsWith("memory_")) {
|
|
1461
|
-
console.info(`[
|
|
1474
|
+
console.info(`[AgentBridge][memory][${level}] ${message}`);
|
|
1475
|
+
}
|
|
1476
|
+
else if (system === "vision") {
|
|
1477
|
+
console.info(`[AgentBridge][vision][${level}] ${message}`);
|
|
1462
1478
|
}
|
|
1463
1479
|
emit({
|
|
1464
1480
|
type: "agent_notification",
|
|
@@ -65,6 +65,27 @@ export async function handleCompactSession(store, manager, id) {
|
|
|
65
65
|
}
|
|
66
66
|
return { ok: true };
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Remember & delete: compact the session (which persists observations as
|
|
70
|
+
* project memory via the compaction hook), then delete the session.
|
|
71
|
+
*
|
|
72
|
+
* This gives the user a way to keep a session's reflections as durable
|
|
73
|
+
* cross-session memory even for short sessions that never hit the compaction
|
|
74
|
+
* threshold naturally.
|
|
75
|
+
*/
|
|
76
|
+
export async function handleRememberAndDeleteSession(store, manager, id) {
|
|
77
|
+
const detail = store.getSession(id);
|
|
78
|
+
if (!detail)
|
|
79
|
+
throw new NotFoundError("Session not found");
|
|
80
|
+
try {
|
|
81
|
+
await manager.compactSession(id);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
throw new BadRequestError(message);
|
|
86
|
+
}
|
|
87
|
+
return { ok: true };
|
|
88
|
+
}
|
|
68
89
|
/**
|
|
69
90
|
* Fork a session: create a new session copying all messages from the
|
|
70
91
|
* source, with the `fork_compact_source_id` flag set so the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session-scoped streaming layer for `spectral serve`.
|
|
3
3
|
*
|
|
4
|
-
* Background: prior to this module each WebSocket owned its own `
|
|
4
|
+
* Background: prior to this module each WebSocket owned its own `AgentBridge`
|
|
5
5
|
* instance and the routes layer enforced single-writer-wins (4001 eviction)
|
|
6
6
|
* to keep that bridge unique per session. That model lost data on browser
|
|
7
7
|
* refresh — the WS close torn down the pi process mid-stream, and a re-open
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
* for now — streams accumulate for the lifetime of the server process.
|
|
38
38
|
*/
|
|
39
39
|
import { randomUUID } from "node:crypto";
|
|
40
|
-
import {
|
|
40
|
+
import { AgentBridge } from "./agent-bridge.js";
|
|
41
41
|
import { getMemoryState, isSourceEntry, rawTokensSinceLastBound, rawTokensSinceLastCompaction, } from "../memory/branch.js";
|
|
42
42
|
import { observationPoolTokens, renderSummary } from "../memory/compaction.js";
|
|
43
43
|
import { loadConfig } from "../memory/config.js";
|
|
44
44
|
import { setProjectObsStore } from "../memory/project-observations-store.js";
|
|
45
45
|
import { estimateStringTokens } from "../memory/tokens.js";
|
|
46
46
|
import { reflectionContent, reflectionId } from "../memory/types.js";
|
|
47
|
-
const DEFAULT_BRIDGE_FACTORY = (args) => new
|
|
47
|
+
const DEFAULT_BRIDGE_FACTORY = (args) => new AgentBridge(args);
|
|
48
48
|
/** Safety limit for autonomous loop iterations per session. */
|
|
49
49
|
const MAX_LOOP_ITERATIONS = 100;
|
|
50
50
|
/**
|
|
@@ -646,7 +646,7 @@ export class SessionStreamManager {
|
|
|
646
646
|
assistantText: "",
|
|
647
647
|
};
|
|
648
648
|
// 4. Fire pi. `prompt` resolves on agent_end; errors are handled inside
|
|
649
|
-
//
|
|
649
|
+
// AgentBridge (it emits `error` for us). We don't await — broadcast is
|
|
650
650
|
// driven by the bridge's emit callback.
|
|
651
651
|
void stream.bridge.prompt(content, images);
|
|
652
652
|
}
|
|
@@ -1127,7 +1127,7 @@ export class SessionStreamManager {
|
|
|
1127
1127
|
}
|
|
1128
1128
|
// Broadcast first, then maybe close out the turn. agent_end clears the
|
|
1129
1129
|
// buffer because by that point the assistant message is already in
|
|
1130
|
-
// SQLite (
|
|
1130
|
+
// SQLite (AgentBridge calls onAssistantMessageComplete on message_end,
|
|
1131
1131
|
// which fires before agent_end).
|
|
1132
1132
|
//
|
|
1133
1133
|
// Track context window state from token_usage events — the bridge emits
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aexol/spectral",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "AI coding agent for Aexol with relay-based browser access.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"bin": {
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
"coding-agent",
|
|
31
31
|
"ai",
|
|
32
32
|
"cli",
|
|
33
|
-
"pi",
|
|
34
33
|
"claude",
|
|
35
34
|
"openai",
|
|
36
35
|
"agent",
|
|
@@ -46,6 +45,10 @@
|
|
|
46
45
|
"url": "https://gitlab.aexol.com/aexol/spectral/-/issues"
|
|
47
46
|
},
|
|
48
47
|
"license": "MIT",
|
|
48
|
+
"spectralConfig": {
|
|
49
|
+
"name": "spectral",
|
|
50
|
+
"configDir": ".spectral"
|
|
51
|
+
},
|
|
49
52
|
"publishConfig": {
|
|
50
53
|
"access": "public"
|
|
51
54
|
},
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|