@elyracode/coding-agent 0.5.12 → 0.6.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/core/agent-session.d.ts.map +1 -1
  3. package/dist/core/agent-session.js +6 -1
  4. package/dist/core/agent-session.js.map +1 -1
  5. package/dist/core/compaction/compaction.d.ts.map +1 -1
  6. package/dist/core/compaction/compaction.js +35 -7
  7. package/dist/core/compaction/compaction.js.map +1 -1
  8. package/dist/core/compaction/utils.d.ts.map +1 -1
  9. package/dist/core/compaction/utils.js +1 -1
  10. package/dist/core/compaction/utils.js.map +1 -1
  11. package/dist/core/extensions/types.d.ts +1 -1
  12. package/dist/core/extensions/types.d.ts.map +1 -1
  13. package/dist/core/extensions/types.js.map +1 -1
  14. package/dist/core/keybindings.d.ts +1 -1
  15. package/dist/core/keybindings.d.ts.map +1 -1
  16. package/dist/core/keybindings.js.map +1 -1
  17. package/dist/core/sdk.d.ts +1 -1
  18. package/dist/core/sdk.d.ts.map +1 -1
  19. package/dist/core/sdk.js +25 -27
  20. package/dist/core/sdk.js.map +1 -1
  21. package/dist/core/settings-manager.d.ts +4 -0
  22. package/dist/core/settings-manager.d.ts.map +1 -1
  23. package/dist/core/settings-manager.js +8 -0
  24. package/dist/core/settings-manager.js.map +1 -1
  25. package/dist/core/slash-commands.d.ts.map +1 -1
  26. package/dist/core/slash-commands.js +1 -0
  27. package/dist/core/slash-commands.js.map +1 -1
  28. package/dist/core/smart-router.d.ts +2 -2
  29. package/dist/core/smart-router.d.ts.map +1 -1
  30. package/dist/core/smart-router.js +64 -10
  31. package/dist/core/smart-router.js.map +1 -1
  32. package/dist/core/system-prompt.d.ts.map +1 -1
  33. package/dist/core/system-prompt.js +2 -9
  34. package/dist/core/system-prompt.js.map +1 -1
  35. package/dist/modes/interactive/components/footer.d.ts +2 -0
  36. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  37. package/dist/modes/interactive/components/footer.js +11 -0
  38. package/dist/modes/interactive/components/footer.js.map +1 -1
  39. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  40. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  41. package/dist/modes/interactive/interactive-mode.js +14 -0
  42. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  43. package/dist/utils/clipboard-image.d.ts.map +1 -1
  44. package/dist/utils/clipboard-image.js +1 -1
  45. package/dist/utils/clipboard-image.js.map +1 -1
  46. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  47. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  48. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  49. package/examples/extensions/sandbox/package-lock.json +2 -2
  50. package/examples/extensions/sandbox/package.json +1 -1
  51. package/examples/extensions/with-deps/package-lock.json +2 -2
  52. package/examples/extensions/with-deps/package.json +1 -1
  53. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"smart-router.js","sourceRoot":"","sources":["../../src/core/smart-router.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Smart model routing for automatic tier-based model selection.\n *\n * Analyzes conversation signals after each turn and selects the optimal model\n * tier (fast/balanced/powerful) for the next turn based on task complexity.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@elyracode/agent-core\";\nimport type { Api, AssistantMessage, Model, ToolCall, ToolResultMessage, UserMessage } from \"@elyracode/ai\";\nimport { modelsAreEqual } from \"@elyracode/ai\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface SmartRouterConfig {\n\tenabled: boolean;\n\t/** Available models the router can pick from. Must have at least one. */\n\tavailableModels: Model<Api>[];\n\t/** The user's preferred/current model -- used as the Tier 2 default and fallback. */\n\tpreferredModel: Model<Api>;\n}\n\nexport interface TurnAnalysis {\n\t/** The messages in the conversation so far. */\n\tmessages: readonly AgentMessage[];\n\t/** Current thinking level. */\n\tthinkingLevel: ThinkingLevel;\n}\n\nexport type ComplexityTier = \"fast\" | \"balanced\" | \"powerful\";\n\nexport interface SmartRouterResult {\n\tmodel: Model<Api>;\n\ttier: ComplexityTier;\n\treason: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Output cost thresholds ($/million tokens) for tier classification. */\nconst TIER_1_MAX_COST = 3;\nconst TIER_3_MIN_COST = 20;\n\n/** Tool names considered read-only (no side effects). */\nconst READ_ONLY_TOOLS: ReadonlySet<string> = new Set([\"read\", \"grep\", \"find\", \"ls\"]);\n\n/** Keywords in user messages that signal complex work. */\nconst COMPLEX_KEYWORDS: readonly string[] = [\n\t\"refactor\",\n\t\"architect\",\n\t\"redesign\",\n\t\"migrate\",\n\t\"rewrite\",\n\t\"debug\",\n\t\"fix the bug\",\n\t\"security audit\",\n];\n\n/** Short user message threshold (characters). */\nconst SHORT_MESSAGE_THRESHOLD = 100;\n\n/** Message count threshold for complex multi-step tasks. */\nconst LONG_CONVERSATION_THRESHOLD = 20;\n\n/** File edit count threshold per turn for complexity escalation. */\nconst MANY_EDITS_THRESHOLD = 3;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction isUserMessage(msg: AgentMessage): msg is UserMessage {\n\treturn \"role\" in msg && (msg as UserMessage).role === \"user\";\n}\n\nfunction isAssistantMessage(msg: AgentMessage): msg is AssistantMessage {\n\treturn \"role\" in msg && (msg as AssistantMessage).role === \"assistant\";\n}\n\nfunction isToolResultMessage(msg: AgentMessage): msg is ToolResultMessage {\n\treturn \"role\" in msg && (msg as ToolResultMessage).role === \"toolResult\";\n}\n\n/** Extract the plain-text content of a user message. */\nfunction getUserMessageText(msg: UserMessage): string {\n\tif (typeof msg.content === \"string\") return msg.content;\n\treturn msg.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\" \");\n}\n\n/** Extract tool calls from an assistant message's content. */\nfunction getToolCalls(msg: AssistantMessage): ToolCall[] {\n\treturn msg.content.filter((c): c is ToolCall => c.type === \"toolCall\");\n}\n\n/** True when all tool calls in a message are read-only. */\nfunction allToolCallsReadOnly(toolCalls: ToolCall[]): boolean {\n\treturn toolCalls.length > 0 && toolCalls.every((tc) => READ_ONLY_TOOLS.has(tc.name));\n}\n\n/** Count write/edit tool calls in an assistant message. */\nfunction countFileEditCalls(toolCalls: ToolCall[]): number {\n\treturn toolCalls.filter((tc) => tc.name === \"write\" || tc.name === \"edit\").length;\n}\n\n/**\n * Collect tool result messages that follow an assistant message.\n * Walks forward from the assistant message index, collecting consecutive\n * tool results (there may be other message types interleaved in custom\n * AgentMessage streams, so we stop at the next user or assistant message).\n */\nfunction getToolResultsAfter(messages: readonly AgentMessage[], assistantIndex: number): ToolResultMessage[] {\n\tconst results: ToolResultMessage[] = [];\n\tfor (let i = assistantIndex + 1; i < messages.length; i++) {\n\t\tconst msg = messages[i];\n\t\tif (isUserMessage(msg) || isAssistantMessage(msg)) break;\n\t\tif (isToolResultMessage(msg)) results.push(msg);\n\t}\n\treturn results;\n}\n\n/** True when the text contains any of the complex keywords (case-insensitive). */\nfunction containsComplexKeyword(text: string): boolean {\n\tconst lower = text.toLowerCase();\n\treturn COMPLEX_KEYWORDS.some((kw) => lower.includes(kw));\n}\n\n// ============================================================================\n// Tier classification\n// ============================================================================\n\n/** Classify a model into its cost tier. */\nfunction modelTier(model: Model<Api>): ComplexityTier {\n\tconst cost = model.cost.output;\n\tif (cost < TIER_1_MAX_COST) return \"fast\";\n\tif (cost < TIER_3_MIN_COST) return \"balanced\";\n\treturn \"powerful\";\n}\n\n/** Classify the complexity tier for the next turn. */\nexport function classifyComplexity(analysis: TurnAnalysis): { tier: ComplexityTier; reason: string } {\n\tconst { messages, thinkingLevel } = analysis;\n\n\t// High thinking level -> complex\n\tif (thinkingLevel === \"high\" || thinkingLevel === \"xhigh\") {\n\t\treturn { tier: \"powerful\", reason: `thinking level is set to ${thinkingLevel}` };\n\t}\n\n\t// Long conversation -> complex\n\tif (messages.length > LONG_CONVERSATION_THRESHOLD) {\n\t\treturn { tier: \"powerful\", reason: `long conversation (${messages.length} messages)` };\n\t}\n\n\t// Find the last assistant message and its tool results\n\tlet lastAssistantIndex = -1;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tif (isAssistantMessage(messages[i])) {\n\t\t\tlastAssistantIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Find the last user message\n\tlet lastUserMessage: UserMessage | undefined;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tif (isUserMessage(messages[i])) {\n\t\t\tlastUserMessage = messages[i] as UserMessage;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Check for complex signals from the last assistant turn\n\tif (lastAssistantIndex >= 0) {\n\t\tconst lastAssistant = messages[lastAssistantIndex] as AssistantMessage;\n\t\tconst toolCalls = getToolCalls(lastAssistant);\n\t\tconst toolResults = getToolResultsAfter(messages, lastAssistantIndex);\n\n\t\t// Errors in last turn -> complex\n\t\tconst hasErrors = toolResults.some((tr) => tr.isError);\n\t\tif (hasErrors) {\n\t\t\treturn { tier: \"powerful\", reason: \"tool errors in last turn\" };\n\t\t}\n\n\t\t// Many file edits in last turn -> complex\n\t\tif (countFileEditCalls(toolCalls) > MANY_EDITS_THRESHOLD) {\n\t\t\treturn { tier: \"powerful\", reason: `many file edits in last turn (${countFileEditCalls(toolCalls)})` };\n\t\t}\n\n\t\t// All read-only tools and no errors -> check for simple\n\t\tif (allToolCallsReadOnly(toolCalls) && !hasErrors) {\n\t\t\t// If the last user message is also short and simple, use fast tier\n\t\t\tif (lastUserMessage) {\n\t\t\t\tconst text = getUserMessageText(lastUserMessage);\n\t\t\t\tif (text.length < SHORT_MESSAGE_THRESHOLD && !containsComplexKeyword(text)) {\n\t\t\t\t\treturn { tier: \"fast\", reason: \"read-only tools with short user message\" };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check user message for complex keywords\n\tif (lastUserMessage) {\n\t\tconst text = getUserMessageText(lastUserMessage);\n\t\tif (containsComplexKeyword(text)) {\n\t\t\treturn { tier: \"powerful\", reason: \"complex keywords in user message\" };\n\t\t}\n\n\t\t// Short question with no prior assistant context -> fast\n\t\tif (lastAssistantIndex < 0 && text.length < SHORT_MESSAGE_THRESHOLD) {\n\t\t\treturn { tier: \"fast\", reason: \"short initial message\" };\n\t\t}\n\t}\n\n\treturn { tier: \"balanced\", reason: \"default\" };\n}\n\n// ============================================================================\n// Model selection\n// ============================================================================\n\n/** Select the best model for a given complexity tier from available models. */\nexport function selectModelForTier(tier: ComplexityTier, config: SmartRouterConfig): Model<Api> {\n\tconst { availableModels, preferredModel } = config;\n\n\t// Partition models into tiers\n\tconst tierModels = availableModels.filter((m) => modelTier(m) === tier);\n\n\tif (tierModels.length === 0) {\n\t\t// Requested tier has no models, fall back to preferred\n\t\treturn preferredModel;\n\t}\n\n\t// Pick the model with the largest context window (best value within tier)\n\tlet best = tierModels[0];\n\tfor (let i = 1; i < tierModels.length; i++) {\n\t\tif (tierModels[i].contextWindow > best.contextWindow) {\n\t\t\tbest = tierModels[i];\n\t\t}\n\t}\n\treturn best;\n}\n\n// ============================================================================\n// Main entry\n// ============================================================================\n\n/**\n * Analyze the turn and return a model recommendation.\n *\n * Returns undefined when:\n * - Smart routing is disabled\n * - The selected model is the same as the preferred model (no change needed)\n */\nexport function routeNextTurn(analysis: TurnAnalysis, config: SmartRouterConfig): SmartRouterResult | undefined {\n\tif (!config.enabled) return undefined;\n\n\tconst { tier, reason } = classifyComplexity(analysis);\n\tconst model = selectModelForTier(tier, config);\n\n\t// No change needed if the router picks the same model the user already has\n\tif (modelsAreEqual(model, config.preferredModel)) return undefined;\n\n\treturn { model, tier, reason };\n}\n"]}
