@letta-ai/letta-code 0.33.7 → 0.34.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 (77) hide show
  1. package/dist/agent-presets.js +2 -2
  2. package/dist/agent-presets.js.map +2 -2
  3. package/dist/ask-user-question.cjs +128 -0
  4. package/dist/ask-user-question.cjs.map +10 -0
  5. package/dist/ask-user-question.js +88 -0
  6. package/dist/ask-user-question.js.map +10 -0
  7. package/dist/channels-public.js.map +1 -1
  8. package/dist/gateway-core.js +6 -20
  9. package/dist/gateway-core.js.map +5 -6
  10. package/dist/mcp-client.js +8 -7
  11. package/dist/mcp-client.js.map +3 -3
  12. package/dist/mcp-oauth.js +23 -9
  13. package/dist/mcp-oauth.js.map +4 -4
  14. package/dist/types/agent/skills.d.ts.map +1 -1
  15. package/dist/types/agent-settings.d.ts +23 -0
  16. package/dist/types/agent-settings.d.ts.map +1 -0
  17. package/dist/types/ask-user-question.d.ts +31 -0
  18. package/dist/types/ask-user-question.d.ts.map +1 -0
  19. package/dist/types/backend/dev/pi-ollama-provider.d.ts +7 -16
  20. package/dist/types/backend/dev/pi-ollama-provider.d.ts.map +1 -1
  21. package/dist/types/channels/gateway-core.d.ts.map +1 -1
  22. package/dist/types/channels/message-channel-formatting.d.ts.map +1 -1
  23. package/dist/types/cli/helpers/tool-name-mapping.d.ts +0 -8
  24. package/dist/types/cli/helpers/tool-name-mapping.d.ts.map +1 -1
  25. package/dist/types/constants.d.ts +1 -1
  26. package/dist/types/constants.d.ts.map +1 -1
  27. package/dist/types/experiments/manager.d.ts.map +1 -1
  28. package/dist/types/experiments/types.d.ts +1 -1
  29. package/dist/types/experiments/types.d.ts.map +1 -1
  30. package/dist/types/mcp-client.d.ts +4 -0
  31. package/dist/types/mcp-client.d.ts.map +1 -1
  32. package/dist/types/mcp-oauth-public.d.ts +10 -1
  33. package/dist/types/mcp-oauth-public.d.ts.map +1 -1
  34. package/dist/types/permissions/checker.d.ts.map +1 -1
  35. package/dist/types/settings-manager.d.ts +5 -14
  36. package/dist/types/settings-manager.d.ts.map +1 -1
  37. package/dist/types/telemetry/error-reporting.d.ts +8 -0
  38. package/dist/types/telemetry/error-reporting.d.ts.map +1 -1
  39. package/dist/types/telemetry/index.d.ts +2 -7
  40. package/dist/types/telemetry/index.d.ts.map +1 -1
  41. package/dist/types/tools/client-preferences.d.ts +8 -0
  42. package/dist/types/tools/client-preferences.d.ts.map +1 -0
  43. package/dist/types/tools/impl/ask-user-question.d.ts +6 -18
  44. package/dist/types/tools/impl/ask-user-question.d.ts.map +1 -1
  45. package/dist/types/tools/impl/monitor.d.ts +0 -1
  46. package/dist/types/tools/impl/monitor.d.ts.map +1 -1
  47. package/dist/types/tools/impl/watch-pr.d.ts.map +1 -1
  48. package/dist/types/tools/manager.d.ts.map +1 -1
  49. package/dist/types/tools/tool-definitions.d.ts +1 -1
  50. package/dist/types/tools/toolset-catalog.d.ts +1 -1
  51. package/dist/types/tools/toolset-catalog.d.ts.map +1 -1
  52. package/dist/types/tools/toolset.d.ts.map +1 -1
  53. package/dist/types/types/client-preferences.d.ts +7 -0
  54. package/dist/types/types/client-preferences.d.ts.map +1 -0
  55. package/dist/types/types/protocol_v2.d.ts +5 -8
  56. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  57. package/dist/types/types/teleport-protocol.d.ts +3 -0
  58. package/dist/types/types/teleport-protocol.d.ts.map +1 -1
  59. package/dist/types/utils/task-notifications.d.ts +0 -1
  60. package/dist/types/utils/task-notifications.d.ts.map +1 -1
  61. package/dist/types/websocket/listener/protocol-outbound.d.ts.map +1 -1
  62. package/dist/types/websocket/listener/queue.d.ts.map +1 -1
  63. package/dist/types/websocket/listener/runtime.d.ts.map +1 -1
  64. package/dist/types/websocket/listener/types.d.ts +1 -9
  65. package/dist/types/websocket/listener/types.d.ts.map +1 -1
  66. package/image-resize-worker.js +2 -6449
  67. package/letta.js +41728 -41891
  68. package/package.json +16 -2
  69. package/scripts/isolated-unit-tests.json +10 -0
  70. package/scripts/smoke-image-worker.mjs +68 -0
  71. package/scripts/source-file-size-baseline.json +13 -13
  72. package/scripts/stage-pypi-deps.mjs +1 -11
  73. package/scripts/unit-test-impact.test.cjs +2 -1
  74. package/skills/curating-memory-palace/SKILL.md +147 -0
  75. package/skills/initializing-memory/SKILL.md +1 -1
  76. package/dist/types/tools/interactive-policy.d.ts +0 -12
  77. package/dist/types/tools/interactive-policy.d.ts.map +0 -1
@@ -1713,7 +1713,7 @@ user: What is the codebase structure?
1713
1713
  assistant: [Uses the Agent tool with subagent_type=general-purpose]
1714
1714
  </example>
1715
1715
 
1716
- Tools are executed in a user-selected permission mode. When you attempt to call a tool that is not automatically allowed by the user's permission mode or permission settings, the user will be prompted so that they can approve or deny the execution. If the user denies a tool you call, do not re-attempt the exact same tool call. Instead, think about why the user has denied the tool call and adjust your approach. If you do not understand why the user has denied a tool call, use the AskUserQuestion to ask them.
1716
+ Tools are executed in a user-selected permission mode. When you attempt to call a tool that is not automatically allowed by the user's permission mode or permission settings, the user will be prompted so that they can approve or deny the execution. If the user denies a tool you call, do not re-attempt the exact same tool call. Instead, think about why the user has denied the tool call and adjust your approach. If you do not understand why the user has denied a tool call, ask them using AskUserQuestion when available, or an ordinary message otherwise.
1717
1717
 
1718
1718
  - Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.
1719
1719
  - The conversation has unlimited context through automatic summarization.
@@ -2586,4 +2586,4 @@ export {
2586
2586
  DEFAULT_CREATED_AGENT_BASE_TOOLS
2587
2587
  };
2588
2588
 
2589
- //# debugId=89470D292266CA6164756E2164756E21
2589
+ //# debugId=006C28A4424C7CD564756E2164756E21
@@ -3,7 +3,7 @@
3
3
  "sources": ["../src/agent/agent-tags.ts", "../src/constants.ts", "../src/agent/memory-constants.ts", "../src/agent/prompt-assets.ts", "../src/agent/memory.ts", "../src/agent/model-catalog.ts", "../src/agent/personality-presets.ts", "../src/agent/create-agent-request.ts"],
