@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
|
@@ -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);
|
|
@@ -6,45 +6,15 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import * as _bundledPiAgentCore from "../../../agent-core/index.js";
|
|
10
|
-
import * as _bundledPiAi from "../../../ai/index.js";
|
|
11
|
-
import * as _bundledPiAiOauth from "../../../ai/oauth.js";
|
|
12
9
|
import { createJiti } from "@mariozechner/jiti";
|
|
13
|
-
|
|
14
|
-
// These MUST be static so Bun bundles them into the compiled binary.
|
|
15
|
-
// The virtualModules option then makes them available to extensions.
|
|
16
|
-
import * as _bundledTypebox from "typebox";
|
|
17
|
-
import * as _bundledTypeboxCompile from "typebox/compile";
|
|
18
|
-
import * as _bundledTypeboxValue from "typebox/value";
|
|
19
|
-
import { CONFIG_DIR_NAME, getAgentDir, isBunBinary } from "../../config.js";
|
|
20
|
-
// NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,
|
|
21
|
-
// avoiding a circular dependency. Extensions can import from ../../index.ts.
|
|
22
|
-
import * as _bundledPiCodingAgent from "../../index.js";
|
|
10
|
+
import { CONFIG_DIR_NAME, getAgentDir } from "../../config.js";
|
|
23
11
|
import { resolvePath } from "../../utils/paths.js";
|
|
24
12
|
import { createEventBus } from "../event-bus.js";
|
|
25
13
|
import { execCommand } from "../exec.js";
|
|
26
14
|
import { createSyntheticSourceInfo } from "../source-info.js";
|
|
27
|
-
/** Modules available to extensions via virtualModules (for compiled Bun binary) */
|
|
28
|
-
const VIRTUAL_MODULES = {
|
|
29
|
-
typebox: _bundledTypebox,
|
|
30
|
-
"typebox/compile": _bundledTypeboxCompile,
|
|
31
|
-
"typebox/value": _bundledTypeboxValue,
|
|
32
|
-
"@sinclair/typebox": _bundledTypebox,
|
|
33
|
-
"@sinclair/typebox/compile": _bundledTypeboxCompile,
|
|
34
|
-
"@sinclair/typebox/value": _bundledTypeboxValue,
|
|
35
|
-
"../../../agent-core/index.ts": _bundledPiAgentCore,
|
|
36
|
-
"../../../ai/index.ts": _bundledPiAi,
|
|
37
|
-
"../../../ai/oauth.ts": _bundledPiAiOauth,
|
|
38
|
-
"../../index.ts": _bundledPiCodingAgent,
|
|
39
|
-
"@mariozechner/pi-agent": _bundledPiAgentCore,
|
|
40
|
-
"@mariozechner/pi-ai": _bundledPiAi,
|
|
41
|
-
"@mariozechner/pi-ai/oauth": _bundledPiAiOauth,
|
|
42
|
-
"@mariozechner/pi-coding-agent": _bundledPiCodingAgent,
|
|
43
|
-
};
|
|
44
15
|
const require = createRequire(import.meta.url);
|
|
45
16
|
/**
|
|
46
17
|
* Get aliases for jiti (used in Node.js/development mode).
|
|
47
|
-
* In Bun binary mode, virtualModules is used instead.
|
|
48
18
|
*/
|
|
49
19
|
let _aliases = null;
|
|
50
20
|
function getAliases() {
|
|
@@ -263,10 +233,7 @@ function createExtensionAPI(extension, runtime, cwd, eventBus) {
|
|
|
263
233
|
async function loadExtensionModule(extensionPath) {
|
|
264
234
|
const jiti = createJiti(import.meta.url, {
|
|
265
235
|
moduleCache: false,
|
|
266
|
-
|
|
267
|
-
// Also disable tryNative so jiti handles ALL imports (not just the entry point)
|
|
268
|
-
// In Node.js/dev: use aliases to resolve to node_modules paths
|
|
269
|
-
...(isBunBinary ? { virtualModules: VIRTUAL_MODULES, tryNative: false } : { alias: getAliases() }),
|
|
236
|
+
alias: getAliases(),
|
|
270
237
|
});
|
|
271
238
|
const module = await jiti.import(extensionPath, { default: true });
|
|
272
239
|
const factory = module;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Extension runner - executes extensions and manages their lifecycle.
|
|
3
3
|
*/
|
|
4
|
-
import { theme } from "../../modes/interactive/theme/theme.js";
|
|
5
4
|
// Extension shortcuts compete with canonical keybinding ids from keybindings.json.
|
|
6
5
|
// Only editor-global shortcuts are reserved here. Picker-specific bindings are not.
|
|
7
6
|
const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
|
|
@@ -80,7 +79,7 @@ const noOpUIContext = {
|
|
|
80
79
|
setEditorComponent: () => { },
|
|
81
80
|
getEditorComponent: () => undefined,
|
|
82
81
|
get theme() {
|
|
83
|
-
return
|
|
82
|
+
return {};
|
|
84
83
|
},
|
|
85
84
|
getAllThemes: () => [],
|
|
86
85
|
getTheme: () => undefined,
|
|
@@ -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).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility for validating thinking level strings.
|
|
3
|
+
* Extracted from cli/args.ts (TUI-only, now deleted).
|
|
4
|
+
*/
|
|
5
|
+
const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
6
|
+
export function isValidThinkingLevel(level) {
|
|
7
|
+
return VALID_THINKING_LEVELS.includes(level);
|
|
8
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { modelsAreEqual } from "../../ai/index.js";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { minimatch } from "minimatch";
|
|
7
|
-
import { isValidThinkingLevel } from "
|
|
7
|
+
import { isValidThinkingLevel } from "./model-resolver-utils.js";
|
|
8
8
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
9
9
|
/** Default model IDs for each known provider */
|
|
10
10
|
export const defaultModelPerProvider = {
|
|
@@ -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);
|
|
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
2
2
|
import { join, resolve, sep } from "node:path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { CONFIG_DIR_NAME } from "../config.js";
|
|
5
|
-
import { loadThemeFromPath } from "
|
|
5
|
+
import { loadThemeFromPath } from "./theme.js";
|
|
6
6
|
import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.js";
|
|
7
7
|
import { createEventBus } from "./event-bus.js";
|
|
8
8
|
import { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from "./extensions/loader.js";
|
|
@@ -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) {
|
|
@@ -193,9 +193,7 @@ export class SettingsManager {
|
|
|
193
193
|
settings.skills !== null &&
|
|
194
194
|
!Array.isArray(settings.skills)) {
|
|
195
195
|
const skillsSettings = settings.skills;
|
|
196
|
-
|
|
197
|
-
settings.enableSkillCommands = skillsSettings.enableSkillCommands;
|
|
198
|
-
}
|
|
196
|
+
// enableSkillCommands is deprecated — only migrate the custom directories
|
|
199
197
|
if (Array.isArray(skillsSettings.customDirectories) && skillsSettings.customDirectories.length > 0) {
|
|
200
198
|
settings.skills = skillsSettings.customDirectories;
|
|
201
199
|
}
|
|
@@ -367,14 +365,6 @@ export class SettingsManager {
|
|
|
367
365
|
this.errors = [];
|
|
368
366
|
return drained;
|
|
369
367
|
}
|
|
370
|
-
getLastChangelogVersion() {
|
|
371
|
-
return this.settings.lastChangelogVersion;
|
|
372
|
-
}
|
|
373
|
-
setLastChangelogVersion(version) {
|
|
374
|
-
this.globalSettings.lastChangelogVersion = version;
|
|
375
|
-
this.markModified("lastChangelogVersion");
|
|
376
|
-
this.save();
|
|
377
|
-
}
|
|
378
368
|
getSessionDir() {
|
|
379
369
|
const sessionDir = this.settings.sessionDir;
|
|
380
370
|
return sessionDir ? normalizePath(sessionDir) : sessionDir;
|
|
@@ -438,14 +428,6 @@ export class SettingsManager {
|
|
|
438
428
|
this.markModified("followUpMode");
|
|
439
429
|
this.save();
|
|
440
430
|
}
|
|
441
|
-
getTheme() {
|
|
442
|
-
return this.settings.theme;
|
|
443
|
-
}
|
|
444
|
-
setTheme(theme) {
|
|
445
|
-
this.globalSettings.theme = theme;
|
|
446
|
-
this.markModified("theme");
|
|
447
|
-
this.save();
|
|
448
|
-
}
|
|
449
431
|
getDefaultThinkingLevel() {
|
|
450
432
|
return this.settings.defaultThinkingLevel;
|
|
451
433
|
}
|
|
@@ -539,14 +521,6 @@ export class SettingsManager {
|
|
|
539
521
|
maxRetryDelayMs: this.settings.retry?.provider?.maxRetryDelayMs ?? 60000,
|
|
540
522
|
};
|
|
541
523
|
}
|
|
542
|
-
getHideThinkingBlock() {
|
|
543
|
-
return this.settings.hideThinkingBlock ?? false;
|
|
544
|
-
}
|
|
545
|
-
setHideThinkingBlock(hide) {
|
|
546
|
-
this.globalSettings.hideThinkingBlock = hide;
|
|
547
|
-
this.markModified("hideThinkingBlock");
|
|
548
|
-
this.save();
|
|
549
|
-
}
|
|
550
524
|
getShellPath() {
|
|
551
525
|
return this.settings.shellPath;
|
|
552
526
|
}
|
|
@@ -555,14 +529,6 @@ export class SettingsManager {
|
|
|
555
529
|
this.markModified("shellPath");
|
|
556
530
|
this.save();
|
|
557
531
|
}
|
|
558
|
-
getQuietStartup() {
|
|
559
|
-
return this.settings.quietStartup ?? false;
|
|
560
|
-
}
|
|
561
|
-
setQuietStartup(quiet) {
|
|
562
|
-
this.globalSettings.quietStartup = quiet;
|
|
563
|
-
this.markModified("quietStartup");
|
|
564
|
-
this.save();
|
|
565
|
-
}
|
|
566
532
|
getShellCommandPrefix() {
|
|
567
533
|
return this.settings.shellCommandPrefix;
|
|
568
534
|
}
|
|
@@ -579,14 +545,6 @@ export class SettingsManager {
|
|
|
579
545
|
this.markModified("npmCommand");
|
|
580
546
|
this.save();
|
|
581
547
|
}
|
|
582
|
-
getCollapseChangelog() {
|
|
583
|
-
return this.settings.collapseChangelog ?? false;
|
|
584
|
-
}
|
|
585
|
-
setCollapseChangelog(collapse) {
|
|
586
|
-
this.globalSettings.collapseChangelog = collapse;
|
|
587
|
-
this.markModified("collapseChangelog");
|
|
588
|
-
this.save();
|
|
589
|
-
}
|
|
590
548
|
getEnableInstallTelemetry() {
|
|
591
549
|
return this.settings.enableInstallTelemetry ?? true;
|
|
592
550
|
}
|
|
@@ -651,83 +609,9 @@ export class SettingsManager {
|
|
|
651
609
|
this.markProjectModified("prompts");
|
|
652
610
|
this.saveProjectSettings(projectSettings);
|
|
653
611
|
}
|
|
654
|
-
getThemePaths() {
|
|
655
|
-
return [...(this.settings.themes ?? [])];
|
|
656
|
-
}
|
|
657
|
-
setThemePaths(paths) {
|
|
658
|
-
this.globalSettings.themes = paths;
|
|
659
|
-
this.markModified("themes");
|
|
660
|
-
this.save();
|
|
661
|
-
}
|
|
662
|
-
setProjectThemePaths(paths) {
|
|
663
|
-
const projectSettings = structuredClone(this.projectSettings);
|
|
664
|
-
projectSettings.themes = paths;
|
|
665
|
-
this.markProjectModified("themes");
|
|
666
|
-
this.saveProjectSettings(projectSettings);
|
|
667
|
-
}
|
|
668
|
-
getEnableSkillCommands() {
|
|
669
|
-
return this.settings.enableSkillCommands ?? true;
|
|
670
|
-
}
|
|
671
|
-
setEnableSkillCommands(enabled) {
|
|
672
|
-
this.globalSettings.enableSkillCommands = enabled;
|
|
673
|
-
this.markModified("enableSkillCommands");
|
|
674
|
-
this.save();
|
|
675
|
-
}
|
|
676
612
|
getThinkingBudgets() {
|
|
677
613
|
return this.settings.thinkingBudgets;
|
|
678
614
|
}
|
|
679
|
-
getShowImages() {
|
|
680
|
-
return this.settings.terminal?.showImages ?? true;
|
|
681
|
-
}
|
|
682
|
-
setShowImages(show) {
|
|
683
|
-
if (!this.globalSettings.terminal) {
|
|
684
|
-
this.globalSettings.terminal = {};
|
|
685
|
-
}
|
|
686
|
-
this.globalSettings.terminal.showImages = show;
|
|
687
|
-
this.markModified("terminal", "showImages");
|
|
688
|
-
this.save();
|
|
689
|
-
}
|
|
690
|
-
getImageWidthCells() {
|
|
691
|
-
const width = this.settings.terminal?.imageWidthCells;
|
|
692
|
-
if (typeof width !== "number" || !Number.isFinite(width)) {
|
|
693
|
-
return 60;
|
|
694
|
-
}
|
|
695
|
-
return Math.max(1, Math.floor(width));
|
|
696
|
-
}
|
|
697
|
-
setImageWidthCells(width) {
|
|
698
|
-
if (!this.globalSettings.terminal) {
|
|
699
|
-
this.globalSettings.terminal = {};
|
|
700
|
-
}
|
|
701
|
-
this.globalSettings.terminal.imageWidthCells = Math.max(1, Math.floor(width));
|
|
702
|
-
this.markModified("terminal", "imageWidthCells");
|
|
703
|
-
this.save();
|
|
704
|
-
}
|
|
705
|
-
getClearOnShrink() {
|
|
706
|
-
// Settings takes precedence, then env var, then default false
|
|
707
|
-
if (this.settings.terminal?.clearOnShrink !== undefined) {
|
|
708
|
-
return this.settings.terminal.clearOnShrink;
|
|
709
|
-
}
|
|
710
|
-
return process.env.PI_CLEAR_ON_SHRINK === "1";
|
|
711
|
-
}
|
|
712
|
-
setClearOnShrink(enabled) {
|
|
713
|
-
if (!this.globalSettings.terminal) {
|
|
714
|
-
this.globalSettings.terminal = {};
|
|
715
|
-
}
|
|
716
|
-
this.globalSettings.terminal.clearOnShrink = enabled;
|
|
717
|
-
this.markModified("terminal", "clearOnShrink");
|
|
718
|
-
this.save();
|
|
719
|
-
}
|
|
720
|
-
getShowTerminalProgress() {
|
|
721
|
-
return this.settings.terminal?.showTerminalProgress ?? false;
|
|
722
|
-
}
|
|
723
|
-
setShowTerminalProgress(enabled) {
|
|
724
|
-
if (!this.globalSettings.terminal) {
|
|
725
|
-
this.globalSettings.terminal = {};
|
|
726
|
-
}
|
|
727
|
-
this.globalSettings.terminal.showTerminalProgress = enabled;
|
|
728
|
-
this.markModified("terminal", "showTerminalProgress");
|
|
729
|
-
this.save();
|
|
730
|
-
}
|
|
731
615
|
getImageAutoResize() {
|
|
732
616
|
return this.settings.images?.autoResize ?? true;
|
|
733
617
|
}
|
|
@@ -758,57 +642,4 @@ export class SettingsManager {
|
|
|
758
642
|
this.markModified("enabledModels");
|
|
759
643
|
this.save();
|
|
760
644
|
}
|
|
761
|
-
getDoubleEscapeAction() {
|
|
762
|
-
return this.settings.doubleEscapeAction ?? "tree";
|
|
763
|
-
}
|
|
764
|
-
setDoubleEscapeAction(action) {
|
|
765
|
-
this.globalSettings.doubleEscapeAction = action;
|
|
766
|
-
this.markModified("doubleEscapeAction");
|
|
767
|
-
this.save();
|
|
768
|
-
}
|
|
769
|
-
getTreeFilterMode() {
|
|
770
|
-
const mode = this.settings.treeFilterMode;
|
|
771
|
-
const valid = ["default", "no-tools", "user-only", "labeled-only", "all"];
|
|
772
|
-
return mode && valid.includes(mode) ? mode : "default";
|
|
773
|
-
}
|
|
774
|
-
setTreeFilterMode(mode) {
|
|
775
|
-
this.globalSettings.treeFilterMode = mode;
|
|
776
|
-
this.markModified("treeFilterMode");
|
|
777
|
-
this.save();
|
|
778
|
-
}
|
|
779
|
-
getShowHardwareCursor() {
|
|
780
|
-
return this.settings.showHardwareCursor ?? process.env.PI_HARDWARE_CURSOR === "1";
|
|
781
|
-
}
|
|
782
|
-
setShowHardwareCursor(enabled) {
|
|
783
|
-
this.globalSettings.showHardwareCursor = enabled;
|
|
784
|
-
this.markModified("showHardwareCursor");
|
|
785
|
-
this.save();
|
|
786
|
-
}
|
|
787
|
-
getEditorPaddingX() {
|
|
788
|
-
return this.settings.editorPaddingX ?? 0;
|
|
789
|
-
}
|
|
790
|
-
setEditorPaddingX(padding) {
|
|
791
|
-
this.globalSettings.editorPaddingX = Math.max(0, Math.min(3, Math.floor(padding)));
|
|
792
|
-
this.markModified("editorPaddingX");
|
|
793
|
-
this.save();
|
|
794
|
-
}
|
|
795
|
-
getAutocompleteMaxVisible() {
|
|
796
|
-
return this.settings.autocompleteMaxVisible ?? 5;
|
|
797
|
-
}
|
|
798
|
-
setAutocompleteMaxVisible(maxVisible) {
|
|
799
|
-
this.globalSettings.autocompleteMaxVisible = Math.max(3, Math.min(20, Math.floor(maxVisible)));
|
|
800
|
-
this.markModified("autocompleteMaxVisible");
|
|
801
|
-
this.save();
|
|
802
|
-
}
|
|
803
|
-
getCodeBlockIndent() {
|
|
804
|
-
return this.settings.markdown?.codeBlockIndent ?? " ";
|
|
805
|
-
}
|
|
806
|
-
getWarnings() {
|
|
807
|
-
return { ...(this.settings.warnings ?? {}) };
|
|
808
|
-
}
|
|
809
|
-
setWarnings(warnings) {
|
|
810
|
-
this.globalSettings.warnings = { ...warnings };
|
|
811
|
-
this.markModified("warnings");
|
|
812
|
-
this.save();
|
|
813
|
-
}
|
|
814
645
|
}
|
|
@@ -78,10 +78,12 @@ export function buildSystemPrompt(options) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
// Always include these
|
|
81
|
+
addGuideline("Never assign coding or implementation work to people — you write the code. Non-coding tasks (review, design, ops, coordination) can be assigned");
|
|
82
|
+
addGuideline("When estimating effort, frame it as a feature scope (e.g. 'this is a ~20-hour coding enhancement') rather than using traditional time measures. Tie every estimate to a clearly defined feature boundary");
|
|
81
83
|
addGuideline("Be concise in your responses");
|
|
82
84
|
addGuideline("Show file paths clearly when working with files");
|
|
83
85
|
const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
|
|
84
|
-
let prompt = `You are an expert coding
|
|
86
|
+
let prompt = `You are an expert coding agent operating inside pi, a coding agent harness. You read files, execute commands, edit code, and write new files. All coding and implementation work is yours — never delegate it. Non-coding tasks (review, design decisions, ops) may be assigned to people. When discussing effort, frame each enhancement around a ~20-hour coding scope tied to a concrete feature boundary, avoiding calendar-day or week-based estimates.
|
|
85
87
|
|
|
86
88
|
Available tools:
|
|
87
89
|
${toolsList}
|
|
@@ -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
|
}
|