1
+ {"version":3,"file":"smart-router.js","sourceRoot":"","sources":["../../src/core/smart-router.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Smart model routing for automatic tier-based model selection.\n *\n * Analyzes conversation signals after each turn and selects the optimal model\n * tier (fast/balanced/powerful) for the next turn based on task complexity.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@elyracode/agent-core\";\nimport type { Api, AssistantMessage, Model, ToolCall, ToolResultMessage, UserMessage } from \"@elyracode/ai\";\nimport { modelsAreEqual } from \"@elyracode/ai\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface SmartRouterConfig {\n\tenabled: boolean;\n\t/** Available models the router can pick from. Must have at least one. */\n\tavailableModels: Model<Api>[];\n\t/** The user's preferred/current model -- used as the Tier 2 default and fallback. */\n\tpreferredModel: Model<Api>;\n}\n\nexport interface TurnAnalysis {\n\t/** The messages in the conversation so far. */\n\tmessages: readonly AgentMessage[];\n\t/** Current thinking level. */\n\tthinkingLevel: ThinkingLevel;\n}\n\nexport type ComplexityTier = \"fast\" | \"balanced\" | \"powerful\";\n\nexport interface SmartRouterResult {\n\tmodel: Model<Api>;\n\ttier: ComplexityTier;\n\treason: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Output cost thresholds ($/million tokens) for tier classification. */\nconst TIER_1_MAX_COST = 3;\nconst TIER_3_MIN_COST = 20;\n\n/** Tool names considered read-only (no side effects). */\nconst READ_ONLY_TOOLS: ReadonlySet<string> = new Set([\"read\", \"grep\", \"find\", \"ls\"]);\n\n/** Keywords in user messages that signal complex work. */\nconst COMPLEX_KEYWORDS: readonly string[] = [\n\t\"refactor\",\n\t\"architect\",\n\t\"redesign\",\n\t\"migrate\",\n\t\"rewrite\",\n\t\"debug\",\n\t\"fix the bug\",\n\t\"security audit\",\n\t\"optimize\",\n\t\"performance\",\n\t\"investigate\",\n\t\"diagnose\",\n\t\"troubleshoot\",\n\t\"root cause\",\n\t\"race condition\",\n\t\"deadlock\",\n\t\"memory leak\",\n\t\"regression\",\n\t\"breaking change\",\n\t\"backwards compatible\",\n\t\"design pattern\",\n\t\"abstraction\",\n\t\"inheritance\",\n\t\"polymorphism\",\n\t\"dependency injection\",\n\t\"restructure\",\n\t\"consolidate\",\n\t\"decompose\",\n\t\"decouple\",\n\t\"integrate\",\n\t\"implement from scratch\",\n\t\"build a system\",\n\t\"complex\",\n\t\"tricky\",\n\t\"subtle\",\n];\n\n/** Short user message threshold (characters). */\nconst SHORT_MESSAGE_THRESHOLD = 100;\n\n/** Message count threshold for complex multi-step tasks. */\nconst LONG_CONVERSATION_THRESHOLD = 20;\n\n/** File edit count threshold per turn for complexity escalation. */\nconst MANY_EDITS_THRESHOLD = 3;\n\n/** Character count threshold for large tool output. */\nconst LARGE_TOOL_OUTPUT_THRESHOLD = 5000;\n\n/** Conversation length below which read-only de-escalation to fast is allowed. */\nconst READ_ONLY_DEESCALATION_MAX_MESSAGES = 10;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction isUserMessage(msg: AgentMessage): msg is UserMessage {\n\treturn \"role\" in msg && (msg as UserMessage).role === \"user\";\n}\n\nfunction isAssistantMessage(msg: AgentMessage): msg is AssistantMessage {\n\treturn \"role\" in msg && (msg as AssistantMessage).role === \"assistant\";\n}\n\nfunction isToolResultMessage(msg: AgentMessage): msg is ToolResultMessage {\n\treturn \"role\" in msg && (msg as ToolResultMessage).role === \"toolResult\";\n}\n\n/** Extract the plain-text content of a user message. */\nfunction getUserMessageText(msg: UserMessage): string {\n\tif (typeof msg.content === \"string\") return msg.content;\n\treturn msg.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.map((c) => c.text)\n\t\t.join(\" \");\n}\n\n/** Extract tool calls from an assistant message's content. */\nfunction getToolCalls(msg: AssistantMessage): ToolCall[] {\n\treturn msg.content.filter((c): c is ToolCall => c.type === \"toolCall\");\n}\n\n/** True when all tool calls in a message are read-only. */\nfunction allToolCallsReadOnly(toolCalls: ToolCall[]): boolean {\n\treturn toolCalls.length > 0 && toolCalls.every((tc) => READ_ONLY_TOOLS.has(tc.name));\n}\n\n/** Count write/edit tool calls in an assistant message. */\nfunction countFileEditCalls(toolCalls: ToolCall[]): number {\n\treturn toolCalls.filter((tc) => tc.name === \"write\" || tc.name === \"edit\").length;\n}\n\n/**\n * Collect tool result messages that follow an assistant message.\n * Walks forward from the assistant message index, collecting consecutive\n * tool results (there may be other message types interleaved in custom\n * AgentMessage streams, so we stop at the next user or assistant message).\n */\nfunction getToolResultsAfter(messages: readonly AgentMessage[], assistantIndex: number): ToolResultMessage[] {\n\tconst results: ToolResultMessage[] = [];\n\tfor (let i = assistantIndex + 1; i < messages.length; i++) {\n\t\tconst msg = messages[i];\n\t\tif (isUserMessage(msg) || isAssistantMessage(msg)) break;\n\t\tif (isToolResultMessage(msg)) results.push(msg);\n\t}\n\treturn results;\n}\n\n/** True when the text contains any of the complex keywords (case-insensitive). */\nfunction containsComplexKeyword(text: string): boolean {\n\tconst lower = text.toLowerCase();\n\treturn COMPLEX_KEYWORDS.some((kw) => lower.includes(kw));\n}\n\n// ============================================================================\n// Tier classification\n// ============================================================================\n\n/** Classify a model into its cost tier. */\nfunction modelTier(model: Model<Api>): ComplexityTier {\n\tconst cost = model.cost.output;\n\tif (cost < TIER_1_MAX_COST) return \"fast\";\n\tif (cost < TIER_3_MIN_COST) return \"balanced\";\n\treturn \"powerful\";\n}\n\n/** Measure the total text length of a tool result message. */\nfunction toolResultTextLength(tr: ToolResultMessage): number {\n\treturn tr.content\n\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t.reduce((sum, c) => sum + c.text.length, 0);\n}\n\n/** Classify the complexity tier for the next turn. */\nexport function classifyComplexity(\n\tanalysis: TurnAnalysis,\n\tpreviousTier?: ComplexityTier,\n): { tier: ComplexityTier; reason: string } {\n\tconst { messages, thinkingLevel } = analysis;\n\n\t// High thinking level -> complex\n\tif (thinkingLevel === \"high\" || thinkingLevel === \"xhigh\") {\n\t\treturn { tier: \"powerful\", reason: `thinking level is set to ${thinkingLevel}` };\n\t}\n\n\t// Long conversation -> complex\n\tif (messages.length > LONG_CONVERSATION_THRESHOLD) {\n\t\treturn { tier: \"powerful\", reason: `long conversation (${messages.length} messages)` };\n\t}\n\n\t// Find the last assistant message and its tool results\n\tlet lastAssistantIndex = -1;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tif (isAssistantMessage(messages[i])) {\n\t\t\tlastAssistantIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Find the last user message\n\tlet lastUserMessage: UserMessage | undefined;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tif (isUserMessage(messages[i])) {\n\t\t\tlastUserMessage = messages[i] as UserMessage;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Check for complex signals from the last assistant turn\n\tif (lastAssistantIndex >= 0) {\n\t\tconst lastAssistant = messages[lastAssistantIndex] as AssistantMessage;\n\t\tconst toolCalls = getToolCalls(lastAssistant);\n\t\tconst toolResults = getToolResultsAfter(messages, lastAssistantIndex);\n\n\t\t// Errors in last turn -> complex\n\t\tconst hasErrors = toolResults.some((tr) => tr.isError);\n\t\tif (hasErrors) {\n\t\t\treturn { tier: \"powerful\", reason: \"tool errors in last turn\" };\n\t\t}\n\n\t\t// Many file edits in last turn -> complex\n\t\tif (countFileEditCalls(toolCalls) > MANY_EDITS_THRESHOLD) {\n\t\t\treturn applyHysteresis(\n\t\t\t\t{ tier: \"powerful\", reason: `many file edits in last turn (${countFileEditCalls(toolCalls)})` },\n\t\t\t\tpreviousTier,\n\t\t\t);\n\t\t}\n\n\t\t// Large tool outputs suggest complex operations\n\t\tif (toolResults.some((r) => toolResultTextLength(r) > LARGE_TOOL_OUTPUT_THRESHOLD)) {\n\t\t\treturn applyHysteresis({ tier: \"powerful\", reason: \"large tool output detected\" }, previousTier);\n\t\t}\n\n\t\t// All read-only tools and no errors -> check for simple\n\t\tif (allToolCallsReadOnly(toolCalls) && !hasErrors) {\n\t\t\t// If the last user message is also short and simple, use fast tier.\n\t\t\t// Only de-escalate in short conversations; in longer ones a read-only\n\t\t\t// turn in the middle of complex work shouldn't drop to fast.\n\t\t\tif (lastUserMessage && messages.length < READ_ONLY_DEESCALATION_MAX_MESSAGES) {\n\t\t\t\tconst text = getUserMessageText(lastUserMessage);\n\t\t\t\tif (text.length < SHORT_MESSAGE_THRESHOLD && !containsComplexKeyword(text)) {\n\t\t\t\t\treturn applyHysteresis(\n\t\t\t\t\t\t{ tier: \"fast\", reason: \"read-only tools with short user message\" },\n\t\t\t\t\t\tpreviousTier,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check user message for complex keywords\n\tif (lastUserMessage) {\n\t\tconst text = getUserMessageText(lastUserMessage);\n\t\tif (containsComplexKeyword(text)) {\n\t\t\treturn applyHysteresis({ tier: \"powerful\", reason: \"complex keywords in user message\" }, previousTier);\n\t\t}\n\n\t\t// Short question with no prior assistant context -> fast\n\t\tif (lastAssistantIndex < 0 && text.length < SHORT_MESSAGE_THRESHOLD) {\n\t\t\treturn applyHysteresis({ tier: \"fast\", reason: \"short initial message\" }, previousTier);\n\t\t}\n\t}\n\n\treturn applyHysteresis({ tier: \"balanced\", reason: \"default\" }, previousTier);\n}\n\n/**\n * Hysteresis: prevent oscillation by not dropping from powerful directly to fast.\n * If the previous turn was powerful and the current classification is fast,\n * dampen to balanced instead.\n */\nfunction applyHysteresis(\n\tresult: { tier: ComplexityTier; reason: string },\n\tpreviousTier: ComplexityTier | undefined,\n): { tier: ComplexityTier; reason: string } {\n\tif (previousTier === \"powerful\" && result.tier === \"fast\") {\n\t\treturn { tier: \"balanced\", reason: `${result.reason} (dampened from fast: previous turn was powerful)` };\n\t}\n\treturn result;\n}\n\n// ============================================================================\n// Model selection\n// ============================================================================\n\n/** Select the best model for a given complexity tier from available models. */\nexport function selectModelForTier(tier: ComplexityTier, config: SmartRouterConfig): Model<Api> {\n\tconst { availableModels, preferredModel } = config;\n\n\t// Partition models into tiers\n\tconst tierModels = availableModels.filter((m) => modelTier(m) === tier);\n\n\tif (tierModels.length === 0) {\n\t\t// Requested tier has no models, fall back to preferred\n\t\treturn preferredModel;\n\t}\n\n\t// Pick the model with the largest context window (best value within tier)\n\tlet best = tierModels[0];\n\tfor (let i = 1; i < tierModels.length; i++) {\n\t\tif (tierModels[i].contextWindow > best.contextWindow) {\n\t\t\tbest = tierModels[i];\n\t\t}\n\t}\n\treturn best;\n}\n\n// ============================================================================\n// Main entry\n// ============================================================================\n\n/**\n * Analyze the turn and return a model recommendation.\n *\n * Returns undefined when:\n * - Smart routing is disabled\n * - The selected model is the same as the preferred model (no change needed)\n */\nexport function routeNextTurn(\n\tanalysis: TurnAnalysis,\n\tconfig: SmartRouterConfig,\n\tpreviousTier?: ComplexityTier,\n): SmartRouterResult | undefined {\n\tif (!config.enabled) return undefined;\n\n\tconst { tier, reason } = classifyComplexity(analysis, previousTier);\n\tconst model = selectModelForTier(tier, config);\n\n\t// No change needed if the router picks the same model the user already has\n\tif (modelsAreEqual(model, config.preferredModel)) return undefined;\n\n\treturn { model, tier, reason };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACxC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uCAAuC;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,yBAAyB;IACzB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAgL3E","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.js\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.js\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** Persistent codebase memory from previous sessions. */\n\tmemoryContent?: string;\n\t/** Detected technology stack summary (from stack-detector). */\n\tstackInfo?: string;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t\tmemoryContent,\n\t\tstackInfo,\n\t} = options;\n\tconst resolvedCwd = cwd;\n\tconst promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n\tconst now = new Date();\n\tconst year = now.getFullYear();\n\tconst month = String(now.getMonth() + 1).padStart(2, \"0\");\n\tconst day = String(now.getDate()).padStart(2, \"0\");\n\tconst date = `${year}-${month}-${day}`;\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append stack info\n\t\tif (stackInfo) {\n\t\t\tprompt += \"\\n\\n# Technology Stack\\n\\n\";\n\t\t\tprompt += stackInfo;\n\t\t\tprompt += \"\\n\";\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t\t}\n\t\t}\n\n\t\t// Append codebase memory\n\t\tif (memoryContent) {\n\t\t\tprompt += \"\\n\\n# Codebase Memory\\n\\n\";\n\t\t\tprompt += \"Knowledge from previous sessions on this project:\\n\\n\";\n\t\t\tprompt += memoryContent;\n\t\t\tprompt += \"\\n\";\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\n\t\t// Add date and working directory last\n\t\tprompt += `\\nCurrent date: ${date}`;\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\taddGuideline(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant operating inside elyra, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n\nElyra documentation (read only when the user asks about elyra itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), elyra packages (docs/packages.md)\n- When working on elyra topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read elyra .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append stack info\n\tif (stackInfo) {\n\t\tprompt += \"\\n\\n# Technology Stack\\n\\n\";\n\t\tprompt += stackInfo;\n\t\tprompt += \"\\n\";\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t}\n\t}\n\n\t// Append codebase memory\n\tif (memoryContent) {\n\t\tprompt += \"\\n\\n# Codebase Memory\\n\\n\";\n\t\tprompt += \"Knowledge from previous sessions on this project:\\n\\n\";\n\t\tprompt += memoryContent;\n\t\tprompt += \"\\n\";\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\n\t// Add date and working directory last\n\tprompt += `\\nCurrent date: ${date}`;\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n"]}
1
+ {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACxC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uCAAuC;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,yBAAyB;IACzB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAwK3E","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.js\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.js\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** Persistent codebase memory from previous sessions. */\n\tmemoryContent?: string;\n\t/** Detected technology stack summary (from stack-detector). */\n\tstackInfo?: string;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t\tmemoryContent,\n\t\tstackInfo,\n\t} = options;\n\tconst resolvedCwd = cwd;\n\tconst promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append stack info\n\t\tif (stackInfo) {\n\t\t\tprompt += \"\\n\\n# Technology Stack\\n\\n\";\n\t\t\tprompt += stackInfo;\n\t\t\tprompt += \"\\n\";\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t\t}\n\t\t}\n\n\t\t// Append codebase memory\n\t\tif (memoryContent) {\n\t\t\tprompt += \"\\n\\n# Codebase Memory\\n\\n\";\n\t\t\tprompt += \"Knowledge from previous sessions on this project:\\n\\n\";\n\t\t\tprompt += memoryContent;\n\t\t\tprompt += \"\\n\";\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\n\t\t// Add working directory last (date is injected via transformContext for cache stability)\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\taddGuideline(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant operating inside elyra, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n\nElyra documentation (read only when the user asks about elyra itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), elyra packages (docs/packages.md)\n- When working on elyra topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read elyra .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append stack info\n\tif (stackInfo) {\n\t\tprompt += \"\\n\\n# Technology Stack\\n\\n\";\n\t\tprompt += stackInfo;\n\t\tprompt += \"\\n\";\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t}\n\t}\n\n\t// Append codebase memory\n\tif (memoryContent) {\n\t\tprompt += \"\\n\\n# Codebase Memory\\n\\n\";\n\t\tprompt += \"Knowledge from previous sessions on this project:\\n\\n\";\n\t\tprompt += memoryContent;\n\t\tprompt += \"\\n\";\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\n\t// Add working directory last (date is injected via transformContext for cache stability)\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n"]}
@@ -8,11 +8,6 @@ export function buildSystemPrompt(options) {
8
8
  const { customPrompt, selectedTools, toolSnippets, promptGuidelines, appendSystemPrompt, cwd, contextFiles: providedContextFiles, skills: providedSkills, memoryContent, stackInfo, } = options;
9
9
  const resolvedCwd = cwd;
10
10
  const promptCwd = resolvedCwd.replace(/\\/g, "/");
11
- const now = new Date();
12
- const year = now.getFullYear();
13
- const month = String(now.getMonth() + 1).padStart(2, "0");
14
- const day = String(now.getDate()).padStart(2, "0");
15
- const date = `${year}-${month}-${day}`;
16
11
  const appendSection = appendSystemPrompt ? `\n\n${appendSystemPrompt}` : "";
17
12
  const contextFiles = providedContextFiles ?? [];
18
13
  const skills = providedSkills ?? [];
@@ -47,8 +42,7 @@ export function buildSystemPrompt(options) {
47
42
  if (customPromptHasRead && skills.length > 0) {
48
43
  prompt += formatSkillsForPrompt(skills);
49
44
  }
50
- // Add date and working directory last
51
- prompt += `\nCurrent date: ${date}`;
45
+ // Add working directory last (date is injected via transformContext for cache stability)
52
46
  prompt += `\nCurrent working directory: ${promptCwd}`;
53
47
  return prompt;
54
48
  }
@@ -138,8 +132,7 @@ Elyra documentation (read only when the user asks about elyra itself, its SDK, e
138
132
  if (hasRead && skills.length > 0) {
139
133
  prompt += formatSkillsForPrompt(skills);
140
134
  }
141
- // Add date and working directory last
142
- prompt += `\nCurrent date: ${date}`;
135
+ // Add working directory last (date is injected via transformContext for cache stability)
143
136
  prompt += `\nCurrent working directory: ${promptCwd}`;
144
137
  return prompt;
145
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.js\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.js\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** Persistent codebase memory from previous sessions. */\n\tmemoryContent?: string;\n\t/** Detected technology stack summary (from stack-detector). */\n\tstackInfo?: string;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t\tmemoryContent,\n\t\tstackInfo,\n\t} = options;\n\tconst resolvedCwd = cwd;\n\tconst promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n\tconst now = new Date();\n\tconst year = now.getFullYear();\n\tconst month = String(now.getMonth() + 1).padStart(2, \"0\");\n\tconst day = String(now.getDate()).padStart(2, \"0\");\n\tconst date = `${year}-${month}-${day}`;\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append stack info\n\t\tif (stackInfo) {\n\t\t\tprompt += \"\\n\\n# Technology Stack\\n\\n\";\n\t\t\tprompt += stackInfo;\n\t\t\tprompt += \"\\n\";\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t\t}\n\t\t}\n\n\t\t// Append codebase memory\n\t\tif (memoryContent) {\n\t\t\tprompt += \"\\n\\n# Codebase Memory\\n\\n\";\n\t\t\tprompt += \"Knowledge from previous sessions on this project:\\n\\n\";\n\t\t\tprompt += memoryContent;\n\t\t\tprompt += \"\\n\";\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\n\t\t// Add date and working directory last\n\t\tprompt += `\\nCurrent date: ${date}`;\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\taddGuideline(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant operating inside elyra, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n\nElyra documentation (read only when the user asks about elyra itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), elyra packages (docs/packages.md)\n- When working on elyra topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read elyra .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append stack info\n\tif (stackInfo) {\n\t\tprompt += \"\\n\\n# Technology Stack\\n\\n\";\n\t\tprompt += stackInfo;\n\t\tprompt += \"\\n\";\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t}\n\t}\n\n\t// Append codebase memory\n\tif (memoryContent) {\n\t\tprompt += \"\\n\\n# Codebase Memory\\n\\n\";\n\t\tprompt += \"Knowledge from previous sessions on this project:\\n\\n\";\n\t\tprompt += memoryContent;\n\t\tprompt += \"\\n\";\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\n\t// Add date and working directory last\n\tprompt += `\\nCurrent date: ${date}`;\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n"]}
1
+ {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.js\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.js\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** Persistent codebase memory from previous sessions. */\n\tmemoryContent?: string;\n\t/** Detected technology stack summary (from stack-detector). */\n\tstackInfo?: string;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t\tmemoryContent,\n\t\tstackInfo,\n\t} = options;\n\tconst resolvedCwd = cwd;\n\tconst promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append stack info\n\t\tif (stackInfo) {\n\t\t\tprompt += \"\\n\\n# Technology Stack\\n\\n\";\n\t\t\tprompt += stackInfo;\n\t\t\tprompt += \"\\n\";\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t\t}\n\t\t}\n\n\t\t// Append codebase memory\n\t\tif (memoryContent) {\n\t\t\tprompt += \"\\n\\n# Codebase Memory\\n\\n\";\n\t\t\tprompt += \"Knowledge from previous sessions on this project:\\n\\n\";\n\t\t\tprompt += memoryContent;\n\t\t\tprompt += \"\\n\";\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\n\t\t// Add working directory last (date is injected via transformContext for cache stability)\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\taddGuideline(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant operating inside elyra, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n\nElyra documentation (read only when the user asks about elyra itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), elyra packages (docs/packages.md)\n- When working on elyra topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read elyra .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append stack info\n\tif (stackInfo) {\n\t\tprompt += \"\\n\\n# Technology Stack\\n\\n\";\n\t\tprompt += stackInfo;\n\t\tprompt += \"\\n\";\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t}\n\t}\n\n\t// Append codebase memory\n\tif (memoryContent) {\n\t\tprompt += \"\\n\\n# Codebase Memory\\n\\n\";\n\t\tprompt += \"Knowledge from previous sessions on this project:\\n\\n\";\n\t\tprompt += memoryContent;\n\t\tprompt += \"\\n\";\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\n\t// Add working directory last (date is injected via transformContext for cache stability)\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n"]}
@@ -9,9 +9,11 @@ export declare class FooterComponent implements Component {
9
9
  private session;
10
10
  private footerData;
11
11
  private autoCompactEnabled;
12
+ private minimalFooter;
12
13
  constructor(session: AgentSession, footerData: ReadonlyFooterDataProvider);
13
14
  setSession(session: AgentSession): void;
14
15
  setAutoCompactEnabled(enabled: boolean): void;
16
+ setMinimalFooter(enabled: boolean): void;
15
17
  /**
16
18
  * No-op: git branch caching now handled by provider.
17
19
  * Kept for compatibility with existing call sites in interactive-mode.
@@ -1 +1 @@
1
- {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,gBAAgB,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA0BxF;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAI/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IAJnB,OAAO,CAAC,kBAAkB,CAAQ;IAElC,YACS,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC3C;IAEJ,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA0J9B;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@elyracode/tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts (similar to web-ui)\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
1
+ {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,gBAAgB,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA0BxF;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAK/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IALnB,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,aAAa,CAAS;IAE9B,YACS,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC3C;IAEJ,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEvC;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkK9B;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@elyracode/tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts (similar to web-ui)\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate minimalFooter = false;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\tsetMinimalFooter(enabled: boolean): void {\n\t\tthis.minimalFooter = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\tif (this.minimalFooter) {\n\t\t\t// Minimal footer: just cwd/branch and model name\n\t\t\tconst modelName = state.model?.id || \"no-model\";\n\t\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\t\tconst modelLine = truncateToWidth(theme.fg(\"dim\", modelName), width, theme.fg(\"dim\", \"...\"));\n\t\t\treturn [pwdLine, modelLine];\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
@@ -33,6 +33,7 @@ export class FooterComponent {
33
33
  session;
34
34
  footerData;
35
35
  autoCompactEnabled = true;
36
+ minimalFooter = false;
36
37
  constructor(session, footerData) {
37
38
  this.session = session;
38
39
  this.footerData = footerData;
@@ -43,6 +44,9 @@ export class FooterComponent {
43
44
  setAutoCompactEnabled(enabled) {
44
45
  this.autoCompactEnabled = enabled;
45
46
  }
47
+ setMinimalFooter(enabled) {
48
+ this.minimalFooter = enabled;
49
+ }
46
50
  /**
47
51
  * No-op: git branch caching now handled by provider.
48
52
  * Kept for compatibility with existing call sites in interactive-mode.
@@ -96,6 +100,13 @@ export class FooterComponent {
96
100
  if (sessionName) {
97
101
  pwd = `${pwd} • ${sessionName}`;
98
102
  }
103
+ if (this.minimalFooter) {
104
+ // Minimal footer: just cwd/branch and model name
105
+ const modelName = state.model?.id || "no-model";
106
+ const pwdLine = truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "..."));
107
+ const modelLine = truncateToWidth(theme.fg("dim", modelName), width, theme.fg("dim", "..."));
108
+ return [pwdLine, modelLine];
109
+ }
99
110
  // Build stats line
100
111
  const statsParts = [];
101
112
  if (totalInput)
@@ -1 +1 @@
1
- {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG/E,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;GAGG;AACH,SAAS,kBAAkB,CAAC,IAAY,EAAU;IACjD,qFAAqF;IACrF,OAAO,IAAI;SACT,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC;SACzB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,IAAI,EAAE,CAAC;AAAA,CACT;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,KAAa,EAAU;IAC5C,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC1C,IAAI,KAAK,GAAG,KAAK;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,IAAI,KAAK,GAAG,OAAO;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC;IAC3D,IAAI,KAAK,GAAG,QAAQ;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAChE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC;AAAA,CACzC;AAED;;;GAGG;AACH,MAAM,OAAO,eAAe;IAIlB,OAAO;IACP,UAAU;IAJX,kBAAkB,GAAG,IAAI,CAAC;IAElC,YACS,OAAqB,EACrB,UAAsC,EAC7C;uBAFO,OAAO;0BACP,UAAU;IAChB,CAAC;IAEJ,UAAU,CAAC,OAAqB,EAAQ;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAAA,CACvB;IAED,qBAAqB,CAAC,OAAgB,EAAQ;QAC7C,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC;IAAA,CAClC;IAED;;;OAGG;IACH,UAAU,GAAS;QAClB,sDAAsD;IADnC,CAEnB;IAED;;;OAGG;IACH,OAAO,GAAS;QACf,0CAA0C;IAD1B,CAEhB;IAED,MAAM,CAAC,KAAa,EAAY;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAEjC,0FAA0F;QAC1F,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,UAAU,EAAE,EAAE,CAAC;YAC9D,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACpE,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;gBACxC,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;gBAC1C,cAAc,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;gBAChD,eAAe,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC;gBAClD,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAC7C,CAAC;QACF,CAAC;QAED,uEAAuE;QACvE,oEAAoE;QACpE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;QACpD,MAAM,aAAa,GAAG,YAAY,EAAE,aAAa,IAAI,KAAK,CAAC,KAAK,EAAE,aAAa,IAAI,CAAC,CAAC;QACrF,MAAM,mBAAmB,GAAG,YAAY,EAAE,OAAO,IAAI,CAAC,CAAC;QACvD,MAAM,cAAc,GAAG,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAE7F,gCAAgC;QAChC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC;QAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;QACzD,IAAI,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,CAAC;QAED,8BAA8B;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC;QAC9C,IAAI,MAAM,EAAE,CAAC;YACZ,GAAG,GAAG,GAAG,GAAG,KAAK,MAAM,GAAG,CAAC;QAC5B,CAAC;QAED,0BAA0B;QAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC;QACjE,IAAI,WAAW,EAAE,CAAC;YACjB,GAAG,GAAG,GAAG,GAAG,QAAM,WAAW,EAAE,CAAC;QACjC,CAAC;QAED,mBAAmB;QACnB,MAAM,UAAU,GAAG,EAAE,CAAC;QACtB,IAAI,UAAU;YAAE,UAAU,CAAC,IAAI,CAAC,MAAI,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAChE,IAAI,WAAW;YAAE,UAAU,CAAC,IAAI,CAAC,MAAI,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAClE,IAAI,cAAc;YAAE,UAAU,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QACxE,IAAI,eAAe;YAAE,UAAU,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QAE1E,+DAA+D;QAC/D,MAAM,iBAAiB,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACrG,IAAI,SAAS,IAAI,iBAAiB,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YAC/E,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;QAED,6CAA6C;QAC7C,IAAI,iBAAyB,CAAC;QAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,qBAAqB,GAC1B,cAAc,KAAK,GAAG;YACrB,CAAC,CAAC,KAAK,YAAY,CAAC,aAAa,CAAC,GAAG,aAAa,EAAE;YACpD,CAAC,CAAC,GAAG,cAAc,KAAK,YAAY,CAAC,aAAa,CAAC,GAAG,aAAa,EAAE,CAAC;QACxE,IAAI,mBAAmB,GAAG,EAAE,EAAE,CAAC;YAC9B,iBAAiB,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;QAC9D,CAAC;aAAM,IAAI,mBAAmB,GAAG,EAAE,EAAE,CAAC;YACrC,iBAAiB,GAAG,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;QAChE,CAAC;aAAM,CAAC;YACP,iBAAiB,GAAG,qBAAqB,CAAC;QAC3C,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAEnC,IAAI,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAErC,6EAA6E;QAC7E,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,UAAU,CAAC;QAEhD,IAAI,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QAE7C,wCAAwC;QACxC,IAAI,cAAc,GAAG,KAAK,EAAE,CAAC;YAC5B,SAAS,GAAG,eAAe,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACrD,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC;QAED,mFAAmF;QACnF,MAAM,UAAU,GAAG,CAAC,CAAC;QAErB,2DAA2D;QAC3D,IAAI,wBAAwB,GAAG,SAAS,CAAC;QACzC,IAAI,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC;YAC5B,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC;YACnD,wBAAwB;gBACvB,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,mBAAiB,CAAC,CAAC,CAAC,GAAG,SAAS,QAAM,aAAa,EAAE,CAAC;QAC9F,CAAC;QAED,8FAA8F;QAC9F,IAAI,SAAS,GAAG,wBAAwB,CAAC;QACzC,IAAI,IAAI,CAAC,UAAU,CAAC,yBAAyB,EAAE,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YACpE,SAAS,GAAG,IAAI,KAAK,CAAC,KAAM,CAAC,QAAQ,KAAK,wBAAwB,EAAE,CAAC;YACrE,IAAI,cAAc,GAAG,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,GAAG,KAAK,EAAE,CAAC;gBACnE,sBAAsB;gBACtB,SAAS,GAAG,wBAAwB,CAAC;YACtC,CAAC;QACF,CAAC;QAED,MAAM,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,cAAc,CAAC;QAEjE,IAAI,SAAiB,CAAC;QACtB,IAAI,WAAW,IAAI,KAAK,EAAE,CAAC;YAC1B,8CAA8C;YAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,cAAc,GAAG,cAAc,CAAC,CAAC;YACpE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;QAC7C,CAAC;aAAM,CAAC;YACP,8BAA8B;YAC9B,MAAM,iBAAiB,GAAG,KAAK,GAAG,cAAc,GAAG,UAAU,CAAC;YAC9D,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;gBAC3B,MAAM,cAAc,GAAG,eAAe,CAAC,SAAS,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;gBACzE,MAAM,mBAAmB,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;gBACzD,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,cAAc,GAAG,mBAAmB,CAAC,CAAC,CAAC;gBACtF,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,cAAc,CAAC;YAClD,CAAC;iBAAM,CAAC;gBACP,yCAAyC;gBACzC,SAAS,GAAG,SAAS,CAAC;YACvB,CAAC;QACF,CAAC;QAED,uFAAuF;QACvF,qFAAqF;QACrF,sDAAsD;QACtD,MAAM,YAAY,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,sBAAsB;QAC3E,MAAM,YAAY,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAEhD,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACrF,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC;QAErD,wEAAwE;QACxE,MAAM,iBAAiB,GAAG,IAAI,CAAC,UAAU,CAAC,oBAAoB,EAAE,CAAC;QACjE,IAAI,iBAAiB,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;iBAC5D,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;iBACtC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9C,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5C,iFAAiF;YACjF,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACxE,CAAC;QAED,OAAO,KAAK,CAAC;IAAA,CACb;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@elyracode/tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts (similar to web-ui)\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@elyracode/tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts (similar to web-ui)\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate minimalFooter = false;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\tsetMinimalFooter(enabled: boolean): void {\n\t\tthis.minimalFooter = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\tif (this.minimalFooter) {\n\t\t\t// Minimal footer: just cwd/branch and model name\n\t\t\tconst modelName = state.model?.id || \"no-model\";\n\t\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\t\tconst modelLine = truncateToWidth(theme.fg(\"dim\", modelName), width, theme.fg(\"dim\", \"...\"));\n\t\t\treturn [pwdLine, modelLine];\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
@@ -309,6 +309,7 @@ export declare class InteractiveMode {
309
309
  private handleUnpinCommand;
310
310
  private handlePinsCommand;
311
311
  private handleBlueprintCommand;
312
+ private handleFooterCommand;
312
313
  private handleThemeCommand;
313
314
  private showThemeSelector;
314
315
  private handleModelCommand;