@aexol/spectral 0.7.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agents.js +4 -4
- package/dist/agent/index.js +8 -8
- package/dist/cli.js +1 -1
- package/dist/commands/serve.js +1 -1
- package/dist/extensions/spectral-vision-fallback.js +81 -44
- package/dist/mcp/agent-dir.js +1 -1
- package/dist/mcp/config.js +3 -3
- package/dist/mcp/sampling-handler.js +1 -1
- package/dist/mcp/server-manager.js +5 -1
- package/dist/memory/commands/status.js +1 -1
- package/dist/memory/compaction.js +2 -2
- package/dist/memory/config.js +3 -3
- package/dist/memory/debug-log.js +1 -1
- package/dist/memory/observer.js +2 -2
- package/dist/memory/tokens.js +1 -1
- package/dist/memory/tools/read-project-observations.js +2 -2
- package/dist/memory/tools/recall-observation.js +2 -2
- package/dist/relay/auto-research.js +23 -23
- package/dist/relay/dispatcher.js +28 -2
- package/dist/relay/models-fetch.js +2 -2
- package/dist/{pi → sdk}/coding-agent/cli/args.js +4 -4
- package/dist/{pi → sdk}/coding-agent/config.js +9 -9
- package/dist/{pi → sdk}/coding-agent/core/agent-session.js +2 -0
- package/dist/{pi → sdk}/coding-agent/core/compaction/compaction.js +161 -5
- package/dist/{pi → sdk}/coding-agent/core/model-registry.js +11 -4
- package/dist/{pi → sdk}/coding-agent/core/package-manager.js +5 -5
- package/dist/{pi → sdk}/coding-agent/core/sdk.js +1 -1
- package/dist/{pi → sdk}/coding-agent/core/session-manager.js +4 -4
- package/dist/{pi → sdk}/coding-agent/core/telemetry.js +1 -1
- package/dist/{pi → sdk}/coding-agent/migrations.js +3 -3
- package/dist/{pi → sdk}/coding-agent/utils/tools-manager.js +1 -1
- package/dist/{pi → sdk}/coding-agent/utils/version-check.js +2 -2
- package/dist/{pi → sdk}/coding-agent/utils/windows-self-update.js +1 -1
- package/dist/server/{pi-bridge.js → agent-bridge.js} +113 -97
- package/dist/server/handlers/sessions.js +21 -0
- package/dist/server/session-stream.js +5 -5
- package/package.json +6 -3
- /package/dist/{pi → sdk}/agent-core/agent-loop.js +0 -0
- /package/dist/{pi → sdk}/agent-core/agent.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/agent-harness.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/compaction/branch-summarization.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/compaction/compaction.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/compaction/utils.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/env/nodejs.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/messages.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/prompt-templates.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/jsonl-repo.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/jsonl-storage.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/memory-repo.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/memory-storage.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/repo-utils.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/session.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/session/uuid.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/skills.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/system-prompt.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/types.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/utils/shell-output.js +0 -0
- /package/dist/{pi → sdk}/agent-core/harness/utils/truncate.js +0 -0
- /package/dist/{pi → sdk}/agent-core/index.js +0 -0
- /package/dist/{pi → sdk}/agent-core/node.js +0 -0
- /package/dist/{pi → sdk}/agent-core/proxy.js +0 -0
- /package/dist/{pi → sdk}/agent-core/types.js +0 -0
- /package/dist/{pi → sdk}/ai/api-registry.js +0 -0
- /package/dist/{pi → sdk}/ai/cli.js +0 -0
- /package/dist/{pi → sdk}/ai/env-api-keys.js +0 -0
- /package/dist/{pi → sdk}/ai/image-models.generated.js +0 -0
- /package/dist/{pi → sdk}/ai/image-models.js +0 -0
- /package/dist/{pi → sdk}/ai/images-api-registry.js +0 -0
- /package/dist/{pi → sdk}/ai/images.js +0 -0
- /package/dist/{pi → sdk}/ai/index.js +0 -0
- /package/dist/{pi → sdk}/ai/models.generated.js +0 -0
- /package/dist/{pi → sdk}/ai/models.js +0 -0
- /package/dist/{pi → sdk}/ai/oauth.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/anthropic.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/faux.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/github-copilot-headers.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/openai-completions.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/openai-prompt-cache.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/register-builtins.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/simple-options.js +0 -0
- /package/dist/{pi → sdk}/ai/providers/transform-messages.js +0 -0
- /package/dist/{pi → sdk}/ai/session-resources.js +0 -0
- /package/dist/{pi → sdk}/ai/stream.js +0 -0
- /package/dist/{pi → sdk}/ai/types.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/diagnostics.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/event-stream.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/hash.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/headers.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/json-parse.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/node-http-proxy.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/anthropic.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/device-code.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/github-copilot.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/index.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/oauth-page.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/openai-codex.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/pkce.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/oauth/types.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/overflow.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/sanitize-unicode.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/typebox-helpers.js +0 -0
- /package/dist/{pi → sdk}/ai/utils/validation.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/bun/cli.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/bun/restore-sandbox-env.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/cli/file-processor.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/cli/initial-message.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/cli.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/agent-session-runtime.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/agent-session-services.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/auth-guidance.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/auth-storage.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/bash-executor.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/compaction/branch-summarization.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/compaction/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/compaction/utils.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/defaults.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/diagnostics.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/event-bus.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/exec.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/extensions/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/extensions/loader.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/extensions/runner.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/extensions/types.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/extensions/wrapper.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/footer-data-provider.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/http-dispatcher.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/keybindings.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/messages.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/model-resolver.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/output-guard.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/prompt-templates.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/provider-display-names.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/resolve-config-value.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/resource-loader.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/session-cwd.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/settings-manager.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/skills.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/slash-commands.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/source-info.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/system-prompt.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/timings.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/bash.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/edit-diff.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/edit.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/file-mutation-queue.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/find.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/grep.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/ls.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/output-accumulator.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/path-utils.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/read.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/render-utils.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/tool-definition-wrapper.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/truncate.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/core/tools/write.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/main.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/index.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/components/diff.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/components/keybinding-hints.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/components/visual-truncate.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/interactive-mode.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/interactive/theme/theme.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/print-mode.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/rpc/jsonl.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-client.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-mode.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-types.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/ansi.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/changelog.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/child-process.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/clipboard-image.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/clipboard-native.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/clipboard.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/exif-orientation.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/frontmatter.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/fs-watch.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/git.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/html.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/image-convert.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/image-resize.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/mime.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/paths.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/photon.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/pi-user-agent.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/shell.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/sleep.js +0 -0
- /package/dist/{pi → sdk}/coding-agent/utils/syntax-highlight.js +0 -0
package/dist/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) {
|
|
@@ -232,7 +232,7 @@ ${chalk.bold("Options:")}
|
|
|
232
232
|
--export <file> Export session file to HTML and exit
|
|
233
233
|
--list-models [search] List available models (with optional fuzzy search)
|
|
234
234
|
--verbose Force verbose startup (overrides quietStartup setting)
|
|
235
|
-
--offline Disable startup network operations (same as
|
|
235
|
+
--offline Disable startup network operations (same as SPECTRAL_OFFLINE=1)
|
|
236
236
|
--help, -h Show this help
|
|
237
237
|
--version, -v Show version number
|
|
238
238
|
|
|
@@ -324,9 +324,9 @@ ${chalk.bold("Environment Variables:")}
|
|
|
324
324
|
${ENV_AGENT_DIR.padEnd(32)} - Config directory (default: ~/${CONFIG_DIR_NAME}/agent)
|
|
325
325
|
${ENV_SESSION_DIR.padEnd(32)} - Session storage directory (overridden by --session-dir)
|
|
326
326
|
PI_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
SPECTRAL_OFFLINE - Disable startup network operations when set to 1/true/yes
|
|
328
|
+
SPECTRAL_TELEMETRY - Override install telemetry when set to 1/true/yes or 0/false/no
|
|
329
|
+
SPECTRAL_SHARE_VIEWER_URL - Base URL for /share command (default: https://spectral.dev/session/)
|
|
330
330
|
|
|
331
331
|
${chalk.bold("Built-in Tool Names:")}
|
|
332
332
|
read - Read file contents
|
|
@@ -336,28 +336,28 @@ export function getBundledInteractiveAssetPath(name) {
|
|
|
336
336
|
return join(getInteractiveAssetsDir(), name);
|
|
337
337
|
}
|
|
338
338
|
const pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
|
339
|
-
const
|
|
339
|
+
const spectralConfigName = pkg.spectralConfig?.name;
|
|
340
340
|
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.
|
|
341
|
+
export const APP_NAME = spectralConfigName || "spectral";
|
|
342
|
+
export const APP_TITLE = spectralConfigName ? APP_NAME : "spectral";
|
|
343
|
+
export const CONFIG_DIR_NAME = pkg.spectralConfig?.configDir || ".spectral";
|
|
344
344
|
export const VERSION = pkg.version || "0.0.0";
|
|
345
|
-
// e.g.,
|
|
345
|
+
// e.g., SPECTRAL_CODING_AGENT_DIR
|
|
346
346
|
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
|
|
347
347
|
export const ENV_SESSION_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_SESSION_DIR`;
|
|
348
348
|
export function expandTildePath(path) {
|
|
349
349
|
return normalizePath(path);
|
|
350
350
|
}
|
|
351
|
-
const DEFAULT_SHARE_VIEWER_URL = "https://
|
|
351
|
+
const DEFAULT_SHARE_VIEWER_URL = "https://spectral.dev/session/";
|
|
352
352
|
/** Get the share viewer URL for a gist ID */
|
|
353
353
|
export function getShareViewerUrl(gistId) {
|
|
354
|
-
const baseUrl = process.env.
|
|
354
|
+
const baseUrl = process.env.SPECTRAL_SHARE_VIEWER_URL || DEFAULT_SHARE_VIEWER_URL;
|
|
355
355
|
return `${baseUrl}#${gistId}`;
|
|
356
356
|
}
|
|
357
357
|
// =============================================================================
|
|
358
|
-
// User Config Paths (~/.
|
|
358
|
+
// User Config Paths (~/.spectral/agent/*)
|
|
359
359
|
// =============================================================================
|
|
360
|
-
/** Get the agent config directory (e.g., ~/.
|
|
360
|
+
/** Get the agent config directory (e.g., ~/.spectral/agent/) */
|
|
361
361
|
export function getAgentDir() {
|
|
362
362
|
const envDir = process.env[ENV_AGENT_DIR];
|
|
363
363
|
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);
|
|
@@ -543,6 +543,157 @@ export function prepareCompaction(pathEntries, settings) {
|
|
|
543
543
|
};
|
|
544
544
|
}
|
|
545
545
|
// ============================================================================
|
|
546
|
+
// Tool Call Deduplication
|
|
547
|
+
// ============================================================================
|
|
548
|
+
/**
|
|
549
|
+
* Tools that always return the same result for the same arguments.
|
|
550
|
+
* Same (name, args) from any point in the session = duplicate.
|
|
551
|
+
* Only the most recent call is kept.
|
|
552
|
+
*/
|
|
553
|
+
const IDEMPOTENT_READ_TOOLS = new Set(["read"]);
|
|
554
|
+
/**
|
|
555
|
+
* Tools whose output may differ between calls with the same arguments.
|
|
556
|
+
* Deduplication requires comparing actual outputs to determine equivalence.
|
|
557
|
+
*/
|
|
558
|
+
const OUTPUT_DEPENDENT_TOOLS = new Set(["bash"]);
|
|
559
|
+
/**
|
|
560
|
+
* Tools that mutate state. Never deduplicated — chronological ordering matters.
|
|
561
|
+
*/
|
|
562
|
+
const MUTABLE_TOOLS = new Set(["edit", "write"]);
|
|
563
|
+
/**
|
|
564
|
+
* Build a stable string key from a Record's sorted keys.
|
|
565
|
+
* Ensures {a:1, b:2} and {b:2, a:1} produce the same key.
|
|
566
|
+
*/
|
|
567
|
+
function stableArgs(args) {
|
|
568
|
+
const sortedKeys = Object.keys(args).sort();
|
|
569
|
+
const sorted = {};
|
|
570
|
+
for (const key of sortedKeys) {
|
|
571
|
+
sorted[key] = args[key];
|
|
572
|
+
}
|
|
573
|
+
return JSON.stringify(sorted);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Compute the deduplication key for a tool call.
|
|
577
|
+
* - Idempotent-read tools: keyed by (name, args) — same args always same result
|
|
578
|
+
* - Output-dependent tools: keyed by (name, args, output) — output comparison needed
|
|
579
|
+
*/
|
|
580
|
+
function toolCallDedupKey(toolName, args, toolCallId, resultMap) {
|
|
581
|
+
const argsKey = stableArgs(args);
|
|
582
|
+
if (OUTPUT_DEPENDENT_TOOLS.has(toolName)) {
|
|
583
|
+
const output = resultMap.get(toolCallId) ?? "";
|
|
584
|
+
return `${toolName}:${argsKey}:${output}`;
|
|
585
|
+
}
|
|
586
|
+
return `${toolName}:${argsKey}`;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Check whether a tool should be excluded from deduplication.
|
|
590
|
+
* Mutating tools and unknown tools are never deduplicated (conservative).
|
|
591
|
+
*/
|
|
592
|
+
function isMutableOrUnknownTool(toolName) {
|
|
593
|
+
if (MUTABLE_TOOLS.has(toolName))
|
|
594
|
+
return true;
|
|
595
|
+
if (IDEMPOTENT_READ_TOOLS.has(toolName))
|
|
596
|
+
return false;
|
|
597
|
+
if (OUTPUT_DEPENDENT_TOOLS.has(toolName))
|
|
598
|
+
return false;
|
|
599
|
+
// Unknown tools: conservative — never deduplicate
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Deduplicate repeated tool calls in a message array by keeping only the
|
|
604
|
+
* most recent occurrence of each (tool, args) pair.
|
|
605
|
+
*
|
|
606
|
+
* Strategy:
|
|
607
|
+
* - **Idempotent-read tools** (read, grep, glob): same (name, args) → keep last only.
|
|
608
|
+
* These tools return the same content for the same arguments.
|
|
609
|
+
* - **Output-dependent tools** (bash): same (name, args, output) → keep last only.
|
|
610
|
+
* Two bash calls with same command but different output are NOT duplicates.
|
|
611
|
+
* - **Mutating tools** (edit, write): never deduplicated. Chronological ordering
|
|
612
|
+
* of mutations matters for correctness.
|
|
613
|
+
* - **Unknown tools** (extensions, MCP, custom): never deduplicated. Conservative
|
|
614
|
+
* by default — only tools in the known sets above participate.
|
|
615
|
+
*
|
|
616
|
+
* Deduplication removes both the ToolCall block from the assistant message
|
|
617
|
+
* and the corresponding ToolResultMessage from the array.
|
|
618
|
+
*
|
|
619
|
+
* Recalculated each time compaction runs — prompt cache is only impacted
|
|
620
|
+
* alongside compression, not on every turn.
|
|
621
|
+
*/
|
|
622
|
+
export function deduplicateToolCalls(messages) {
|
|
623
|
+
if (messages.length === 0)
|
|
624
|
+
return messages;
|
|
625
|
+
// Phase 1: Build result lookup for output-dependent tools
|
|
626
|
+
const resultMap = new Map();
|
|
627
|
+
for (const msg of messages) {
|
|
628
|
+
if (msg.role === "toolResult" && Array.isArray(msg.content)) {
|
|
629
|
+
const text = msg.content
|
|
630
|
+
.filter((c) => c.type === "text")
|
|
631
|
+
.map((c) => c.text)
|
|
632
|
+
.join("\n");
|
|
633
|
+
resultMap.set(msg.toolCallId, text);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// Phase 2: Walk newest→oldest, collect keys. The first encounter
|
|
637
|
+
// (newest) wins; all earlier tool calls with the same key are duplicates.
|
|
638
|
+
const seen = new Map(); // key → toolCallId (keep newest)
|
|
639
|
+
const duplicateIds = new Set();
|
|
640
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
641
|
+
const msg = messages[i];
|
|
642
|
+
if (msg.role !== "assistant")
|
|
643
|
+
continue;
|
|
644
|
+
if (!("content" in msg) || !Array.isArray(msg.content))
|
|
645
|
+
continue;
|
|
646
|
+
// Iterate blocks newest-first: within a single assistant message,
|
|
647
|
+
// the rightmost tool call is the "most recent" one.
|
|
648
|
+
for (let j = msg.content.length - 1; j >= 0; j--) {
|
|
649
|
+
const block = msg.content[j];
|
|
650
|
+
if (typeof block !== "object" || block === null)
|
|
651
|
+
continue;
|
|
652
|
+
if (!("type" in block) || block.type !== "toolCall")
|
|
653
|
+
continue;
|
|
654
|
+
const toolBlock = block;
|
|
655
|
+
if (isMutableOrUnknownTool(toolBlock.name))
|
|
656
|
+
continue;
|
|
657
|
+
const key = toolCallDedupKey(toolBlock.name, toolBlock.arguments, toolBlock.id, resultMap);
|
|
658
|
+
if (seen.has(key)) {
|
|
659
|
+
duplicateIds.add(toolBlock.id);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
seen.set(key, toolBlock.id);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (duplicateIds.size === 0)
|
|
667
|
+
return messages;
|
|
668
|
+
// Phase 3: Filter out duplicate tool results and strip duplicate
|
|
669
|
+
// ToolCall blocks from assistant messages.
|
|
670
|
+
const deduped = [];
|
|
671
|
+
let modified = false;
|
|
672
|
+
for (const msg of messages) {
|
|
673
|
+
if (msg.role === "toolResult" && duplicateIds.has(msg.toolCallId)) {
|
|
674
|
+
modified = true;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (msg.role === "assistant" && "content" in msg && Array.isArray(msg.content)) {
|
|
678
|
+
const originalLength = msg.content.length;
|
|
679
|
+
const filteredContent = msg.content.filter((block) => {
|
|
680
|
+
if (typeof block !== "object" || block === null)
|
|
681
|
+
return true;
|
|
682
|
+
if (!("type" in block) || block.type !== "toolCall")
|
|
683
|
+
return true;
|
|
684
|
+
return !duplicateIds.has(block.id);
|
|
685
|
+
});
|
|
686
|
+
if (filteredContent.length < originalLength) {
|
|
687
|
+
modified = true;
|
|
688
|
+
deduped.push({ ...msg, content: filteredContent });
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
deduped.push(msg);
|
|
693
|
+
}
|
|
694
|
+
return modified ? deduped : messages;
|
|
695
|
+
}
|
|
696
|
+
// ============================================================================
|
|
546
697
|
// Main compaction function
|
|
547
698
|
// ============================================================================
|
|
548
699
|
const TURN_PREFIX_SUMMARIZATION_PROMPT = `This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.
|
|
@@ -568,22 +719,27 @@ Be concise. Focus on what's needed to understand the kept suffix.`;
|
|
|
568
719
|
*/
|
|
569
720
|
export async function compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn) {
|
|
570
721
|
const { firstKeptEntryId, messagesToSummarize, turnPrefixMessages, isSplitTurn, tokensBefore, previousSummary, fileOps, settings, } = preparation;
|
|
722
|
+
// Remove redundant tool calls before summarization.
|
|
723
|
+
// Deduplication is applied here (not in prepareCompaction) so that
|
|
724
|
+
// the session_before_compact extension hook receives raw messages.
|
|
725
|
+
const dedupedMessages = deduplicateToolCalls(messagesToSummarize);
|
|
726
|
+
const dedupedTurnPrefix = deduplicateToolCalls(turnPrefixMessages);
|
|
571
727
|
// Generate summaries (can be parallel if both needed) and merge into one
|
|
572
728
|
let summary;
|
|
573
|
-
if (isSplitTurn &&
|
|
729
|
+
if (isSplitTurn && dedupedTurnPrefix.length > 0) {
|
|
574
730
|
// Generate both summaries in parallel
|
|
575
731
|
const [historyResult, turnPrefixResult] = await Promise.all([
|
|
576
|
-
|
|
577
|
-
? generateSummary(
|
|
732
|
+
dedupedMessages.length > 0
|
|
733
|
+
? generateSummary(dedupedMessages, model, settings.reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn)
|
|
578
734
|
: Promise.resolve("No prior history."),
|
|
579
|
-
generateTurnPrefixSummary(
|
|
735
|
+
generateTurnPrefixSummary(dedupedTurnPrefix, model, settings.reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn),
|
|
580
736
|
]);
|
|
581
737
|
// Merge into single summary
|
|
582
738
|
summary = `${historyResult}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefixResult}`;
|
|
583
739
|
}
|
|
584
740
|
else {
|
|
585
741
|
// Just generate history summary
|
|
586
|
-
summary = await generateSummary(
|
|
742
|
+
summary = await generateSummary(dedupedMessages, model, settings.reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn);
|
|
587
743
|
}
|
|
588
744
|
// Compute file lists and append to summary
|
|
589
745
|
const { readFiles, modifiedFiles } = computeFileLists(fileOps);
|
|
@@ -241,16 +241,23 @@ export class ModelRegistry {
|
|
|
241
241
|
loadError = undefined;
|
|
242
242
|
authStorage;
|
|
243
243
|
modelsJsonPath;
|
|
244
|
-
constructor(authStorage, modelsJsonPath) {
|
|
244
|
+
constructor(authStorage, modelsJsonPath, loadBuiltins) {
|
|
245
245
|
this.authStorage = authStorage;
|
|
246
246
|
this.modelsJsonPath = modelsJsonPath ? normalizePath(modelsJsonPath) : undefined;
|
|
247
|
-
|
|
247
|
+
if (loadBuiltins) {
|
|
248
|
+
this.loadModels();
|
|
249
|
+
}
|
|
250
|
+
// When loadBuiltins is false, we start with an empty model list
|
|
251
|
+
// — the caller is responsible for populating it via registerProvider().
|
|
252
|
+
// This is used in relay/serve mode where all models come from the
|
|
253
|
+
// backend's synthetic proxy providers, and loading the 16K-line
|
|
254
|
+
// models.generated.ts is pure startup waste.
|
|
248
255
|
}
|
|
249
256
|
static create(authStorage, modelsJsonPath = join(getAgentDir(), "models.json")) {
|
|
250
|
-
return new ModelRegistry(authStorage, modelsJsonPath);
|
|
257
|
+
return new ModelRegistry(authStorage, modelsJsonPath, true);
|
|
251
258
|
}
|
|
252
259
|
static inMemory(authStorage) {
|
|
253
|
-
return new ModelRegistry(authStorage, undefined);
|
|
260
|
+
return new ModelRegistry(authStorage, undefined, false);
|
|
254
261
|
}
|
|
255
262
|
/**
|
|
256
263
|
* Reload models from disk (built-in + custom from models.json).
|
|
@@ -33,7 +33,7 @@ const NETWORK_TIMEOUT_MS = 10000;
|
|
|
33
33
|
const UPDATE_CHECK_CONCURRENCY = 4;
|
|
34
34
|
const GIT_UPDATE_CONCURRENCY = 4;
|
|
35
35
|
function isOfflineModeEnabled() {
|
|
36
|
-
const value = process.env.
|
|
36
|
+
const value = process.env.SPECTRAL_OFFLINE;
|
|
37
37
|
if (!value)
|
|
38
38
|
return false;
|
|
39
39
|
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
@@ -1833,9 +1833,9 @@ export class DefaultPackageManager {
|
|
|
1833
1833
|
this.addResource(target, path, metadata, enabled);
|
|
1834
1834
|
}
|
|
1835
1835
|
};
|
|
1836
|
-
// Project extensions from .
|
|
1836
|
+
// Project extensions from .spectral/
|
|
1837
1837
|
addResources("extensions", collectAutoExtensionEntries(projectDirs.extensions), projectMetadata, projectOverrides.extensions, projectBaseDir);
|
|
1838
|
-
// Project skills from .
|
|
1838
|
+
// Project skills from .spectral/
|
|
1839
1839
|
addResources("skills", collectAutoSkillEntries(projectDirs.skills, "pi"), projectMetadata, projectOverrides.skills, projectBaseDir);
|
|
1840
1840
|
// Project skills from .agents/ (each with its own baseDir)
|
|
1841
1841
|
for (const agentsSkillsDir of projectAgentsSkillDirs) {
|
|
@@ -1848,9 +1848,9 @@ export class DefaultPackageManager {
|
|
|
1848
1848
|
}
|
|
1849
1849
|
addResources("prompts", collectAutoPromptEntries(projectDirs.prompts), projectMetadata, projectOverrides.prompts, projectBaseDir);
|
|
1850
1850
|
addResources("themes", collectAutoThemeEntries(projectDirs.themes), projectMetadata, projectOverrides.themes, projectBaseDir);
|
|
1851
|
-
// User extensions from ~/.
|
|
1851
|
+
// User extensions from ~/.spectral/agent/
|
|
1852
1852
|
addResources("extensions", collectAutoExtensionEntries(userDirs.extensions), userMetadata, userOverrides.extensions, globalBaseDir);
|
|
1853
|
-
// User skills from ~/.
|
|
1853
|
+
// User skills from ~/.spectral/agent/
|
|
1854
1854
|
addResources("skills", collectAutoSkillEntries(userDirs.skills, "pi"), userMetadata, userOverrides.skills, globalBaseDir);
|
|
1855
1855
|
// User skills from ~/.agents/ (with its own baseDir)
|
|
1856
1856
|
const userAgentsBaseDir = dirname(userAgentsSkillsDir);
|
|
@@ -31,7 +31,7 @@ function getAttributionHeaders(model, settingsManager) {
|
|
|
31
31
|
}
|
|
32
32
|
if (model.provider === "openrouter" || model.baseUrl.includes("openrouter.ai")) {
|
|
33
33
|
return {
|
|
34
|
-
"HTTP-Referer": "https://
|
|
34
|
+
"HTTP-Referer": "https://spectral.dev",
|
|
35
35
|
"X-OpenRouter-Title": "pi",
|
|
36
36
|
"X-OpenRouter-Categories": "cli-agent",
|
|
37
37
|
};
|
|
@@ -208,7 +208,7 @@ export function buildSessionContext(entries, leafId, byId) {
|
|
|
208
208
|
}
|
|
209
209
|
/**
|
|
210
210
|
* Compute the default session directory for a cwd.
|
|
211
|
-
* Encodes cwd into a safe directory name under ~/.
|
|
211
|
+
* Encodes cwd into a safe directory name under ~/.spectral/agent/sessions/.
|
|
212
212
|
*/
|
|
213
213
|
export function getDefaultSessionDir(cwd, agentDir = getDefaultAgentDir()) {
|
|
214
214
|
const resolvedCwd = resolvePath(cwd);
|
|
@@ -1002,7 +1002,7 @@ export class SessionManager {
|
|
|
1002
1002
|
/**
|
|
1003
1003
|
* Create a new session.
|
|
1004
1004
|
* @param cwd Working directory (stored in session header)
|
|
1005
|
-
* @param sessionDir Optional session directory. If omitted, uses default (~/.
|
|
1005
|
+
* @param sessionDir Optional session directory. If omitted, uses default (~/.spectral/agent/sessions/<encoded-cwd>/).
|
|
1006
1006
|
*/
|
|
1007
1007
|
static create(cwd, sessionDir) {
|
|
1008
1008
|
const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
|
|
@@ -1027,7 +1027,7 @@ export class SessionManager {
|
|
|
1027
1027
|
/**
|
|
1028
1028
|
* Continue the most recent session, or create new if none.
|
|
1029
1029
|
* @param cwd Working directory
|
|
1030
|
-
* @param sessionDir Optional session directory. If omitted, uses default (~/.
|
|
1030
|
+
* @param sessionDir Optional session directory. If omitted, uses default (~/.spectral/agent/sessions/<encoded-cwd>/).
|
|
1031
1031
|
*/
|
|
1032
1032
|
static continueRecent(cwd, sessionDir) {
|
|
1033
1033
|
const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
|
|
@@ -1089,7 +1089,7 @@ export class SessionManager {
|
|
|
1089
1089
|
/**
|
|
1090
1090
|
* List all sessions for a directory.
|
|
1091
1091
|
* @param cwd Working directory (used to compute default session directory)
|
|
1092
|
-
* @param sessionDir Optional session directory. If omitted, uses default (~/.
|
|
1092
|
+
* @param sessionDir Optional session directory. If omitted, uses default (~/.spectral/agent/sessions/<encoded-cwd>/).
|
|
1093
1093
|
* @param onProgress Optional callback for progress updates (loaded, total)
|
|
1094
1094
|
*/
|
|
1095
1095
|
static async list(cwd, sessionDir, onProgress) {
|
|
@@ -3,6 +3,6 @@ function isTruthyEnvFlag(value) {
|
|
|
3
3
|
return false;
|
|
4
4
|
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
5
5
|
}
|
|
6
|
-
export function isInstallTelemetryEnabled(settingsManager, telemetryEnv = process.env.
|
|
6
|
+
export function isInstallTelemetryEnabled(settingsManager, telemetryEnv = process.env.SPECTRAL_TELEMETRY) {
|
|
7
7
|
return telemetryEnv !== undefined ? isTruthyEnvFlag(telemetryEnv) : settingsManager.getEnableInstallTelemetry();
|
|
8
8
|
}
|
|
@@ -64,10 +64,10 @@ export function migrateAuthToAuthJson() {
|
|
|
64
64
|
return providers;
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
|
-
* Migrate sessions from ~/.
|
|
67
|
+
* Migrate sessions from ~/.spectral/agent/*.jsonl to proper session directories.
|
|
68
68
|
*
|
|
69
|
-
* Bug in v0.30.0: Sessions were saved to ~/.
|
|
70
|
-
* ~/.
|
|
69
|
+
* Bug in v0.30.0: Sessions were saved to ~/.spectral/agent/ instead of
|
|
70
|
+
* ~/.spectral/agent/sessions/<encoded-cwd>/. This migration moves them
|
|
71
71
|
* to the correct location based on the cwd in their session header.
|
|
72
72
|
*
|
|
73
73
|
* See: https://github.com/earendil-works/pi-mono/issues/320
|
|
@@ -10,7 +10,7 @@ const TOOLS_DIR = getBinDir();
|
|
|
10
10
|
const NETWORK_TIMEOUT_MS = 10_000;
|
|
11
11
|
const DOWNLOAD_TIMEOUT_MS = 120_000;
|
|
12
12
|
function isOfflineModeEnabled() {
|
|
13
|
-
const value = process.env.
|
|
13
|
+
const value = process.env.SPECTRAL_OFFLINE;
|
|
14
14
|
if (!value)
|
|
15
15
|
return false;
|
|
16
16
|
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getPiUserAgent } from "./pi-user-agent.js";
|
|
2
|
-
const LATEST_VERSION_URL = "https://
|
|
2
|
+
const LATEST_VERSION_URL = "https://spectral.dev/api/latest-version";
|
|
3
3
|
const DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;
|
|
4
4
|
function parsePackageVersion(version) {
|
|
5
5
|
const match = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
|
|
@@ -41,7 +41,7 @@ export function isNewerPackageVersion(candidateVersion, currentVersion) {
|
|
|
41
41
|
return candidateVersion.trim() !== currentVersion.trim();
|
|
42
42
|
}
|
|
43
43
|
export async function getLatestPiRelease(currentVersion, options = {}) {
|
|
44
|
-
if (process.env.
|
|
44
|
+
if (process.env.SPECTRAL_SKIP_VERSION_CHECK || process.env.SPECTRAL_OFFLINE)
|
|
45
45
|
return undefined;
|
|
46
46
|
const response = await fetch(LATEST_VERSION_URL, {
|
|
47
47
|
headers: {
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { copyFileSync, existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, relative, resolve, toNamespacedPath } from "node:path";
|
|
4
4
|
import { getCwdRelativePath } from "./paths.js";
|
|
5
|
-
const QUARANTINE_DIR_NAME = ".
|
|
5
|
+
const QUARANTINE_DIR_NAME = ".spectral-native-quarantine";
|
|
6
6
|
function normalizePath(path) {
|
|
7
7
|
return toNamespacedPath(resolve(path));
|
|
8
8
|
}
|