@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,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,14 @@
|
|
|
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 kanbanBridgeExtension from "../extensions/kanban-bridge.js";
|
|
59
|
+
import spectralVisionExtension from "../extensions/spectral-vision-fallback.js";
|
|
58
60
|
import subagentExt from "../agent/index.js";
|
|
59
61
|
import designerExtension from "../designer/index.js";
|
|
60
62
|
import observationalMemory from "../memory/index.js";
|
|
@@ -133,7 +135,7 @@ function extractTextFromContent(content) {
|
|
|
133
135
|
*/
|
|
134
136
|
function resolveMcpAdapterEntry() {
|
|
135
137
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
136
|
-
//
|
|
138
|
+
// agent-bridge.ts compiles to dist/server/agent-bridge.js;
|
|
137
139
|
// the bundled MCP adapter sits next door at dist/mcp/index.js.
|
|
138
140
|
const bundledIndex = resolve(__dirname, "..", "mcp", "index.js");
|
|
139
141
|
if (existsSync(bundledIndex))
|
|
@@ -195,6 +197,27 @@ function bareModelId(modelId) {
|
|
|
195
197
|
const idx = modelId.lastIndexOf("/");
|
|
196
198
|
return idx === -1 ? modelId : modelId.slice(idx + 1);
|
|
197
199
|
}
|
|
200
|
+
/** Get model cost from backend credit rates, falling back to hardcoded pricing table. */
|
|
201
|
+
function getModelCost(m) {
|
|
202
|
+
// Prefer backend credit rates when configured
|
|
203
|
+
const hasCredits = m.creditInputPer1M != null ||
|
|
204
|
+
m.creditOutputPer1M != null ||
|
|
205
|
+
m.creditCacheReadPer1M != null ||
|
|
206
|
+
m.creditCacheWritePer1M != null;
|
|
207
|
+
if (hasCredits) {
|
|
208
|
+
return {
|
|
209
|
+
input: m.creditInputPer1M ?? 0,
|
|
210
|
+
output: m.creditOutputPer1M ?? 0,
|
|
211
|
+
cacheRead: m.creditCacheReadPer1M ?? 0,
|
|
212
|
+
cacheWrite: m.creditCacheWritePer1M ?? 0,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// Fall back to hardcoded pricing table when credits aren't configured
|
|
216
|
+
const pricing = lookupPricing(m.modelId);
|
|
217
|
+
return pricing
|
|
218
|
+
? { input: pricing.input, output: pricing.output, cacheRead: pricing.cacheRead, cacheWrite: pricing.cacheWrite }
|
|
219
|
+
: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
220
|
+
}
|
|
198
221
|
/** Look up pricing for a modelId. Returns null when unknown. */
|
|
199
222
|
function lookupPricing(modelId) {
|
|
200
223
|
const bare = bareModelId(modelId);
|
|
@@ -210,23 +233,6 @@ function lookupPricing(modelId) {
|
|
|
210
233
|
}
|
|
211
234
|
return null;
|
|
212
235
|
}
|
|
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
236
|
function inferSyntheticOpenAICompat(model) {
|
|
231
237
|
const bare = bareModelId(model.modelId);
|
|
232
238
|
const isDeepSeek = model.provider === "deepseek" ||
|
|
@@ -372,7 +378,7 @@ function toRestoredMemorySnapshot(snapshot) {
|
|
|
372
378
|
details: normalizeSnapshotDetails(snapshot.details),
|
|
373
379
|
};
|
|
374
380
|
}
|
|
375
|
-
export class
|
|
381
|
+
export class AgentBridge {
|
|
376
382
|
session;
|
|
377
383
|
sessionManager;
|
|
378
384
|
unsubscribe;
|
|
@@ -412,8 +418,8 @@ export class PiBridge {
|
|
|
412
418
|
*/
|
|
413
419
|
async start() {
|
|
414
420
|
if (this.disposed)
|
|
415
|
-
throw new Error("
|
|
416
|
-
const extensionFactories = [aexolMcpExtension, async (pi) => { subagentExt(pi); }, async (pi) => { designerExtension(pi); }, async (pi) => { observationalMemory(pi); }];
|
|
421
|
+
throw new Error("AgentBridge already disposed");
|
|
422
|
+
const extensionFactories = [aexolMcpExtension, kanbanBridgeExtension, async (pi) => { spectralVisionExtension(pi); }, async (pi) => { subagentExt(pi); }, async (pi) => { designerExtension(pi); }, async (pi) => { observationalMemory(pi); }];
|
|
417
423
|
// Load pi-mcp-adapter via jiti so tsc never crawls its .ts files in
|
|
418
424
|
// node_modules. The static `import` was causing tsc to type-check
|
|
419
425
|
// pi-mcp-adapter's source and fail the build on its type errors.
|
|
@@ -428,25 +434,25 @@ export class PiBridge {
|
|
|
428
434
|
}
|
|
429
435
|
}
|
|
430
436
|
catch {
|
|
431
|
-
console.info("[
|
|
437
|
+
console.info("[AgentBridge] pi-mcp-adapter not found; standard MCP servers disabled.");
|
|
432
438
|
}
|
|
433
439
|
}
|
|
434
440
|
else {
|
|
435
|
-
console.info("[
|
|
441
|
+
console.info("[AgentBridge] pi-mcp-adapter not found; standard MCP servers disabled.");
|
|
436
442
|
}
|
|
437
443
|
// ResourceLoader with extensions wired in via factories.
|
|
438
444
|
// Each factory's signature `(pi: ExtensionAPI) => Promise<void>` matches
|
|
439
445
|
// the ExtensionFactory type exactly, so we can pass them directly.
|
|
440
446
|
//
|
|
441
|
-
// Skill discovery: pi's defaults scan ~/.
|
|
442
|
-
// .
|
|
447
|
+
// Skill discovery: pi's defaults scan ~/.spectral/agent/skills/ (user),
|
|
448
|
+
// .spectral/skills/ (project via CONFIG_DIR_NAME), and .agents/skills/
|
|
443
449
|
// (ancestor-walked). We additionally walk ancestors for
|
|
444
450
|
// .opencode/skills and .aexol/skills so OpenCode/Codex/Aexol skills
|
|
445
451
|
// work out of the box.
|
|
446
452
|
const extraSkillPaths = collectAncestorSkillDirs(this.opts.cwd, [".opencode/skills", ".aexol/skills"]);
|
|
447
453
|
const resourceLoader = new DefaultResourceLoader({
|
|
448
454
|
cwd: this.opts.cwd,
|
|
449
|
-
agentDir: this.opts.agentDir ?? `${process.env.HOME ?? ""}/.
|
|
455
|
+
agentDir: this.opts.agentDir ?? `${process.env.HOME ?? ""}/.spectral/agent`,
|
|
450
456
|
extensionFactories,
|
|
451
457
|
noExtensions: false,
|
|
452
458
|
noSkills: false,
|
|
@@ -552,7 +558,7 @@ export class PiBridge {
|
|
|
552
558
|
}
|
|
553
559
|
// system messages are informational only; skip for LLM context
|
|
554
560
|
}
|
|
555
|
-
console.info(`[
|
|
561
|
+
console.info(`[AgentBridge] Rehydrated ${this.opts.history.length} history message(s) into session context`);
|
|
556
562
|
}
|
|
557
563
|
if (this.opts.memorySnapshot) {
|
|
558
564
|
const restoredSnapshot = toRestoredMemorySnapshot(this.opts.memorySnapshot);
|
|
@@ -568,10 +574,10 @@ export class PiBridge {
|
|
|
568
574
|
coveredSourceCount: restoredSnapshot.coveredSourceCount,
|
|
569
575
|
});
|
|
570
576
|
}
|
|
571
|
-
console.info(`[
|
|
577
|
+
console.info(`[AgentBridge] Restored observational memory snapshot covering ${restoredSnapshot.coveredSourceCount} source entr${restoredSnapshot.coveredSourceCount === 1 ? "y" : "ies"}`);
|
|
572
578
|
}
|
|
573
|
-
// Build a model registry that does NOT touch ~/.
|
|
574
|
-
// ~/.
|
|
579
|
+
// Build a model registry that does NOT touch ~/.spectral/agent/auth.json or
|
|
580
|
+
// ~/.spectral/agent/models.json — the backend is now the only source of
|
|
575
581
|
// provider credentials and the only allowed inference target. We then
|
|
576
582
|
// register synthetic providers (`spectral-proxy-anthropic` /
|
|
577
583
|
// `spectral-proxy-openai`) whose `baseUrl` points at the backend's
|
|
@@ -584,39 +590,29 @@ export class PiBridge {
|
|
|
584
590
|
// disk-based auth: this is the single path for `spectral serve`.
|
|
585
591
|
const authStorage = AuthStorage.inMemory();
|
|
586
592
|
this.modelRegistry = ModelRegistry.inMemory(authStorage);
|
|
587
|
-
const fetchModels = this.opts.fetchAllowedModels ?? defaultFetchAllowedModels;
|
|
588
593
|
let allowedModels;
|
|
589
594
|
try {
|
|
590
|
-
allowedModels = await
|
|
591
|
-
backendUrl: this.opts.backendUrl,
|
|
592
|
-
machineJwt: this.opts.machineJwt,
|
|
593
|
-
});
|
|
595
|
+
allowedModels = await this.refreshAllowedModels();
|
|
594
596
|
}
|
|
595
597
|
catch (err) {
|
|
596
598
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
597
599
|
throw new Error(`Failed to fetch allowed models from backend; check SPECTRAL_BACKEND_URL ` +
|
|
598
600
|
`and machine JWT. Underlying error: ${e.message}`);
|
|
599
601
|
}
|
|
600
|
-
this.allowedModels = allowedModels;
|
|
601
|
-
this.registerSyntheticProviders(allowedModels);
|
|
602
602
|
// Build an in-memory SettingsManager seeded with admin-configured
|
|
603
603
|
// defaults from the backend. findInitialModel() will pick up the
|
|
604
604
|
// isDefault model; the vision extension can query isVisionDefault.
|
|
605
605
|
const settingsOverrides = {};
|
|
606
606
|
const defaultModel = allowedModels.find((m) => m.isDefault);
|
|
607
607
|
if (defaultModel) {
|
|
608
|
-
const proxyProvider = defaultModel
|
|
609
|
-
? SPECTRAL_PROXY_ANTHROPIC
|
|
610
|
-
: SPECTRAL_PROXY_OPENAI;
|
|
608
|
+
const proxyProvider = this.proxyProviderForAllowedModel(defaultModel);
|
|
611
609
|
settingsOverrides.defaultProvider = proxyProvider;
|
|
612
610
|
settingsOverrides.defaultModel = defaultModel.modelId;
|
|
613
611
|
console.info(`✓ Default model from backend: ${proxyProvider}/${defaultModel.modelId}`);
|
|
614
612
|
}
|
|
615
613
|
const defaultVisionModel = allowedModels.find((m) => m.isVisionDefault);
|
|
616
614
|
if (defaultVisionModel) {
|
|
617
|
-
const proxyProvider = defaultVisionModel
|
|
618
|
-
? SPECTRAL_PROXY_ANTHROPIC
|
|
619
|
-
: SPECTRAL_PROXY_OPENAI;
|
|
615
|
+
const proxyProvider = this.proxyProviderForAllowedModel(defaultVisionModel);
|
|
620
616
|
settingsOverrides.defaultVisionProvider = proxyProvider;
|
|
621
617
|
settingsOverrides.defaultVisionModel = defaultVisionModel.modelId;
|
|
622
618
|
console.info(`✓ Default vision model from backend: ${proxyProvider}/${defaultVisionModel.modelId}`);
|
|
@@ -649,15 +645,38 @@ export class PiBridge {
|
|
|
649
645
|
// registration.
|
|
650
646
|
try {
|
|
651
647
|
await this.session.bindExtensions({ uiContext });
|
|
652
|
-
console.info("[
|
|
648
|
+
console.info("[AgentBridge] session_start emitted; extensions initialized.");
|
|
653
649
|
}
|
|
654
650
|
catch (err) {
|
|
655
651
|
const msg = err instanceof Error ? err.message : String(err);
|
|
656
|
-
console.warn(`[
|
|
652
|
+
console.warn(`[AgentBridge] session_start failed (extension init error): ${msg}`);
|
|
657
653
|
}
|
|
658
654
|
// Subscribe BEFORE any prompt fires.
|
|
659
655
|
this.unsubscribe = this.session.subscribe((ev) => this.handleEvent(ev));
|
|
660
656
|
}
|
|
657
|
+
proxyProviderForAllowedModel(model) {
|
|
658
|
+
if (model.provider === "anthropic") {
|
|
659
|
+
return SPECTRAL_PROXY_ANTHROPIC;
|
|
660
|
+
}
|
|
661
|
+
if (model.provider === "built-in") {
|
|
662
|
+
return SPECTRAL_PROXY_USER_MODEL;
|
|
663
|
+
}
|
|
664
|
+
return SPECTRAL_PROXY_OPENAI;
|
|
665
|
+
}
|
|
666
|
+
async refreshAllowedModels(opts) {
|
|
667
|
+
if (!this.modelRegistry) {
|
|
668
|
+
throw new Error("AgentBridge model registry unavailable");
|
|
669
|
+
}
|
|
670
|
+
const fetchModels = this.opts.fetchAllowedModels ?? defaultFetchAllowedModels;
|
|
671
|
+
const allowedModels = await fetchModels({
|
|
672
|
+
backendUrl: this.opts.backendUrl,
|
|
673
|
+
machineJwt: this.opts.machineJwt,
|
|
674
|
+
bypassCache: opts?.bypassCache,
|
|
675
|
+
});
|
|
676
|
+
this.allowedModels = allowedModels;
|
|
677
|
+
this.registerSyntheticProviders(allowedModels);
|
|
678
|
+
return allowedModels;
|
|
679
|
+
}
|
|
661
680
|
/**
|
|
662
681
|
* Register one synthetic provider per upstream API shape. Anthropic models
|
|
663
682
|
* go to `${backendUrl}/v1/messages` (Messages API); everything else (OpenAI,
|
|
@@ -684,7 +703,6 @@ export class PiBridge {
|
|
|
684
703
|
authHeader: true,
|
|
685
704
|
api: "anthropic-messages",
|
|
686
705
|
models: anthropicModels.map((m) => {
|
|
687
|
-
const pricing = lookupPricing(m.modelId);
|
|
688
706
|
return {
|
|
689
707
|
id: m.modelId,
|
|
690
708
|
name: m.displayName,
|
|
@@ -697,12 +715,9 @@ export class PiBridge {
|
|
|
697
715
|
// at our synthetic proxy provider so auth resolves to the machine JWT.
|
|
698
716
|
provider: SPECTRAL_PROXY_ANTHROPIC,
|
|
699
717
|
baseUrl,
|
|
700
|
-
reasoning: m.supportsReasoning ??
|
|
718
|
+
reasoning: m.supportsReasoning ?? false,
|
|
701
719
|
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 },
|
|
720
|
+
cost: getModelCost(m),
|
|
706
721
|
contextWindow: m.contextWindow ?? 0,
|
|
707
722
|
maxTokens: 0,
|
|
708
723
|
};
|
|
@@ -716,7 +731,6 @@ export class PiBridge {
|
|
|
716
731
|
authHeader: true,
|
|
717
732
|
api: "openai-completions",
|
|
718
733
|
models: openaiCompatModels.map((m) => {
|
|
719
|
-
const pricing = lookupPricing(m.modelId);
|
|
720
734
|
return {
|
|
721
735
|
id: m.modelId,
|
|
722
736
|
name: m.displayName,
|
|
@@ -727,12 +741,9 @@ export class PiBridge {
|
|
|
727
741
|
// breaking auth lookup against our synthetic proxy provider.
|
|
728
742
|
provider: SPECTRAL_PROXY_OPENAI,
|
|
729
743
|
baseUrl,
|
|
730
|
-
reasoning: m.supportsReasoning ??
|
|
744
|
+
reasoning: m.supportsReasoning ?? false,
|
|
731
745
|
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 },
|
|
746
|
+
cost: getModelCost(m),
|
|
736
747
|
contextWindow: m.contextWindow ?? 0,
|
|
737
748
|
maxTokens: 0,
|
|
738
749
|
compat: inferSyntheticOpenAICompat(m),
|
|
@@ -752,18 +763,15 @@ export class PiBridge {
|
|
|
752
763
|
authHeader: true,
|
|
753
764
|
api: "openai-completions",
|
|
754
765
|
models: userModelEntries.map((m) => {
|
|
755
|
-
const pricing = lookupPricing(m.modelId);
|
|
756
766
|
return {
|
|
757
767
|
id: m.modelId,
|
|
758
768
|
name: m.displayName,
|
|
759
769
|
api: "openai-completions",
|
|
760
770
|
provider: SPECTRAL_PROXY_USER_MODEL,
|
|
761
771
|
baseUrl,
|
|
762
|
-
reasoning: m.supportsReasoning ??
|
|
772
|
+
reasoning: m.supportsReasoning ?? false,
|
|
763
773
|
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 },
|
|
774
|
+
cost: getModelCost(m),
|
|
767
775
|
contextWindow: m.contextWindow ?? 0,
|
|
768
776
|
maxTokens: 0,
|
|
769
777
|
compat: inferSyntheticOpenAICompat(m),
|
|
@@ -844,7 +852,7 @@ export class PiBridge {
|
|
|
844
852
|
if (!modelId)
|
|
845
853
|
return true; // nothing to apply — pi keeps its current model
|
|
846
854
|
if (!this.session)
|
|
847
|
-
throw new Error("
|
|
855
|
+
throw new Error("AgentBridge.start() not called");
|
|
848
856
|
if (this.lastAppliedModelId === modelId)
|
|
849
857
|
return true; // idempotent: same model already in effect
|
|
850
858
|
if (!this.modelRegistry) {
|
|
@@ -857,13 +865,26 @@ export class PiBridge {
|
|
|
857
865
|
});
|
|
858
866
|
return false;
|
|
859
867
|
}
|
|
860
|
-
|
|
868
|
+
let model = this.modelRegistry
|
|
861
869
|
.getAvailable()
|
|
862
870
|
.find((m) => m.id === modelId);
|
|
871
|
+
let refreshError;
|
|
872
|
+
if (!model) {
|
|
873
|
+
try {
|
|
874
|
+
await this.refreshAllowedModels({ bypassCache: true });
|
|
875
|
+
model = this.modelRegistry
|
|
876
|
+
.getAvailable()
|
|
877
|
+
.find((m) => m.id === modelId);
|
|
878
|
+
}
|
|
879
|
+
catch (err) {
|
|
880
|
+
refreshError = err instanceof Error ? err : new Error(String(err));
|
|
881
|
+
this.opts.onError?.(refreshError);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
863
884
|
if (!model) {
|
|
864
885
|
this.opts.emit({
|
|
865
886
|
type: "error",
|
|
866
|
-
message: `Unknown modelId "${modelId}" — not found in pi model registry`,
|
|
887
|
+
message: `Unknown modelId "${modelId}" — not found in pi model registry${refreshError ? ` after refresh: ${refreshError.message}` : ""}`,
|
|
867
888
|
});
|
|
868
889
|
return false;
|
|
869
890
|
}
|
|
@@ -965,16 +986,14 @@ export class PiBridge {
|
|
|
965
986
|
*/
|
|
966
987
|
async prompt(text, images) {
|
|
967
988
|
if (!this.session)
|
|
968
|
-
throw new Error("
|
|
969
|
-
//
|
|
970
|
-
//
|
|
971
|
-
// and
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
: undefined;
|
|
975
|
-
const modelSupportsImages = currentModel?.supportsImages === true;
|
|
989
|
+
throw new Error("AgentBridge.start() not called");
|
|
990
|
+
// Always pass images through to the session.
|
|
991
|
+
// The spectral-vision-fallback extension intercepts images via the
|
|
992
|
+
// `context` event and replaces them with text descriptions using a
|
|
993
|
+
// vision-capable model BEFORE they reach the LLM. This means images
|
|
994
|
+
// work regardless of whether the main model supports them.
|
|
976
995
|
try {
|
|
977
|
-
if (images && images.length > 0
|
|
996
|
+
if (images && images.length > 0) {
|
|
978
997
|
const imageContents = images.map((img) => ({
|
|
979
998
|
type: "image",
|
|
980
999
|
data: img.data,
|
|
@@ -982,20 +1001,6 @@ export class PiBridge {
|
|
|
982
1001
|
}));
|
|
983
1002
|
await this.session.prompt(text, { images: imageContents });
|
|
984
1003
|
}
|
|
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
1004
|
else {
|
|
1000
1005
|
await this.session.prompt(text);
|
|
1001
1006
|
}
|
|
@@ -1017,7 +1022,7 @@ export class PiBridge {
|
|
|
1017
1022
|
*/
|
|
1018
1023
|
async compact(customInstructions) {
|
|
1019
1024
|
if (!this.session)
|
|
1020
|
-
throw new Error("
|
|
1025
|
+
throw new Error("AgentBridge.start() not called");
|
|
1021
1026
|
this.memoryPhase = "compacting";
|
|
1022
1027
|
try {
|
|
1023
1028
|
await this.session.compact(customInstructions);
|
|
@@ -1106,13 +1111,13 @@ export class PiBridge {
|
|
|
1106
1111
|
e.type === "tool_call" ||
|
|
1107
1112
|
e.type === "tool_result");
|
|
1108
1113
|
if (!finalContent && !hasMeaningfulEvent) {
|
|
1109
|
-
console.debug("[
|
|
1114
|
+
console.debug("[agent-bridge] skipping empty intermediate message");
|
|
1110
1115
|
try {
|
|
1111
1116
|
this.opts.onAssistantMessageSkipped?.(messageId);
|
|
1112
1117
|
}
|
|
1113
1118
|
catch (err) {
|
|
1114
1119
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
1115
|
-
console.error(`[
|
|
1120
|
+
console.error(`[agent-bridge] onAssistantMessageSkipped failed: ${e.message}`);
|
|
1116
1121
|
}
|
|
1117
1122
|
return;
|
|
1118
1123
|
}
|
|
@@ -1440,6 +1445,15 @@ function detectMemorySystem(message) {
|
|
|
1440
1445
|
// Keep generic observational-memory messages visible in the landing badge.
|
|
1441
1446
|
return "memory_observer";
|
|
1442
1447
|
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Detect subsystem from notification messages by known prefix.
|
|
1450
|
+
* Returns undefined for generic extension messages without a matching prefix.
|
|
1451
|
+
*/
|
|
1452
|
+
function detectNotifSystem(message) {
|
|
1453
|
+
if (message.startsWith("[spectral-vision]"))
|
|
1454
|
+
return "vision";
|
|
1455
|
+
return detectMemorySystem(message);
|
|
1456
|
+
}
|
|
1443
1457
|
/**
|
|
1444
1458
|
* Create a minimal ExtensionUIContext that forwards `notify()` calls as
|
|
1445
1459
|
* `agent_notification` wire events. All other UI methods are no-ops —
|
|
@@ -1450,15 +1464,18 @@ function detectMemorySystem(message) {
|
|
|
1450
1464
|
function createHeadlessUIContext(emit) {
|
|
1451
1465
|
// Defer to a Proxy so we don't need to stub every method.
|
|
1452
1466
|
// `notify` is the only method called by extensions in serve mode
|
|
1453
|
-
// (observational memory, MCP status bar updates).
|
|
1467
|
+
// (observational memory, MCP status bar updates, spectral-vision).
|
|
1454
1468
|
const handler = {
|
|
1455
1469
|
get(_target, prop) {
|
|
1456
1470
|
if (prop === "notify") {
|
|
1457
1471
|
return (message, type) => {
|
|
1458
1472
|
const level = type ?? "info";
|
|
1459
|
-
const system =
|
|
1473
|
+
const system = detectNotifSystem(message);
|
|
1460
1474
|
if (system?.startsWith("memory_")) {
|
|
1461
|
-
console.info(`[
|
|
1475
|
+
console.info(`[AgentBridge][memory][${level}] ${message}`);
|
|
1476
|
+
}
|
|
1477
|
+
else if (system === "vision") {
|
|
1478
|
+
console.info(`[AgentBridge][vision][${level}] ${message}`);
|
|
1462
1479
|
}
|
|
1463
1480
|
emit({
|
|
1464
1481
|
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.2",
|
|
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
|
},
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workaround for https://github.com/oven-sh/bun/issues/27802
|
|
3
|
-
*
|
|
4
|
-
* Bun compiled binaries have an empty `process.env` when running inside
|
|
5
|
-
* sandbox environments (e.g. nono on Linux/macOS). On Linux we can recover
|
|
6
|
-
* the environment from `/proc/self/environ`.
|
|
7
|
-
*/
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
9
|
-
/**
|
|
10
|
-
* Restore environment variables from `/proc/self/environ` when running
|
|
11
|
-
* inside a sandbox where Bun's `process.env` is empty.
|
|
12
|
-
*/
|
|
13
|
-
export function restoreSandboxEnv() {
|
|
14
|
-
if (!process.versions?.bun)
|
|
15
|
-
return;
|
|
16
|
-
// If process.env already has entries, nothing to fix.
|
|
17
|
-
if (Object.keys(process.env).length > 0)
|
|
18
|
-
return;
|
|
19
|
-
try {
|
|
20
|
-
const data = readFileSync("/proc/self/environ", "utf-8");
|
|
21
|
-
for (const entry of data.split("\0")) {
|
|
22
|
-
const idx = entry.indexOf("=");
|
|
23
|
-
if (idx > 0) {
|
|
24
|
-
process.env[entry.slice(0, idx)] = entry.slice(idx + 1);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// /proc/self/environ may not be readable; ignore.
|
|
30
|
-
}
|
|
31
|
-
}
|