4
4
  "sourcesContent": [
5
5
  "/**\n * Tags that identify Letta Code agents and their capabilities.\n *\n * This module must stay free of Node/backend imports: it is bundled into the\n * browser-safe `@letta-ai/letta-code/agent-presets` package export.\n */\n\n/** Marks an agent as created/managed by Letta Code. */\nexport const LETTA_CODE_ORIGIN_TAG = \"origin:letta-code\";\n\n/** Marks an agent as created by a first-run onboarding flow. */\nexport const ONBOARDING_ORIGIN_TAG = \"origin:onboarding\";\n\n/** Marks an agent as a Letta Code subagent (excluded from prompt management). */\nexport const LETTA_CODE_SUBAGENT_TAG = \"role:subagent\";\n\n/** Marks an agent as using git-backed memory (MemFS). */\nexport const GIT_MEMORY_ENABLED_TAG = \"git-memory-enabled\";\n\nexport interface BuildCreatedAgentTagsOptions {\n tags?: string[] | null;\n isSubagent?: boolean;\n enableMemfs?: boolean;\n}\n\nexport function buildCreatedAgentTags(\n options: BuildCreatedAgentTagsOptions = {},\n): string[] {\n const tags = [LETTA_CODE_ORIGIN_TAG];\n if (options.isSubagent) {\n tags.push(LETTA_CODE_SUBAGENT_TAG);\n }\n if (options.enableMemfs) {\n tags.push(GIT_MEMORY_ENABLED_TAG);\n }\n if (options.tags && Array.isArray(options.tags)) {\n tags.push(...options.tags);\n }\n return Array.from(new Set(tags));\n}\n",
6
- "/**\n * Application-wide constants\n */\n\n/**\n * Default model ID to use when no model is specified\n */\nexport const DEFAULT_MODEL_ID = \"auto\";\n\n/**\n * Default model handle to use for conversation compaction / summarization.\n */\nexport const DEFAULT_SUMMARIZATION_MODEL = \"letta/auto\";\n\n/**\n * Default model handle for lightweight conversation title generation.\n */\nexport const DEFAULT_TITLE_SUMMARIZATION_MODEL = \"openai/gpt-5.6-luna\";\n\n/**\n * Default agent name when creating a new agent\n */\nexport const DEFAULT_AGENT_NAME = \"Letta Code\";\n\n/**\n * Message displayed when user interrupts tool execution\n */\nexport const INTERRUPTED_BY_USER = \"Interrupted by user\";\n\n/**\n * Synthetic tool result injected when a turn ended without completing all tool calls\n * (e.g. process crash or unhandled stream error). Settled automatically at the start\n * of the next turn so the conversation history stays valid for the provider.\n */\nexport const TURN_DID_NOT_COMPLETE = \"Turn did not complete\";\n\n/**\n * XML tag used to wrap system reminder content injected into messages\n */\nexport const SYSTEM_REMINDER_TAG = \"system-reminder\";\nexport const SYSTEM_REMINDER_OPEN = `<${SYSTEM_REMINDER_TAG}>`;\nexport const SYSTEM_REMINDER_CLOSE = `</${SYSTEM_REMINDER_TAG}>`;\n// Legacy tag kept for parsing/backward compatibility with older saved messages.\nexport const SYSTEM_ALERT_TAG = \"system-alert\";\nexport const SYSTEM_ALERT_OPEN = `<${SYSTEM_ALERT_TAG}>`;\nexport const SYSTEM_ALERT_CLOSE = `</${SYSTEM_ALERT_TAG}>`;\n\n/**\n * How often (in turns) to check for memfs sync conflicts, even without\n * filesystem change events. Catches block-only changes (e.g. ADE/API edits).\n */\nexport const MEMFS_CONFLICT_CHECK_INTERVAL = 5;\n\n/**\n * Header displayed before compaction summary when conversation context is truncated\n */\nexport const COMPACTION_SUMMARY_HEADER =\n \"(Earlier messages in this conversation have been compacted to free up context, summarized below)\";\n\n/**\n * Status bar thresholds - only show indicators when values exceed these\n */\n// Show token count after 100 estimated tokens (shows exact count until 1k, then compact)\nexport const TOKEN_DISPLAY_THRESHOLD = 100;\n// Show elapsed time after 2 minutes (in ms)\nexport const ELAPSED_DISPLAY_THRESHOLD_MS = 60 * 1000;\n",
6
+ "/**\n * Application-wide constants\n */\n\n/**\n * Default model ID to use when no model is specified\n */\nexport const DEFAULT_MODEL_ID = \"auto\";\n\n/**\n * Default model handle to use for conversation compaction / summarization.\n */\nexport const DEFAULT_SUMMARIZATION_MODEL = \"letta/auto\";\n\n/**\n * Default model handle for lightweight conversation title generation.\n */\nexport const DEFAULT_TITLE_SUMMARIZATION_MODEL = \"openai/gpt-6-luna\";\n\n/**\n * Default agent name when creating a new agent\n */\nexport const DEFAULT_AGENT_NAME = \"Letta Code\";\n\n/**\n * Message displayed when user interrupts tool execution\n */\nexport const INTERRUPTED_BY_USER = \"Interrupted by user\";\n\n/**\n * Synthetic tool result injected when a turn ended without completing all tool calls\n * (e.g. process crash or unhandled stream error). Settled automatically at the start\n * of the next turn so the conversation history stays valid for the provider.\n */\nexport const TURN_DID_NOT_COMPLETE = \"Turn did not complete\";\n\n/**\n * XML tag used to wrap system reminder content injected into messages\n */\nexport const SYSTEM_REMINDER_TAG = \"system-reminder\";\nexport const SYSTEM_REMINDER_OPEN = `<${SYSTEM_REMINDER_TAG}>`;\nexport const SYSTEM_REMINDER_CLOSE = `</${SYSTEM_REMINDER_TAG}>`;\n// Legacy tag kept for parsing/backward compatibility with older saved messages.\nexport const SYSTEM_ALERT_TAG = \"system-alert\";\nexport const SYSTEM_ALERT_OPEN = `<${SYSTEM_ALERT_TAG}>`;\nexport const SYSTEM_ALERT_CLOSE = `</${SYSTEM_ALERT_TAG}>`;\n\n/**\n * How often (in turns) to check for memfs sync conflicts, even without\n * filesystem change events. Catches block-only changes (e.g. ADE/API edits).\n */\nexport const MEMFS_CONFLICT_CHECK_INTERVAL = 5;\n\n/**\n * Header displayed before compaction summary when conversation context is truncated\n */\nexport const COMPACTION_SUMMARY_HEADER =\n \"(Earlier messages in this conversation have been compacted to free up context, summarized below)\";\n\n/**\n * Status bar thresholds - only show indicators when values exceed these\n */\n// Show token count after 100 estimated tokens (shows exact count until 1k, then compact)\nexport const TOKEN_DISPLAY_THRESHOLD = 100;\n// Show elapsed time after 2 minutes (in ms)\nexport const ELAPSED_DISPLAY_THRESHOLD_MS = 60 * 1000;\n",
7
7
  "export const READ_ONLY_BLOCK_LABELS = [\"memory_filesystem\"] as const;\n",
8
8
  "// Additional system prompts for /system command\n\nimport approvalRecoveryAlert from \"./prompts/approval_recovery_alert.txt\";\nimport humanPrompt from \"./prompts/human.mdx\";\nimport humanKawaiiPrompt from \"./prompts/human_kawaii.mdx\";\nimport humanLinusPrompt from \"./prompts/human_linus.mdx\";\nimport humanMemoPrompt from \"./prompts/human_memo.mdx\";\nimport humanTutorialPrompt from \"./prompts/human_tutorial.mdx\";\nimport interruptRecoveryAlert from \"./prompts/interrupt_recovery_alert.txt\";\nimport lettaMemfsPrompt from \"./prompts/letta.md\";\nimport lettaLocalMemfsPrompt from \"./prompts/letta_local_memfs.md\";\nimport lettaNoMemfsPrompt from \"./prompts/letta_no_memfs.md\";\nimport lettaRootMemfsPrompt from \"./prompts/letta_root_memfs.md\";\nimport memoryFilesystemPrompt from \"./prompts/memory_filesystem.mdx\";\nimport onboardingPrompt from \"./prompts/onboarding.mdx\";\nimport onboardingLocalPrompt from \"./prompts/onboarding_local.mdx\";\nimport personaPrompt from \"./prompts/persona.mdx\";\nimport personaBlankPrompt from \"./prompts/persona_blank.mdx\";\nimport personaKawaiiPrompt from \"./prompts/persona_kawaii.mdx\";\nimport personaLinusPrompt from \"./prompts/persona_linus.mdx\";\nimport personaMemoPrompt from \"./prompts/persona_memo.mdx\";\nimport personaTutorialPrompt from \"./prompts/persona_tutorial.mdx\";\nimport projectPrompt from \"./prompts/project.mdx\";\nimport skillCreatorModePrompt from \"./prompts/skill_creator_mode.md\";\nimport sourceClaudePrompt from \"./prompts/source_claude.md\";\nimport sourceCodexPrompt from \"./prompts/source_codex.md\";\nimport sourceGeminiPrompt from \"./prompts/source_gemini.md\";\n\nimport stylePrompt from \"./prompts/style.mdx\";\n\nexport const SYSTEM_PROMPT = lettaNoMemfsPrompt;\n\nexport const SKILL_CREATOR_PROMPT = skillCreatorModePrompt;\nexport const APPROVAL_RECOVERY_PROMPT = approvalRecoveryAlert;\nexport const INTERRUPT_RECOVERY_ALERT = interruptRecoveryAlert;\n\nexport const MEMORY_PROMPTS: Record<string, string> = {\n \"persona.mdx\": personaPrompt,\n \"persona_blank.mdx\": personaBlankPrompt,\n \"persona_kawaii.mdx\": personaKawaiiPrompt,\n \"persona_linus.mdx\": personaLinusPrompt,\n \"persona_memo.mdx\": personaMemoPrompt,\n \"persona_tutorial.mdx\": personaTutorialPrompt,\n \"human.mdx\": humanPrompt,\n \"human_kawaii.mdx\": humanKawaiiPrompt,\n \"human_linus.mdx\": humanLinusPrompt,\n \"human_memo.mdx\": humanMemoPrompt,\n \"human_tutorial.mdx\": humanTutorialPrompt,\n \"project.mdx\": projectPrompt,\n\n \"memory_filesystem.mdx\": memoryFilesystemPrompt,\n \"onboarding.mdx\": onboardingPrompt,\n \"onboarding_local.mdx\": onboardingLocalPrompt,\n \"style.mdx\": stylePrompt,\n};\n\n// System prompt options for /system command\nexport interface SystemPromptOption {\n id: string;\n label: string;\n description: string;\n content: string;\n memfsContent?: string;\n rootMemfsContent?: string;\n localMemfsContent?: string;\n isDefault?: boolean;\n isFeatured?: boolean;\n}\n\nexport const SYSTEM_PROMPTS: SystemPromptOption[] = [\n {\n id: \"default\",\n label: \"Default\",\n description: \"Alias for letta\",\n content: lettaNoMemfsPrompt,\n memfsContent: lettaMemfsPrompt,\n rootMemfsContent: lettaRootMemfsPrompt,\n localMemfsContent: lettaLocalMemfsPrompt,\n isDefault: true,\n isFeatured: true,\n },\n {\n id: \"letta\",\n label: \"Letta Code\",\n description: \"Full Letta Code system prompt\",\n content: lettaNoMemfsPrompt,\n memfsContent: lettaMemfsPrompt,\n rootMemfsContent: lettaRootMemfsPrompt,\n localMemfsContent: lettaLocalMemfsPrompt,\n isFeatured: true,\n },\n {\n id: \"source-claude\",\n label: \"Claude Code\",\n description: \"Source-faithful Claude Code prompt (for benchmarking)\",\n content: sourceClaudePrompt,\n },\n {\n id: \"source-codex\",\n label: \"Codex\",\n description: \"Source-faithful OpenAI Codex prompt (for benchmarking)\",\n content: sourceCodexPrompt,\n },\n {\n id: \"source-gemini\",\n label: \"Gemini CLI\",\n description: \"Source-faithful Gemini CLI prompt (for benchmarking)\",\n content: sourceGeminiPrompt,\n },\n];\n\nexport type MemoryPromptMode =\n | \"standard\"\n | \"memfs\"\n | \"root-memfs\"\n | \"local-memfs\";\n\nexport function getSystemPromptVariantContents(\n prompt: SystemPromptOption,\n): string[] {\n return [\n prompt.content,\n prompt.memfsContent,\n prompt.rootMemfsContent,\n prompt.localMemfsContent,\n ].filter((content): content is string => typeof content === \"string\");\n}\n\n/**\n * Check if a preset ID exists in SYSTEM_PROMPTS.\n */\nexport function isKnownPreset(id: string): boolean {\n return SYSTEM_PROMPTS.some((p) => p.id === id);\n}\n\n/**\n * Deterministic rebuild of a system prompt from a known preset + memory mode.\n * Throws on unknown preset (prevents stale/renamed presets from silently rewriting prompts).\n */\nexport function buildSystemPrompt(\n presetId: string,\n memoryMode: MemoryPromptMode,\n): string {\n const preset = SYSTEM_PROMPTS.find((p) => p.id === presetId);\n if (!preset) {\n throw new Error(\n `Unknown preset \"${presetId}\" — cannot rebuild system prompt`,\n );\n }\n if (memoryMode === \"local-memfs\") {\n return (\n preset.localMemfsContent ??\n preset.memfsContent ??\n preset.content\n ).trim();\n }\n if (memoryMode === \"root-memfs\") {\n return (\n preset.rootMemfsContent ??\n preset.memfsContent ??\n preset.content\n ).trim();\n }\n if (memoryMode === \"memfs\") {\n return (preset.memfsContent ?? preset.content).trim();\n }\n\n return preset.content.trim();\n}\n\n/**\n * Returns true if the agent is not on the current default preset\n * and would benefit from switching to `/system default`.\n */\nexport function shouldRecommendDefaultPrompt(\n currentPrompt: string,\n memoryMode: MemoryPromptMode,\n): boolean {\n const defaultPrompt = buildSystemPrompt(\"default\", memoryMode);\n return currentPrompt !== defaultPrompt;\n}\n",
9
9
  "/**\n * Agent memory block management\n * Loads memory blocks from .mdx files in src/agent/prompts\n */\n\nimport type { CreateBlock } from \"@letta-ai/letta-client/resources/blocks/blocks\";\nimport { READ_ONLY_BLOCK_LABELS } from \"./memory-constants\";\nimport { MEMORY_PROMPTS } from \"./prompt-assets\";\n\n/**\n * The memory block labels every standard (non-MemFS) agent is created with.\n * Each maps to a `<label>.mdx` file in src/agent/prompts. Per-project blocks\n * (skills/loaded_skills) were removed in LET-7353 — skills are now injected\n * via system reminders, leaving only these defaults.\n */\nexport const MEMORY_BLOCK_LABELS = [\"persona\", \"human\"] as const;\n\n/**\n * Block labels that should be read-only (agent cannot modify via memory tools).\n */\nexport { READ_ONLY_BLOCK_LABELS };\n\n/**\n * Parse frontmatter and content from an .mdx file\n */\nexport function parseMdxFrontmatter(content: string): {\n frontmatter: Record<string, string>;\n body: string;\n} {\n const frontmatterRegex = /^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/;\n const match = content.match(frontmatterRegex);\n\n if (!match || !match[1] || !match[2]) {\n return { frontmatter: {}, body: content };\n }\n\n const frontmatterText = match[1];\n const body = match[2];\n const frontmatter: Record<string, string> = {};\n\n // Parse YAML-like frontmatter (simple key: value pairs)\n for (const line of frontmatterText.split(\"\\n\")) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim();\n const value = line.slice(colonIndex + 1).trim();\n frontmatter[key] = value;\n }\n }\n\n return { frontmatter, body: body.trim() };\n}\n\n/**\n * Load memory blocks from .mdx files in src/agent/prompts\n */\nasync function loadMemoryBlocksFromMdx(): Promise<CreateBlock[]> {\n const memoryBlocks: CreateBlock[] = [];\n\n const mdxFiles = MEMORY_BLOCK_LABELS.map((label) => `${label}.mdx`);\n\n for (const filename of mdxFiles) {\n try {\n const content = MEMORY_PROMPTS[filename];\n if (!content) {\n console.warn(`Missing embedded prompt file: ${filename}`);\n continue;\n }\n const { frontmatter, body } = parseMdxFrontmatter(content);\n\n const label = frontmatter.label || filename.replace(\".mdx\", \"\");\n const block: CreateBlock = {\n label,\n value: body,\n };\n\n if (frontmatter.description) {\n block.description = frontmatter.description;\n }\n\n // Set read-only for blocks managed by specific tools (not memory tools)\n if ((READ_ONLY_BLOCK_LABELS as readonly string[]).includes(label)) {\n block.read_only = true;\n }\n\n memoryBlocks.push(block);\n } catch (error) {\n console.error(`Error loading ${filename}:`, error);\n }\n }\n\n return memoryBlocks;\n}\n\n// Cache for loaded memory blocks\nlet cachedMemoryBlocks: CreateBlock[] | null = null;\n\n/**\n * Get default starter memory blocks for new agents\n */\nexport async function getDefaultMemoryBlocks(): Promise<CreateBlock[]> {\n if (!cachedMemoryBlocks) {\n cachedMemoryBlocks = await loadMemoryBlocksFromMdx();\n }\n return cachedMemoryBlocks;\n}\n",
@@ -12,6 +12,6 @@
12
12
  "/**\n * Pure builder for the `POST /v1/agents` wire payload of a Letta Code agent.\n *\n * This is the shared creation-policy choke point for the CLI and external\n * harness surfaces. It owns the defaults that must not drift between callers:\n * agent type, prompt mode, tags, server tools, initial messages, parallel tool\n * calls, and compaction. Callers may supply either a Letta Code personality or\n * their own identity blocks without rebuilding that policy.\n *\n * Everything reachable from this module must stay free of Node/backend imports\n * so it can be bundled in the browser-safe `agent-presets` package export.\n */\n\nimport type { CreateBlock } from \"@letta-ai/letta-client/resources/blocks/blocks\";\nimport { DEFAULT_SUMMARIZATION_MODEL } from \"@/constants\";\nimport { buildCreatedAgentTags } from \"./agent-tags\";\nimport { getDefaultMemoryBlocks } from \"./memory\";\nimport { getDefaultModel, resolveModel } from \"./model-catalog\";\nimport {\n buildPersonalityMemoryBlocks,\n getPersonalityCreationTags,\n getPersonalityDefaultMemoryFiles,\n getPersonalityOption,\n type PersonalityId,\n} from \"./personality-presets\";\nimport { buildSystemPrompt, type MemoryPromptMode } from \"./prompt-assets\";\n\n/** Agent type used for all Letta Code agents. */\nexport const LETTA_CODE_AGENT_TYPE = \"letta_v1_agent\";\n\n/**\n * Server-side tools attached to created agents. Client-side tools (Read,\n * Write, Bash, etc.) are passed via client_tools at runtime instead.\n */\nexport const DEFAULT_CREATED_AGENT_BASE_TOOLS = [\"web_search\", \"fetch_webpage\"];\n\nexport type CreateAgentMemoryBlock = CreateBlock;\n\n/**\n * Root marker that makes the hosted memory repository use MemFS v2.\n * The server seeds the root `MEMORY.md` layout only when the create request\n * includes a block with this label.\n */\nexport const DEFAULT_ROOT_MEMORY_BLOCK: CreateAgentMemoryBlock = {\n label: \"MEMORY\",\n value: \"# Memory\\n\",\n description: \"Root memory index.\",\n};\n\nexport interface BuildCreateAgentRequestOptions {\n personalityId?: PersonalityId;\n name?: string;\n description?: string;\n /** Model ID or handle. Personality default, then catalog default, applies. */\n model?: string;\n /** Complete prompt override. Null delegates default selection to the server. */\n system?: string | null;\n /** Whether the target is Letta Cloud; callers determine this with their backend's cloud detection. */\n isLettaCloud?: boolean;\n memoryPromptMode?: MemoryPromptMode;\n /**\n * Caller-defined identity. With a personality, matching labels replace its\n * blocks and new labels append after the personality blocks.\n */\n memoryBlocks?: CreateAgentMemoryBlock[];\n blockIds?: string[];\n /** Extra tags appended after canonical Letta Code and personality tags. */\n extraTags?: string[];\n enableMemfs?: boolean;\n isSubagent?: boolean;\n /** Exact server-side tools. Omission uses the Letta Code web-tool defaults. */\n baseTools?: string[];\n embedding?: string;\n hidden?: boolean;\n parallelToolCalls?: boolean;\n compactionModel?: string;\n}\n\nexport interface CreateAgentRequest {\n agent_type: typeof LETTA_CODE_AGENT_TYPE;\n name?: string;\n description?: string;\n model: string;\n system: string | null;\n memory_blocks?: CreateAgentMemoryBlock[];\n block_ids?: string[];\n tags: string[];\n tools: string[];\n include_base_tools: false;\n include_base_tool_rules: false;\n initial_message_sequence: never[];\n parallel_tool_calls: boolean;\n compaction_settings: { model: string };\n embedding?: string;\n hidden?: boolean;\n}\n\nexport type CreateAgentRequestForPersonality = CreateAgentRequest & {\n name: string;\n description: string;\n memory_blocks: CreateAgentMemoryBlock[];\n profile_picture?: {\n content: string;\n };\n};\n\nfunction mergeMemoryBlocks(\n base: CreateAgentMemoryBlock[],\n overrides: CreateAgentMemoryBlock[] | undefined,\n): CreateAgentMemoryBlock[] {\n const blocks = base.map((block) => ({ ...block }));\n for (const override of overrides ?? []) {\n const index = blocks.findIndex((block) => block.label === override.label);\n if (index >= 0) {\n blocks[index] = { ...override };\n } else {\n blocks.push({ ...override });\n }\n }\n return blocks;\n}\n\nfunction ensureRootMemoryBlock(\n blocks: CreateAgentMemoryBlock[],\n): CreateAgentMemoryBlock[] {\n return blocks.some((block) => block.label === DEFAULT_ROOT_MEMORY_BLOCK.label)\n ? blocks\n : [{ ...DEFAULT_ROOT_MEMORY_BLOCK }, ...blocks];\n}\n\n/** Build the canonical Core create-agent request for a Letta Code agent. */\nexport async function buildCreateAgentRequest(\n options: BuildCreateAgentRequestOptions = {},\n): Promise<CreateAgentRequest> {\n const personality = options.personalityId\n ? getPersonalityOption(options.personalityId)\n : undefined;\n const modelIdentifier = options.model ?? personality?.defaultModel;\n const modelHandle = modelIdentifier\n ? resolveModel(modelIdentifier)\n : getDefaultModel();\n if (!modelHandle) {\n throw new Error(`Unknown model: ${modelIdentifier}`);\n }\n\n if (\n !options.isSubagent &&\n options.enableMemfs !== undefined &&\n options.memoryPromptMode !== undefined &&\n options.enableMemfs !== (options.memoryPromptMode !== \"standard\")\n ) {\n throw new Error(\n \"enableMemfs and memoryPromptMode must describe the same memory mode\",\n );\n }\n const enableMemfs = options.isSubagent\n ? false\n : (options.enableMemfs ?? options.memoryPromptMode !== \"standard\");\n const memoryPromptMode = options.isSubagent\n ? \"standard\"\n : (options.memoryPromptMode ?? (enableMemfs ? \"root-memfs\" : \"standard\"));\n const personalityTags = options.personalityId\n ? getPersonalityCreationTags(options.personalityId)\n : [];\n const personalityBlocks = options.personalityId\n ? buildPersonalityMemoryBlocks(\n options.personalityId,\n await getDefaultMemoryBlocks(),\n )\n : [];\n const hasSuppliedMemoryBlocks =\n options.personalityId !== undefined || options.memoryBlocks !== undefined;\n const suppliedMemoryBlocks = hasSuppliedMemoryBlocks\n ? mergeMemoryBlocks(personalityBlocks, options.memoryBlocks)\n : [];\n // Root-layout agents must send the MEMORY block so the server seeds a\n // MemFS v2 repository; without it the initial repo uses the legacy\n // system/ layout even when the prompt mode says otherwise.\n const memoryBlocks = options.isSubagent\n ? undefined\n : memoryPromptMode === \"root-memfs\"\n ? ensureRootMemoryBlock(suppliedMemoryBlocks)\n : hasSuppliedMemoryBlocks\n ? suppliedMemoryBlocks\n : undefined;\n const blockIds = options.isSubagent ? undefined : options.blockIds;\n\n return {\n agent_type: LETTA_CODE_AGENT_TYPE,\n ...(options.name !== undefined || personality\n ? { name: options.name ?? personality?.label }\n : {}),\n ...(options.description !== undefined || personality\n ? { description: options.description ?? personality?.description }\n : {}),\n model: modelHandle,\n system:\n options.system !== undefined\n ? options.system\n : options.isLettaCloud\n ? null\n : buildSystemPrompt(\"default\", memoryPromptMode),\n ...(memoryBlocks !== undefined ? { memory_blocks: memoryBlocks } : {}),\n ...(blockIds && blockIds.length > 0 ? { block_ids: blockIds } : {}),\n tags: buildCreatedAgentTags({\n enableMemfs,\n isSubagent: options.isSubagent,\n tags: [...personalityTags, ...(options.extraTags ?? [])],\n }),\n tools: [...(options.baseTools ?? DEFAULT_CREATED_AGENT_BASE_TOOLS)],\n include_base_tools: false,\n include_base_tool_rules: false,\n initial_message_sequence: [],\n parallel_tool_calls: options.parallelToolCalls ?? true,\n compaction_settings: {\n model: options.compactionModel ?? DEFAULT_SUMMARIZATION_MODEL,\n },\n ...(options.embedding !== undefined\n ? { embedding: options.embedding }\n : {}),\n ...(options.isSubagent\n ? { hidden: true }\n : options.hidden !== undefined\n ? { hidden: options.hidden }\n : {}),\n };\n}\n\n/** Compatibility wrapper for callers that create a personality agent. */\nexport async function buildCreateAgentRequestForPersonality(params: {\n personalityId: PersonalityId;\n name?: string;\n description?: string;\n model?: string;\n extraTags?: string[];\n /** This browser-safe wrapper defaults to Cloud; pass false for a local server. */\n isLettaCloud?: boolean;\n}): Promise<CreateAgentRequestForPersonality> {\n const request = (await buildCreateAgentRequest({\n ...params,\n isLettaCloud: params.isLettaCloud ?? true,\n })) as CreateAgentRequestForPersonality;\n const profilePicture = getPersonalityDefaultMemoryFiles(\n params.personalityId,\n ).find((file) => file.path === \"profile.png\");\n if (!profilePicture) {\n return request;\n }\n const { getPersonalityAssetBase64 } = await import(\n \"./personality-asset-content\"\n );\n return {\n ...request,\n profile_picture: {\n content: await getPersonalityAssetBase64(profilePicture.assetId),\n },\n };\n}\n"
13
13
  ],
