@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.
Files changed (197) hide show
  1. package/dist/agent/agents.js +4 -4
  2. package/dist/agent/index.js +8 -8
  3. package/dist/cli.js +1 -1
  4. package/dist/commands/serve.js +1 -1
  5. package/dist/extensions/kanban-bridge.js +668 -0
  6. package/dist/extensions/spectral-vision-fallback.js +84 -46
  7. package/dist/mcp/agent-dir.js +1 -1
  8. package/dist/mcp/config.js +3 -3
  9. package/dist/mcp/init.js +1 -9
  10. package/dist/mcp/sampling-handler.js +1 -1
  11. package/dist/mcp/server-manager.js +5 -1
  12. package/dist/memory/commands/status.js +1 -1
  13. package/dist/memory/compaction.js +2 -2
  14. package/dist/memory/config.js +3 -3
  15. package/dist/memory/debug-log.js +1 -1
  16. package/dist/memory/index.js +2 -0
  17. package/dist/memory/observer.js +2 -2
  18. package/dist/memory/tokens.js +1 -1
  19. package/dist/memory/tools/read-project-observations.js +2 -2
  20. package/dist/memory/tools/recall-observation.js +2 -2
  21. package/dist/memory/tools/write-project-observation.js +60 -0
  22. package/dist/relay/auto-research.js +57 -23
  23. package/dist/relay/dispatcher.js +28 -2
  24. package/dist/relay/models-fetch.js +2 -2
  25. package/dist/{pi → sdk}/ai/env-api-keys.js +9 -49
  26. package/dist/{pi → sdk}/ai/utils/oauth/anthropic.js +1 -1
  27. package/dist/{pi → sdk}/ai/utils/oauth/openai-codex.js +1 -1
  28. package/dist/{pi → sdk}/coding-agent/config.js +11 -78
  29. package/dist/{pi → sdk}/coding-agent/core/agent-session.js +2 -0
  30. package/dist/{pi → sdk}/coding-agent/core/compaction/compaction.js +161 -5
  31. package/dist/{pi → sdk}/coding-agent/core/extensions/loader.js +2 -35
  32. package/dist/{pi → sdk}/coding-agent/core/extensions/runner.js +1 -2
  33. package/dist/{pi → sdk}/coding-agent/core/model-registry.js +11 -4
  34. package/dist/sdk/coding-agent/core/model-resolver-utils.js +8 -0
  35. package/dist/{pi → sdk}/coding-agent/core/model-resolver.js +1 -1
  36. package/dist/{pi → sdk}/coding-agent/core/package-manager.js +5 -5
  37. package/dist/{pi → sdk}/coding-agent/core/resource-loader.js +1 -1
  38. package/dist/{pi → sdk}/coding-agent/core/sdk.js +1 -1
  39. package/dist/{pi → sdk}/coding-agent/core/session-manager.js +4 -4
  40. package/dist/{pi → sdk}/coding-agent/core/settings-manager.js +1 -170
  41. package/dist/{pi → sdk}/coding-agent/core/system-prompt.js +3 -1
  42. package/dist/{pi → sdk}/coding-agent/core/telemetry.js +1 -1
  43. package/dist/sdk/coding-agent/core/theme.js +202 -0
  44. package/dist/{pi → sdk}/coding-agent/core/tools/bash.js +17 -18
  45. package/dist/{pi → sdk}/coding-agent/core/tools/edit.js +7 -8
  46. package/dist/{pi → sdk}/coding-agent/core/tools/find.js +9 -13
  47. package/dist/{pi → sdk}/coding-agent/core/tools/grep.js +10 -14
  48. package/dist/{pi → sdk}/coding-agent/core/tools/ls.js +9 -10
  49. package/dist/{pi → sdk}/coding-agent/core/tools/read.js +15 -25
  50. package/dist/{pi/coding-agent/modes/interactive/components/diff.js → sdk/coding-agent/core/tools/render-diff.js} +18 -31
  51. package/dist/{pi → sdk}/coding-agent/core/tools/write.js +10 -11
  52. package/dist/{pi → sdk}/coding-agent/index.js +7 -5
  53. package/dist/{pi → sdk}/coding-agent/migrations.js +3 -3
  54. package/dist/{pi → sdk}/coding-agent/modes/index.js +0 -1
  55. package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-mode.js +2 -2
  56. package/dist/{pi → sdk}/coding-agent/utils/photon.js +2 -10
  57. package/dist/sdk/coding-agent/utils/pi-user-agent.js +3 -0
  58. package/dist/{pi → sdk}/coding-agent/utils/tools-manager.js +1 -1
  59. package/dist/{pi → sdk}/coding-agent/utils/version-check.js +2 -2
  60. package/dist/{pi → sdk}/coding-agent/utils/windows-self-update.js +1 -1
  61. package/dist/server/{pi-bridge.js → agent-bridge.js} +114 -97
  62. package/dist/server/handlers/sessions.js +21 -0
  63. package/dist/server/session-stream.js +5 -5
  64. package/package.json +6 -3
  65. package/dist/pi/coding-agent/bun/cli.js +0 -7
  66. package/dist/pi/coding-agent/bun/restore-sandbox-env.js +0 -31
  67. package/dist/pi/coding-agent/cli/args.js +0 -340
  68. package/dist/pi/coding-agent/cli/file-processor.js +0 -82
  69. package/dist/pi/coding-agent/cli/initial-message.js +0 -21
  70. package/dist/pi/coding-agent/core/footer-data-provider.js +0 -309
  71. package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +0 -35
  72. package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +0 -26
  73. package/dist/pi/coding-agent/modes/interactive/interactive-mode.js +0 -3
  74. package/dist/pi/coding-agent/modes/interactive/theme/theme.js +0 -1022
  75. package/dist/pi/coding-agent/utils/pi-user-agent.js +0 -4
  76. /package/dist/{pi → sdk}/agent-core/agent-loop.js +0 -0
  77. /package/dist/{pi → sdk}/agent-core/agent.js +0 -0
  78. /package/dist/{pi → sdk}/agent-core/harness/agent-harness.js +0 -0
  79. /package/dist/{pi → sdk}/agent-core/harness/compaction/branch-summarization.js +0 -0
  80. /package/dist/{pi → sdk}/agent-core/harness/compaction/compaction.js +0 -0
  81. /package/dist/{pi → sdk}/agent-core/harness/compaction/utils.js +0 -0
  82. /package/dist/{pi → sdk}/agent-core/harness/env/nodejs.js +0 -0
  83. /package/dist/{pi → sdk}/agent-core/harness/messages.js +0 -0
  84. /package/dist/{pi → sdk}/agent-core/harness/prompt-templates.js +0 -0
  85. /package/dist/{pi → sdk}/agent-core/harness/session/jsonl-repo.js +0 -0
  86. /package/dist/{pi → sdk}/agent-core/harness/session/jsonl-storage.js +0 -0
  87. /package/dist/{pi → sdk}/agent-core/harness/session/memory-repo.js +0 -0
  88. /package/dist/{pi → sdk}/agent-core/harness/session/memory-storage.js +0 -0
  89. /package/dist/{pi → sdk}/agent-core/harness/session/repo-utils.js +0 -0
  90. /package/dist/{pi → sdk}/agent-core/harness/session/session.js +0 -0
  91. /package/dist/{pi → sdk}/agent-core/harness/session/uuid.js +0 -0
  92. /package/dist/{pi → sdk}/agent-core/harness/skills.js +0 -0
  93. /package/dist/{pi → sdk}/agent-core/harness/system-prompt.js +0 -0
  94. /package/dist/{pi → sdk}/agent-core/harness/types.js +0 -0
  95. /package/dist/{pi → sdk}/agent-core/harness/utils/shell-output.js +0 -0
  96. /package/dist/{pi → sdk}/agent-core/harness/utils/truncate.js +0 -0
  97. /package/dist/{pi → sdk}/agent-core/index.js +0 -0
  98. /package/dist/{pi → sdk}/agent-core/node.js +0 -0
  99. /package/dist/{pi → sdk}/agent-core/proxy.js +0 -0
  100. /package/dist/{pi → sdk}/agent-core/types.js +0 -0
  101. /package/dist/{pi → sdk}/ai/api-registry.js +0 -0
  102. /package/dist/{pi → sdk}/ai/cli.js +0 -0
  103. /package/dist/{pi → sdk}/ai/image-models.generated.js +0 -0
  104. /package/dist/{pi → sdk}/ai/image-models.js +0 -0
  105. /package/dist/{pi → sdk}/ai/images-api-registry.js +0 -0
  106. /package/dist/{pi → sdk}/ai/images.js +0 -0
  107. /package/dist/{pi → sdk}/ai/index.js +0 -0
  108. /package/dist/{pi → sdk}/ai/models.generated.js +0 -0
  109. /package/dist/{pi → sdk}/ai/models.js +0 -0
  110. /package/dist/{pi → sdk}/ai/oauth.js +0 -0
  111. /package/dist/{pi → sdk}/ai/providers/anthropic.js +0 -0
  112. /package/dist/{pi → sdk}/ai/providers/faux.js +0 -0
  113. /package/dist/{pi → sdk}/ai/providers/github-copilot-headers.js +0 -0
  114. /package/dist/{pi → sdk}/ai/providers/openai-completions.js +0 -0
  115. /package/dist/{pi → sdk}/ai/providers/openai-prompt-cache.js +0 -0
  116. /package/dist/{pi → sdk}/ai/providers/register-builtins.js +0 -0
  117. /package/dist/{pi → sdk}/ai/providers/simple-options.js +0 -0
  118. /package/dist/{pi → sdk}/ai/providers/transform-messages.js +0 -0
  119. /package/dist/{pi → sdk}/ai/session-resources.js +0 -0
  120. /package/dist/{pi → sdk}/ai/stream.js +0 -0
  121. /package/dist/{pi → sdk}/ai/types.js +0 -0
  122. /package/dist/{pi → sdk}/ai/utils/diagnostics.js +0 -0
  123. /package/dist/{pi → sdk}/ai/utils/event-stream.js +0 -0
  124. /package/dist/{pi → sdk}/ai/utils/hash.js +0 -0
  125. /package/dist/{pi → sdk}/ai/utils/headers.js +0 -0
  126. /package/dist/{pi → sdk}/ai/utils/json-parse.js +0 -0
  127. /package/dist/{pi → sdk}/ai/utils/node-http-proxy.js +0 -0
  128. /package/dist/{pi → sdk}/ai/utils/oauth/device-code.js +0 -0
  129. /package/dist/{pi → sdk}/ai/utils/oauth/github-copilot.js +0 -0
  130. /package/dist/{pi → sdk}/ai/utils/oauth/index.js +0 -0
  131. /package/dist/{pi → sdk}/ai/utils/oauth/oauth-page.js +0 -0
  132. /package/dist/{pi → sdk}/ai/utils/oauth/pkce.js +0 -0
  133. /package/dist/{pi → sdk}/ai/utils/oauth/types.js +0 -0
  134. /package/dist/{pi → sdk}/ai/utils/overflow.js +0 -0
  135. /package/dist/{pi → sdk}/ai/utils/sanitize-unicode.js +0 -0
  136. /package/dist/{pi → sdk}/ai/utils/typebox-helpers.js +0 -0
  137. /package/dist/{pi → sdk}/ai/utils/validation.js +0 -0
  138. /package/dist/{pi → sdk}/coding-agent/cli.js +0 -0
  139. /package/dist/{pi → sdk}/coding-agent/core/agent-session-runtime.js +0 -0
  140. /package/dist/{pi → sdk}/coding-agent/core/agent-session-services.js +0 -0
  141. /package/dist/{pi → sdk}/coding-agent/core/auth-guidance.js +0 -0
  142. /package/dist/{pi → sdk}/coding-agent/core/auth-storage.js +0 -0
  143. /package/dist/{pi → sdk}/coding-agent/core/bash-executor.js +0 -0
  144. /package/dist/{pi → sdk}/coding-agent/core/compaction/branch-summarization.js +0 -0
  145. /package/dist/{pi → sdk}/coding-agent/core/compaction/index.js +0 -0
  146. /package/dist/{pi → sdk}/coding-agent/core/compaction/utils.js +0 -0
  147. /package/dist/{pi → sdk}/coding-agent/core/defaults.js +0 -0
  148. /package/dist/{pi → sdk}/coding-agent/core/diagnostics.js +0 -0
  149. /package/dist/{pi → sdk}/coding-agent/core/event-bus.js +0 -0
  150. /package/dist/{pi → sdk}/coding-agent/core/exec.js +0 -0
  151. /package/dist/{pi → sdk}/coding-agent/core/extensions/index.js +0 -0
  152. /package/dist/{pi → sdk}/coding-agent/core/extensions/types.js +0 -0
  153. /package/dist/{pi → sdk}/coding-agent/core/extensions/wrapper.js +0 -0
  154. /package/dist/{pi → sdk}/coding-agent/core/http-dispatcher.js +0 -0
  155. /package/dist/{pi → sdk}/coding-agent/core/index.js +0 -0
  156. /package/dist/{pi → sdk}/coding-agent/core/keybindings.js +0 -0
  157. /package/dist/{pi → sdk}/coding-agent/core/messages.js +0 -0
  158. /package/dist/{pi → sdk}/coding-agent/core/output-guard.js +0 -0
  159. /package/dist/{pi → sdk}/coding-agent/core/prompt-templates.js +0 -0
  160. /package/dist/{pi → sdk}/coding-agent/core/provider-display-names.js +0 -0
  161. /package/dist/{pi → sdk}/coding-agent/core/resolve-config-value.js +0 -0
  162. /package/dist/{pi → sdk}/coding-agent/core/session-cwd.js +0 -0
  163. /package/dist/{pi → sdk}/coding-agent/core/skills.js +0 -0
  164. /package/dist/{pi → sdk}/coding-agent/core/slash-commands.js +0 -0
  165. /package/dist/{pi → sdk}/coding-agent/core/source-info.js +0 -0
  166. /package/dist/{pi → sdk}/coding-agent/core/timings.js +0 -0
  167. /package/dist/{pi → sdk}/coding-agent/core/tools/edit-diff.js +0 -0
  168. /package/dist/{pi → sdk}/coding-agent/core/tools/file-mutation-queue.js +0 -0
  169. /package/dist/{pi → sdk}/coding-agent/core/tools/index.js +0 -0
  170. /package/dist/{pi → sdk}/coding-agent/core/tools/output-accumulator.js +0 -0
  171. /package/dist/{pi → sdk}/coding-agent/core/tools/path-utils.js +0 -0
  172. /package/dist/{pi → sdk}/coding-agent/core/tools/render-utils.js +0 -0
  173. /package/dist/{pi → sdk}/coding-agent/core/tools/tool-definition-wrapper.js +0 -0
  174. /package/dist/{pi → sdk}/coding-agent/core/tools/truncate.js +0 -0
  175. /package/dist/{pi → sdk}/coding-agent/main.js +0 -0
  176. /package/dist/{pi → sdk}/coding-agent/modes/print-mode.js +0 -0
  177. /package/dist/{pi → sdk}/coding-agent/modes/rpc/jsonl.js +0 -0
  178. /package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-client.js +0 -0
  179. /package/dist/{pi → sdk}/coding-agent/modes/rpc/rpc-types.js +0 -0
  180. /package/dist/{pi → sdk}/coding-agent/utils/ansi.js +0 -0
  181. /package/dist/{pi → sdk}/coding-agent/utils/changelog.js +0 -0
  182. /package/dist/{pi → sdk}/coding-agent/utils/child-process.js +0 -0
  183. /package/dist/{pi → sdk}/coding-agent/utils/clipboard-image.js +0 -0
  184. /package/dist/{pi → sdk}/coding-agent/utils/clipboard-native.js +0 -0
  185. /package/dist/{pi → sdk}/coding-agent/utils/clipboard.js +0 -0
  186. /package/dist/{pi → sdk}/coding-agent/utils/exif-orientation.js +0 -0
  187. /package/dist/{pi → sdk}/coding-agent/utils/frontmatter.js +0 -0
  188. /package/dist/{pi → sdk}/coding-agent/utils/fs-watch.js +0 -0
  189. /package/dist/{pi → sdk}/coding-agent/utils/git.js +0 -0
  190. /package/dist/{pi → sdk}/coding-agent/utils/html.js +0 -0
  191. /package/dist/{pi → sdk}/coding-agent/utils/image-convert.js +0 -0
  192. /package/dist/{pi → sdk}/coding-agent/utils/image-resize.js +0 -0
  193. /package/dist/{pi → sdk}/coding-agent/utils/mime.js +0 -0
  194. /package/dist/{pi → sdk}/coding-agent/utils/paths.js +0 -0
  195. /package/dist/{pi → sdk}/coding-agent/utils/shell.js +0 -0
  196. /package/dist/{pi → sdk}/coding-agent/utils/sleep.js +0 -0
  197. /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 && turnPrefixMessages.length > 0) {
729
+ if (isSplitTurn && dedupedTurnPrefix.length > 0) {
574
730
  // Generate both summaries in parallel
575
731
  const [historyResult, turnPrefixResult] = await Promise.all([
576
- messagesToSummarize.length > 0
577
- ? generateSummary(messagesToSummarize, model, settings.reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn)
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(turnPrefixMessages, model, settings.reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn),
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(messagesToSummarize, model, settings.reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn);
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
- // Static imports of packages that extensions may use.
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
- // In Bun binary: use virtualModules for bundled packages (no filesystem resolution)
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 theme;
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
- this.loadModels();
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 "../cli/args.js";
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.PI_OFFLINE;
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 .pi/
1836
+ // Project extensions from .spectral/
1837
1837
  addResources("extensions", collectAutoExtensionEntries(projectDirs.extensions), projectMetadata, projectOverrides.extensions, projectBaseDir);
1838
- // Project skills from .pi/
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 ~/.pi/agent/
1851
+ // User extensions from ~/.spectral/agent/
1852
1852
  addResources("extensions", collectAutoExtensionEntries(userDirs.extensions), userMetadata, userOverrides.extensions, globalBaseDir);
1853
- // User skills from ~/.pi/agent/
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 "../modes/interactive/theme/theme.js";
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://pi.dev",
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 ~/.pi/agent/sessions/.
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 (~/.pi/agent/sessions/<encoded-cwd>/).
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 (~/.pi/agent/sessions/<encoded-cwd>/).
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 (~/.pi/agent/sessions/<encoded-cwd>/).
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
- if (skillsSettings.enableSkillCommands !== undefined && settings.enableSkillCommands === undefined) {
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 assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.
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.PI_TELEMETRY) {
6
+ export function isInstallTelemetryEnabled(settingsManager, telemetryEnv = process.env.SPECTRAL_TELEMETRY) {
7
7
  return telemetryEnv !== undefined ? isTruthyEnvFlag(telemetryEnv) : settingsManager.getEnableInstallTelemetry();
8
8
  }