@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,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
|
"",
|
|
@@ -532,6 +532,40 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
532
532
|
"",
|
|
533
533
|
"Start by collecting project context, then generate the most impactful extensions",
|
|
534
534
|
"and update AGENTS.md when done.",
|
|
535
|
+
"",
|
|
536
|
+
"### 6. Persist Key Discoveries as Durable Observations",
|
|
537
|
+
"",
|
|
538
|
+
"As you analyze the project, you will discover conventions, rules, and patterns",
|
|
539
|
+
"that future agent sessions should know. Write these as durable cross-session",
|
|
540
|
+
"observations using the `write_project_observation` tool:",
|
|
541
|
+
"",
|
|
542
|
+
"```",
|
|
543
|
+
"write_project_observation({",
|
|
544
|
+
" content: \"backend/src/models.ts is auto-generated by Axolotl — never edit by hand\",",
|
|
545
|
+
" relevance: \"critical\"",
|
|
546
|
+
"})",
|
|
547
|
+
"```",
|
|
548
|
+
"",
|
|
549
|
+
"**What to capture:**",
|
|
550
|
+
"",
|
|
551
|
+
"- **NEVER-EDIT markers** (relevance: critical): Auto-generated files that must",
|
|
552
|
+
" be regenerated instead of edited. Seen a comment like 'DO NOT EDIT'? Persist it.",
|
|
553
|
+
"- **Import/macro conventions** (relevance: high): Rules like \"all Deno imports",
|
|
554
|
+
" MUST use .ts extension\" or \"use workspace imports, not relative paths\".",
|
|
555
|
+
"- **Code-generation commands** (relevance: high): The exact shell commands to",
|
|
556
|
+
" regenerate generated files (e.g. 'cd backend && deno task axolotl:build').",
|
|
557
|
+
"- **Directory roles** (relevance: medium): What each top-level directory contains",
|
|
558
|
+
" (e.g. 'packages/parser/ is zero-dependency — never add imports here').",
|
|
559
|
+
"- **Runtime boundaries** (relevance: medium): Which packages use Deno vs Node.",
|
|
560
|
+
"- **Database/API patterns** (relevance: medium): How the backend is structured.",
|
|
561
|
+
"",
|
|
562
|
+
"**Guidelines:**",
|
|
563
|
+
"",
|
|
564
|
+
"- Write 5–10 observations covering the most important discoveries.",
|
|
565
|
+
"- Each observation is a single self-contained sentence — no markdown, no lists.",
|
|
566
|
+
"- Focus on facts that would PREVENT MISTAKES or SAVE TIME in future sessions.",
|
|
567
|
+
"- Deduplication is automatic by content hash — writing the same fact twice is harmless.",
|
|
568
|
+
"- Future sessions retrieve these via `read_project_observations(\"query\")`.",
|
|
535
569
|
].join("\n");
|
|
536
570
|
}
|
|
537
571
|
// ---------------------------------------------------------------------------
|
|
@@ -539,7 +573,7 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
|
|
|
539
573
|
// ---------------------------------------------------------------------------
|
|
540
574
|
const AR_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes — generous for LLM-based analysis
|
|
541
575
|
/**
|
|
542
|
-
* Execute auto-research for a project through the existing
|
|
576
|
+
* Execute auto-research for a project through the existing AgentBridge.
|
|
543
577
|
*
|
|
544
578
|
* Caller (dispatcher) is fire-and-forget — this function is `void` and
|
|
545
579
|
* all errors are surfaced as `auto_research_error` events on the wire
|
|
@@ -547,7 +581,7 @@ const AR_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes — generous for LLM-based a
|
|
|
547
581
|
*
|
|
548
582
|
* This replaces the old subprocess-spawning approach. Instead of launching
|
|
549
583
|
* a separate pi process (which lacks backend proxy credentials), we send
|
|
550
|
-
* the auto-research task through the session's existing
|
|
584
|
+
* the auto-research task through the session's existing AgentBridge. The agent
|
|
551
585
|
* uses the session's model, backend proxy, and all available tools.
|
|
552
586
|
*/
|
|
553
587
|
export function handleAutoResearch(input, deps) {
|
|
@@ -700,7 +734,7 @@ export function handleAutoResearch(input, deps) {
|
|
|
700
734
|
// then timeout fires and calls finalize() again — but finalize() is
|
|
701
735
|
// gated by watcherFired, so it's a no-op. The only issue is we don't
|
|
702
736
|
// clearTimeout on watcher fire — but that's fine, the timer is harmless.
|
|
703
|
-
// --- Send the prompt through the existing
|
|
737
|
+
// --- Send the prompt through the existing AgentBridge (backend proxy) ---
|
|
704
738
|
manager.prompt(sessionId, taskContent, storedModelId).catch((err) => {
|
|
705
739
|
if (watcherFired)
|
|
706
740
|
return; // already handled by watcher
|
package/dist/relay/dispatcher.js
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
import { BadRequestError, NotFoundError } from "../server/handlers/errors.js";
|
|
42
42
|
import { handlePathAutocomplete } from "../server/handlers/paths-autocomplete.js";
|
|
43
43
|
import { handleBindStudioProject, handleCreateProject, handleDeleteProject, handleListProjects, handleListSessionsByProject, handleUpdateProject, } from "../server/handlers/projects.js";
|
|
44
|
-
import { handleCompactSession, handleCreateSession, handleDeleteSession, handleForkSession, handleGetSessionDetail, handleGetSessionMemoryDetails, handleGetSessionMemoryStatus, handleUpdateSession, } from "../server/handlers/sessions.js";
|
|
44
|
+
import { handleCompactSession, handleCreateSession, handleDeleteSession, handleForkSession, handleRememberAndDeleteSession, handleGetSessionDetail, handleGetSessionMemoryDetails, handleGetSessionMemoryStatus, handleUpdateSession, } from "../server/handlers/sessions.js";
|
|
45
45
|
import { handleClearPromptQueue, handleEnqueuePrompt, handleGetPromptQueue, handleRemovePrompt, } from "../server/handlers/queue.js";
|
|
46
46
|
import { shutdownState } from "../server/shutdown.js";
|
|
47
47
|
import { handleAutoResearch } from "./auto-research.js";
|
|
@@ -126,6 +126,14 @@ export function matchRoute(method, path) {
|
|
|
126
126
|
return { route: "compact_session", id };
|
|
127
127
|
return null;
|
|
128
128
|
}
|
|
129
|
+
// /api/sessions/:id/remember-and-delete
|
|
130
|
+
const rememberDeleteMatch = /^\/api\/sessions\/([^/]+)\/remember-and-delete$/.exec(cleanPath);
|
|
131
|
+
if (rememberDeleteMatch) {
|
|
132
|
+
const id = decodeURIComponent(rememberDeleteMatch[1]);
|
|
133
|
+
if (method === "POST")
|
|
134
|
+
return { route: "remember_and_delete_session", id };
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
129
137
|
// /api/sessions/:id/fork
|
|
130
138
|
const forkMatch = /^\/api\/sessions\/([^/]+)\/fork$/.exec(cleanPath);
|
|
131
139
|
if (forkMatch) {
|
|
@@ -341,6 +349,24 @@ async function dispatchRoute(match, body, deps) {
|
|
|
341
349
|
}
|
|
342
350
|
case "compact_session":
|
|
343
351
|
return await handleCompactSession(store, manager, id);
|
|
352
|
+
case "remember_and_delete_session": {
|
|
353
|
+
// Compact first — the compaction hook's persistProjectObservations
|
|
354
|
+
// runs inside compactSession, writing reflections to cross-session
|
|
355
|
+
// durable memory. On failure (e.g. no API key), we let the error
|
|
356
|
+
// propagate so the session is NOT deleted.
|
|
357
|
+
await handleRememberAndDeleteSession(store, manager, id);
|
|
358
|
+
const detail = store.getSession(id);
|
|
359
|
+
manager.disposeSessionStream(id);
|
|
360
|
+
handleDeleteSession(store, id);
|
|
361
|
+
if (detail) {
|
|
362
|
+
safePublish(publishMetaEvent, logger, {
|
|
363
|
+
type: "session_deleted",
|
|
364
|
+
projectId: detail.projectId,
|
|
365
|
+
sessionId: id,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return { ok: true };
|
|
369
|
+
}
|
|
344
370
|
case "fork_session": {
|
|
345
371
|
const session = handleForkSession(store, id, asObject(body));
|
|
346
372
|
safePublish(publishMetaEvent, logger, {
|
|
@@ -682,7 +708,7 @@ export function detachAllSubscribers(manager, subscribers) {
|
|
|
682
708
|
}
|
|
683
709
|
/**
|
|
684
710
|
* Dispatch an `auto_research` frame. Sends the auto-research task through
|
|
685
|
-
* the existing
|
|
711
|
+
* the existing AgentBridge (backend proxy) instead of spawning a separate pi
|
|
686
712
|
* subprocess. This ensures auto-research uses the same model and API keys
|
|
687
713
|
* as the active session.
|
|
688
714
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fetch the admin-managed list of allowed base models from the backend.
|
|
3
3
|
*
|
|
4
|
-
* Used by `
|
|
4
|
+
* Used by `AgentBridge` at startup to register synthetic providers
|
|
5
5
|
* (`spectral-proxy-anthropic` / `spectral-proxy-openai`) that route every
|
|
6
6
|
* inference call through the backend's `/v1/messages` and
|
|
7
7
|
* `/v1/chat/completions` endpoints. The backend authenticates the call
|
|
@@ -32,7 +32,7 @@ const QUERY = `query AvailableBaseModels { availableBaseModels { name provider u
|
|
|
32
32
|
/**
|
|
33
33
|
* Fetch the whitelist of allowed base models. Throws on any failure with a
|
|
34
34
|
* message tailored for an operator running `spectral serve` — the caller
|
|
35
|
-
* (
|
|
35
|
+
* (AgentBridge.start) lets the throw propagate so the WS subscriber sees a
|
|
36
36
|
* clear error event instead of a silent fall-through to "no models".
|
|
37
37
|
*/
|
|
38
38
|
export async function fetchAllowedModels(opts) {
|
|
@@ -6,8 +6,8 @@ const dynamicImport = (specifier) => import(specifier);
|
|
|
6
6
|
const NODE_FS_SPECIFIER = "node:" + "fs";
|
|
7
7
|
const NODE_OS_SPECIFIER = "node:" + "os";
|
|
8
8
|
const NODE_PATH_SPECIFIER = "node:" + "path";
|
|
9
|
-
// Eagerly load in Node.js
|
|
10
|
-
if (typeof process !== "undefined" &&
|
|
9
|
+
// Eagerly load in Node.js environment only
|
|
10
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
11
11
|
dynamicImport(NODE_FS_SPECIFIER).then((m) => {
|
|
12
12
|
_existsSync = m.existsSync;
|
|
13
13
|
});
|
|
@@ -18,38 +18,6 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
|
|
|
18
18
|
_join = m.join;
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
-
let _procEnvCache = null;
|
|
22
|
-
/**
|
|
23
|
-
* Fallback for https://github.com/oven-sh/bun/issues/27802
|
|
24
|
-
* Bun compiled binaries have an empty `process.env` inside sandbox
|
|
25
|
-
* environments on Linux. We can recover the env from `/proc/self/environ`.
|
|
26
|
-
*/
|
|
27
|
-
function getProcEnv(key) {
|
|
28
|
-
if (!process.versions?.bun)
|
|
29
|
-
return undefined;
|
|
30
|
-
if (typeof process === "undefined")
|
|
31
|
-
return undefined;
|
|
32
|
-
// If process.env already has entries, the bug is not triggered.
|
|
33
|
-
if (Object.keys(process.env).length > 0)
|
|
34
|
-
return undefined;
|
|
35
|
-
if (_procEnvCache === null) {
|
|
36
|
-
_procEnvCache = new Map();
|
|
37
|
-
try {
|
|
38
|
-
const { readFileSync } = require("node:fs");
|
|
39
|
-
const data = readFileSync("/proc/self/environ", "utf-8");
|
|
40
|
-
for (const entry of data.split("\0")) {
|
|
41
|
-
const idx = entry.indexOf("=");
|
|
42
|
-
if (idx > 0) {
|
|
43
|
-
_procEnvCache.set(entry.slice(0, idx), entry.slice(idx + 1));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
// /proc/self/environ may not be readable.
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return _procEnvCache.get(key);
|
|
52
|
-
}
|
|
53
21
|
let cachedVertexAdcCredentialsExists = null;
|
|
54
22
|
function hasVertexAdcCredentials() {
|
|
55
23
|
if (cachedVertexAdcCredentialsExists === null) {
|
|
@@ -57,7 +25,7 @@ function hasVertexAdcCredentials() {
|
|
|
57
25
|
// return false WITHOUT caching so the next call retries once they're ready.
|
|
58
26
|
// Only cache false permanently in a browser environment where fs is never available.
|
|
59
27
|
if (!_existsSync || !_homedir || !_join) {
|
|
60
|
-
const isNode = typeof process !== "undefined" &&
|
|
28
|
+
const isNode = typeof process !== "undefined" && process.versions?.node;
|
|
61
29
|
if (!isNode) {
|
|
62
30
|
// Definitively in a browser — safe to cache false permanently
|
|
63
31
|
cachedVertexAdcCredentialsExists = false;
|
|
@@ -65,7 +33,7 @@ function hasVertexAdcCredentials() {
|
|
|
65
33
|
return false;
|
|
66
34
|
}
|
|
67
35
|
// Check GOOGLE_APPLICATION_CREDENTIALS env var first (standard way)
|
|
68
|
-
const gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS
|
|
36
|
+
const gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
69
37
|
if (gacPath) {
|
|
70
38
|
cachedVertexAdcCredentialsExists = _existsSync(gacPath);
|
|
71
39
|
}
|
|
@@ -121,23 +89,21 @@ export function findEnvKeys(provider) {
|
|
|
121
89
|
const envVars = getApiKeyEnvVars(provider);
|
|
122
90
|
if (!envVars)
|
|
123
91
|
return undefined;
|
|
124
|
-
const found = envVars.filter((envVar) => !!process.env[envVar]
|
|
92
|
+
const found = envVars.filter((envVar) => !!process.env[envVar]);
|
|
125
93
|
return found.length > 0 ? found : undefined;
|
|
126
94
|
}
|
|
127
95
|
export function getEnvApiKey(provider) {
|
|
128
96
|
const envKeys = findEnvKeys(provider);
|
|
129
97
|
if (envKeys?.[0]) {
|
|
130
|
-
return process.env[envKeys[0]]
|
|
98
|
+
return process.env[envKeys[0]];
|
|
131
99
|
}
|
|
132
100
|
// Vertex AI supports either an explicit API key or Application Default Credentials.
|
|
133
101
|
// Auth is configured via `gcloud auth application-default login`.
|
|
134
102
|
if (provider === "google-vertex") {
|
|
135
103
|
const hasCredentials = hasVertexAdcCredentials();
|
|
136
104
|
const hasProject = !!(process.env.GOOGLE_CLOUD_PROJECT ||
|
|
137
|
-
process.env.GCLOUD_PROJECT
|
|
138
|
-
|
|
139
|
-
getProcEnv("GCLOUD_PROJECT"));
|
|
140
|
-
const hasLocation = !!(process.env.GOOGLE_CLOUD_LOCATION || getProcEnv("GOOGLE_CLOUD_LOCATION"));
|
|
105
|
+
process.env.GCLOUD_PROJECT);
|
|
106
|
+
const hasLocation = !!(process.env.GOOGLE_CLOUD_LOCATION);
|
|
141
107
|
if (hasCredentials && hasProject && hasLocation) {
|
|
142
108
|
return "<authenticated>";
|
|
143
109
|
}
|
|
@@ -155,13 +121,7 @@ export function getEnvApiKey(provider) {
|
|
|
155
121
|
process.env.AWS_BEARER_TOKEN_BEDROCK ||
|
|
156
122
|
process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ||
|
|
157
123
|
process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI ||
|
|
158
|
-
process.env.AWS_WEB_IDENTITY_TOKEN_FILE
|
|
159
|
-
getProcEnv("AWS_PROFILE") ||
|
|
160
|
-
(getProcEnv("AWS_ACCESS_KEY_ID") && getProcEnv("AWS_SECRET_ACCESS_KEY")) ||
|
|
161
|
-
getProcEnv("AWS_BEARER_TOKEN_BEDROCK") ||
|
|
162
|
-
getProcEnv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") ||
|
|
163
|
-
getProcEnv("AWS_CONTAINER_CREDENTIALS_FULL_URI") ||
|
|
164
|
-
getProcEnv("AWS_WEB_IDENTITY_TOKEN_FILE")) {
|
|
124
|
+
process.env.AWS_WEB_IDENTITY_TOKEN_FILE) {
|
|
165
125
|
return "<authenticated>";
|
|
166
126
|
}
|
|
167
127
|
}
|
|
@@ -21,7 +21,7 @@ async function getNodeApis() {
|
|
|
21
21
|
if (nodeApis)
|
|
22
22
|
return nodeApis;
|
|
23
23
|
if (!nodeApisPromise) {
|
|
24
|
-
if (typeof process === "undefined" ||
|
|
24
|
+
if (typeof process === "undefined" || !process.versions?.node) {
|
|
25
25
|
throw new Error("Anthropic OAuth is only available in Node.js environments");
|
|
26
26
|
}
|
|
27
27
|
nodeApisPromise = import("node:http").then((httpModule) => ({
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// NEVER convert to top-level imports - breaks browser/Vite builds
|
|
8
8
|
let _randomBytes = null;
|
|
9
9
|
let _http = null;
|
|
10
|
-
if (typeof process !== "undefined" &&
|
|
10
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
11
11
|
import("node:crypto").then((m) => {
|
|
12
12
|
_randomBytes = m.randomBytes;
|
|
13
13
|
});
|
|
@@ -9,13 +9,6 @@ import { normalizePath } from "./utils/paths.js";
|
|
|
9
9
|
// =============================================================================
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
11
|
const __dirname = dirname(__filename);
|
|
12
|
-
/**
|
|
13
|
-
* Detect if we're running as a Bun compiled binary.
|
|
14
|
-
* Bun binaries have import.meta.url containing "$bunfs", "~BUN", or "%7EBUN" (Bun's virtual filesystem path)
|
|
15
|
-
*/
|
|
16
|
-
export const isBunBinary = import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN");
|
|
17
|
-
/** Detect if Bun is the runtime (compiled binary or bun run) */
|
|
18
|
-
export const isBunRuntime = !!process.versions.bun;
|
|
19
12
|
function makeSelfUpdateCommand(installStep, uninstallStep) {
|
|
20
13
|
if (!uninstallStep)
|
|
21
14
|
return installStep;
|
|
@@ -33,9 +26,6 @@ function makeSelfUpdateCommandStep(command, args) {
|
|
|
33
26
|
};
|
|
34
27
|
}
|
|
35
28
|
export function detectInstallMethod() {
|
|
36
|
-
if (isBunBinary) {
|
|
37
|
-
return "bun-binary";
|
|
38
|
-
}
|
|
39
29
|
const resolvedPath = `${__dirname}\0${process.execPath || ""}`.toLowerCase().replace(/\\/g, "/");
|
|
40
30
|
if (resolvedPath.includes("/pnpm/") || resolvedPath.includes("/.pnpm/")) {
|
|
41
31
|
return "pnpm";
|
|
@@ -43,9 +33,6 @@ export function detectInstallMethod() {
|
|
|
43
33
|
if (resolvedPath.includes("/yarn/") || resolvedPath.includes("/.yarn/")) {
|
|
44
34
|
return "yarn";
|
|
45
35
|
}
|
|
46
|
-
if (isBunRuntime || resolvedPath.includes("/install/global/node_modules/")) {
|
|
47
|
-
return "bun";
|
|
48
|
-
}
|
|
49
36
|
if (resolvedPath.includes("/npm/") || resolvedPath.includes("/node_modules/")) {
|
|
50
37
|
return "npm";
|
|
51
38
|
}
|
|
@@ -74,8 +61,6 @@ function getInferredNpmInstall() {
|
|
|
74
61
|
}
|
|
75
62
|
function getSelfUpdateCommandForMethod(method, installedPackageName, updatePackageName = installedPackageName, npmCommand) {
|
|
76
63
|
switch (method) {
|
|
77
|
-
case "bun-binary":
|
|
78
|
-
return undefined;
|
|
79
64
|
case "pnpm":
|
|
80
65
|
return makeSelfUpdateCommand(makeSelfUpdateCommandStep("pnpm", ["install", "-g", "--ignore-scripts", updatePackageName]), updatePackageName === installedPackageName
|
|
81
66
|
? undefined
|
|
@@ -84,10 +69,6 @@ function getSelfUpdateCommandForMethod(method, installedPackageName, updatePacka
|
|
|
84
69
|
return makeSelfUpdateCommand(makeSelfUpdateCommandStep("yarn", ["global", "add", "--ignore-scripts", updatePackageName]), updatePackageName === installedPackageName
|
|
85
70
|
? undefined
|
|
86
71
|
: makeSelfUpdateCommandStep("yarn", ["global", "remove", installedPackageName]));
|
|
87
|
-
case "bun":
|
|
88
|
-
return makeSelfUpdateCommand(makeSelfUpdateCommandStep("bun", ["install", "-g", "--ignore-scripts", updatePackageName]), updatePackageName === installedPackageName
|
|
89
|
-
? undefined
|
|
90
|
-
: makeSelfUpdateCommandStep("bun", ["uninstall", "-g", installedPackageName]));
|
|
91
72
|
case "npm": {
|
|
92
73
|
const [command = "npm", ...npmArgs] = npmCommand ?? [];
|
|
93
74
|
const inferred = npmCommand?.length ? undefined : getInferredNpmInstall();
|
|
@@ -126,16 +107,6 @@ function getGlobalPackageRoots(method, _packageName, npmCommand) {
|
|
|
126
107
|
case "npm": {
|
|
127
108
|
const configured = !!npmCommand?.length;
|
|
128
109
|
const [command = "npm", ...npmArgs] = npmCommand ?? [];
|
|
129
|
-
if (configured && command === "bun") {
|
|
130
|
-
const bunBin = readCommandOutput(command, [...npmArgs, "pm", "bin", "-g"], {
|
|
131
|
-
requireSuccess: true,
|
|
132
|
-
});
|
|
133
|
-
const roots = [join(homedir(), ".bun", "install", "global", "node_modules")];
|
|
134
|
-
if (bunBin) {
|
|
135
|
-
roots.push(join(dirname(bunBin), "install", "global", "node_modules"));
|
|
136
|
-
}
|
|
137
|
-
return roots;
|
|
138
|
-
}
|
|
139
110
|
const root = readCommandOutput(command, [...npmArgs, "root", "-g"], {
|
|
140
111
|
requireSuccess: configured,
|
|
141
112
|
});
|
|
@@ -150,15 +121,6 @@ function getGlobalPackageRoots(method, _packageName, npmCommand) {
|
|
|
150
121
|
const dir = readCommandOutput("yarn", ["global", "dir"]);
|
|
151
122
|
return dir ? [dir, join(dir, "node_modules")] : [];
|
|
152
123
|
}
|
|
153
|
-
case "bun": {
|
|
154
|
-
const bunBin = readCommandOutput("bun", ["pm", "bin", "-g"]);
|
|
155
|
-
const roots = [join(homedir(), ".bun", "install", "global", "node_modules")];
|
|
156
|
-
if (bunBin) {
|
|
157
|
-
roots.push(join(dirname(bunBin), "install", "global", "node_modules"));
|
|
158
|
-
}
|
|
159
|
-
return roots;
|
|
160
|
-
}
|
|
161
|
-
case "bun-binary":
|
|
162
124
|
case "unknown":
|
|
163
125
|
return [];
|
|
164
126
|
}
|
|
@@ -229,9 +191,6 @@ export function getSelfUpdateCommand(packageName, npmCommand, updatePackageName
|
|
|
229
191
|
}
|
|
230
192
|
export function getSelfUpdateUnavailableInstruction(packageName, npmCommand, updatePackageName = packageName) {
|
|
231
193
|
const method = detectInstallMethod();
|
|
232
|
-
if (method === "bun-binary") {
|
|
233
|
-
return `Download from: https://github.com/earendil-works/pi-mono/releases/latest`;
|
|
234
|
-
}
|
|
235
194
|
const command = getSelfUpdateCommandForMethod(method, packageName, updatePackageName, npmCommand);
|
|
236
195
|
if (command) {
|
|
237
196
|
if (isManagedByGlobalPackageManager(method, packageName, npmCommand) && !isSelfUpdatePathWritable()) {
|
|
@@ -254,7 +213,6 @@ export function getUpdateInstruction(packageName) {
|
|
|
254
213
|
// =============================================================================
|
|
255
214
|
/**
|
|
256
215
|
* Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
|
|
257
|
-
* - For Bun binary: returns the directory containing the executable
|
|
258
216
|
* - For Node.js (dist/): returns __dirname (the dist/ directory)
|
|
259
217
|
* - For tsx (src/): returns parent directory (the package root)
|
|
260
218
|
*/
|
|
@@ -264,10 +222,6 @@ export function getPackageDir() {
|
|
|
264
222
|
if (envDir) {
|
|
265
223
|
return normalizePath(envDir);
|
|
266
224
|
}
|
|
267
|
-
if (isBunBinary) {
|
|
268
|
-
// Bun binary: process.execPath points to the compiled executable
|
|
269
|
-
return dirname(process.execPath);
|
|
270
|
-
}
|
|
271
225
|
// Node.js: walk up from __dirname until we find package.json
|
|
272
226
|
let dir = __dirname;
|
|
273
227
|
while (dir !== dirname(dir)) {
|
|
@@ -281,14 +235,10 @@ export function getPackageDir() {
|
|
|
281
235
|
}
|
|
282
236
|
/**
|
|
283
237
|
* Get path to built-in themes directory (shipped with package)
|
|
284
|
-
* - For Bun binary: theme/ next to executable
|
|
285
238
|
* - For Node.js (dist/): dist/modes/interactive/theme/
|
|
286
239
|
* - For tsx (src/): src/modes/interactive/theme/
|
|
287
240
|
*/
|
|
288
241
|
export function getThemesDir() {
|
|
289
|
-
if (isBunBinary) {
|
|
290
|
-
return join(getPackageDir(), "theme");
|
|
291
|
-
}
|
|
292
242
|
// Theme is in modes/interactive/theme/ relative to src/ or dist/
|
|
293
243
|
const packageDir = getPackageDir();
|
|
294
244
|
const srcOrDist = existsSync(join(packageDir, "src")) ? "src" : "dist";
|
|
@@ -296,7 +246,8 @@ export function getThemesDir() {
|
|
|
296
246
|
}
|
|
297
247
|
/**
|
|
298
248
|
* Get path to HTML export template directory (shipped with package)
|
|
299
|
-
* - For
|
|
249
|
+
* - For Node.js (dist/): returns __dirname (the dist/ directory)
|
|
250
|
+
* - For tsx (src/): returns parent directory (the package root)
|
|
300
251
|
/** Get path to package.json */
|
|
301
252
|
export function getPackageJsonPath() {
|
|
302
253
|
return join(getPackageDir(), "package.json");
|
|
@@ -317,47 +268,29 @@ export function getExamplesPath() {
|
|
|
317
268
|
export function getChangelogPath() {
|
|
318
269
|
return resolve(join(getPackageDir(), "CHANGELOG.md"));
|
|
319
270
|
}
|
|
320
|
-
/**
|
|
321
|
-
* Get path to built-in interactive assets directory.
|
|
322
|
-
* - For Bun binary: assets/ next to executable
|
|
323
|
-
* - For Node.js (dist/): dist/modes/interactive/assets/
|
|
324
|
-
* - For tsx (src/): src/modes/interactive/assets/
|
|
325
|
-
*/
|
|
326
|
-
export function getInteractiveAssetsDir() {
|
|
327
|
-
if (isBunBinary) {
|
|
328
|
-
return join(getPackageDir(), "assets");
|
|
329
|
-
}
|
|
330
|
-
const packageDir = getPackageDir();
|
|
331
|
-
const srcOrDist = existsSync(join(packageDir, "src")) ? "src" : "dist";
|
|
332
|
-
return join(packageDir, srcOrDist, "modes", "interactive", "assets");
|
|
333
|
-
}
|
|
334
|
-
/** Get path to a bundled interactive asset */
|
|
335
|
-
export function getBundledInteractiveAssetPath(name) {
|
|
336
|
-
return join(getInteractiveAssetsDir(), name);
|
|
337
|
-
}
|
|
338
271
|
const pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
|
339
|
-
const
|
|
272
|
+
const spectralConfigName = pkg.spectralConfig?.name;
|
|
340
273
|
export const PACKAGE_NAME = pkg.name || "index.ts";
|
|
341
|
-
export const APP_NAME =
|
|
342
|
-
export const APP_TITLE =
|
|
343
|
-
export const CONFIG_DIR_NAME = pkg.
|
|
274
|
+
export const APP_NAME = spectralConfigName || "spectral";
|
|
275
|
+
export const APP_TITLE = spectralConfigName ? APP_NAME : "spectral";
|
|
276
|
+
export const CONFIG_DIR_NAME = pkg.spectralConfig?.configDir || ".spectral";
|
|
344
277
|
export const VERSION = pkg.version || "0.0.0";
|
|
345
|
-
// e.g.,
|
|
278
|
+
// e.g., SPECTRAL_CODING_AGENT_DIR
|
|
346
279
|
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
|
|
347
280
|
export const ENV_SESSION_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_SESSION_DIR`;
|
|
348
281
|
export function expandTildePath(path) {
|
|
349
282
|
return normalizePath(path);
|
|
350
283
|
}
|
|
351
|
-
const DEFAULT_SHARE_VIEWER_URL = "https://
|
|
284
|
+
const DEFAULT_SHARE_VIEWER_URL = "https://spectral.dev/session/";
|
|
352
285
|
/** Get the share viewer URL for a gist ID */
|
|
353
286
|
export function getShareViewerUrl(gistId) {
|
|
354
|
-
const baseUrl = process.env.
|
|
287
|
+
const baseUrl = process.env.SPECTRAL_SHARE_VIEWER_URL || DEFAULT_SHARE_VIEWER_URL;
|
|
355
288
|
return `${baseUrl}#${gistId}`;
|
|
356
289
|
}
|
|
357
290
|
// =============================================================================
|
|
358
|
-
// User Config Paths (~/.
|
|
291
|
+
// User Config Paths (~/.spectral/agent/*)
|
|
359
292
|
// =============================================================================
|
|
360
|
-
/** Get the agent config directory (e.g., ~/.
|
|
293
|
+
/** Get the agent config directory (e.g., ~/.spectral/agent/) */
|
|
361
294
|
export function getAgentDir() {
|
|
362
295
|
const envDir = process.env[ENV_AGENT_DIR];
|
|
363
296
|
if (envDir) {
|
|
@@ -465,6 +465,8 @@ export class AgentSession {
|
|
|
465
465
|
*/
|
|
466
466
|
dispose() {
|
|
467
467
|
this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().");
|
|
468
|
+
this.abortRetry();
|
|
469
|
+
this.agent.abort();
|
|
468
470
|
this._disconnectFromAgent();
|
|
469
471
|
this._eventListeners = [];
|
|
470
472
|
cleanupSessionResources(this.sessionId);
|