14
14
  "mappings": ";;;;;AAQO,IAAM,wBAAwB;AAG9B,IAAM,wBAAwB;AAG9B,IAAM,0BAA0B;AAGhC,IAAM,yBAAyB;AAQ/B,SAAS,qBAAqB,CACnC,UAAwC,CAAC,GAC/B;AAAA,EACV,MAAM,OAAO,CAAC,qBAAqB;AAAA,EACnC,IAAI,QAAQ,YAAY;AAAA,IACtB,KAAK,KAAK,uBAAuB;AAAA,EACnC;AAAA,EACA,IAAI,QAAQ,aAAa;AAAA,IACvB,KAAK,KAAK,sBAAsB;AAAA,EAClC;AAAA,EACA,IAAI,QAAQ,QAAQ,MAAM,QAAQ,QAAQ,IAAI,GAAG;AAAA,IAC/C,KAAK,KAAK,GAAG,QAAQ,IAAI;AAAA,EAC3B;AAAA,EACA,OAAO,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA;;AC1B1B,IAAM,8BAA8B;AA2BpC,IAAM,sBAAsB;AAC5B,IAAM,uBAAuB,IAAI;AACjC,IAAM,wBAAwB,KAAK;AAEnC,IAAM,mBAAmB;AACzB,IAAM,oBAAoB,IAAI;AAC9B,IAAM,qBAAqB,KAAK;AAoBhC,IAAM,+BAA+B,KAAK;;;ACjE1C,IAAM,yBAAyB,CAAC,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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",
15
- "debugId": "89470D292266CA6164756E2164756E21",
15
+ "debugId": "006C28A4424C7CD564756E2164756E21",
16
16
  "names": []
17
17
  }
@@ -0,0 +1,128 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
8
+ var __toCommonJS = (from) => {
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
10
+ if (entry)
11
+ return entry;
12
+ entry = __defProp({}, "__esModule", { value: true });
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
21
+ __moduleCache.set(from, entry);
22
+ return entry;
23
+ };
24
+ var __moduleCache;
25
+ var __returnValue = (v) => v;
26
+ function __exportSetter(name, newValue) {
27
+ this[name] = __returnValue.bind(null, newValue);
28
+ }
29
+ var __export = (target, all) => {
30
+ for (var name in all)
31
+ __defProp(target, name, {
32
+ get: all[name],
33
+ enumerable: true,
34
+ configurable: true,
35
+ set: __exportSetter.bind(all, name)
36
+ });
37
+ };
38
+
39
+ // src/ask-user-question.ts
40
+ var exports_ask_user_question = {};
41
+ __export(exports_ask_user_question, {
42
+ prepareAskUserQuestionNotif: () => prepareAskUserQuestionNotif,
43
+ parseAskUserQuestionReceipt: () => parseAskUserQuestionReceipt,
44
+ parseAskUserQuestionNotif: () => parseAskUserQuestionNotif,
45
+ isAskUserQuestions: () => isAskUserQuestions
46
+ });
47
+ module.exports = __toCommonJS(exports_ask_user_question);
48
+ function isRecord(value) {
49
+ return value !== null && typeof value === "object" && !Array.isArray(value);
50
+ }
51
+ function isNonemptyString(value) {
52
+ return typeof value === "string" && value.trim().length > 0;
53
+ }
54
+ function isAskUserQuestions(value) {
55
+ return Array.isArray(value) && value.length > 0 && value.length <= 4 && value.every((question) => isRecord(question) && isNonemptyString(question.question) && isNonemptyString(question.header) && (question.multiSelect === undefined || typeof question.multiSelect === "boolean") && Array.isArray(question.options) && question.options.length >= 2 && question.options.length <= 4 && question.options.every((option) => isRecord(option) && isNonemptyString(option.label) && isNonemptyString(option.description))) && new Set(value.map((question) => question.question)).size === value.length;
56
+ }
57
+ function parseJson(value) {
58
+ try {
59
+ return JSON.parse(value);
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+ function hasQuestionIdentity(value) {
65
+ return isRecord(value) && value.version === 2 && isNonemptyString(value.toolCallId) && isAskUserQuestions(value.questions);
66
+ }
67
+ function parseAskUserQuestionReceipt(value) {
68
+ const receipt = typeof value === "string" ? parseJson(value) : value;
69
+ if (!hasQuestionIdentity(receipt) || receipt.type !== "ask_user_question")
70
+ return null;
71
+ return {
72
+ type: receipt.type,
73
+ version: 2,
74
+ toolCallId: receipt.toolCallId,
75
+ questions: receipt.questions
76
+ };
77
+ }
78
+ function parseResponse(value) {
79
+ if (!hasQuestionIdentity(value) || value.type !== "ask_user_question_response")
80
+ return null;
81
+ const base = {
82
+ type: value.type,
83
+ version: 2,
84
+ toolCallId: value.toolCallId,
85
+ questions: value.questions
86
+ };
87
+ if (value.status === "dismissed" && value.answers === undefined)
88
+ return { ...base, status: "dismissed" };
89
+ if (value.status !== "answered" || !isRecord(value.answers))
90
+ return null;
91
+ const answers = value.answers;
92
+ if (Object.keys(answers).length !== value.questions.length || !value.questions.every((question) => Object.hasOwn(answers, question.question) && isNonemptyString(answers[question.question])))
93
+ return null;
94
+ return {
95
+ ...base,
96
+ status: "answered",
97
+ answers
98
+ };
99
+ }
100
+ function escapeXml(value) {
101
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
102
+ }
103
+ function prepareAskUserQuestionNotif(response) {
104
+ const parsed = parseResponse(response);
105
+ if (!parsed)
106
+ throw new Error("Invalid AskUserQuestion response");
107
+ return `<task-notification>
108
+ <task-id>${escapeXml(parsed.toolCallId)}</task-id>
109
+ <summary>User ${parsed.status} your questions.</summary>
110
+ <ask-user-question-response>${escapeXml(JSON.stringify(parsed))}</ask-user-question-response>
111
+ </task-notification>`;
112
+ }
113
+ function parseAskUserQuestionNotif(text) {
114
+ const responses = [];
115
+ const notifications = text.matchAll(/<task-notification>(?:(?!<task-notification>)[\s\S])*?<\/task-notification>/g);
116
+ for (const [notification] of notifications) {
117
+ const content = notification.match(/<ask-user-question-response>([\s\S]*?)<\/ask-user-question-response>/)?.[1];
118
+ if (!content)
119
+ continue;
120
+ const decoded = content.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
121
+ const response = parseResponse(parseJson(decoded));
122
+ if (response)
123
+ responses.push(response);
124
+ }
125
+ return responses;
126
+ }
127
+
128
+ //# debugId=9D909147504B11BC64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ask-user-question.ts"],
4
+ "sourcesContent": [
5
+ "/** Browser-safe question receipts and user-message notifications. */\nexport interface AskUserQuestion {\n question: string;\n header: string;\n options: Array<{ label: string; description: string }>;\n multiSelect?: boolean;\n}\n\nexport interface AskUserQuestionReceipt {\n type: \"ask_user_question\";\n version: 2;\n toolCallId: string;\n questions: AskUserQuestion[];\n}\n\nexport interface AskUserQuestionResponse {\n type: \"ask_user_question_response\";\n version: 2;\n toolCallId: string;\n questions: AskUserQuestion[];\n status: \"answered\" | \"dismissed\";\n answers?: Record<string, string>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isNonemptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\nexport function isAskUserQuestions(value: unknown): value is AskUserQuestion[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.length <= 4 &&\n value.every(\n (question) =>\n isRecord(question) &&\n isNonemptyString(question.question) &&\n isNonemptyString(question.header) &&\n (question.multiSelect === undefined ||\n typeof question.multiSelect === \"boolean\") &&\n Array.isArray(question.options) &&\n question.options.length >= 2 &&\n question.options.length <= 4 &&\n question.options.every(\n (option: unknown) =>\n isRecord(option) &&\n isNonemptyString(option.label) &&\n isNonemptyString(option.description),\n ),\n ) &&\n new Set(value.map((question) => question.question)).size === value.length\n );\n}\n\nfunction parseJson(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\nfunction hasQuestionIdentity(value: unknown): value is Record<\n string,\n unknown\n> & {\n version: 2;\n toolCallId: string;\n questions: AskUserQuestion[];\n} {\n return (\n isRecord(value) &&\n value.version === 2 &&\n isNonemptyString(value.toolCallId) &&\n isAskUserQuestions(value.questions)\n );\n}\n\nexport function parseAskUserQuestionReceipt(\n value: unknown,\n): AskUserQuestionReceipt | null {\n const receipt = typeof value === \"string\" ? parseJson(value) : value;\n if (!hasQuestionIdentity(receipt) || receipt.type !== \"ask_user_question\")\n return null;\n return {\n type: receipt.type,\n version: 2,\n toolCallId: receipt.toolCallId,\n questions: receipt.questions,\n };\n}\n\nfunction parseResponse(value: unknown): AskUserQuestionResponse | null {\n if (\n !hasQuestionIdentity(value) ||\n value.type !== \"ask_user_question_response\"\n )\n return null;\n const base = {\n type: value.type,\n version: 2,\n toolCallId: value.toolCallId,\n questions: value.questions,\n } as const;\n if (value.status === \"dismissed\" && value.answers === undefined)\n return { ...base, status: \"dismissed\" };\n if (value.status !== \"answered\" || !isRecord(value.answers)) return null;\n const answers = value.answers;\n if (\n Object.keys(answers).length !== value.questions.length ||\n !value.questions.every(\n (question) =>\n Object.hasOwn(answers, question.question) &&\n isNonemptyString(answers[question.question]),\n )\n )\n return null;\n return {\n ...base,\n status: \"answered\",\n answers: answers as Record<string, string>,\n };\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\n/** Return content for a user-role message submitted through normal message delivery. */\nexport function prepareAskUserQuestionNotif(\n response: AskUserQuestionResponse,\n): string {\n const parsed = parseResponse(response);\n if (!parsed) throw new Error(\"Invalid AskUserQuestion response\");\n return `<task-notification>\\n<task-id>${escapeXml(parsed.toolCallId)}</task-id>\\n<summary>User ${parsed.status} your questions.</summary>\\n<ask-user-question-response>${escapeXml(JSON.stringify(parsed))}</ask-user-question-response>\\n</task-notification>`;\n}\n\n/** Match only complete notifications; queued messages may coalesce several together. */\nexport function parseAskUserQuestionNotif(\n text: string,\n): AskUserQuestionResponse[] {\n const responses: AskUserQuestionResponse[] = [];\n const notifications = text.matchAll(\n /<task-notification>(?:(?!<task-notification>)[\\s\\S])*?<\\/task-notification>/g,\n );\n for (const [notification] of notifications) {\n const content = notification.match(\n /<ask-user-question-response>([\\s\\S]*?)<\\/ask-user-question-response>/,\n )?.[1];\n if (!content) continue;\n const decoded = content\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&amp;/g, \"&\");\n const response = parseResponse(parseJson(decoded));\n if (response) responses.push(response);\n }\n return responses;\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "9D909147504B11BC64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,88 @@
1
+ // src/ask-user-question.ts
2
+ function isRecord(value) {
3
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ function isNonemptyString(value) {
6
+ return typeof value === "string" && value.trim().length > 0;
7
+ }
8
+ function isAskUserQuestions(value) {
9
+ return Array.isArray(value) && value.length > 0 && value.length <= 4 && value.every((question) => isRecord(question) && isNonemptyString(question.question) && isNonemptyString(question.header) && (question.multiSelect === undefined || typeof question.multiSelect === "boolean") && Array.isArray(question.options) && question.options.length >= 2 && question.options.length <= 4 && question.options.every((option) => isRecord(option) && isNonemptyString(option.label) && isNonemptyString(option.description))) && new Set(value.map((question) => question.question)).size === value.length;
10
+ }
11
+ function parseJson(value) {
12
+ try {
13
+ return JSON.parse(value);
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+ function hasQuestionIdentity(value) {
19
+ return isRecord(value) && value.version === 2 && isNonemptyString(value.toolCallId) && isAskUserQuestions(value.questions);
20
+ }
21
+ function parseAskUserQuestionReceipt(value) {
22
+ const receipt = typeof value === "string" ? parseJson(value) : value;
23
+ if (!hasQuestionIdentity(receipt) || receipt.type !== "ask_user_question")
24
+ return null;
25
+ return {
26
+ type: receipt.type,
27
+ version: 2,
28
+ toolCallId: receipt.toolCallId,
29
+ questions: receipt.questions
30
+ };
31
+ }
32
+ function parseResponse(value) {
33
+ if (!hasQuestionIdentity(value) || value.type !== "ask_user_question_response")
34
+ return null;
35
+ const base = {
36
+ type: value.type,
37
+ version: 2,
38
+ toolCallId: value.toolCallId,
39
+ questions: value.questions
40
+ };
41
+ if (value.status === "dismissed" && value.answers === undefined)
42
+ return { ...base, status: "dismissed" };
43
+ if (value.status !== "answered" || !isRecord(value.answers))
44
+ return null;
45
+ const answers = value.answers;
46
+ if (Object.keys(answers).length !== value.questions.length || !value.questions.every((question) => Object.hasOwn(answers, question.question) && isNonemptyString(answers[question.question])))
47
+ return null;
48
+ return {
49
+ ...base,
50
+ status: "answered",
51
+ answers
52
+ };
53
+ }
54
+ function escapeXml(value) {
55
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
56
+ }
57
+ function prepareAskUserQuestionNotif(response) {
58
+ const parsed = parseResponse(response);
59
+ if (!parsed)
60
+ throw new Error("Invalid AskUserQuestion response");
61
+ return `<task-notification>
62
+ <task-id>${escapeXml(parsed.toolCallId)}</task-id>
63
+ <summary>User ${parsed.status} your questions.</summary>
64
+ <ask-user-question-response>${escapeXml(JSON.stringify(parsed))}</ask-user-question-response>
65
+ </task-notification>`;
66
+ }
67
+ function parseAskUserQuestionNotif(text) {
68
+ const responses = [];
69
+ const notifications = text.matchAll(/<task-notification>(?:(?!<task-notification>)[\s\S])*?<\/task-notification>/g);
70
+ for (const [notification] of notifications) {
71
+ const content = notification.match(/<ask-user-question-response>([\s\S]*?)<\/ask-user-question-response>/)?.[1];
72
+ if (!content)
73
+ continue;
74
+ const decoded = content.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
75
+ const response = parseResponse(parseJson(decoded));
76
+ if (response)
77
+ responses.push(response);
78
+ }
79
+ return responses;
80
+ }
81
+ export {
82
+ prepareAskUserQuestionNotif,
83
+ parseAskUserQuestionReceipt,
84
+ parseAskUserQuestionNotif,
85
+ isAskUserQuestions
86
+ };
87
+
88
+ //# debugId=F9A038729960456364756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ask-user-question.ts"],
4
+ "sourcesContent": [
5
+ "/** Browser-safe question receipts and user-message notifications. */\nexport interface AskUserQuestion {\n question: string;\n header: string;\n options: Array<{ label: string; description: string }>;\n multiSelect?: boolean;\n}\n\nexport interface AskUserQuestionReceipt {\n type: \"ask_user_question\";\n version: 2;\n toolCallId: string;\n questions: AskUserQuestion[];\n}\n\nexport interface AskUserQuestionResponse {\n type: \"ask_user_question_response\";\n version: 2;\n toolCallId: string;\n questions: AskUserQuestion[];\n status: \"answered\" | \"dismissed\";\n answers?: Record<string, string>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isNonemptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\nexport function isAskUserQuestions(value: unknown): value is AskUserQuestion[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.length <= 4 &&\n value.every(\n (question) =>\n isRecord(question) &&\n isNonemptyString(question.question) &&\n isNonemptyString(question.header) &&\n (question.multiSelect === undefined ||\n typeof question.multiSelect === \"boolean\") &&\n Array.isArray(question.options) &&\n question.options.length >= 2 &&\n question.options.length <= 4 &&\n question.options.every(\n (option: unknown) =>\n isRecord(option) &&\n isNonemptyString(option.label) &&\n isNonemptyString(option.description),\n ),\n ) &&\n new Set(value.map((question) => question.question)).size === value.length\n );\n}\n\nfunction parseJson(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\nfunction hasQuestionIdentity(value: unknown): value is Record<\n string,\n unknown\n> & {\n version: 2;\n toolCallId: string;\n questions: AskUserQuestion[];\n} {\n return (\n isRecord(value) &&\n value.version === 2 &&\n isNonemptyString(value.toolCallId) &&\n isAskUserQuestions(value.questions)\n );\n}\n\nexport function parseAskUserQuestionReceipt(\n value: unknown,\n): AskUserQuestionReceipt | null {\n const receipt = typeof value === \"string\" ? parseJson(value) : value;\n if (!hasQuestionIdentity(receipt) || receipt.type !== \"ask_user_question\")\n return null;\n return {\n type: receipt.type,\n version: 2,\n toolCallId: receipt.toolCallId,\n questions: receipt.questions,\n };\n}\n\nfunction parseResponse(value: unknown): AskUserQuestionResponse | null {\n if (\n !hasQuestionIdentity(value) ||\n value.type !== \"ask_user_question_response\"\n )\n return null;\n const base = {\n type: value.type,\n version: 2,\n toolCallId: value.toolCallId,\n questions: value.questions,\n } as const;\n if (value.status === \"dismissed\" && value.answers === undefined)\n return { ...base, status: \"dismissed\" };\n if (value.status !== \"answered\" || !isRecord(value.answers)) return null;\n const answers = value.answers;\n if (\n Object.keys(answers).length !== value.questions.length ||\n !value.questions.every(\n (question) =>\n Object.hasOwn(answers, question.question) &&\n isNonemptyString(answers[question.question]),\n )\n )\n return null;\n return {\n ...base,\n status: \"answered\",\n answers: answers as Record<string, string>,\n };\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\n/** Return content for a user-role message submitted through normal message delivery. */\nexport function prepareAskUserQuestionNotif(\n response: AskUserQuestionResponse,\n): string {\n const parsed = parseResponse(response);\n if (!parsed) throw new Error(\"Invalid AskUserQuestion response\");\n return `<task-notification>\\n<task-id>${escapeXml(parsed.toolCallId)}</task-id>\\n<summary>User ${parsed.status} your questions.</summary>\\n<ask-user-question-response>${escapeXml(JSON.stringify(parsed))}</ask-user-question-response>\\n</task-notification>`;\n}\n\n/** Match only complete notifications; queued messages may coalesce several together. */\nexport function parseAskUserQuestionNotif(\n text: string,\n): AskUserQuestionResponse[] {\n const responses: AskUserQuestionResponse[] = [];\n const notifications = text.matchAll(\n /<task-notification>(?:(?!<task-notification>)[\\s\\S])*?<\\/task-notification>/g,\n );\n for (const [notification] of notifications) {\n const content = notification.match(\n /<ask-user-question-response>([\\s\\S]*?)<\\/ask-user-question-response>/,\n )?.[1];\n if (!content) continue;\n const decoded = content\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&amp;/g, \"&\");\n const response = parseResponse(parseJson(decoded));\n if (response) responses.push(response);\n }\n return responses;\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "F9A038729960456364756E2164756E21",
9
+ "names": []
10
+ }
@@ -7,7 +7,7 @@
7
7
  "import type { ChannelTurnProgressUpdate } from \"./types\";\n\nexport interface LettaStreamErrorParams {\n errorType?: string;\n message: string;\n detail?: string;\n runId?: string;\n}\n\nexport interface CollectLettaSseAssistantTextResult {\n text: string;\n chunkCount: number;\n stopReason?: string;\n}\n\nexport interface CollectLettaSseAssistantTextOptions {\n onDelta?: (delta: unknown) => void | Promise<void>;\n onProgressUpdate?: (\n update: ChannelTurnProgressUpdate,\n ) => void | Promise<void>;\n progressBuilder?: {\n buildUpdates(delta: unknown): ChannelTurnProgressUpdate[];\n };\n}\n\nexport interface FormatLettaStreamCoreErrorOptions {\n includeDetail?: boolean;\n}\n\ninterface LettaSseTextContentPart {\n type: \"text\";\n text: string;\n signature?: string;\n}\n\ninterface LettaSseAssistantMessage {\n message_type: \"assistant_message\";\n content?: string | LettaSseTextContentPart[];\n}\n\ninterface LettaSseErrorMessage {\n message_type: \"error_message\";\n error_type?: string;\n message?: string;\n detail?: string;\n run_id?: string;\n}\n\ninterface LettaSseStopReasonMessage {\n message_type: \"stop_reason\";\n stop_reason?: string;\n}\n\nexport const LETTA_STREAM_NO_ASSISTANT_MESSAGE_ERROR =\n \"No assistant message received in stream\";\n\nexport class LettaStreamCoreError extends Error {\n readonly errorType?: string;\n readonly detail?: string;\n readonly runId?: string;\n\n constructor(params: LettaStreamErrorParams) {\n super(params.message);\n this.name = \"LettaStreamCoreError\";\n this.errorType = params.errorType;\n this.detail = params.detail;\n this.runId = params.runId;\n }\n}\n\nexport class LettaStreamNoAssistantMessageError extends Error {\n constructor() {\n super(LETTA_STREAM_NO_ASSISTANT_MESSAGE_ERROR);\n this.name = \"LettaStreamNoAssistantMessageError\";\n }\n}\n\nfunction normalizeErrorLine(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nexport function formatLettaStreamCoreErrorForChannel(\n error: LettaStreamCoreError,\n options: FormatLettaStreamCoreErrorOptions = {},\n): string {\n const message =\n normalizeErrorLine(error.message) ?? \"Core failed to generate a response.\";\n const detail = normalizeErrorLine(error.detail);\n\n if (options.includeDetail === false || !detail || detail === message) {\n return message;\n }\n\n return `${message}\\n${detail}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\";\n}\n\nfunction isOptionalString(value: unknown): value is string | undefined {\n return typeof value === \"undefined\" || typeof value === \"string\";\n}\n\nfunction isLettaSseTextContentPart(\n value: unknown,\n): value is LettaSseTextContentPart {\n if (!isRecord(value)) {\n return false;\n }\n return (\n value.type === \"text\" &&\n typeof value.text === \"string\" &&\n isOptionalString(value.signature)\n );\n}\n\nfunction isLettaSseAssistantMessage(\n value: unknown,\n): value is LettaSseAssistantMessage {\n if (!isRecord(value) || value.message_type !== \"assistant_message\") {\n return false;\n }\n const content = value.content;\n return (\n typeof content === \"undefined\" ||\n typeof content === \"string\" ||\n (Array.isArray(content) && content.every(isLettaSseTextContentPart))\n );\n}\n\nfunction isLettaSseErrorMessage(value: unknown): value is LettaSseErrorMessage {\n if (!isRecord(value) || value.message_type !== \"error_message\") {\n return false;\n }\n return (\n isOptionalString(value.error_type) &&\n isOptionalString(value.message) &&\n isOptionalString(value.detail) &&\n isOptionalString(value.run_id)\n );\n}\n\nfunction isLettaSseStopReasonMessage(\n value: unknown,\n): value is LettaSseStopReasonMessage {\n if (!isRecord(value) || value.message_type !== \"stop_reason\") {\n return false;\n }\n return isOptionalString(value.stop_reason);\n}\n\nfunction coreErrorFromMessage(\n message: LettaSseErrorMessage,\n): LettaStreamCoreError {\n return new LettaStreamCoreError({\n errorType: message.error_type,\n message: message.message ?? \"Core failed to generate a response.\",\n detail: message.detail,\n runId: message.run_id,\n });\n}\n\nfunction collectAssistantContent(message: LettaSseAssistantMessage): string[] {\n if (typeof message.content === \"string\") {\n return [message.content];\n }\n if (Array.isArray(message.content)) {\n return message.content.map((part) => part.text);\n }\n return [];\n}\n\nexport async function collectLettaSseAssistantText(\n body: ReadableStream<Uint8Array>,\n options: CollectLettaSseAssistantTextOptions = {},\n): Promise<CollectLettaSseAssistantTextResult> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n const assistantMessages: string[] = [];\n let stopReason: string | undefined;\n\n async function processData(data: string): Promise<void> {\n if (data === \"[DONE]\") {\n return;\n }\n\n const parsed: unknown = JSON.parse(data);\n await options.onDelta?.(parsed);\n const progressUpdates = options.progressBuilder?.buildUpdates(parsed) ?? [];\n for (const update of progressUpdates) {\n await options.onProgressUpdate?.(update);\n }\n\n if (isLettaSseErrorMessage(parsed)) {\n throw coreErrorFromMessage(parsed);\n }\n if (isLettaSseAssistantMessage(parsed)) {\n assistantMessages.push(...collectAssistantContent(parsed));\n return;\n }\n if (isLettaSseStopReasonMessage(parsed)) {\n stopReason = parsed.stop_reason;\n }\n }\n\n async function processLine(line: string): Promise<void> {\n if (!line.trim() || !line.startsWith(\"data: \")) {\n return;\n }\n await processData(line.slice(6));\n }\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n await processLine(line);\n }\n }\n\n const flushed = decoder.decode();\n if (flushed) {\n buffer += flushed;\n }\n if (buffer) {\n await processLine(buffer);\n }\n } finally {\n reader.releaseLock();\n }\n\n const text = assistantMessages.join(\" \").trim();\n if (!text) {\n throw new LettaStreamNoAssistantMessageError();\n }\n\n return {\n text,\n chunkCount: assistantMessages.length,\n stopReason,\n };\n}\n",
8
8
  "const CONVERSATION_BUSY_ERROR_PATTERNS = [\n /another request is (?:currently )?(?:being )?processed/i,\n /currently being processed for this conversation/i,\n /busy with another active run/i,\n /already processing for this conversation/i,\n /turn still running/i,\n];\n\nexport const CONVERSATION_BUSY_TITLE = \"Turn still running\";\n\nexport function isConversationBusyErrorText(\n errorText: string | null | undefined,\n): boolean {\n if (!errorText) return false;\n return CONVERSATION_BUSY_ERROR_PATTERNS.some((pattern) =>\n pattern.test(errorText),\n );\n}\n\nexport function buildConversationBusyErrorBody(\n automaticRetry: boolean,\n): string {\n return automaticRetry\n ? \"Another request is already processing for this conversation. I’ll wait for it to finish and retry automatically.\"\n : \"Another request is already processing for this conversation. Please wait for it to finish, then try again.\";\n}\n\nexport function formatConversationBusyErrorMessage(options: {\n automaticRetry?: boolean;\n runId?: string;\n}): string {\n const lines = [\n CONVERSATION_BUSY_TITLE,\n buildConversationBusyErrorBody(options.automaticRetry ?? false),\n ];\n if (options.runId) {\n lines.push(\"\", `Run ID: ${options.runId}`);\n }\n return lines.join(\"\\n\");\n}\n",
9
9
  "import {\n buildConversationBusyErrorBody,\n CONVERSATION_BUSY_TITLE,\n isConversationBusyErrorText,\n} from \"@/utils/conversation-busy-error\";\n\nconst RAW_LOOP_ERROR_PATTERN = /^Unexpected stop reason:\\s*error$/i;\nconst APP_CHAT_URL_PATTERN = /https:\\/\\/app\\.letta\\.com\\/chat\\/\\S+/i;\nconst ESCAPE_CHARACTER = String.fromCharCode(27);\nconst OSC8_PREFIX = `${ESCAPE_CHARACTER}]8;;`;\nconst OSC8_TERMINATOR = `${ESCAPE_CHARACTER}\\\\`;\n\nconst APPROVAL_PENDING_ERROR_PATTERNS = [\n /waiting for approval/i,\n /pending request before continuing/i,\n /approve or deny the pending request/i,\n];\n\nconst DATABASE_LOCK_TIMEOUT_PATTERN =\n /\\bcancel(?:l)?ing statement due to lock timeout\\b/i;\nconst POSTGRES_LOCK_NOT_AVAILABLE_CODE_PATTERN = /\\b55P03\\b/i;\nconst POSTGRES_LOCK_NOT_AVAILABLE_SQLSTATE_PATTERN =\n /\\b(?:sqlstate|pgcode)\\s*[:=]?\\s*[\"']?55P03\\b/i;\nconst POSTGRES_LOCK_NOT_AVAILABLE_CONTEXT_PATTERN =\n /\\b(?:postgres(?:ql)?|psycopg|sqlalchemy|lock[_ -]?not[_ -]?available|lock timeout)\\b/i;\n\nconst RUN_ID_PATTERNS = [\n /\"run_id\"\\s*:\\s*\"([^\"\\\\]+)\"/i,\n /\\brun[_\\s-]?id\\b[\"']?\\s*[:=]\\s*[\"']?([A-Za-z0-9_-]+)/i,\n /\\(run:\\s*([A-Za-z0-9_-]+)\\)/i,\n];\n\nexport type ChannelLifecycleErrorKind =\n | \"approval_pending\"\n | \"conversation_busy\"\n | \"database_lock_timeout\"\n | \"generic\";\n\nexport interface ChannelLifecycleErrorDisplayOptions {\n automaticRetry?: boolean;\n runId?: string | null;\n}\n\nexport interface ChannelLifecycleErrorDisplay {\n kind: ChannelLifecycleErrorKind;\n title: string;\n body: string;\n runId?: string;\n}\n\nexport interface ChannelLifecycleErrorFormatOptions\n extends ChannelLifecycleErrorDisplayOptions {\n codeBlock?: boolean;\n maxLength?: number;\n}\n\nexport const CHANNEL_LIFECYCLE_FALLBACK_ERROR_MESSAGE =\n \"Something went wrong while processing that message. Please try again.\";\n\nexport const CHANNEL_LIFECYCLE_APPROVAL_PENDING_MESSAGE =\n \"The agent is still waiting on a tool approval from an earlier turn. Please approve or deny that pending request, then send your message again.\";\n\nexport const CHANNEL_LIFECYCLE_TRANSIENT_ERROR_MESSAGE =\n \"A temporary error interrupted this turn. Please try again.\";\n\nexport const CHANNEL_LIFECYCLE_CONVERSATION_BUSY_TITLE =\n CONVERSATION_BUSY_TITLE;\n\nfunction stripTrailingRunIdPunctuation(runId: string): string {\n return runId.replace(/[)\"'.,;]+$/g, \"\");\n}\n\nexport function extractChannelLifecycleRunId(\n errorText: string | null | undefined,\n): string | undefined {\n if (!errorText) return undefined;\n for (const pattern of RUN_ID_PATTERNS) {\n const match = errorText.match(pattern);\n const runId = match?.[1]?.trim();\n if (runId) {\n return stripTrailingRunIdPunctuation(runId);\n }\n }\n return undefined;\n}\n\nfunction stripOsc8TerminalLinks(errorText: string): string {\n let output = \"\";\n let cursor = 0;\n\n while (cursor < errorText.length) {\n const linkStart = errorText.indexOf(OSC8_PREFIX, cursor);\n if (linkStart === -1) {\n output += errorText.slice(cursor);\n break;\n }\n\n const labelStart = errorText.indexOf(\n OSC8_TERMINATOR,\n linkStart + OSC8_PREFIX.length,\n );\n if (labelStart === -1) {\n output += errorText.slice(cursor);\n break;\n }\n\n const closeStart = errorText.indexOf(\n OSC8_PREFIX,\n labelStart + OSC8_TERMINATOR.length,\n );\n if (closeStart === -1) {\n output += errorText.slice(cursor);\n break;\n }\n\n const closeEnd = errorText.indexOf(\n OSC8_TERMINATOR,\n closeStart + OSC8_PREFIX.length,\n );\n if (closeEnd === -1) {\n output += errorText.slice(cursor);\n break;\n }\n\n output += errorText.slice(cursor, linkStart);\n output += errorText.slice(labelStart + OSC8_TERMINATOR.length, closeStart);\n cursor = closeEnd + OSC8_TERMINATOR.length;\n }\n\n return output;\n}\n\nexport function sanitizeChannelLifecycleErrorText(\n errorText: string | null | undefined,\n): string {\n if (!errorText) return \"\";\n\n const withoutTerminalLinks = stripOsc8TerminalLinks(errorText);\n\n return withoutTerminalLinks\n .split(\"\\n\")\n .filter((line) => {\n const trimmed = line.trim();\n if (/^View agent:/i.test(trimmed)) return false;\n if (APP_CHAT_URL_PATTERN.test(trimmed)) return false;\n return true;\n })\n .join(\"\\n\")\n .replace(/[ \\t]+\\n/g, \"\\n\")\n .trim();\n}\n\nfunction truncateLifecycleMessage(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return `${text.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;\n}\n\nfunction isDatabaseLockErrorText(errorText: string): boolean {\n return (\n DATABASE_LOCK_TIMEOUT_PATTERN.test(errorText) ||\n POSTGRES_LOCK_NOT_AVAILABLE_SQLSTATE_PATTERN.test(errorText) ||\n (POSTGRES_LOCK_NOT_AVAILABLE_CODE_PATTERN.test(errorText) &&\n POSTGRES_LOCK_NOT_AVAILABLE_CONTEXT_PATTERN.test(errorText))\n );\n}\n\nexport function getChannelLifecycleErrorDisplay(\n errorText: string | null | undefined,\n options: ChannelLifecycleErrorDisplayOptions = {},\n): ChannelLifecycleErrorDisplay {\n const normalized = sanitizeChannelLifecycleErrorText(errorText);\n const optionsRunId = options.runId?.trim();\n const runId = optionsRunId || extractChannelLifecycleRunId(errorText);\n\n if (!normalized || RAW_LOOP_ERROR_PATTERN.test(normalized)) {\n return {\n kind: \"generic\",\n title: \"Turn failed\",\n body: CHANNEL_LIFECYCLE_FALLBACK_ERROR_MESSAGE,\n runId,\n };\n }\n\n if (isDatabaseLockErrorText(normalized)) {\n return {\n kind: \"database_lock_timeout\",\n title: \"Turn failed\",\n body: CHANNEL_LIFECYCLE_TRANSIENT_ERROR_MESSAGE,\n runId,\n };\n }\n\n if (isConversationBusyErrorText(normalized)) {\n return {\n kind: \"conversation_busy\",\n title: CHANNEL_LIFECYCLE_CONVERSATION_BUSY_TITLE,\n body: buildConversationBusyErrorBody(options.automaticRetry ?? false),\n runId,\n };\n }\n\n if (\n APPROVAL_PENDING_ERROR_PATTERNS.some((pattern) => pattern.test(normalized))\n ) {\n return {\n kind: \"approval_pending\",\n title: \"Turn failed\",\n body: CHANNEL_LIFECYCLE_APPROVAL_PENDING_MESSAGE,\n runId,\n };\n }\n\n return {\n kind: \"generic\",\n title: \"Turn failed\",\n body: normalized,\n runId,\n };\n}\n\nexport function normalizeChannelLifecycleErrorMessage(\n errorText: string | null | undefined,\n options: ChannelLifecycleErrorDisplayOptions = {},\n): string {\n return getChannelLifecycleErrorDisplay(errorText, options).body;\n}\n\nexport function formatChannelLifecycleErrorMessage(\n errorText: string | null | undefined,\n options: ChannelLifecycleErrorFormatOptions = {},\n): string {\n const display = getChannelLifecycleErrorDisplay(errorText, options);\n const maxLength = options.maxLength ?? Number.POSITIVE_INFINITY;\n const body = truncateLifecycleMessage(display.body, maxLength);\n\n if (display.kind === \"conversation_busy\") {\n const lines = [display.title, body];\n if (display.runId) {\n lines.push(\"\", `Run ID: ${display.runId}`);\n }\n return lines.join(\"\\n\");\n }\n\n if (display.kind === \"database_lock_timeout\") {\n const lines = [`${display.title}:`, body];\n if (display.runId) {\n lines.push(\"\", `Run ID: ${display.runId}`);\n }\n return lines.join(\"\\n\");\n }\n\n if (options.codeBlock) {\n const escaped = body.replace(/```/g, \"``\\u200b`\");\n const lines = [`${display.title}:`, \"```\", escaped, \"```\"];\n if (display.runId) {\n lines.push(\"\", `Run ID: ${display.runId}`);\n }\n return lines.join(\"\\n\");\n }\n\n const lines = [`${display.title}:`, body];\n if (display.runId) {\n lines.push(\"\", `Run ID: ${display.runId}`);\n }\n return lines.join(\"\\n\");\n}\n",
10
- "/**\n * Application-wide constants\n */\n\n/**\n * Default model ID to use when no model is specified\n */\nexport const DEFAULT_MODEL_ID = \"auto\";\n\n/**\n * Default model handle to use for conversation compaction / summarization.\n */\nexport const DEFAULT_SUMMARIZATION_MODEL = \"letta/auto\";\n\n/**\n * Default model handle for lightweight conversation title generation.\n */\nexport const DEFAULT_TITLE_SUMMARIZATION_MODEL = \"openai/gpt-5.6-luna\";\n\n/**\n * Default agent name when creating a new agent\n */\nexport const DEFAULT_AGENT_NAME = \"Letta Code\";\n\n/**\n * Message displayed when user interrupts tool execution\n */\nexport const INTERRUPTED_BY_USER = \"Interrupted by user\";\n\n/**\n * Synthetic tool result injected when a turn ended without completing all tool calls\n * (e.g. process crash or unhandled stream error). Settled automatically at the start\n * of the next turn so the conversation history stays valid for the provider.\n */\nexport const TURN_DID_NOT_COMPLETE = \"Turn did not complete\";\n\n/**\n * XML tag used to wrap system reminder content injected into messages\n */\nexport const SYSTEM_REMINDER_TAG = \"system-reminder\";\nexport const SYSTEM_REMINDER_OPEN = `<${SYSTEM_REMINDER_TAG}>`;\nexport const SYSTEM_REMINDER_CLOSE = `</${SYSTEM_REMINDER_TAG}>`;\n// Legacy tag kept for parsing/backward compatibility with older saved messages.\nexport const SYSTEM_ALERT_TAG = \"system-alert\";\nexport const SYSTEM_ALERT_OPEN = `<${SYSTEM_ALERT_TAG}>`;\nexport const SYSTEM_ALERT_CLOSE = `</${SYSTEM_ALERT_TAG}>`;\n\n/**\n * How often (in turns) to check for memfs sync conflicts, even without\n * filesystem change events. Catches block-only changes (e.g. ADE/API edits).\n */\nexport const MEMFS_CONFLICT_CHECK_INTERVAL = 5;\n\n/**\n * Header displayed before compaction summary when conversation context is truncated\n */\nexport const COMPACTION_SUMMARY_HEADER =\n \"(Earlier messages in this conversation have been compacted to free up context, summarized below)\";\n\n/**\n * Status bar thresholds - only show indicators when values exceed these\n */\n// Show token count after 100 estimated tokens (shows exact count until 1k, then compact)\nexport const TOKEN_DISPLAY_THRESHOLD = 100;\n// Show elapsed time after 2 minutes (in ms)\nexport const ELAPSED_DISPLAY_THRESHOLD_MS = 60 * 1000;\n",
10
+ "/**\n * Application-wide constants\n */\n\n/**\n * Default model ID to use when no model is specified\n */\nexport const DEFAULT_MODEL_ID = \"auto\";\n\n/**\n * Default model handle to use for conversation compaction / summarization.\n */\nexport const DEFAULT_SUMMARIZATION_MODEL = \"letta/auto\";\n\n/**\n * Default model handle for lightweight conversation title generation.\n */\nexport const DEFAULT_TITLE_SUMMARIZATION_MODEL = \"openai/gpt-6-luna\";\n\n/**\n * Default agent name when creating a new agent\n */\nexport const DEFAULT_AGENT_NAME = \"Letta Code\";\n\n/**\n * Message displayed when user interrupts tool execution\n */\nexport const INTERRUPTED_BY_USER = \"Interrupted by user\";\n\n/**\n * Synthetic tool result injected when a turn ended without completing all tool calls\n * (e.g. process crash or unhandled stream error). Settled automatically at the start\n * of the next turn so the conversation history stays valid for the provider.\n */\nexport const TURN_DID_NOT_COMPLETE = \"Turn did not complete\";\n\n/**\n * XML tag used to wrap system reminder content injected into messages\n */\nexport const SYSTEM_REMINDER_TAG = \"system-reminder\";\nexport const SYSTEM_REMINDER_OPEN = `<${SYSTEM_REMINDER_TAG}>`;\nexport const SYSTEM_REMINDER_CLOSE = `</${SYSTEM_REMINDER_TAG}>`;\n// Legacy tag kept for parsing/backward compatibility with older saved messages.\nexport const SYSTEM_ALERT_TAG = \"system-alert\";\nexport const SYSTEM_ALERT_OPEN = `<${SYSTEM_ALERT_TAG}>`;\nexport const SYSTEM_ALERT_CLOSE = `</${SYSTEM_ALERT_TAG}>`;\n\n/**\n * How often (in turns) to check for memfs sync conflicts, even without\n * filesystem change events. Catches block-only changes (e.g. ADE/API edits).\n */\nexport const MEMFS_CONFLICT_CHECK_INTERVAL = 5;\n\n/**\n * Header displayed before compaction summary when conversation context is truncated\n */\nexport const COMPACTION_SUMMARY_HEADER =\n \"(Earlier messages in this conversation have been compacted to free up context, summarized below)\";\n\n/**\n * Status bar thresholds - only show indicators when values exceed these\n */\n// Show token count after 100 estimated tokens (shows exact count until 1k, then compact)\nexport const TOKEN_DISPLAY_THRESHOLD = 100;\n// Show elapsed time after 2 minutes (in ms)\nexport const ELAPSED_DISPLAY_THRESHOLD_MS = 60 * 1000;\n",
11
11
  "/**\n * XML formatting for channel notifications.\n *\n * Produces structured XML that the agent receives as message content.\n * Follows the same escaping patterns used in taskNotifications.ts.\n */\n\nimport type { MessageCreate } from \"@letta-ai/letta-client/resources/agents/agents\";\nimport type { ChannelUserMention } from \"@/channels/message-references\";\nimport { SYSTEM_REMINDER_CLOSE, SYSTEM_REMINDER_OPEN } from \"@/constants\";\nimport type {\n ChannelMessageAttachment,\n ChannelThreadContextEntry,\n InboundChannelMessage,\n} from \"./types\";\n\n/**\n * Escape XML text-node content without over-escaping quotes that should remain\n * readable inside the rendered message body.\n */\nfunction escapeXmlText(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\n/**\n * Escape XML attribute values, including quotes.\n */\nfunction escapeXmlAttribute(text: string): string {\n return escapeXmlText(text).replace(/\"/g, \"&quot;\").replace(/'/g, \"&apos;\");\n}\n\nfunction sanitizeMentionDisplayName(mention: ChannelUserMention): string {\n const name = mention.displayName\n .replace(/[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}]+/gu, \" \")\n .replace(/\\s+/gu, \" \")\n .trim()\n .slice(0, 80)\n .trim();\n return name || mention.userId;\n}\n\nfunction buildTextWithUserMentions(\n text: string,\n mentions: ChannelUserMention[] | undefined,\n): string {\n if (!mentions || mentions.length === 0) return escapeXmlText(text);\n let cursor = 0;\n const parts: string[] = [];\n for (const mention of mentions) {\n if (\n !Number.isInteger(mention.start) ||\n !Number.isInteger(mention.end) ||\n mention.start < cursor ||\n mention.end <= mention.start ||\n mention.end > text.length ||\n !mention.userId.trim()\n ) {\n return escapeXmlText(text);\n }\n parts.push(escapeXmlText(text.slice(cursor, mention.start)));\n const displayName = sanitizeMentionDisplayName(mention);\n parts.push(\n `<mention id=\"${escapeXmlAttribute(mention.userId)}\">@${escapeXmlText(displayName)}</mention>`,\n );\n cursor = mention.end;\n }\n parts.push(escapeXmlText(text.slice(cursor)));\n return parts.join(\"\");\n}\n\nfunction formatMebibytes(bytes: number): string {\n const mebibytes = bytes / (1024 * 1024);\n const rounded =\n mebibytes >= 100 ? Math.round(mebibytes).toString() : mebibytes.toFixed(1);\n return `${rounded.replace(/\\.0$/, \"\")} MiB`;\n}\n\nfunction hasNotificationAttachmentPaths(msg: InboundChannelMessage): boolean {\n if (msg.attachments?.some((attachment) => attachment.localPath)) {\n return true;\n }\n if (\n msg.threadContext?.starter?.attachments?.some(\n (attachment) => attachment.localPath,\n )\n ) {\n return true;\n }\n return Boolean(\n msg.threadContext?.history?.some((entry) =>\n entry.attachments?.some((attachment) => attachment.localPath),\n ),\n );\n}\n\nfunction buildChannelAttachmentReminderText(\n msg: InboundChannelMessage,\n): string | undefined {\n if (!hasNotificationAttachmentPaths(msg)) return undefined;\n return [\n SYSTEM_REMINDER_OPEN,\n \"If this notification includes attachment local_path values, you may be able to inspect those files using local file or image tools available in your current toolset (for example Read or ViewImage), using the local_path.\",\n SYSTEM_REMINDER_CLOSE,\n ].join(\"\\n\");\n}\n\ntype AttachmentXmlContext = {\n channel: string;\n accountId?: string;\n chatId: string;\n messageId?: string;\n};\n\nfunction buildAttachmentXml(\n attachment: ChannelMessageAttachment,\n context: AttachmentXmlContext,\n): string {\n const attrs = [`kind=\"${escapeXmlAttribute(attachment.kind)}\"`];\n\n if (attachment.localPath) {\n attrs.push(`local_path=\"${escapeXmlAttribute(attachment.localPath)}\"`);\n } else {\n attrs.push('download_status=\"not_downloaded\"');\n }\n\n if (attachment.id) {\n attrs.push(`attachment_id=\"${escapeXmlAttribute(attachment.id)}\"`);\n }\n if (attachment.name) {\n attrs.push(`name=\"${escapeXmlAttribute(attachment.name)}\"`);\n }\n if (attachment.mimeType) {\n attrs.push(`mime_type=\"${escapeXmlAttribute(attachment.mimeType)}\"`);\n }\n if (typeof attachment.sizeBytes === \"number\") {\n attrs.push(`size_bytes=\"${attachment.sizeBytes}\"`);\n }\n const sourceMessageId = attachment.sourceMessageId ?? context.messageId;\n if (!attachment.localPath && sourceMessageId) {\n attrs.push(`source_message_id=\"${escapeXmlAttribute(sourceMessageId)}\"`);\n }\n if (!attachment.localPath && attachment.sourceThreadId) {\n attrs.push(\n `source_thread_id=\"${escapeXmlAttribute(attachment.sourceThreadId)}\"`,\n );\n }\n if (attachment.downloadReason) {\n attrs.push(\n `download_reason=\"${escapeXmlAttribute(attachment.downloadReason)}\"`,\n );\n }\n if (typeof attachment.autoDownloadLimitBytes === \"number\") {\n attrs.push(\n `auto_download_limit_bytes=\"${attachment.autoDownloadLimitBytes}\"`,\n );\n }\n\n const children: string[] = [];\n if (attachment.transcription) {\n children.push(\n `<attempted_transcription>${escapeXmlText(attachment.transcription)}</attempted_transcription>`,\n );\n }\n if (attachment.transcriptionError) {\n children.push(\n `<attempted_transcription_error>${escapeXmlText(attachment.transcriptionError)}</attempted_transcription_error>`,\n );\n }\n if (\n !attachment.localPath &&\n context.channel === \"slack\" &&\n attachment.id &&\n sourceMessageId\n ) {\n const accountArg = context.accountId\n ? `, accountId=\"${escapeXmlAttribute(context.accountId)}\"`\n : \"\";\n const threadArg = attachment.sourceThreadId\n ? `, threadId=\"${escapeXmlAttribute(attachment.sourceThreadId)}\"`\n : \"\";\n const action = `MessageChannel with action=\"download-file\", channel=\"slack\", chat_id=\"${escapeXmlAttribute(context.chatId)}\"${accountArg}${threadArg}, attachmentId=\"${escapeXmlAttribute(attachment.id)}\", and messageId=\"${escapeXmlAttribute(sourceMessageId)}\"`;\n if (attachment.downloadReason === \"exceeds_auto_download_limit\") {\n const sizeNote =\n typeof attachment.sizeBytes === \"number\"\n ? `This file is ${formatMebibytes(attachment.sizeBytes)}${\n typeof attachment.autoDownloadLimitBytes === \"number\"\n ? `, above the ${formatMebibytes(attachment.autoDownloadLimitBytes)} automatic download limit`\n : \"\"\n }. `\n : \"\";\n children.push(\n `<download-instruction>${sizeNote}Call ${action}. The tool downloads the file into the same Slack inbound attachment directory and returns its local_path. Large downloads continue in the background instead of blocking; a task notification delivers the local_path when they finish. Do not ask the sender to reattach it.</download-instruction>`,\n );\n } else {\n children.push(\n `<download-retry>Automatic download did not complete. Call ${action} to retry. The action may return a precise error if Slack still cannot provide the file.</download-retry>`,\n );\n }\n }\n\n if (children.length > 0) {\n return `<attachment ${attrs.join(\" \")}>\\n ${children.join(\"\\n \")}\\n</attachment>`;\n }\n\n return `<attachment ${attrs.join(\" \")} />`;\n}\n\nfunction canEmitInlineImageContentPart(mimeType: string): boolean {\n const normalized = mimeType.split(\";\")[0]?.trim().toLowerCase();\n return (\n !!normalized &&\n normalized.startsWith(\"image/\") &&\n normalized !== \"image/svg+xml\"\n );\n}\n\nfunction buildReactionXml(msg: InboundChannelMessage): string | null {\n if (!msg.reaction) {\n return null;\n }\n\n const attrs = [\n `action=\"${escapeXmlAttribute(msg.reaction.action)}\"`,\n `emoji=\"${escapeXmlAttribute(msg.reaction.emoji)}\"`,\n `target_message_id=\"${escapeXmlAttribute(msg.reaction.targetMessageId)}\"`,\n ];\n\n if (msg.reaction.targetSenderId) {\n attrs.push(\n `target_sender_id=\"${escapeXmlAttribute(msg.reaction.targetSenderId)}\"`,\n );\n }\n\n return `<reaction ${attrs.join(\" \")} />`;\n}\n\nfunction buildReplyContextXml(msg: InboundChannelMessage): string | null {\n const replyContext = msg.replyContext;\n if (!replyContext) {\n return null;\n }\n\n const attrs: string[] = [];\n if (replyContext.messageId) {\n attrs.push(`message_id=\"${escapeXmlAttribute(replyContext.messageId)}\"`);\n }\n if (replyContext.senderId) {\n attrs.push(`sender_id=\"${escapeXmlAttribute(replyContext.senderId)}\"`);\n }\n if (replyContext.senderName) {\n attrs.push(`sender_name=\"${escapeXmlAttribute(replyContext.senderName)}\"`);\n }\n\n const attrString = attrs.length > 0 ? ` ${attrs.join(\" \")}` : \"\";\n if (replyContext.text?.trim()) {\n const text = buildTextWithUserMentions(\n replyContext.text,\n replyContext.userMentions,\n );\n return `<reply-context${attrString}>\\n${text}\\n</reply-context>`;\n }\n return `<reply-context${attrString} />`;\n}\n\nfunction buildThreadContextEntryXml(\n tagName: string,\n entry: ChannelThreadContextEntry,\n context: Omit<AttachmentXmlContext, \"messageId\">,\n): string {\n const attrs: string[] = [];\n if (entry.senderId) {\n attrs.push(`sender_id=\"${escapeXmlAttribute(entry.senderId)}\"`);\n }\n if (entry.senderName) {\n attrs.push(`sender_name=\"${escapeXmlAttribute(entry.senderName)}\"`);\n }\n if (entry.messageId) {\n attrs.push(`message_id=\"${escapeXmlAttribute(entry.messageId)}\"`);\n }\n\n const attrString = attrs.length > 0 ? ` ${attrs.join(\" \")}` : \"\";\n const body = [\n ...(entry.text\n ? [buildTextWithUserMentions(entry.text, entry.userMentions)]\n : []),\n ...(entry.attachments ?? []).map((attachment) =>\n buildAttachmentXml(attachment, {\n ...context,\n messageId: entry.messageId,\n }),\n ),\n ].join(\"\\n\");\n return `<${tagName}${attrString}>\\n${body}\\n</${tagName}>`;\n}\n\nfunction buildThreadContextXml(msg: InboundChannelMessage): string | null {\n const threadContext = msg.threadContext;\n if (!threadContext) {\n return null;\n }\n\n const parts: string[] = [];\n if (threadContext.starter) {\n parts.push(\n buildThreadContextEntryXml(\"thread-starter\", threadContext.starter, {\n channel: msg.channel,\n accountId: msg.accountId,\n chatId: msg.chatId,\n }),\n );\n }\n const historyEntries = threadContext.history ?? [];\n if (historyEntries.length > 0) {\n parts.push(\n [\n \"<thread-history>\",\n ...historyEntries.map((entry) =>\n buildThreadContextEntryXml(\"thread-message\", entry, {\n channel: msg.channel,\n accountId: msg.accountId,\n chatId: msg.chatId,\n }),\n ),\n \"</thread-history>\",\n ].join(\"\\n\"),\n );\n }\n\n if (parts.length === 0) {\n return null;\n }\n\n const attrs = threadContext.label\n ? ` label=\"${escapeXmlAttribute(threadContext.label)}\"`\n : \"\";\n return [`<thread-context${attrs}>`, ...parts, \"</thread-context>\"].join(\"\\n\");\n}\n\n/**\n * Format an inbound channel message as XML for the agent.\n *\n * Example output:\n * ```xml\n * <channel-notification source=\"telegram\" chat_id=\"12345\" sender_id=\"67890\" sender_name=\"John\">\n * Hello from Telegram!\n * </channel-notification>\n * ```\n */\nexport function buildChannelNotificationXml(\n msg: InboundChannelMessage,\n): string {\n const attrs: string[] = [\n `source=\"${escapeXmlAttribute(msg.channel)}\"`,\n `chat_id=\"${escapeXmlAttribute(msg.chatId)}\"`,\n `sender_id=\"${escapeXmlAttribute(msg.senderId)}\"`,\n ];\n\n if (msg.accountId) {\n attrs.push(`account_id=\"${escapeXmlAttribute(msg.accountId)}\"`);\n }\n\n if (msg.senderName) {\n attrs.push(`sender_name=\"${escapeXmlAttribute(msg.senderName)}\"`);\n }\n\n if (msg.messageId) {\n attrs.push(`message_id=\"${escapeXmlAttribute(msg.messageId)}\"`);\n }\n\n if (msg.threadId) {\n attrs.push(`thread_id=\"${escapeXmlAttribute(msg.threadId)}\"`);\n }\n if (msg.routedBy) {\n attrs.push(`routed_by=\"${escapeXmlAttribute(msg.routedBy)}\"`);\n }\n\n const attrString = attrs.join(\" \");\n const escapedText = msg.text\n ? buildTextWithUserMentions(msg.text, msg.userMentions)\n : \"\";\n const reactionXml = buildReactionXml(msg);\n const replyContextXml = buildReplyContextXml(msg);\n const threadContextXml = buildThreadContextXml(msg);\n const attachmentXml = (msg.attachments ?? []).map((attachment) =>\n buildAttachmentXml(attachment, {\n channel: msg.channel,\n accountId: msg.accountId,\n chatId: msg.chatId,\n messageId: msg.messageId,\n }),\n );\n const body = [\n threadContextXml,\n replyContextXml,\n reactionXml,\n ...attachmentXml,\n escapedText,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n\n return `<channel-notification ${attrString}>\\n${body}\\n</channel-notification>`;\n}\n\n/**\n * Format an inbound channel message as structured content parts.\n *\n * Attachment guidance is emitted only when this event contains inspectable\n * local paths. Reply semantics live in the scoped MessageChannel definition.\n */\nexport function formatChannelNotification(\n msg: InboundChannelMessage,\n): MessageCreate[\"content\"] {\n const attachmentReminder = buildChannelAttachmentReminderText(msg);\n return [\n ...(attachmentReminder\n ? [{ type: \"text\" as const, text: attachmentReminder }]\n : []),\n { type: \"text\", text: buildChannelNotificationXml(msg) },\n ...(msg.attachments ?? []).flatMap((attachment) => {\n if (\n attachment.kind !== \"image\" ||\n typeof attachment.imageDataBase64 !== \"string\" ||\n attachment.imageDataBase64.length === 0 ||\n typeof attachment.mimeType !== \"string\" ||\n !canEmitInlineImageContentPart(attachment.mimeType)\n ) {\n return [];\n }\n\n return [\n {\n type: \"image\" as const,\n source: {\n type: \"base64\" as const,\n media_type: attachment.mimeType,\n data: attachment.imageDataBase64,\n },\n },\n ];\n }),\n ] as MessageCreate[\"content\"];\n}\n",
12
12
  "import type {\n ChannelRoute,\n ChannelTurnSource,\n InboundChannelMessage,\n OutboundChannelMessage,\n} from \"./types\";\nimport { buildChannelNotificationXml } from \"./xml\";\n\nexport interface BuildChannelTurnSourceParams {\n message: InboundChannelMessage;\n route: ChannelRoute;\n}\n\nexport interface FormatInboundChannelMessageParams {\n message: InboundChannelMessage;\n}\n\nexport interface ChannelBatchMessage {\n text: string;\n senderId?: string;\n senderName?: string;\n timestamp?: string | number;\n channelTurnSource?: ChannelTurnSource;\n}\n\nexport interface FormatBatchedChannelMessagesParams {\n messages: ChannelBatchMessage[];\n}\n\nexport interface BuildOutboundChannelMessageFromTurnSourceParams {\n turnSource: ChannelTurnSource;\n text: string;\n}\n\nexport function buildChannelTurnSource(\n params: BuildChannelTurnSourceParams,\n): ChannelTurnSource {\n const { message, route } = params;\n return {\n channel: message.channel,\n accountId: message.accountId ?? route.accountId,\n chatId: message.chatId,\n chatType: message.chatType ?? route.chatType,\n senderId: message.senderId,\n senderTeamId: message.senderTeamId,\n messageId: message.messageId,\n threadId: message.threadId ?? route.threadId ?? null,\n agentId: route.agentId,\n conversationId: route.conversationId,\n };\n}\n\nexport function formatInboundChannelMessageForAgent(\n params: FormatInboundChannelMessageParams,\n): string {\n return buildChannelNotificationXml(params.message);\n}\n\nfunction formatLegacyBatchedSender(message: ChannelBatchMessage): string {\n const senderName = message.senderName?.trim();\n const senderId = message.senderId?.trim() ?? \"unknown\";\n if (senderName && senderName !== senderId) {\n return `${senderName}:${senderId}`;\n }\n return senderId;\n}\n\nexport function formatBatchedChannelMessagesForAgent(\n params: FormatBatchedChannelMessagesParams,\n): string {\n const { messages } = params;\n if (messages.length === 0) {\n return \"\";\n }\n const firstMessage = messages[0];\n if (messages.length === 1 && firstMessage) {\n return firstMessage.text;\n }\n\n if (messages.every((message) => message.channelTurnSource)) {\n const formatted = messages.map((message) => message.text).join(\"\\n\");\n return `--- Batched Channel Messages (${messages.length}) ---\\n${formatted}\\n--- End Batched Channel Messages ---`;\n }\n\n const formatted = messages\n .map((message) => {\n const timestamp = message.timestamp ?? \"unknown\";\n return `[user@${formatLegacyBatchedSender(message)}]<${timestamp}>:${message.text}`;\n })\n .join(\"\\n\");\n\n return `--- Batched Messages (${messages.length}) ---\\n${formatted}\\n--- End Batched Messages ---`;\n}\n\nexport function buildOutboundChannelMessageFromTurnSource(\n params: BuildOutboundChannelMessageFromTurnSourceParams,\n): OutboundChannelMessage {\n const { turnSource } = params;\n return {\n channel: turnSource.channel,\n accountId: turnSource.accountId,\n chatId: turnSource.chatId,\n threadId: turnSource.threadId,\n text: params.text,\n agentId: turnSource.agentId,\n conversationId: turnSource.conversationId,\n };\n}\n",
13
13
  "const MAX_PROGRESS_TEXT_LENGTH = 140;\nexport const MAX_PROGRESS_DETAILS_LENGTH = 180;\nconst MAX_SHELL_PROGRESS_DETAILS_LENGTH = 64;\nconst MAX_SUBAGENT_PROGRESS_DETAILS_LENGTH = 180;\nconst ESCAPE_CODE = String.fromCharCode(27);\nconst ANSI_ESCAPE_RE = new RegExp(`${ESCAPE_CODE}\\\\[[0-9;?]*[ -/]*[@-~]`, \"g\");\nconst SECRET_ASSIGNMENT_RE =\n /\\b([A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASS|API[_-]?KEY|ACCESS[_-]?KEY)[A-Z0-9_]*)\\s*=\\s*(\"[^\"]*\"|'[^']*'|\\S+)/gi;\nconst SECRET_JSON_RE =\n /([\"']?(?:token|secret|password|api[_-]?key|access[_-]?key)[\"']?\\s*[:=]\\s*)(\"[^\"]*\"|'[^']*'|\\S+)/gi;\n\nfunction isTaskTool(name: string): boolean {\n return (\n name === \"Task\" || name === \"task\" || name === \"Agent\" || name === \"agent\"\n );\n}\n\nfunction isShellTool(name: string): boolean {\n const normalized = name.toLowerCase();\n return normalized === \"bash\" || normalized === \"exec_command\";\n}\n\nfunction isWebSearchTool(name: string): boolean {\n const normalized = name.toLowerCase();\n return normalized === \"web_search\" || normalized === \"websearch\";\n}\n\nfunction isSearchTool(name: string): boolean {\n return name.toLowerCase() === \"grep\";\n}\n\nfunction isGlobTool(name: string): boolean {\n return name.toLowerCase() === \"glob\";\n}\n\nfunction isFileReadTool(name: string): boolean {\n return name.toLowerCase() === \"read\";\n}\n\nfunction isFileWriteTool(name: string): boolean {\n const normalized = name.toLowerCase();\n return normalized === \"write\";\n}\n\nfunction isFileEditTool(name: string): boolean {\n const normalized = name.toLowerCase();\n return normalized === \"edit\" || normalized === \"applypatch\";\n}\n\nfunction getPathBaseName(filePath: string): string {\n return filePath.split(/[\\\\/]/).filter(Boolean).pop() ?? filePath;\n}\n\nexport function asRecord(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\"\n ? (value as Record<string, unknown>)\n : null;\n}\n\nexport function firstNonEmptyString(...values: unknown[]): string | undefined {\n for (const value of values) {\n if (typeof value === \"string\" && value.trim().length > 0) {\n return value;\n }\n }\n return undefined;\n}\n\nexport function truncateChannelProgressText(\n value: string,\n maxLength: number,\n marker = \"...\",\n): string {\n if (value.length <= maxLength) {\n return value;\n }\n if (maxLength <= marker.length) {\n return marker.slice(0, Math.max(0, maxLength));\n }\n return `${value.slice(0, maxLength - marker.length).trimEnd()}${marker}`;\n}\n\nfunction replaceControlCharacters(value: string): string {\n let result = \"\";\n for (let index = 0; index < value.length; index += 1) {\n const character = value[index] ?? \"\";\n const code = character.charCodeAt(0);\n result +=\n code <= 8 ||\n code === 11 ||\n code === 12 ||\n (code >= 14 && code <= 31) ||\n code === 127\n ? \" \"\n : character;\n }\n return result;\n}\n\n/**\n * Shared sanitization core for channel-facing progress text: strips ANSI\n * escapes and control characters, redacts secret-looking assignments, and\n * neutralizes platform mentions. Platform adapters layer their own escaping\n * (and truncation marker) on top of this instead of maintaining parallel\n * redaction rules.\n */\nexport function sanitizeChannelProgressCore(value: unknown): string {\n const raw = typeof value === \"string\" ? value : String(value ?? \"\");\n const redacted = raw\n .replace(ANSI_ESCAPE_RE, \"\")\n .replace(SECRET_ASSIGNMENT_RE, \"$1=[redacted]\")\n .replace(SECRET_JSON_RE, \"$1[redacted]\");\n return replaceControlCharacters(redacted)\n .replace(/[\\r\\n\\t]+/g, \" \")\n .replace(/@(?=channel|here|everyone|[A-Za-z0-9._-]+)/gi, \"@\\u200b\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nexport function sanitizeChannelProgressText(\n value: unknown,\n maxLength: number = MAX_PROGRESS_TEXT_LENGTH,\n): string {\n return truncateChannelProgressText(\n sanitizeChannelProgressCore(value),\n maxLength,\n );\n}\n\nexport function sanitizeChannelProgressIdentifier(\n value: unknown,\n fallback: string,\n): string {\n const text = sanitizeChannelProgressText(value, 64);\n if (!text) {\n return fallback;\n }\n const cleaned = text.replace(/[^A-Za-z0-9_.:/ -]/g, \"\").trim();\n return cleaned || fallback;\n}\n\nfunction summarizeShellCommand(command: string): string {\n const normalized = sanitizeChannelProgressText(command, 10_000);\n if (!normalized) {\n return \"\";\n }\n\n // Preview the first command segment (or two short ones) and drop pipelines\n // so multi-step shell invocations stay readable at Slack-row lengths.\n const segments = normalized\n .split(/\\s*;\\s*/)\n .map((segment) => segment.trim())\n .filter(Boolean);\n const firstTwoSegments = segments.slice(0, 2).join(\"; \");\n const previewSource =\n firstTwoSegments.length > 0 && firstTwoSegments.length <= 70\n ? firstTwoSegments\n : (segments[0] ?? normalized);\n const withoutPipeline = previewSource.split(/\\s*\\|\\s*/)[0] ?? previewSource;\n\n return sanitizeChannelProgressText(\n withoutPipeline.trim() || normalized,\n MAX_SHELL_PROGRESS_DETAILS_LENGTH,\n );\n}\n\ntype SkillDescriptionLookup =\n | ReadonlyMap<string, string>\n | Readonly<Record<string, string | undefined>>;\n\nexport type ChannelTurnProgressBuilderOptions = {\n skillDescriptionsByName?: SkillDescriptionLookup;\n};\n\nexport type ToolCallSummary = {\n id?: string;\n name?: string;\n argumentsText?: string;\n};\n\nfunction getSkillNameFromArguments(\n parsedArguments: Record<string, unknown>,\n): string | undefined {\n return firstNonEmptyString(parsedArguments.skill, parsedArguments.skillName);\n}\n\nfunction getFragmentedSkillName(summary: ToolCallSummary): string | undefined {\n const skillMatch = summary.argumentsText?.match(\n /\"(?:skill|skillName)\"\\s*:\\s*\"([^\"]+)\"/,\n );\n return skillMatch?.[1];\n}\n\nfunction resolveSkillDescription(\n skillName: string | undefined,\n options: ChannelTurnProgressBuilderOptions | undefined,\n): string | undefined {\n const lookup = options?.skillDescriptionsByName;\n if (!skillName || !lookup) {\n return undefined;\n }\n if (\"get\" in lookup && typeof lookup.get === \"function\") {\n return firstNonEmptyString(lookup.get(skillName));\n }\n return firstNonEmptyString(\n (lookup as Readonly<Record<string, string | undefined>>)[skillName],\n );\n}\n\nfunction formatSkillProgressTitleFromName(\n skillName: string | undefined,\n): string | undefined {\n const sanitized = sanitizeChannelProgressText(\n skillName,\n MAX_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized ? `Skill: ${sanitized}` : undefined;\n}\n\nfunction formatShellProgressDetailsFromArguments(\n parsedArguments: Record<string, unknown>,\n): string | undefined {\n const description = firstNonEmptyString(parsedArguments.description);\n if (description) {\n return (\n sanitizeChannelProgressText(description, MAX_PROGRESS_DETAILS_LENGTH) ||\n undefined\n );\n }\n\n const commandPreview = firstNonEmptyString(\n parsedArguments.command,\n parsedArguments.cmd,\n );\n return summarizeShellCommand(commandPreview ?? \"\") || undefined;\n}\n\nfunction formatFragmentedShellProgressDetails(\n summary: ToolCallSummary,\n): string | undefined {\n const descriptionMatch = summary.argumentsText?.match(\n /\"description\"\\s*:\\s*\"([^\"]+)\"/,\n );\n if (descriptionMatch?.[1]) {\n return (\n sanitizeChannelProgressText(\n descriptionMatch[1],\n MAX_PROGRESS_DETAILS_LENGTH,\n ) || undefined\n );\n }\n\n // Do not expose command previews from incomplete shell-tool JSON. Slack's\n // task stream appends, rather than replaces, changed details for the same\n // task id; sending a provisional command before the streamed description\n // arrives leaves both strings glued together in the preview.\n return undefined;\n}\n\nfunction formatSubagentProgressDetailsFromArguments(\n parsedArguments: Record<string, unknown>,\n): string | undefined {\n const preview = firstNonEmptyString(\n parsedArguments.prompt,\n parsedArguments.description,\n parsedArguments.subject,\n );\n const sanitized = sanitizeChannelProgressText(\n preview,\n MAX_SUBAGENT_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized || undefined;\n}\n\nfunction formatFragmentedSubagentProgressDetails(\n summary: ToolCallSummary,\n): string | undefined {\n const previewMatch = summary.argumentsText?.match(\n /\"(?:prompt|description|subject)\"\\s*:\\s*\"([^\"]+)\"/,\n );\n if (!previewMatch?.[1]) {\n return undefined;\n }\n const sanitized = sanitizeChannelProgressText(\n previewMatch[1],\n MAX_SUBAGENT_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized || undefined;\n}\n\nexport type ToolReturnSummary = {\n summary: ToolCallSummary;\n status: \"completed\" | \"error\";\n errorDetails?: string;\n};\n\nexport function parseToolArguments(\n value: string | undefined,\n): Record<string, unknown> | null {\n if (!value) {\n return null;\n }\n try {\n return asRecord(JSON.parse(value));\n } catch {\n return null;\n }\n}\n\nfunction isFetchWebpageToolName(name: string | undefined): boolean {\n return (\n name === \"fetch_webpage\" ||\n name === \"FetchWebpage\" ||\n name === \"fetchWebpage\"\n );\n}\n\nexport function isSkillToolName(name: string | undefined): boolean {\n if (!name) {\n return false;\n }\n const normalized = name.includes(\".\")\n ? name.slice(name.lastIndexOf(\".\") + 1)\n : name;\n return normalized === \"Skill\" || normalized === \"skill\";\n}\n\nfunction isFilePathToolName(name: string): boolean {\n return isFileReadTool(name) || isFileWriteTool(name) || isFileEditTool(name);\n}\n\nfunction getFileToolKind(name: string): \"read\" | \"write\" | \"update\" | null {\n if (isFileReadTool(name)) {\n return \"read\";\n }\n if (isFileWriteTool(name)) {\n return \"write\";\n }\n if (isFileEditTool(name)) {\n return \"update\";\n }\n return null;\n}\n\nfunction countProgressLines(value: string | undefined): number {\n return value ? value.split(\"\\n\").length : 0;\n}\n\ntype LineChangeSummary = {\n additions?: number;\n deletions?: number;\n};\n\nfunction formatLineChangeSummary(summary: LineChangeSummary | null): string {\n if (!summary) {\n return \"\";\n }\n const parts: string[] = [];\n if (summary.additions !== undefined) {\n parts.push(`+${summary.additions}`);\n }\n if (summary.deletions !== undefined) {\n parts.push(`-${summary.deletions}`);\n }\n return parts.length > 0 ? ` ${parts.join(\" \")}` : \"\";\n}\n\nfunction extractFilePathFromArguments(\n parsedArguments: Record<string, unknown>,\n): string | undefined {\n return firstNonEmptyString(\n parsedArguments.file_path,\n parsedArguments.filePath,\n parsedArguments.path,\n );\n}\n\nfunction formatProgressFileName(\n filePath: string | undefined,\n): string | undefined {\n if (!filePath) {\n return undefined;\n }\n const fileName = getPathBaseName(filePath) || filePath;\n return (\n sanitizeChannelProgressText(fileName, MAX_PROGRESS_DETAILS_LENGTH) ||\n undefined\n );\n}\n\nfunction getEditLineChangeSummary(\n parsedArguments: Record<string, unknown>,\n): LineChangeSummary | null {\n const oldString = firstNonEmptyString(parsedArguments.old_string);\n const newString = firstNonEmptyString(parsedArguments.new_string);\n if (oldString === undefined || newString === undefined) {\n return null;\n }\n return {\n additions: countProgressLines(newString),\n deletions: countProgressLines(oldString),\n };\n}\n\nfunction getWriteLineChangeSummary(\n parsedArguments: Record<string, unknown>,\n): LineChangeSummary | null {\n const content = firstNonEmptyString(parsedArguments.content);\n if (content === undefined) {\n return null;\n }\n return {\n additions: countProgressLines(content),\n };\n}\n\nfunction getFileLineChangeSummary(\n name: string,\n parsedArguments: Record<string, unknown>,\n): LineChangeSummary | null {\n if (isFileWriteTool(name)) {\n return getWriteLineChangeSummary(parsedArguments);\n }\n if (isFileEditTool(name)) {\n return getEditLineChangeSummary(parsedArguments);\n }\n return null;\n}\n\nfunction getFileToolVerb(\n kind: \"read\" | \"write\" | \"update\",\n status: \"started\" | \"completed\" | \"error\",\n): string {\n if (status === \"error\") {\n if (kind === \"read\") {\n return \"Tried to read\";\n }\n if (kind === \"write\") {\n return \"Tried to write\";\n }\n return \"Tried to update\";\n }\n if (status === \"started\") {\n if (kind === \"read\") {\n return \"Reading\";\n }\n if (kind === \"write\") {\n return \"Writing\";\n }\n return \"Updating\";\n }\n if (kind === \"read\") {\n return \"Read\";\n }\n if (kind === \"write\") {\n return \"Wrote\";\n }\n return \"Updated\";\n}\n\nfunction formatFileToolProgressTitle(\n name: string,\n parsedArguments: Record<string, unknown>,\n status: \"started\" | \"completed\" | \"error\",\n): string | undefined {\n const kind = getFileToolKind(name);\n if (!kind) {\n return undefined;\n }\n const fileName = formatProgressFileName(\n extractFilePathFromArguments(parsedArguments),\n );\n if (!fileName) {\n return undefined;\n }\n const stats =\n status === \"completed\"\n ? formatLineChangeSummary(getFileLineChangeSummary(name, parsedArguments))\n : \"\";\n return `${getFileToolVerb(kind, status)} ${fileName}${stats}`;\n}\n\nfunction formatFragmentedFileToolProgressTitle(\n summary: ToolCallSummary,\n status: \"started\" | \"completed\" | \"error\",\n): string | undefined {\n if (!summary.name || !summary.argumentsText) {\n return undefined;\n }\n const kind = getFileToolKind(summary.name);\n if (!kind) {\n return undefined;\n }\n const filePathMatch = summary.argumentsText.match(\n /\"(?:file_path|filePath|path)\"\\s*:\\s*\"([^\"]+)\"/,\n );\n const fileName = formatProgressFileName(filePathMatch?.[1]);\n if (!fileName) {\n return undefined;\n }\n return `${getFileToolVerb(kind, status)} ${fileName}`;\n}\n\nexport function formatToolProgressTitle(\n summary: ToolCallSummary,\n status: \"started\" | \"completed\" | \"error\",\n): string | undefined {\n if (!summary.name || !summary.argumentsText) {\n return undefined;\n }\n\n const parsedArguments = parseToolArguments(summary.argumentsText);\n if (parsedArguments) {\n if (isSkillToolName(summary.name)) {\n return formatSkillProgressTitleFromName(\n getSkillNameFromArguments(parsedArguments),\n );\n }\n if (isFilePathToolName(summary.name)) {\n return formatFileToolProgressTitle(summary.name, parsedArguments, status);\n }\n return undefined;\n }\n\n if (isSkillToolName(summary.name)) {\n return formatSkillProgressTitleFromName(getFragmentedSkillName(summary));\n }\n if (isFilePathToolName(summary.name)) {\n return formatFragmentedFileToolProgressTitle(summary, status);\n }\n return undefined;\n}\n\nfunction formatSkillProgressDetailsFromArguments(\n parsedArguments: Record<string, unknown>,\n options: ChannelTurnProgressBuilderOptions | undefined,\n): string | undefined {\n const skillName = getSkillNameFromArguments(parsedArguments);\n const detail = resolveSkillDescription(skillName, options) ?? skillName;\n const sanitized = sanitizeChannelProgressText(\n detail,\n MAX_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized || undefined;\n}\n\nfunction formatFragmentedSkillProgressDetails(\n summary: ToolCallSummary,\n options: ChannelTurnProgressBuilderOptions | undefined,\n): string | undefined {\n const skillName = getFragmentedSkillName(summary);\n const detail = resolveSkillDescription(skillName, options) ?? skillName;\n const sanitized = sanitizeChannelProgressText(\n detail,\n MAX_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized || undefined;\n}\n\nexport function formatToolProgressDetails(\n summary: ToolCallSummary,\n options?: ChannelTurnProgressBuilderOptions,\n): string | undefined {\n if (!summary.name || !summary.argumentsText) {\n return undefined;\n }\n\n // Try JSON parse first (complete arguments)\n const parsedArguments = parseToolArguments(summary.argumentsText);\n if (parsedArguments) {\n if (isWebSearchTool(summary.name)) {\n const query = firstNonEmptyString(parsedArguments.query);\n const sanitized = sanitizeChannelProgressText(\n query,\n MAX_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized || undefined;\n }\n\n if (isFetchWebpageToolName(summary.name)) {\n const url = firstNonEmptyString(parsedArguments.url);\n const sanitized = sanitizeChannelProgressText(\n url,\n MAX_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized || undefined;\n }\n\n if (isSkillToolName(summary.name)) {\n return formatSkillProgressDetailsFromArguments(parsedArguments, options);\n }\n\n if (isTaskTool(summary.name)) {\n return formatSubagentProgressDetailsFromArguments(parsedArguments);\n }\n\n if (isShellTool(summary.name)) {\n return formatShellProgressDetailsFromArguments(parsedArguments);\n }\n\n if (isFilePathToolName(summary.name)) {\n const filePath = firstNonEmptyString(\n parsedArguments.file_path,\n parsedArguments.filePath,\n parsedArguments.path,\n );\n const sanitized = sanitizeChannelProgressText(\n filePath,\n MAX_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized || undefined;\n }\n\n if (isGlobTool(summary.name) || isSearchTool(summary.name)) {\n const pattern = firstNonEmptyString(parsedArguments.pattern);\n const sanitized = sanitizeChannelProgressText(\n pattern,\n MAX_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized || undefined;\n }\n\n return undefined;\n }\n\n // Fallback: extract known preview fields from fragmented/incomplete JSON.\n if (isShellTool(summary.name)) {\n return formatFragmentedShellProgressDetails(summary);\n }\n\n if (isSkillToolName(summary.name)) {\n return formatFragmentedSkillProgressDetails(summary, options);\n }\n\n if (isTaskTool(summary.name)) {\n return formatFragmentedSubagentProgressDetails(summary);\n }\n\n if (isFilePathToolName(summary.name)) {\n const filePathMatch = summary.argumentsText.match(\n /\"file_path\"\\s*:\\s*\"([^\"]+)\"/,\n );\n if (filePathMatch?.[1]) {\n const sanitized = sanitizeChannelProgressText(\n filePathMatch[1],\n MAX_PROGRESS_DETAILS_LENGTH,\n );\n return sanitized || undefined;\n }\n }\n\n return undefined;\n}\n",
@@ -485,22 +485,6 @@ class ChannelControlRequestCoordinator {
485
485
  return true;
486
486
  }
487
487
  }
488
- // src/tools/interactive-policy.ts
489
- var INTERACTIVE_APPROVAL_TOOLS = new Set(["AskUserQuestion"]);
490
- var INTERACTIVE_USER_INPUT_TOOL_NAMES = [
491
- "AskUserQuestion"
492
- ];
493
- var RUNTIME_USER_INPUT_TOOLS = new Set(INTERACTIVE_USER_INPUT_TOOL_NAMES);
494
- var HEADLESS_AUTO_ALLOW_TOOLS = new Set;
495
- function getInteractiveApprovalKind(toolName) {
496
- switch (toolName) {
497
- case "AskUserQuestion":
498
- return "ask_user_question";
499
- default:
500
- return null;
501
- }
502
- }
503
-
504
488
  // src/channels/gateway-sources.ts
505
489
  function sourceRouteKey(source) {
506
490
  return [
@@ -1449,7 +1433,8 @@ class ChannelGateway {
1449
1433
  client_message_id: delivery.clientMessageId
1450
1434
  }
1451
1435
  ],
1452
- image_failure_mode: "drop"
1436
+ image_failure_mode: "drop",
1437
+ client_preferences: {}
1453
1438
  }
1454
1439
  });
1455
1440
  if (!response.accepted) {
@@ -1994,7 +1979,7 @@ class ChannelGateway {
1994
1979
  return;
1995
1980
  this.enqueueHook(state, () => this.hooks.onControlRequest({
1996
1981
  requestId: message.request_id,
1997
- kind: getInteractiveApprovalKind(message.request.tool_name) ?? "generic_tool_approval",
1982
+ kind: "generic_tool_approval",
1998
1983
  source,
1999
1984
  toolName: message.request.tool_name,
2000
1985
  input: message.request.input
@@ -2257,7 +2242,8 @@ function replaceSlackMarkdownLinks(text, placeholders) {
2257
2242
  index++;
2258
2243
  continue;
2259
2244
  }
2260
- result += createSlackPlaceholder(placeholders, `<${escapeSlackMrkdwnSegment(link.url)}|${formatSlackLinkLabel(link.label)}>`);
2245
+ const label = restoreSlackPlaceholders(formatSlackLinkLabel(link.label), placeholders);
2246
+ result += createSlackPlaceholder(placeholders, `<${escapeSlackMrkdwnSegment(link.url)}|${label}>`);
2261
2247
  index = link.endIndex;
2262
2248
  }
2263
2249
  return result;
@@ -3075,4 +3061,4 @@ export {
3075
3061
  ChannelControlRequestCoordinator
3076
3062
  };
3077
3063
 
3078
- //# debugId=39779204B75018A464756E2164756E21
3064
+ //# debugId=B90B3E001C72BB3264756E2164756E21