@xopcai/xopc 0.0.15 → 0.0.16

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 (157) hide show
  1. package/dist/extensions/feishu/src/adapters/onboard-cli.d.ts +7 -0
  2. package/dist/extensions/feishu/src/adapters/onboard-cli.js +432 -0
  3. package/dist/extensions/feishu/src/adapters/onboard-cli.js.map +1 -0
  4. package/dist/extensions/feishu/src/auth/pairing.d.ts +7 -0
  5. package/dist/extensions/feishu/src/auth/pairing.js +45 -0
  6. package/dist/extensions/feishu/src/auth/pairing.js.map +1 -0
  7. package/dist/extensions/feishu/src/auth/paths.d.ts +2 -0
  8. package/dist/extensions/feishu/src/auth/paths.js +18 -0
  9. package/dist/extensions/feishu/src/auth/paths.js.map +1 -0
  10. package/dist/extensions/feishu/src/directory/directory-adapter.d.ts +2 -0
  11. package/dist/extensions/feishu/src/directory/directory-adapter.js +27 -0
  12. package/dist/extensions/feishu/src/directory/directory-adapter.js.map +1 -0
  13. package/dist/extensions/feishu/src/format.d.ts +21 -0
  14. package/dist/extensions/feishu/src/format.js +99 -0
  15. package/dist/extensions/feishu/src/format.js.map +1 -0
  16. package/dist/extensions/feishu/src/index.d.ts +5 -0
  17. package/dist/extensions/feishu/src/index.js +3 -0
  18. package/dist/extensions/feishu/src/outbound/actions.d.ts +51 -0
  19. package/dist/extensions/feishu/src/outbound/actions.js +62 -0
  20. package/dist/extensions/feishu/src/outbound/actions.js.map +1 -0
  21. package/dist/extensions/feishu/src/outbound/media-load.d.ts +12 -0
  22. package/dist/extensions/feishu/src/outbound/media-load.js +125 -0
  23. package/dist/extensions/feishu/src/outbound/media-load.js.map +1 -0
  24. package/dist/extensions/feishu/src/outbound/outbound-adapter.d.ts +2 -0
  25. package/dist/extensions/feishu/src/outbound/outbound-adapter.js +201 -0
  26. package/dist/extensions/feishu/src/outbound/outbound-adapter.js.map +1 -0
  27. package/dist/extensions/feishu/src/plugin.d.ts +70 -0
  28. package/dist/extensions/feishu/src/plugin.js +313 -0
  29. package/dist/extensions/feishu/src/plugin.js.map +1 -0
  30. package/dist/extensions/feishu/src/schema/config-schema.d.ts +215 -0
  31. package/dist/extensions/feishu/src/schema/config-schema.js +198 -0
  32. package/dist/extensions/feishu/src/schema/config-schema.js.map +1 -0
  33. package/dist/extensions/feishu/src/state/accounts.d.ts +38 -0
  34. package/dist/extensions/feishu/src/state/accounts.js +96 -0
  35. package/dist/extensions/feishu/src/state/accounts.js.map +1 -0
  36. package/dist/extensions/feishu/src/state/message-bindings.d.ts +11 -0
  37. package/dist/extensions/feishu/src/state/message-bindings.js +41 -0
  38. package/dist/extensions/feishu/src/state/message-bindings.js.map +1 -0
  39. package/dist/extensions/feishu/src/state/thread-bindings.js +46 -0
  40. package/dist/extensions/feishu/src/state/thread-bindings.js.map +1 -0
  41. package/dist/extensions/feishu/src/status/doctor.d.ts +2 -0
  42. package/dist/extensions/feishu/src/status/doctor.js +38 -0
  43. package/dist/extensions/feishu/src/status/doctor.js.map +1 -0
  44. package/dist/extensions/feishu/src/status/status-adapter.d.ts +3 -0
  45. package/dist/extensions/feishu/src/status/status-adapter.js +45 -0
  46. package/dist/extensions/feishu/src/status/status-adapter.js.map +1 -0
  47. package/dist/extensions/feishu/src/streaming/streaming-adapter.d.ts +3 -0
  48. package/dist/extensions/feishu/src/streaming/streaming-adapter.js +242 -0
  49. package/dist/extensions/feishu/src/streaming/streaming-adapter.js.map +1 -0
  50. package/dist/extensions/feishu/src/subagent-hooks.js +52 -0
  51. package/dist/extensions/feishu/src/subagent-hooks.js.map +1 -0
  52. package/dist/extensions/feishu/src/tools/docx/docx-batch-insert.js +95 -0
  53. package/dist/extensions/feishu/src/tools/docx/docx-batch-insert.js.map +1 -0
  54. package/dist/extensions/feishu/src/tools/docx/docx-color-text.js +75 -0
  55. package/dist/extensions/feishu/src/tools/docx/docx-color-text.js.map +1 -0
  56. package/dist/extensions/feishu/src/tools/docx/docx-table-ops.js +173 -0
  57. package/dist/extensions/feishu/src/tools/docx/docx-table-ops.js.map +1 -0
  58. package/dist/extensions/feishu/src/tools/docx/docx-types.js +1 -0
  59. package/dist/extensions/feishu/src/tools/tools.d.ts +5 -0
  60. package/dist/extensions/feishu/src/tools/tools.js +46 -0
  61. package/dist/extensions/feishu/src/tools/tools.js.map +1 -0
  62. package/dist/extensions/feishu/src/transport/client/client.d.ts +6 -0
  63. package/dist/extensions/feishu/src/transport/client/client.js +41 -0
  64. package/dist/extensions/feishu/src/transport/client/client.js.map +1 -0
  65. package/dist/extensions/feishu/src/transport/client/lark-sdk-logger.d.ts +13 -0
  66. package/dist/extensions/feishu/src/transport/client/lark-sdk-logger.js +104 -0
  67. package/dist/extensions/feishu/src/transport/client/lark-sdk-logger.js.map +1 -0
  68. package/dist/extensions/feishu/src/transport/reliability/dedupe.d.ts +7 -0
  69. package/dist/extensions/feishu/src/transport/reliability/dedupe.js +30 -0
  70. package/dist/extensions/feishu/src/transport/reliability/dedupe.js.map +1 -0
  71. package/dist/extensions/feishu/src/transport/socket-mode/monitor.d.ts +19 -0
  72. package/dist/extensions/feishu/src/transport/socket-mode/monitor.js +326 -0
  73. package/dist/extensions/feishu/src/transport/socket-mode/monitor.js.map +1 -0
  74. package/dist/extensions/feishu/src/transport/socket-mode/retry.d.ts +1 -0
  75. package/dist/extensions/feishu/src/transport/socket-mode/retry.js +10 -0
  76. package/dist/extensions/feishu/src/transport/socket-mode/retry.js.map +1 -0
  77. package/dist/extensions/feishu/src/transport/text/mentions.d.ts +1 -0
  78. package/dist/extensions/feishu/src/transport/text/mentions.js +9 -0
  79. package/dist/extensions/feishu/src/transport/text/mentions.js.map +1 -0
  80. package/dist/extensions/feishu/src/transport/webhook/monitor.d.ts +19 -0
  81. package/dist/extensions/feishu/src/transport/webhook/monitor.js +271 -0
  82. package/dist/extensions/feishu/src/transport/webhook/monitor.js.map +1 -0
  83. package/dist/extensions/feishu/src/ui/config-surface.d.ts +2 -0
  84. package/dist/extensions/feishu/src/ui/config-surface.js +6 -0
  85. package/dist/extensions/feishu/src/ui/config-surface.js.map +1 -0
  86. package/dist/extensions/feishu/xopc.extension.json +18 -0
  87. package/dist/extensions/telegram/xopc.extension.json +20 -0
  88. package/dist/extensions/weixin/xopc.extension.json +17 -0
  89. package/dist/gateway/static/root/assets/{agents-Be8iYqc2.js → agents-Dy5cGVVQ.js} +2 -2
  90. package/dist/gateway/static/root/assets/{agents-Be8iYqc2.js.map → agents-Dy5cGVVQ.js.map} +1 -1
  91. package/dist/gateway/static/root/assets/{apps-page-BLNTewgA.js → apps-page-BOpDR0Lz.js} +2 -2
  92. package/dist/gateway/static/root/assets/{apps-page-BLNTewgA.js.map → apps-page-BOpDR0Lz.js.map} +1 -1
  93. package/dist/gateway/static/root/assets/channels-settings-CrCesccB.js +9 -0
  94. package/dist/gateway/static/root/assets/channels-settings-CrCesccB.js.map +1 -0
  95. package/dist/gateway/static/root/assets/{cron-page-HoSnHbPX.js → cron-page-B_XY0gPt.js} +2 -2
  96. package/dist/gateway/static/root/assets/{cron-page-HoSnHbPX.js.map → cron-page-B_XY0gPt.js.map} +1 -1
  97. package/dist/gateway/static/root/assets/{cron-utils-D1mrWEee.js → cron-utils-BYdnLwhl.js} +2 -2
  98. package/dist/gateway/static/root/assets/{cron-utils-D1mrWEee.js.map → cron-utils-BYdnLwhl.js.map} +1 -1
  99. package/dist/gateway/static/root/assets/{dist-CowQhLuH.js → dist-DvaA5uNp.js} +2 -2
  100. package/dist/gateway/static/root/assets/{dist-CowQhLuH.js.map → dist-DvaA5uNp.js.map} +1 -1
  101. package/dist/gateway/static/root/assets/{extension-debug-page-bjoNo6JH.js → extension-debug-page-CPSk7gFW.js} +2 -2
  102. package/dist/gateway/static/root/assets/{extension-debug-page-bjoNo6JH.js.map → extension-debug-page-CPSk7gFW.js.map} +1 -1
  103. package/dist/gateway/static/root/assets/{extension-page-NU-MTrtq.js → extension-page-COdbk9I6.js} +2 -2
  104. package/dist/gateway/static/root/assets/{extension-page-NU-MTrtq.js.map → extension-page-COdbk9I6.js.map} +1 -1
  105. package/dist/gateway/static/root/assets/{extension-settings-page-_kAL2Nt-.js → extension-settings-page-BlEz2Ily.js} +2 -2
  106. package/dist/gateway/static/root/assets/{extension-settings-page-_kAL2Nt-.js.map → extension-settings-page-BlEz2Ily.js.map} +1 -1
  107. package/dist/gateway/static/root/assets/index-BQNdJlkw.css +1 -0
  108. package/dist/gateway/static/root/assets/index-tm9ZY35l.js +144 -0
  109. package/dist/gateway/static/root/assets/{index-DoudkP0H.js.map → index-tm9ZY35l.js.map} +1 -1
  110. package/dist/gateway/static/root/assets/{logs-page-BYPAmUPJ.js → logs-page-LSa0jmLO.js} +2 -2
  111. package/dist/gateway/static/root/assets/{logs-page-BYPAmUPJ.js.map → logs-page-LSa0jmLO.js.map} +1 -1
  112. package/dist/gateway/static/root/assets/sessions-page-cn2fi_V3.js +2 -0
  113. package/dist/gateway/static/root/assets/sessions-page-cn2fi_V3.js.map +1 -0
  114. package/dist/gateway/static/root/assets/{settings-page-BWE5R4rm.js → settings-page-CyHd5szQ.js} +2 -2
  115. package/dist/gateway/static/root/assets/{settings-page-BWE5R4rm.js.map → settings-page-CyHd5szQ.js.map} +1 -1
  116. package/dist/gateway/static/root/assets/{skills-page-CA2NcCUa.js → skills-page-irjxwW9u.js} +2 -2
  117. package/dist/gateway/static/root/assets/{skills-page-CA2NcCUa.js.map → skills-page-irjxwW9u.js.map} +1 -1
  118. package/dist/gateway/static/root/channel-icons/feishu.svg +12 -0
  119. package/dist/gateway/static/root/channel-icons/lark.svg +12 -0
  120. package/dist/gateway/static/root/channel-icons/telegram.svg +1 -0
  121. package/dist/gateway/static/root/channel-icons/wechat.svg +1 -0
  122. package/dist/gateway/static/root/channel-icons/weixin.svg +1 -0
  123. package/dist/gateway/static/root/index.html +2 -2
  124. package/dist/package.js +1 -1
  125. package/dist/src/agent/agent-manager.d.ts +1 -0
  126. package/dist/src/agent/agent-manager.js +1 -0
  127. package/dist/src/agent/agent-manager.js.map +1 -1
  128. package/dist/src/agent/service.js +1 -0
  129. package/dist/src/agent/service.js.map +1 -1
  130. package/dist/src/agent/tools/delegate-tool.d.ts +8 -0
  131. package/dist/src/agent/tools/delegate-tool.js +60 -2
  132. package/dist/src/agent/tools/delegate-tool.js.map +1 -1
  133. package/dist/src/agent/tools/factory.d.ts +1 -0
  134. package/dist/src/agent/tools/factory.js +2 -0
  135. package/dist/src/agent/tools/factory.js.map +1 -1
  136. package/dist/src/channels/envelope-timestamp.d.ts +5 -0
  137. package/dist/src/channels/envelope-timestamp.js +10 -1
  138. package/dist/src/channels/envelope-timestamp.js.map +1 -1
  139. package/dist/src/channels/feishu/index.d.ts +5 -0
  140. package/dist/src/channels/feishu/index.js +4 -0
  141. package/dist/src/chat-commands/types.d.ts +1 -1
  142. package/dist/src/extensions/types/hooks.d.ts +46 -1
  143. package/dist/src/extensions/types/hooks.js +3 -0
  144. package/dist/src/extensions/types/hooks.js.map +1 -1
  145. package/dist/src/gateway/service.js +1 -1
  146. package/dist/src/generated/bundled-channel-plugins.d.ts +2 -1
  147. package/dist/src/generated/bundled-channel-plugins.js +8 -2
  148. package/dist/src/generated/bundled-channel-plugins.js.map +1 -1
  149. package/dist/src/session/session-title.js +2 -1
  150. package/dist/src/session/session-title.js.map +1 -1
  151. package/package.json +2 -2
  152. package/dist/gateway/static/root/assets/channels-settings-p-9taPc_.js +0 -9
  153. package/dist/gateway/static/root/assets/channels-settings-p-9taPc_.js.map +0 -1
  154. package/dist/gateway/static/root/assets/index-CbNEU6bw.css +0 -1
  155. package/dist/gateway/static/root/assets/index-DoudkP0H.js +0 -144
  156. package/dist/gateway/static/root/assets/sessions-page-cNZK0pkU.js +0 -2
  157. package/dist/gateway/static/root/assets/sessions-page-cNZK0pkU.js.map +0 -1
@@ -25,6 +25,7 @@ export interface ToolFactoryDeps {
25
25
  chatId: string;
26
26
  sessionKey: string;
27
27
  } | null;
28
+ hookRunner?: import('../../extensions/index.js').ExtensionHookRunner;
28
29
  bus: MessageBus;
29
30
  toolExecutorConfig?: Partial<ToolExecutorConfig>;
30
31
  /** Agent defaults (image tools, etc.); use getter so hot-reloaded config applies. */
@@ -164,6 +164,8 @@ var AgentToolsFactory = class {
164
164
  },
165
165
  bus: this.deps.bus,
166
166
  getConfig: () => this.deps.getConfig?.(),
167
+ getCurrentContext: () => this.deps.getCurrentContext?.() ?? null,
168
+ hookRunner: this.deps.hookRunner,
167
169
  toolExecutorConfig: this.deps.toolExecutorConfig
168
170
  })] : [],
169
171
  ...optionalTools
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","names":["parseRoutingSessionKey"],"sources":["../../../../src/agent/tools/factory.ts"],"sourcesContent":["/**\n * Agent Tools Factory - Creates and configures agent tools\n *\n * Centralizes tool creation logic to keep service.ts focused on orchestration.\n *\n * TTS: auto TTS is applied at the ChannelManager via maybeApplyTtsToPayload().\n * Optional \\`text_to_speech\\` tool sends explicit voice when TTS is enabled.\n */\n\nimport type { AgentTool } from '@mariozechner/pi-agent-core';\nimport type { Model, Api } from '@mariozechner/pi-ai';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport {\n createReadFileTool,\n createWriteFileTool,\n createEditFileTool,\n createListDirTool,\n createGrepTool,\n createFindTool,\n createShellTool,\n createWebSearchTool,\n webFetchTool,\n createWebExtractTool,\n createMessageTool,\n createSendMediaTool,\n createMemorySearchTool,\n createMemoryGetTool,\n createTodoTool,\n createSessionStatusTool,\n createClarifyTool,\n} from './index.js';\nimport { createCuratedMemoryTool } from './curated-memory-tool.js';\nimport { createSessionSearchTool } from './session-search-tool.js';\nimport type { BuiltinMemoryStore } from '../memory/builtin-memory-store.js';\nimport type { MemoryManager } from '../memory/manager.js';\nimport { shouldRegisterCuratedMemoryTool } from '../memory/memory-config.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { parseSessionKey as parseRoutingSessionKey } from '../../routing/session-key.js';\nimport type { GatewayClarifyRequestFn } from './clarify-tool.js';\nimport { createImageTool } from './image-tool.js';\nimport { createImageGenerateTool } from './image-generate-tool.js';\nimport { BrowserManager, createBrowserTools } from './browser/index.js';\nimport { createDelegateTool } from './delegate-tool.js';\nimport { buildSandboxToolMap, createExecuteCodeTool } from './execute-code-tool.js';\nimport { createCronjobTool } from './cronjob-tool.js';\nimport type { CronService } from '../../cron/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type { SkillManager } from '../skills/skill-manager.js';\nimport { wrapToolsWithProtection, type ToolExecutorConfig } from './executor.js';\nimport { createSkillsListTool, createSkillViewTool } from './skills-tools.js';\nimport { createSkillManageTool } from './skill-manage-tool.js';\nimport { createTextToSpeechTool } from './tts-tool.js';\nimport { mergeTtsConfigFromAppConfig } from '../../voice/tts/merge-config.js';\n\nconst log = createLogger('AgentToolsFactory');\n\n/** Channels where `clarify` can block for a user answer (web UI, Telegram, CLI readline). */\nconst CLARIFY_SUPPORTED_CHANNELS = new Set(['webchat', 'telegram', 'cli']);\n\nfunction clarifyTransportSource(sessionKey: string): string | undefined {\n const parsed = parseRoutingSessionKey(sessionKey);\n if (parsed) return parsed.source;\n // Fallback for simple `<channel>:<chatId>` keys used by webchat and CLI.\n const first = sessionKey.split(':').filter(Boolean)[0] ?? '';\n if (first === 'cli' || first === 'webchat') return first;\n return undefined;\n}\n\nexport interface ToolFactoryDeps {\n workspace: string;\n extensionRegistry?: any;\n getCurrentContext: () => { channel: string; chatId: string; sessionKey: string } | null;\n bus: MessageBus;\n toolExecutorConfig?: Partial<ToolExecutorConfig>;\n /** Agent defaults (image tools, etc.); use getter so hot-reloaded config applies. */\n getConfig?: () => Config | undefined;\n /** Session / default chat model for vision tool description. */\n getPrimaryModel?: () => Model<Api>;\n /** Built-in curated memory store (agent home `memories/`). */\n getBuiltinMemoryStore?: () => BuiltinMemoryStore;\n /** Memory orchestration (prefetch/sync + external tools). */\n getMemoryManager?: () => MemoryManager;\n /** Session store for `session_search`. */\n getSessionStore?: () => SessionStore;\n /** When set (gateway webchat), enables the `clarify` tool. */\n gatewayClarify?: { requestClarification: GatewayClarifyRequestFn };\n /** Gateway: enables the `cronjob` tool. */\n getCronService?: () => CronService | undefined;\n /** Current session skill indexing (tool gating + allowlist); used by skills_list / skill_view. */\n getSkillIndexingContext?: () =>\n | { registeredToolNames: string[]; skillAllowlist?: string[] }\n | undefined;\n /** After skill_manage mutates disk, reload skills + refresh agent prompts (optional). */\n onSkillsFilesystemMutate?: () => void;\n /** Names registered via skill_view for shell env passthrough. */\n getSkillPassthroughEnvVarNames?: () => string[];\n /** Add declared env names for the current session (no values stored). */\n registerSkillEnvPassthrough?: (names: string[]) => void;\n}\n\nexport interface CreateCoreToolsOptions {\n /** Workspace root for file/shell tools (defaults to factory workspace). */\n workspace?: string;\n /** `…/agents/<id>/bootstrap` — used so `read_file` can find SOUL.md etc. by filename. */\n bootstrapDir?: string;\n /** Tool `name` values to omit (e.g. `shell`, `extensions` for extension tools). */\n disabledTools?: Set<string>;\n /** Optional primary model for image tool heuristics. */\n getPrimaryModel?: () => Model<Api>;\n getBuiltinMemoryStore?: () => BuiltinMemoryStore;\n getMemoryManager?: () => MemoryManager;\n /** When set, registers `skills_list` and `skill_view` bound to this workspace\\'s skills. */\n getSkillManager?: () => SkillManager;\n}\n\nexport class AgentToolsFactory {\n private browserManager: BrowserManager | null = null;\n\n constructor(private deps: ToolFactoryDeps) {}\n\n private ensureBrowserManager(): BrowserManager {\n if (!this.browserManager) {\n this.browserManager = new BrowserManager({\n getHeadless: () => this.deps.getConfig?.()?.agents?.defaults?.browser?.headless !== false,\n });\n }\n return this.browserManager;\n }\n\n /** Close Playwright and all pages (gateway stop, agent manager dispose, or config hot-reload). */\n async shutdownBrowser(): Promise<void> {\n if (!this.browserManager) {\n return;\n }\n await this.browserManager.shutdown();\n this.browserManager = null;\n }\n\n /** Drop the tab for a session when its agent instance is removed. */\n async closeBrowserPageForSession(sessionKey: string): Promise<void> {\n await this.browserManager?.closePage(sessionKey);\n }\n\n createCoreTools(options?: CreateCoreToolsOptions): AgentTool<any, any>[] {\n const workspace = options?.workspace ?? this.deps.workspace;\n const { bus } = this.deps;\n const getPrimary = options?.getPrimaryModel ?? this.deps.getPrimaryModel;\n const getBuiltin = options?.getBuiltinMemoryStore ?? this.deps.getBuiltinMemoryStore;\n const getMemMgr = options?.getMemoryManager ?? this.deps.getMemoryManager;\n const getSkillMgr = options?.getSkillManager;\n const disabled = options?.disabledTools;\n\n const primary = getPrimary?.();\n const modelHasVision = primary?.input?.includes('image') ?? false;\n const cfg = this.deps.getConfig?.();\n const imageTool = createImageTool({\n config: cfg,\n workspace,\n modelHasVision,\n });\n const imageGenerateTool = createImageGenerateTool({\n config: cfg,\n workspace,\n });\n\n const optionalTools = [imageTool, imageGenerateTool].filter((t) => t != null) as any[];\n\n const readTool = createReadFileTool(workspace, { bootstrapDir: options?.bootstrapDir });\n const writeTool = createWriteFileTool(workspace);\n const editTool = createEditFileTool(workspace);\n const listDir = createListDirTool(workspace);\n const grep = createGrepTool(workspace);\n const find = createFindTool(workspace);\n\n const core: AgentTool<any, any>[] = [\n createSessionStatusTool(),\n createClarifyTool({\n resolveAskUser: () => {\n const req = this.deps.gatewayClarify?.requestClarification;\n if (!req) return null;\n const ctx = this.deps.getCurrentContext();\n if (!ctx?.sessionKey) return null;\n const source = clarifyTransportSource(ctx.sessionKey);\n if (!source || !CLARIFY_SUPPORTED_CHANNELS.has(source)) return null;\n return (r) => req(ctx.sessionKey, r);\n },\n }),\n createTodoTool({\n getSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n }),\n ...(getSkillMgr\n ? [\n createSkillsListTool({\n getSkillManager: getSkillMgr,\n getSkillIndexingContext: this.deps.getSkillIndexingContext,\n }),\n createSkillViewTool({\n getSkillManager: getSkillMgr,\n getSkillIndexingContext: this.deps.getSkillIndexingContext,\n registerSkillEnvPassthrough: this.deps.registerSkillEnvPassthrough,\n }),\n createSkillManageTool({\n getSkillManager: getSkillMgr,\n getWorkspace: () => workspace,\n onSkillsFilesystemMutate: this.deps.onSkillsFilesystemMutate,\n }),\n ]\n : []),\n readTool,\n writeTool,\n editTool,\n listDir,\n grep,\n find,\n createShellTool(workspace, {\n getSkillPassthroughEnvVarNames: this.deps.getSkillPassthroughEnvVarNames,\n }),\n createWebSearchTool(() => this.deps.getConfig?.()),\n webFetchTool,\n createWebExtractTool({ getConfig: () => this.deps.getConfig?.() }),\n // Note: TTS is NOT handled by send_message tool anymore\n // TTS is applied at the ChannelManager dispatch layer\n createMessageTool(bus, () => this.deps.getCurrentContext()),\n ...(mergeTtsConfigFromAppConfig(cfg?.tts).enabled\n ? [\n createTextToSpeechTool({\n bus,\n getContext: () => this.deps.getCurrentContext(),\n getConfig: () => this.deps.getConfig?.(),\n }),\n ]\n : []),\n createSendMediaTool(workspace, bus, () => this.deps.getCurrentContext()),\n createMemorySearchTool(workspace),\n createMemoryGetTool(workspace),\n ...(getBuiltin && shouldRegisterCuratedMemoryTool(this.deps.getConfig?.())\n ? [\n createCuratedMemoryTool(getBuiltin, {\n onMemoryWrite: (action, target, content) => {\n getMemMgr?.().onMemoryWrite(action, target, content);\n },\n }),\n ]\n : []),\n ...(getMemMgr?.().getAdditionalTools() ?? []),\n ...(this.deps.getSessionStore\n ? [\n createSessionSearchTool({\n getSessionStore: this.deps.getSessionStore,\n getConfig: this.deps.getConfig,\n getCurrentSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n }),\n ]\n : []),\n ...(this.deps.getCronService\n ? [\n createCronjobTool({\n getCronService: this.deps.getCronService,\n }),\n ]\n : []),\n ...(cfg?.agents?.defaults?.browser?.enabled === true\n ? createBrowserTools({\n getManager: () => this.ensureBrowserManager(),\n getTaskId: () => this.deps.getCurrentContext()?.sessionKey ?? 'default',\n getConfig: () => this.deps.getConfig?.(),\n })\n : []),\n ...(cfg?.agents?.defaults?.delegate?.enabled === true && primary\n ? [\n createDelegateTool({\n workspace,\n getSubagentModel: () => {\n const gp = options?.getPrimaryModel ?? this.deps.getPrimaryModel;\n const m = gp?.();\n if (!m) {\n throw new Error('No primary model configured for delegate_task');\n }\n return m;\n },\n bus: this.deps.bus,\n getConfig: () => this.deps.getConfig?.(),\n toolExecutorConfig: this.deps.toolExecutorConfig,\n }),\n ]\n : []),\n ...optionalTools,\n ];\n\n return filterToolsByDisabledSet(core, disabled);\n }\n\n createAllTools(coreOptions?: CreateCoreToolsOptions): AgentTool<any, any>[] {\n const coreTools = this.createCoreTools(coreOptions);\n const disableExtensions = coreOptions?.disabledTools?.has('extensions');\n const cfg = this.deps.getConfig?.();\n\n let bundled: AgentTool<any, any>[];\n if (!this.deps.extensionRegistry || disableExtensions) {\n bundled = coreTools;\n } else {\n const extensionTools = this.deps.extensionRegistry.getAllTools();\n log.info({ count: extensionTools.length }, 'Loaded extension tools');\n bundled = [...coreTools, ...extensionTools];\n }\n\n const wrapped = wrapToolsWithProtection(bundled, this.deps.toolExecutorConfig);\n\n const executeEnabled =\n cfg?.agents?.defaults?.executeCode?.enabled === true &&\n !coreOptions?.disabledTools?.has('execute_code');\n\n if (executeEnabled) {\n const sandboxMap = buildSandboxToolMap(wrapped);\n const executeTool = createExecuteCodeTool({ getSandboxToolMap: () => sandboxMap });\n const wrappedExecute = wrapToolsWithProtection([executeTool as any], this.deps.toolExecutorConfig);\n return [...wrapped, ...wrappedExecute];\n }\n\n return wrapped;\n }\n}\n\nfunction filterToolsByDisabledSet(\n tools: any[],\n disabled: Set<string> | undefined,\n): any[] {\n if (!disabled || disabled.size === 0) {\n return tools;\n }\n return tools.filter((t) => !disabled.has(t.name));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory.js","names":["parseRoutingSessionKey"],"sources":["../../../../src/agent/tools/factory.ts"],"sourcesContent":["/**\n * Agent Tools Factory - Creates and configures agent tools\n *\n * Centralizes tool creation logic to keep service.ts focused on orchestration.\n *\n * TTS: auto TTS is applied at the ChannelManager via maybeApplyTtsToPayload().\n * Optional \\`text_to_speech\\` tool sends explicit voice when TTS is enabled.\n */\n\nimport type { AgentTool } from '@mariozechner/pi-agent-core';\nimport type { Model, Api } from '@mariozechner/pi-ai';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport {\n createReadFileTool,\n createWriteFileTool,\n createEditFileTool,\n createListDirTool,\n createGrepTool,\n createFindTool,\n createShellTool,\n createWebSearchTool,\n webFetchTool,\n createWebExtractTool,\n createMessageTool,\n createSendMediaTool,\n createMemorySearchTool,\n createMemoryGetTool,\n createTodoTool,\n createSessionStatusTool,\n createClarifyTool,\n} from './index.js';\nimport { createCuratedMemoryTool } from './curated-memory-tool.js';\nimport { createSessionSearchTool } from './session-search-tool.js';\nimport type { BuiltinMemoryStore } from '../memory/builtin-memory-store.js';\nimport type { MemoryManager } from '../memory/manager.js';\nimport { shouldRegisterCuratedMemoryTool } from '../memory/memory-config.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { parseSessionKey as parseRoutingSessionKey } from '../../routing/session-key.js';\nimport type { GatewayClarifyRequestFn } from './clarify-tool.js';\nimport { createImageTool } from './image-tool.js';\nimport { createImageGenerateTool } from './image-generate-tool.js';\nimport { BrowserManager, createBrowserTools } from './browser/index.js';\nimport { createDelegateTool } from './delegate-tool.js';\nimport { buildSandboxToolMap, createExecuteCodeTool } from './execute-code-tool.js';\nimport { createCronjobTool } from './cronjob-tool.js';\nimport type { CronService } from '../../cron/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type { SkillManager } from '../skills/skill-manager.js';\nimport { wrapToolsWithProtection, type ToolExecutorConfig } from './executor.js';\nimport { createSkillsListTool, createSkillViewTool } from './skills-tools.js';\nimport { createSkillManageTool } from './skill-manage-tool.js';\nimport { createTextToSpeechTool } from './tts-tool.js';\nimport { mergeTtsConfigFromAppConfig } from '../../voice/tts/merge-config.js';\n\nconst log = createLogger('AgentToolsFactory');\n\n/** Channels where `clarify` can block for a user answer (web UI, Telegram, CLI readline). */\nconst CLARIFY_SUPPORTED_CHANNELS = new Set(['webchat', 'telegram', 'cli']);\n\nfunction clarifyTransportSource(sessionKey: string): string | undefined {\n const parsed = parseRoutingSessionKey(sessionKey);\n if (parsed) return parsed.source;\n // Fallback for simple `<channel>:<chatId>` keys used by webchat and CLI.\n const first = sessionKey.split(':').filter(Boolean)[0] ?? '';\n if (first === 'cli' || first === 'webchat') return first;\n return undefined;\n}\n\nexport interface ToolFactoryDeps {\n workspace: string;\n extensionRegistry?: any;\n getCurrentContext: () => { channel: string; chatId: string; sessionKey: string } | null;\n hookRunner?: import('../../extensions/index.js').ExtensionHookRunner;\n bus: MessageBus;\n toolExecutorConfig?: Partial<ToolExecutorConfig>;\n /** Agent defaults (image tools, etc.); use getter so hot-reloaded config applies. */\n getConfig?: () => Config | undefined;\n /** Session / default chat model for vision tool description. */\n getPrimaryModel?: () => Model<Api>;\n /** Built-in curated memory store (agent home `memories/`). */\n getBuiltinMemoryStore?: () => BuiltinMemoryStore;\n /** Memory orchestration (prefetch/sync + external tools). */\n getMemoryManager?: () => MemoryManager;\n /** Session store for `session_search`. */\n getSessionStore?: () => SessionStore;\n /** When set (gateway webchat), enables the `clarify` tool. */\n gatewayClarify?: { requestClarification: GatewayClarifyRequestFn };\n /** Gateway: enables the `cronjob` tool. */\n getCronService?: () => CronService | undefined;\n /** Current session skill indexing (tool gating + allowlist); used by skills_list / skill_view. */\n getSkillIndexingContext?: () =>\n | { registeredToolNames: string[]; skillAllowlist?: string[] }\n | undefined;\n /** After skill_manage mutates disk, reload skills + refresh agent prompts (optional). */\n onSkillsFilesystemMutate?: () => void;\n /** Names registered via skill_view for shell env passthrough. */\n getSkillPassthroughEnvVarNames?: () => string[];\n /** Add declared env names for the current session (no values stored). */\n registerSkillEnvPassthrough?: (names: string[]) => void;\n}\n\nexport interface CreateCoreToolsOptions {\n /** Workspace root for file/shell tools (defaults to factory workspace). */\n workspace?: string;\n /** `…/agents/<id>/bootstrap` — used so `read_file` can find SOUL.md etc. by filename. */\n bootstrapDir?: string;\n /** Tool `name` values to omit (e.g. `shell`, `extensions` for extension tools). */\n disabledTools?: Set<string>;\n /** Optional primary model for image tool heuristics. */\n getPrimaryModel?: () => Model<Api>;\n getBuiltinMemoryStore?: () => BuiltinMemoryStore;\n getMemoryManager?: () => MemoryManager;\n /** When set, registers `skills_list` and `skill_view` bound to this workspace\\'s skills. */\n getSkillManager?: () => SkillManager;\n}\n\nexport class AgentToolsFactory {\n private browserManager: BrowserManager | null = null;\n\n constructor(private deps: ToolFactoryDeps) {}\n\n private ensureBrowserManager(): BrowserManager {\n if (!this.browserManager) {\n this.browserManager = new BrowserManager({\n getHeadless: () => this.deps.getConfig?.()?.agents?.defaults?.browser?.headless !== false,\n });\n }\n return this.browserManager;\n }\n\n /** Close Playwright and all pages (gateway stop, agent manager dispose, or config hot-reload). */\n async shutdownBrowser(): Promise<void> {\n if (!this.browserManager) {\n return;\n }\n await this.browserManager.shutdown();\n this.browserManager = null;\n }\n\n /** Drop the tab for a session when its agent instance is removed. */\n async closeBrowserPageForSession(sessionKey: string): Promise<void> {\n await this.browserManager?.closePage(sessionKey);\n }\n\n createCoreTools(options?: CreateCoreToolsOptions): AgentTool<any, any>[] {\n const workspace = options?.workspace ?? this.deps.workspace;\n const { bus } = this.deps;\n const getPrimary = options?.getPrimaryModel ?? this.deps.getPrimaryModel;\n const getBuiltin = options?.getBuiltinMemoryStore ?? this.deps.getBuiltinMemoryStore;\n const getMemMgr = options?.getMemoryManager ?? this.deps.getMemoryManager;\n const getSkillMgr = options?.getSkillManager;\n const disabled = options?.disabledTools;\n\n const primary = getPrimary?.();\n const modelHasVision = primary?.input?.includes('image') ?? false;\n const cfg = this.deps.getConfig?.();\n const imageTool = createImageTool({\n config: cfg,\n workspace,\n modelHasVision,\n });\n const imageGenerateTool = createImageGenerateTool({\n config: cfg,\n workspace,\n });\n\n const optionalTools = [imageTool, imageGenerateTool].filter((t) => t != null) as any[];\n\n const readTool = createReadFileTool(workspace, { bootstrapDir: options?.bootstrapDir });\n const writeTool = createWriteFileTool(workspace);\n const editTool = createEditFileTool(workspace);\n const listDir = createListDirTool(workspace);\n const grep = createGrepTool(workspace);\n const find = createFindTool(workspace);\n\n const core: AgentTool<any, any>[] = [\n createSessionStatusTool(),\n createClarifyTool({\n resolveAskUser: () => {\n const req = this.deps.gatewayClarify?.requestClarification;\n if (!req) return null;\n const ctx = this.deps.getCurrentContext();\n if (!ctx?.sessionKey) return null;\n const source = clarifyTransportSource(ctx.sessionKey);\n if (!source || !CLARIFY_SUPPORTED_CHANNELS.has(source)) return null;\n return (r) => req(ctx.sessionKey, r);\n },\n }),\n createTodoTool({\n getSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n }),\n ...(getSkillMgr\n ? [\n createSkillsListTool({\n getSkillManager: getSkillMgr,\n getSkillIndexingContext: this.deps.getSkillIndexingContext,\n }),\n createSkillViewTool({\n getSkillManager: getSkillMgr,\n getSkillIndexingContext: this.deps.getSkillIndexingContext,\n registerSkillEnvPassthrough: this.deps.registerSkillEnvPassthrough,\n }),\n createSkillManageTool({\n getSkillManager: getSkillMgr,\n getWorkspace: () => workspace,\n onSkillsFilesystemMutate: this.deps.onSkillsFilesystemMutate,\n }),\n ]\n : []),\n readTool,\n writeTool,\n editTool,\n listDir,\n grep,\n find,\n createShellTool(workspace, {\n getSkillPassthroughEnvVarNames: this.deps.getSkillPassthroughEnvVarNames,\n }),\n createWebSearchTool(() => this.deps.getConfig?.()),\n webFetchTool,\n createWebExtractTool({ getConfig: () => this.deps.getConfig?.() }),\n // Note: TTS is NOT handled by send_message tool anymore\n // TTS is applied at the ChannelManager dispatch layer\n createMessageTool(bus, () => this.deps.getCurrentContext()),\n ...(mergeTtsConfigFromAppConfig(cfg?.tts).enabled\n ? [\n createTextToSpeechTool({\n bus,\n getContext: () => this.deps.getCurrentContext(),\n getConfig: () => this.deps.getConfig?.(),\n }),\n ]\n : []),\n createSendMediaTool(workspace, bus, () => this.deps.getCurrentContext()),\n createMemorySearchTool(workspace),\n createMemoryGetTool(workspace),\n ...(getBuiltin && shouldRegisterCuratedMemoryTool(this.deps.getConfig?.())\n ? [\n createCuratedMemoryTool(getBuiltin, {\n onMemoryWrite: (action, target, content) => {\n getMemMgr?.().onMemoryWrite(action, target, content);\n },\n }),\n ]\n : []),\n ...(getMemMgr?.().getAdditionalTools() ?? []),\n ...(this.deps.getSessionStore\n ? [\n createSessionSearchTool({\n getSessionStore: this.deps.getSessionStore,\n getConfig: this.deps.getConfig,\n getCurrentSessionKey: () => this.deps.getCurrentContext()?.sessionKey,\n }),\n ]\n : []),\n ...(this.deps.getCronService\n ? [\n createCronjobTool({\n getCronService: this.deps.getCronService,\n }),\n ]\n : []),\n ...(cfg?.agents?.defaults?.browser?.enabled === true\n ? createBrowserTools({\n getManager: () => this.ensureBrowserManager(),\n getTaskId: () => this.deps.getCurrentContext()?.sessionKey ?? 'default',\n getConfig: () => this.deps.getConfig?.(),\n })\n : []),\n ...(cfg?.agents?.defaults?.delegate?.enabled === true && primary\n ? [\n createDelegateTool({\n workspace,\n getSubagentModel: () => {\n const gp = options?.getPrimaryModel ?? this.deps.getPrimaryModel;\n const m = gp?.();\n if (!m) {\n throw new Error('No primary model configured for delegate_task');\n }\n return m;\n },\n bus: this.deps.bus,\n getConfig: () => this.deps.getConfig?.(),\n getCurrentContext: () => this.deps.getCurrentContext?.() ?? null,\n hookRunner: this.deps.hookRunner,\n toolExecutorConfig: this.deps.toolExecutorConfig,\n }),\n ]\n : []),\n ...optionalTools,\n ];\n\n return filterToolsByDisabledSet(core, disabled);\n }\n\n createAllTools(coreOptions?: CreateCoreToolsOptions): AgentTool<any, any>[] {\n const coreTools = this.createCoreTools(coreOptions);\n const disableExtensions = coreOptions?.disabledTools?.has('extensions');\n const cfg = this.deps.getConfig?.();\n\n let bundled: AgentTool<any, any>[];\n if (!this.deps.extensionRegistry || disableExtensions) {\n bundled = coreTools;\n } else {\n const extensionTools = this.deps.extensionRegistry.getAllTools();\n log.info({ count: extensionTools.length }, 'Loaded extension tools');\n bundled = [...coreTools, ...extensionTools];\n }\n\n const wrapped = wrapToolsWithProtection(bundled, this.deps.toolExecutorConfig);\n\n const executeEnabled =\n cfg?.agents?.defaults?.executeCode?.enabled === true &&\n !coreOptions?.disabledTools?.has('execute_code');\n\n if (executeEnabled) {\n const sandboxMap = buildSandboxToolMap(wrapped);\n const executeTool = createExecuteCodeTool({ getSandboxToolMap: () => sandboxMap });\n const wrappedExecute = wrapToolsWithProtection([executeTool as any], this.deps.toolExecutorConfig);\n return [...wrapped, ...wrappedExecute];\n }\n\n return wrapped;\n }\n}\n\nfunction filterToolsByDisabledSet(\n tools: any[],\n disabled: Set<string> | undefined,\n): any[] {\n if (!disabled || disabled.size === 0) {\n return tools;\n }\n return tools.filter((t) => !disabled.has(t.name));\n}\n"],"mappings":"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"}
@@ -1,2 +1,7 @@
1
1
  export declare function formatEnvelopeTimestamp(timezone?: string, now?: Date): string;
2
2
  export declare function prependEnvelopeTimestamp(content: string, timezone?: string): string;
3
+ /**
4
+ * Remove a single leading envelope timestamp prefix from inbound text (session auto-title, etc.).
5
+ * Does not strip arbitrary `[…]` — only the date+time-shaped prefix from {@link formatEnvelopeTimestamp}.
6
+ */
7
+ export declare function stripEnvelopeTimestampPrefix(text: string): string;
@@ -30,7 +30,16 @@ function prependEnvelopeTimestamp(content, timezone) {
30
30
  if (!content.trim()) return content;
31
31
  return `[${formatEnvelopeTimestamp(timezone)}] ${content}`;
32
32
  }
33
+ /** Matches `[YYYY-MM-DD HH:MM]` plus optional ` TZ` inside brackets, as produced by {@link prependEnvelopeTimestamp}. */
34
+ const ENVELOPE_TIMESTAMP_PREFIX_RE = /^\[\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?:\s+[^\]]+)?\]\s*/;
35
+ /**
36
+ * Remove a single leading envelope timestamp prefix from inbound text (session auto-title, etc.).
37
+ * Does not strip arbitrary `[…]` — only the date+time-shaped prefix from {@link formatEnvelopeTimestamp}.
38
+ */
39
+ function stripEnvelopeTimestampPrefix(text) {
40
+ return text.replace(ENVELOPE_TIMESTAMP_PREFIX_RE, "");
41
+ }
33
42
  //#endregion
34
- export { formatEnvelopeTimestamp, prependEnvelopeTimestamp };
43
+ export { formatEnvelopeTimestamp, prependEnvelopeTimestamp, stripEnvelopeTimestampPrefix };
35
44
 
36
45
  //# sourceMappingURL=envelope-timestamp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"envelope-timestamp.js","names":[],"sources":["../../../src/channels/envelope-timestamp.ts"],"sourcesContent":["export function formatEnvelopeTimestamp(timezone?: string, now: Date = new Date()): string {\n try {\n const resolvedTimezone =\n timezone?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: resolvedTimezone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n timeZoneName: 'short',\n }).formatToParts(now);\n\n const map: Record<string, string> = {};\n for (const part of parts) {\n if (part.type !== 'literal') {\n map[part.type] = part.value;\n }\n }\n\n const year = map.year ?? '';\n const month = map.month ?? '';\n const day = map.day ?? '';\n const hour = map.hour ?? '';\n const minute = map.minute ?? '';\n const tzName = map.timeZoneName ?? '';\n\n if (!year || !month || !day || !hour || !minute) {\n return `${now.toISOString().slice(0, 16).replace('T', ' ')} UTC`;\n }\n\n return `${year}-${month}-${day} ${hour}:${minute}${tzName ? ` ${tzName}` : ''}`;\n } catch {\n return `${now.toISOString().slice(0, 16).replace('T', ' ')} UTC`;\n }\n}\n\nexport function prependEnvelopeTimestamp(content: string, timezone?: string): string {\n const text = content.trim();\n if (!text) {\n return content;\n }\n const timestamp = formatEnvelopeTimestamp(timezone);\n return `[${timestamp}] ${content}`;\n}\n\n"],"mappings":";AAAA,SAAgB,wBAAwB,UAAmB,sBAAY,IAAI,MAAM,EAAU;AACzF,KAAI;EACF,MAAM,mBACJ,UAAU,MAAM,IAAI,KAAK,gBAAgB,CAAC,iBAAiB,CAAC,YAAY;EAC1E,MAAM,QAAQ,IAAI,KAAK,eAAe,SAAS;GAC7C,UAAU;GACV,MAAM;GACN,OAAO;GACP,KAAK;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;GACR,cAAc;GACf,CAAC,CAAC,cAAc,IAAI;EAErB,MAAM,MAA8B,EAAE;AACtC,OAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,UAChB,KAAI,KAAK,QAAQ,KAAK;EAI1B,MAAM,OAAO,IAAI,QAAQ;EACzB,MAAM,QAAQ,IAAI,SAAS;EAC3B,MAAM,MAAM,IAAI,OAAO;EACvB,MAAM,OAAO,IAAI,QAAQ;EACzB,MAAM,SAAS,IAAI,UAAU;EAC7B,MAAM,SAAS,IAAI,gBAAgB;AAEnC,MAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,OACvC,QAAO,GAAG,IAAI,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC;AAG7D,SAAO,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,SAAS,SAAS,IAAI,WAAW;SACrE;AACN,SAAO,GAAG,IAAI,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC;;;AAI/D,SAAgB,yBAAyB,SAAiB,UAA2B;AAEnF,KAAI,CADS,QAAQ,MACZ,CACP,QAAO;AAGT,QAAO,IADW,wBAAwB,SACtB,CAAC,IAAI"}
1
+ {"version":3,"file":"envelope-timestamp.js","names":[],"sources":["../../../src/channels/envelope-timestamp.ts"],"sourcesContent":["export function formatEnvelopeTimestamp(timezone?: string, now: Date = new Date()): string {\n try {\n const resolvedTimezone =\n timezone?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: resolvedTimezone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n timeZoneName: 'short',\n }).formatToParts(now);\n\n const map: Record<string, string> = {};\n for (const part of parts) {\n if (part.type !== 'literal') {\n map[part.type] = part.value;\n }\n }\n\n const year = map.year ?? '';\n const month = map.month ?? '';\n const day = map.day ?? '';\n const hour = map.hour ?? '';\n const minute = map.minute ?? '';\n const tzName = map.timeZoneName ?? '';\n\n if (!year || !month || !day || !hour || !minute) {\n return `${now.toISOString().slice(0, 16).replace('T', ' ')} UTC`;\n }\n\n return `${year}-${month}-${day} ${hour}:${minute}${tzName ? ` ${tzName}` : ''}`;\n } catch {\n return `${now.toISOString().slice(0, 16).replace('T', ' ')} UTC`;\n }\n}\n\nexport function prependEnvelopeTimestamp(content: string, timezone?: string): string {\n const text = content.trim();\n if (!text) {\n return content;\n }\n const timestamp = formatEnvelopeTimestamp(timezone);\n return `[${timestamp}] ${content}`;\n}\n\n/** Matches `[YYYY-MM-DD HH:MM]` plus optional ` TZ` inside brackets, as produced by {@link prependEnvelopeTimestamp}. */\nconst ENVELOPE_TIMESTAMP_PREFIX_RE =\n /^\\[\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}(?:\\s+[^\\]]+)?\\]\\s*/;\n\n/**\n * Remove a single leading envelope timestamp prefix from inbound text (session auto-title, etc.).\n * Does not strip arbitrary `[…]` — only the date+time-shaped prefix from {@link formatEnvelopeTimestamp}.\n */\nexport function stripEnvelopeTimestampPrefix(text: string): string {\n return text.replace(ENVELOPE_TIMESTAMP_PREFIX_RE, '');\n}\n\n"],"mappings":";AAAA,SAAgB,wBAAwB,UAAmB,sBAAY,IAAI,MAAM,EAAU;AACzF,KAAI;EACF,MAAM,mBACJ,UAAU,MAAM,IAAI,KAAK,gBAAgB,CAAC,iBAAiB,CAAC,YAAY;EAC1E,MAAM,QAAQ,IAAI,KAAK,eAAe,SAAS;GAC7C,UAAU;GACV,MAAM;GACN,OAAO;GACP,KAAK;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;GACR,cAAc;GACf,CAAC,CAAC,cAAc,IAAI;EAErB,MAAM,MAA8B,EAAE;AACtC,OAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,UAChB,KAAI,KAAK,QAAQ,KAAK;EAI1B,MAAM,OAAO,IAAI,QAAQ;EACzB,MAAM,QAAQ,IAAI,SAAS;EAC3B,MAAM,MAAM,IAAI,OAAO;EACvB,MAAM,OAAO,IAAI,QAAQ;EACzB,MAAM,SAAS,IAAI,UAAU;EAC7B,MAAM,SAAS,IAAI,gBAAgB;AAEnC,MAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,OACvC,QAAO,GAAG,IAAI,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC;AAG7D,SAAO,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,SAAS,SAAS,IAAI,WAAW;SACrE;AACN,SAAO,GAAG,IAAI,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC;;;AAI/D,SAAgB,yBAAyB,SAAiB,UAA2B;AAEnF,KAAI,CADS,QAAQ,MACZ,CACP,QAAO;AAGT,QAAO,IADW,wBAAwB,SACtB,CAAC,IAAI;;;AAI3B,MAAM,+BACJ;;;;;AAMF,SAAgB,6BAA6B,MAAsB;AACjE,QAAO,KAAK,QAAQ,8BAA8B,GAAG"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Stable imports for the bundled Feishu channel (implementation under extensions/feishu).
3
+ */
4
+ export { feishuPlugin, FeishuChannelPlugin } from '../../../extensions/feishu/src/plugin.js';
5
+ export { defineChannelPluginEntry } from '../../../extensions/feishu/src/index.js';
@@ -0,0 +1,4 @@
1
+ import { defineChannelPluginEntry } from "../../extensions/sdk/channel-entry.js";
2
+ import { FeishuChannelPlugin, feishuPlugin } from "../../../extensions/feishu/src/plugin.js";
3
+ import "../../../extensions/feishu/src/index.js";
4
+ export { FeishuChannelPlugin, defineChannelPluginEntry, feishuPlugin };
@@ -8,7 +8,7 @@ import type { Config } from '../config/schema.js';
8
8
  import type { AgentMessage } from '@mariozechner/pi-agent-core';
9
9
  import type { ThinkLevel, ReasoningLevel, VerboseLevel } from '../agent/transcript/thinking-types.js';
10
10
  import type { SessionConfigStore } from '../session/index.js';
11
- export type MessageSource = 'telegram' | 'weixin' | 'webui' | 'cli' | 'api' | 'system' | 'gateway';
11
+ export type MessageSource = 'telegram' | 'weixin' | 'feishu' | 'webui' | 'cli' | 'api' | 'system' | 'gateway';
12
12
  export interface UnifiedMessage {
13
13
  /** Message source platform */
14
14
  source: MessageSource;
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import type { AgentMessage } from '@mariozechner/pi-agent-core';
8
8
  export type { AgentMessage };
9
- export type ExtensionHookEvent = 'before_agent_start' | 'agent_end' | 'before_compaction' | 'after_compaction' | 'message_received' | 'message_sending' | 'message_sent' | 'before_tool_call' | 'after_tool_call' | 'session_start' | 'session_end' | 'gateway_start' | 'gateway_stop' | 'context' | 'input' | 'turn_start' | 'turn_end' | 'tool_execution_start' | 'tool_execution_update' | 'tool_execution_end' | 'before_model_resolve' | 'before_prompt_build' | 'llm_input' | 'llm_output' | 'inbound_claim' | 'before_reset' | 'before_message_write';
9
+ export type ExtensionHookEvent = 'before_agent_start' | 'agent_end' | 'before_compaction' | 'after_compaction' | 'message_received' | 'message_sending' | 'message_sent' | 'before_tool_call' | 'after_tool_call' | 'session_start' | 'session_end' | 'gateway_start' | 'gateway_stop' | 'context' | 'input' | 'turn_start' | 'turn_end' | 'tool_execution_start' | 'tool_execution_update' | 'tool_execution_end' | 'before_model_resolve' | 'before_prompt_build' | 'llm_input' | 'llm_output' | 'inbound_claim' | 'before_reset' | 'before_message_write' | 'subagent_spawning' | 'subagent_delivery_target' | 'subagent_ended';
10
10
  /**
11
11
  * Hook execution mode determines how handlers are processed:
12
12
  * - void: Fire-and-forget, parallel execution (for notifications)
@@ -132,6 +132,48 @@ export interface HookToolExecutionEndEvent {
132
132
  error?: string;
133
133
  durationMs?: number;
134
134
  }
135
+ export interface HookSubagentSpawningEvent {
136
+ childSessionKey: string;
137
+ requester?: {
138
+ channel?: string;
139
+ accountId?: string;
140
+ to?: string;
141
+ threadId?: string | number;
142
+ };
143
+ threadRequested?: boolean;
144
+ agentId?: string;
145
+ label?: string;
146
+ }
147
+ export type HookSubagentSpawningResult = {
148
+ status: 'ok';
149
+ threadBindingReady?: boolean;
150
+ } | {
151
+ status: 'error';
152
+ error: string;
153
+ } | void;
154
+ export interface HookSubagentDeliveryTargetEvent {
155
+ childSessionKey: string;
156
+ requesterSessionKey?: string;
157
+ requesterOrigin?: {
158
+ channel?: string;
159
+ accountId?: string;
160
+ to?: string;
161
+ threadId?: string | number;
162
+ };
163
+ expectsCompletionMessage?: boolean;
164
+ }
165
+ export type HookSubagentDeliveryTargetResult = {
166
+ origin: {
167
+ channel: string;
168
+ accountId?: string;
169
+ to?: string;
170
+ threadId?: string | number;
171
+ };
172
+ } | void;
173
+ export interface HookSubagentEndedEvent {
174
+ targetSessionKey: string;
175
+ accountId?: string;
176
+ }
135
177
  /**
136
178
  * Strongly typed handler map - each hook has precise event/result types
137
179
  */
@@ -154,6 +196,9 @@ export type HookHandlerMap = {
154
196
  tool_execution_start: (event: HookToolExecutionStartEvent, ctx: HookAgentContext) => Promise<void> | void;
155
197
  tool_execution_update: (event: HookToolExecutionUpdateEvent, ctx: HookAgentContext) => Promise<void> | void;
156
198
  tool_execution_end: (event: HookToolExecutionEndEvent, ctx: HookAgentContext) => Promise<void> | void;
199
+ subagent_spawning: (event: HookSubagentSpawningEvent, ctx: HookAgentContext) => Promise<HookSubagentSpawningResult> | HookSubagentSpawningResult;
200
+ subagent_delivery_target: (event: HookSubagentDeliveryTargetEvent, ctx: HookAgentContext) => Promise<HookSubagentDeliveryTargetResult> | HookSubagentDeliveryTargetResult;
201
+ subagent_ended: (event: HookSubagentEndedEvent, ctx: HookAgentContext) => Promise<void> | void;
157
202
  session_start: (event: SessionStartContext, ctx: HookAgentContext) => Promise<void> | void;
158
203
  session_end: (event: SessionEndContext, ctx: HookAgentContext) => Promise<void> | void;
159
204
  gateway_start: (event: GatewayStartContext, ctx: HookAgentContext) => Promise<void> | void;
@@ -21,6 +21,7 @@ const HOOK_EXECUTION_MODES = {
21
21
  "tool_execution_update": "void",
22
22
  "tool_execution_end": "void",
23
23
  "before_reset": "void",
24
+ "subagent_ended": "void",
24
25
  "before_agent_start": "modifying",
25
26
  "before_model_resolve": "modifying",
26
27
  "before_prompt_build": "modifying",
@@ -29,6 +30,8 @@ const HOOK_EXECUTION_MODES = {
29
30
  "context": "modifying",
30
31
  "input": "modifying",
31
32
  "before_message_write": "modifying",
33
+ "subagent_spawning": "modifying",
34
+ "subagent_delivery_target": "modifying",
32
35
  "inbound_claim": "claiming"
33
36
  };
34
37
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","names":[],"sources":["../../../../src/extensions/types/hooks.ts"],"sourcesContent":["/**\n * Extension System - Hook Types\n * \n * Hook events, handlers, and context types.\n * Strongly typed hook system with execution modes.\n */\n\nimport type { AgentMessage } from '@mariozechner/pi-agent-core';\n\n// Re-export AgentMessage for use in hook system\nexport type { AgentMessage };\n\n// ============================================================================\n// Hook Event Types ( Strongly Typed)\n// ============================================================================\n\nexport type ExtensionHookEvent = \n // Existing hooks\n | 'before_agent_start'\n | 'agent_end'\n | 'before_compaction'\n | 'after_compaction'\n | 'message_received'\n | 'message_sending'\n | 'message_sent'\n | 'before_tool_call'\n | 'after_tool_call'\n | 'session_start'\n | 'session_end'\n | 'gateway_start'\n | 'gateway_stop'\n // Enhanced hooks\n | 'context'\n | 'input'\n | 'turn_start'\n | 'turn_end'\n // Tool execution lifecycle\n | 'tool_execution_start'\n | 'tool_execution_update'\n | 'tool_execution_end'\n // LLM Observation hooks\n | 'before_model_resolve'\n | 'before_prompt_build'\n | 'llm_input'\n | 'llm_output'\n // Inbound claim hook\n | 'inbound_claim'\n // Reset hook\n | 'before_reset'\n // Message write hook\n | 'before_message_write';\n\n// ============================================================================\n// Hook Execution Modes \n// ============================================================================\n\n/**\n * Hook execution mode determines how handlers are processed:\n * - void: Fire-and-forget, parallel execution (for notifications)\n * - modifying: Sequential execution, results merge (for modifications)\n * - claiming: First handler to return handled:true wins (for exclusive handling)\n */\nexport type HookExecutionMode = 'void' | 'modifying' | 'claiming';\n\n/**\n * Maps each hook event to its execution mode\n */\nexport const HOOK_EXECUTION_MODES: Record<ExtensionHookEvent, HookExecutionMode> = {\n // Void: Fire-and-forget, parallel execution\n 'message_received': 'void',\n 'message_sent': 'void',\n 'agent_end': 'void',\n 'llm_input': 'void',\n 'llm_output': 'void',\n 'after_tool_call': 'void',\n 'session_start': 'void',\n 'session_end': 'void',\n 'gateway_start': 'void',\n 'gateway_stop': 'void',\n 'before_compaction': 'void',\n 'after_compaction': 'void',\n 'turn_start': 'void',\n 'turn_end': 'void',\n 'tool_execution_start': 'void',\n 'tool_execution_update': 'void',\n 'tool_execution_end': 'void',\n 'before_reset': 'void',\n \n // Modifying: Sequential execution, results merge\n 'before_agent_start': 'modifying',\n 'before_model_resolve': 'modifying',\n 'before_prompt_build': 'modifying',\n 'message_sending': 'modifying',\n 'before_tool_call': 'modifying',\n 'context': 'modifying',\n 'input': 'modifying',\n 'before_message_write': 'modifying',\n \n // Claiming: First handler with handled:true wins\n 'inbound_claim': 'claiming',\n};\n\n// ============================================================================\n// Untyped hook handler (extensions may use before wiring strong types)\n// ============================================================================\n\nexport type ExtensionHookHandler = (event: unknown, context?: unknown) => unknown | Promise<unknown>;\n\nexport interface HookOptions {\n priority?: number;\n once?: boolean;\n}\n\n// ============================================================================\n// Strongly Typed Hook Handler Map\n// ============================================================================\n\n// Forward declarations for circular references\nexport interface HookAgentContext {\n timestamp?: Date;\n extensionId?: string;\n sessionKey?: string;\n agentId?: string;\n}\n\n// LLM Observation Hook Types\nexport interface HookBeforeModelResolveEvent {\n prompt: string;\n model?: string;\n provider?: string;\n}\n\nexport interface HookBeforeModelResolveResult {\n modelOverride?: string;\n providerOverride?: string;\n}\n\nexport interface HookBeforePromptBuildEvent {\n prompt: string;\n messages?: Array<{ role: string; content: string }>;\n}\n\nexport interface HookBeforePromptBuildResult {\n prompt?: string;\n prependContext?: string;\n}\n\nexport interface HookLlmInputEvent {\n runId: string;\n provider: string;\n model: string;\n prompt: string;\n systemPrompt?: string;\n messages?: Array<{ role: string; content: string }>;\n temperature?: number;\n maxTokens?: number;\n}\n\nexport interface HookLlmOutputEvent {\n runId: string;\n provider: string;\n model: string;\n content: string;\n usage?: {\n input?: number;\n output?: number;\n total?: number;\n };\n finishReason?: string;\n}\n\n// Inbound Claim Hook Types\nexport interface HookInboundClaimEvent {\n channelId: string;\n from: string;\n content: string;\n timestamp?: Date;\n}\n\nexport interface HookInboundClaimResult {\n handled: boolean;\n response?: string;\n}\n\n// Before Reset Hook Types\nexport interface HookBeforeResetEvent {\n sessionKey: string;\n reason?: 'user_request' | 'timeout' | 'error' | 'manual';\n}\n\nexport interface HookBeforeResetResult {\n allowReset: boolean;\n reason?: string;\n}\n\n// Before Message Write Hook Types\nexport interface HookBeforeMessageWriteEvent {\n channelId: string;\n to: string;\n content: string;\n}\n\nexport interface HookBeforeMessageWriteResult {\n content?: string;\n cancel?: boolean;\n reason?: string;\n}\n\n// Turn Hook Types\nexport interface HookTurnStartEvent {\n turnId: string;\n prompt?: string;\n agentId?: string;\n sessionKey?: string;\n}\n\nexport interface HookTurnEndEvent {\n turnId: string;\n response?: string;\n error?: string;\n durationMs?: number;\n}\n\n// Tool Execution Hook Types\nexport interface HookToolExecutionStartEvent {\n toolName: string;\n params: Record<string, unknown>;\n executionId: string;\n}\n\nexport interface HookToolExecutionUpdateEvent {\n toolName: string;\n executionId: string;\n progress?: number;\n message?: string;\n}\n\nexport interface HookToolExecutionEndEvent {\n toolName: string;\n executionId: string;\n result?: unknown;\n error?: string;\n durationMs?: number;\n}\n\n// ============================================================================\n// Strongly Typed Handler Map \n// ============================================================================\n\n/**\n * Strongly typed handler map - each hook has precise event/result types\n */\nexport type HookHandlerMap = {\n // Agent lifecycle\n before_agent_start: (\n event: BeforeAgentStartContext,\n ctx: HookAgentContext,\n ) => Promise<BeforeAgentStartResult | void> | BeforeAgentStartResult | void;\n \n before_model_resolve: (\n event: HookBeforeModelResolveEvent,\n ctx: HookAgentContext,\n ) => Promise<HookBeforeModelResolveResult | void> | HookBeforeModelResolveResult | void;\n \n before_prompt_build: (\n event: HookBeforePromptBuildEvent,\n ctx: HookAgentContext,\n ) => Promise<HookBeforePromptBuildResult | void> | HookBeforePromptBuildResult | void;\n \n llm_input: (\n event: HookLlmInputEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n llm_output: (\n event: HookLlmOutputEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n agent_end: (\n event: AgentEndContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Compaction\n before_compaction: (\n event: BeforeCompactionContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n after_compaction: (\n event: AfterCompactionContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Messages\n message_received: (\n event: MessageReceivedContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n message_sending: (\n event: MessageSendingContext,\n ctx: HookAgentContext,\n ) => Promise<MessageSendingResult | void> | MessageSendingResult | void;\n \n message_sent: (\n event: MessageSentContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n inbound_claim: (\n event: HookInboundClaimEvent,\n ctx: HookAgentContext,\n ) => Promise<HookInboundClaimResult | void> | HookInboundClaimResult | void;\n \n before_message_write: (\n event: HookBeforeMessageWriteEvent,\n ctx: HookAgentContext,\n ) => Promise<HookBeforeMessageWriteResult | void> | HookBeforeMessageWriteResult | void;\n \n // Tools\n before_tool_call: (\n event: BeforeToolCallContext,\n ctx: HookAgentContext,\n ) => Promise<BeforeToolCallResult | void> | BeforeToolCallResult | void;\n \n after_tool_call: (\n event: AfterToolCallContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n tool_execution_start: (\n event: HookToolExecutionStartEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n tool_execution_update: (\n event: HookToolExecutionUpdateEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n tool_execution_end: (\n event: HookToolExecutionEndEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Session\n session_start: (\n event: SessionStartContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n session_end: (\n event: SessionEndContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Gateway\n gateway_start: (\n event: GatewayStartContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n gateway_stop: (\n event: GatewayStopContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Enhanced (xopc-specific)\n context: (\n event: ContextEvent,\n ctx: HookAgentContext,\n ) => Promise<ContextResult | void> | ContextResult | void;\n \n input: (\n event: InputEvent,\n ctx: HookAgentContext,\n ) => Promise<InputResult | void> | InputResult | void;\n \n // Turn lifecycle\n turn_start: (\n event: HookTurnStartEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n turn_end: (\n event: HookTurnEndEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Reset\n before_reset: (\n event: HookBeforeResetEvent,\n ctx: HookAgentContext,\n ) => Promise<HookBeforeResetResult | void> | HookBeforeResetResult | void;\n};\n\n// ============================================================================\n// Shared hook context fields\n// ============================================================================\n\nexport interface HookContext {\n timestamp?: Date;\n extensionId?: string;\n sessionKey?: string;\n agentId?: string;\n}\n\nexport interface BeforeAgentStartContext extends HookContext {\n prompt: string;\n messages?: unknown[];\n}\n\nexport interface BeforeAgentStartResult {\n systemPrompt?: string;\n prependContext?: string;\n}\n\nexport interface AgentEndContext extends HookContext {\n messages: unknown[];\n success: boolean;\n error?: string;\n durationMs?: number;\n}\n\nexport interface BeforeCompactionContext extends HookContext {\n messageCount: number;\n tokenCount?: number;\n}\n\nexport interface AfterCompactionContext extends HookContext {\n messageCount: number;\n tokenCount?: number;\n compactedCount: number;\n}\n\nexport interface MessageReceivedContext extends HookContext {\n channelId: string;\n from: string;\n content: string;\n timestamp?: Date;\n metadata?: Record<string, unknown>;\n}\n\nexport interface MessageSendingContext extends HookContext {\n to: string;\n content: string;\n /** Channel id (e.g. telegram) when sending through ChannelManager. */\n channel?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface MessageSendingResult {\n content?: string;\n cancel?: boolean;\n cancelReason?: string;\n}\n\nexport interface MessageSentContext extends HookContext {\n to: string;\n content: string;\n success: boolean;\n error?: string;\n channel?: string;\n}\n\nexport interface BeforeToolCallContext extends HookContext {\n toolName: string;\n params: Record<string, unknown>;\n}\n\nexport interface BeforeToolCallResult {\n params?: Record<string, unknown>;\n block?: boolean;\n blockReason?: string;\n}\n\nexport interface AfterToolCallContext extends HookContext {\n toolName: string;\n params: Record<string, unknown>;\n result?: unknown;\n error?: string;\n durationMs?: number;\n}\n\nexport interface SessionStartContext extends HookContext {\n sessionId: string;\n resumedFrom?: string;\n}\n\nexport interface SessionEndContext extends HookContext {\n sessionId: string;\n reason?: 'completed' | 'error' | 'timeout' | 'user_request';\n}\n\nexport interface GatewayStartContext extends HookContext {\n port: number;\n host: string;\n}\n\nexport interface GatewayStopContext extends HookContext {\n port: number;\n reason?: string;\n}\n\n// ============================================================================\n// Enhanced Hook Event Types\n// ============================================================================\n\nexport interface ContextEvent {\n messages: Array<{ role: string; content: string }>;\n agentId?: string;\n sessionKey?: string;\n}\n\nexport interface ContextResult {\n messages: Array<{ role: string; content: string }>;\n}\n\nexport interface InputEvent {\n text: string;\n images?: string[];\n channelId?: string;\n from?: string;\n}\n\nexport interface InputResult {\n action: 'continue' | 'handled' | 'blocked';\n text?: string;\n images?: string[];\n response?: string;\n skipAgent?: boolean;\n}\n\nexport interface TurnEvent {\n turnId: string;\n prompt?: string;\n agentId?: string;\n sessionKey?: string;\n}\n\nexport interface TurnResult {\n context?: string;\n skipTurn?: boolean;\n}\n"],"mappings":";;;;AAmEA,MAAa,uBAAsE;CAEjF,oBAAoB;CACpB,gBAAgB;CAChB,aAAa;CACb,aAAa;CACb,cAAc;CACd,mBAAmB;CACnB,iBAAiB;CACjB,eAAe;CACf,iBAAiB;CACjB,gBAAgB;CAChB,qBAAqB;CACrB,oBAAoB;CACpB,cAAc;CACd,YAAY;CACZ,wBAAwB;CACxB,yBAAyB;CACzB,sBAAsB;CACtB,gBAAgB;CAGhB,sBAAsB;CACtB,wBAAwB;CACxB,uBAAuB;CACvB,mBAAmB;CACnB,oBAAoB;CACpB,WAAW;CACX,SAAS;CACT,wBAAwB;CAGxB,iBAAiB;CAClB"}
1
+ {"version":3,"file":"hooks.js","names":[],"sources":["../../../../src/extensions/types/hooks.ts"],"sourcesContent":["/**\n * Extension System - Hook Types\n * \n * Hook events, handlers, and context types.\n * Strongly typed hook system with execution modes.\n */\n\nimport type { AgentMessage } from '@mariozechner/pi-agent-core';\n\n// Re-export AgentMessage for use in hook system\nexport type { AgentMessage };\n\n// ============================================================================\n// Hook Event Types ( Strongly Typed)\n// ============================================================================\n\nexport type ExtensionHookEvent = \n // Existing hooks\n | 'before_agent_start'\n | 'agent_end'\n | 'before_compaction'\n | 'after_compaction'\n | 'message_received'\n | 'message_sending'\n | 'message_sent'\n | 'before_tool_call'\n | 'after_tool_call'\n | 'session_start'\n | 'session_end'\n | 'gateway_start'\n | 'gateway_stop'\n // Enhanced hooks\n | 'context'\n | 'input'\n | 'turn_start'\n | 'turn_end'\n // Tool execution lifecycle\n | 'tool_execution_start'\n | 'tool_execution_update'\n | 'tool_execution_end'\n // LLM Observation hooks\n | 'before_model_resolve'\n | 'before_prompt_build'\n | 'llm_input'\n | 'llm_output'\n // Inbound claim hook\n | 'inbound_claim'\n // Reset hook\n | 'before_reset'\n // Message write hook\n | 'before_message_write'\n // Sub-agent lifecycle (openclaw parity surface)\n | 'subagent_spawning'\n | 'subagent_delivery_target'\n | 'subagent_ended';\n\n// ============================================================================\n// Hook Execution Modes \n// ============================================================================\n\n/**\n * Hook execution mode determines how handlers are processed:\n * - void: Fire-and-forget, parallel execution (for notifications)\n * - modifying: Sequential execution, results merge (for modifications)\n * - claiming: First handler to return handled:true wins (for exclusive handling)\n */\nexport type HookExecutionMode = 'void' | 'modifying' | 'claiming';\n\n/**\n * Maps each hook event to its execution mode\n */\nexport const HOOK_EXECUTION_MODES: Record<ExtensionHookEvent, HookExecutionMode> = {\n // Void: Fire-and-forget, parallel execution\n 'message_received': 'void',\n 'message_sent': 'void',\n 'agent_end': 'void',\n 'llm_input': 'void',\n 'llm_output': 'void',\n 'after_tool_call': 'void',\n 'session_start': 'void',\n 'session_end': 'void',\n 'gateway_start': 'void',\n 'gateway_stop': 'void',\n 'before_compaction': 'void',\n 'after_compaction': 'void',\n 'turn_start': 'void',\n 'turn_end': 'void',\n 'tool_execution_start': 'void',\n 'tool_execution_update': 'void',\n 'tool_execution_end': 'void',\n 'before_reset': 'void',\n 'subagent_ended': 'void',\n \n // Modifying: Sequential execution, results merge\n 'before_agent_start': 'modifying',\n 'before_model_resolve': 'modifying',\n 'before_prompt_build': 'modifying',\n 'message_sending': 'modifying',\n 'before_tool_call': 'modifying',\n 'context': 'modifying',\n 'input': 'modifying',\n 'before_message_write': 'modifying',\n 'subagent_spawning': 'modifying',\n 'subagent_delivery_target': 'modifying',\n \n // Claiming: First handler with handled:true wins\n 'inbound_claim': 'claiming',\n};\n\n// ============================================================================\n// Untyped hook handler (extensions may use before wiring strong types)\n// ============================================================================\n\nexport type ExtensionHookHandler = (event: unknown, context?: unknown) => unknown | Promise<unknown>;\n\nexport interface HookOptions {\n priority?: number;\n once?: boolean;\n}\n\n// ============================================================================\n// Strongly Typed Hook Handler Map\n// ============================================================================\n\n// Forward declarations for circular references\nexport interface HookAgentContext {\n timestamp?: Date;\n extensionId?: string;\n sessionKey?: string;\n agentId?: string;\n}\n\n// LLM Observation Hook Types\nexport interface HookBeforeModelResolveEvent {\n prompt: string;\n model?: string;\n provider?: string;\n}\n\nexport interface HookBeforeModelResolveResult {\n modelOverride?: string;\n providerOverride?: string;\n}\n\nexport interface HookBeforePromptBuildEvent {\n prompt: string;\n messages?: Array<{ role: string; content: string }>;\n}\n\nexport interface HookBeforePromptBuildResult {\n prompt?: string;\n prependContext?: string;\n}\n\nexport interface HookLlmInputEvent {\n runId: string;\n provider: string;\n model: string;\n prompt: string;\n systemPrompt?: string;\n messages?: Array<{ role: string; content: string }>;\n temperature?: number;\n maxTokens?: number;\n}\n\nexport interface HookLlmOutputEvent {\n runId: string;\n provider: string;\n model: string;\n content: string;\n usage?: {\n input?: number;\n output?: number;\n total?: number;\n };\n finishReason?: string;\n}\n\n// Inbound Claim Hook Types\nexport interface HookInboundClaimEvent {\n channelId: string;\n from: string;\n content: string;\n timestamp?: Date;\n}\n\nexport interface HookInboundClaimResult {\n handled: boolean;\n response?: string;\n}\n\n// Before Reset Hook Types\nexport interface HookBeforeResetEvent {\n sessionKey: string;\n reason?: 'user_request' | 'timeout' | 'error' | 'manual';\n}\n\nexport interface HookBeforeResetResult {\n allowReset: boolean;\n reason?: string;\n}\n\n// Before Message Write Hook Types\nexport interface HookBeforeMessageWriteEvent {\n channelId: string;\n to: string;\n content: string;\n}\n\nexport interface HookBeforeMessageWriteResult {\n content?: string;\n cancel?: boolean;\n reason?: string;\n}\n\n// Turn Hook Types\nexport interface HookTurnStartEvent {\n turnId: string;\n prompt?: string;\n agentId?: string;\n sessionKey?: string;\n}\n\nexport interface HookTurnEndEvent {\n turnId: string;\n response?: string;\n error?: string;\n durationMs?: number;\n}\n\n// Tool Execution Hook Types\nexport interface HookToolExecutionStartEvent {\n toolName: string;\n params: Record<string, unknown>;\n executionId: string;\n}\n\nexport interface HookToolExecutionUpdateEvent {\n toolName: string;\n executionId: string;\n progress?: number;\n message?: string;\n}\n\nexport interface HookToolExecutionEndEvent {\n toolName: string;\n executionId: string;\n result?: unknown;\n error?: string;\n durationMs?: number;\n}\n\n// ============================================================================\n// Sub-agent hook types (delegate_task + future subagent sessions)\n// ============================================================================\n\nexport interface HookSubagentSpawningEvent {\n childSessionKey: string;\n requester?: {\n channel?: string;\n accountId?: string;\n to?: string;\n threadId?: string | number;\n };\n threadRequested?: boolean;\n agentId?: string;\n label?: string;\n}\n\nexport type HookSubagentSpawningResult =\n | { status: 'ok'; threadBindingReady?: boolean }\n | { status: 'error'; error: string }\n | void;\n\nexport interface HookSubagentDeliveryTargetEvent {\n childSessionKey: string;\n requesterSessionKey?: string;\n requesterOrigin?: {\n channel?: string;\n accountId?: string;\n to?: string;\n threadId?: string | number;\n };\n expectsCompletionMessage?: boolean;\n}\n\nexport type HookSubagentDeliveryTargetResult =\n | {\n origin: {\n channel: string;\n accountId?: string;\n to?: string;\n threadId?: string | number;\n };\n }\n | void;\n\nexport interface HookSubagentEndedEvent {\n targetSessionKey: string;\n accountId?: string;\n}\n\n// ============================================================================\n// Strongly Typed Handler Map \n// ============================================================================\n\n/**\n * Strongly typed handler map - each hook has precise event/result types\n */\nexport type HookHandlerMap = {\n // Agent lifecycle\n before_agent_start: (\n event: BeforeAgentStartContext,\n ctx: HookAgentContext,\n ) => Promise<BeforeAgentStartResult | void> | BeforeAgentStartResult | void;\n \n before_model_resolve: (\n event: HookBeforeModelResolveEvent,\n ctx: HookAgentContext,\n ) => Promise<HookBeforeModelResolveResult | void> | HookBeforeModelResolveResult | void;\n \n before_prompt_build: (\n event: HookBeforePromptBuildEvent,\n ctx: HookAgentContext,\n ) => Promise<HookBeforePromptBuildResult | void> | HookBeforePromptBuildResult | void;\n \n llm_input: (\n event: HookLlmInputEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n llm_output: (\n event: HookLlmOutputEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n agent_end: (\n event: AgentEndContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Compaction\n before_compaction: (\n event: BeforeCompactionContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n after_compaction: (\n event: AfterCompactionContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Messages\n message_received: (\n event: MessageReceivedContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n message_sending: (\n event: MessageSendingContext,\n ctx: HookAgentContext,\n ) => Promise<MessageSendingResult | void> | MessageSendingResult | void;\n \n message_sent: (\n event: MessageSentContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n inbound_claim: (\n event: HookInboundClaimEvent,\n ctx: HookAgentContext,\n ) => Promise<HookInboundClaimResult | void> | HookInboundClaimResult | void;\n \n before_message_write: (\n event: HookBeforeMessageWriteEvent,\n ctx: HookAgentContext,\n ) => Promise<HookBeforeMessageWriteResult | void> | HookBeforeMessageWriteResult | void;\n \n // Tools\n before_tool_call: (\n event: BeforeToolCallContext,\n ctx: HookAgentContext,\n ) => Promise<BeforeToolCallResult | void> | BeforeToolCallResult | void;\n \n after_tool_call: (\n event: AfterToolCallContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n tool_execution_start: (\n event: HookToolExecutionStartEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n tool_execution_update: (\n event: HookToolExecutionUpdateEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n tool_execution_end: (\n event: HookToolExecutionEndEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n\n subagent_spawning: (\n event: HookSubagentSpawningEvent,\n ctx: HookAgentContext,\n ) => Promise<HookSubagentSpawningResult> | HookSubagentSpawningResult;\n\n subagent_delivery_target: (\n event: HookSubagentDeliveryTargetEvent,\n ctx: HookAgentContext,\n ) => Promise<HookSubagentDeliveryTargetResult> | HookSubagentDeliveryTargetResult;\n\n subagent_ended: (\n event: HookSubagentEndedEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Session\n session_start: (\n event: SessionStartContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n session_end: (\n event: SessionEndContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Gateway\n gateway_start: (\n event: GatewayStartContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n gateway_stop: (\n event: GatewayStopContext,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Enhanced (xopc-specific)\n context: (\n event: ContextEvent,\n ctx: HookAgentContext,\n ) => Promise<ContextResult | void> | ContextResult | void;\n \n input: (\n event: InputEvent,\n ctx: HookAgentContext,\n ) => Promise<InputResult | void> | InputResult | void;\n \n // Turn lifecycle\n turn_start: (\n event: HookTurnStartEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n turn_end: (\n event: HookTurnEndEvent,\n ctx: HookAgentContext,\n ) => Promise<void> | void;\n \n // Reset\n before_reset: (\n event: HookBeforeResetEvent,\n ctx: HookAgentContext,\n ) => Promise<HookBeforeResetResult | void> | HookBeforeResetResult | void;\n};\n\n// ============================================================================\n// Shared hook context fields\n// ============================================================================\n\nexport interface HookContext {\n timestamp?: Date;\n extensionId?: string;\n sessionKey?: string;\n agentId?: string;\n}\n\nexport interface BeforeAgentStartContext extends HookContext {\n prompt: string;\n messages?: unknown[];\n}\n\nexport interface BeforeAgentStartResult {\n systemPrompt?: string;\n prependContext?: string;\n}\n\nexport interface AgentEndContext extends HookContext {\n messages: unknown[];\n success: boolean;\n error?: string;\n durationMs?: number;\n}\n\nexport interface BeforeCompactionContext extends HookContext {\n messageCount: number;\n tokenCount?: number;\n}\n\nexport interface AfterCompactionContext extends HookContext {\n messageCount: number;\n tokenCount?: number;\n compactedCount: number;\n}\n\nexport interface MessageReceivedContext extends HookContext {\n channelId: string;\n from: string;\n content: string;\n timestamp?: Date;\n metadata?: Record<string, unknown>;\n}\n\nexport interface MessageSendingContext extends HookContext {\n to: string;\n content: string;\n /** Channel id (e.g. telegram) when sending through ChannelManager. */\n channel?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface MessageSendingResult {\n content?: string;\n cancel?: boolean;\n cancelReason?: string;\n}\n\nexport interface MessageSentContext extends HookContext {\n to: string;\n content: string;\n success: boolean;\n error?: string;\n channel?: string;\n}\n\nexport interface BeforeToolCallContext extends HookContext {\n toolName: string;\n params: Record<string, unknown>;\n}\n\nexport interface BeforeToolCallResult {\n params?: Record<string, unknown>;\n block?: boolean;\n blockReason?: string;\n}\n\nexport interface AfterToolCallContext extends HookContext {\n toolName: string;\n params: Record<string, unknown>;\n result?: unknown;\n error?: string;\n durationMs?: number;\n}\n\nexport interface SessionStartContext extends HookContext {\n sessionId: string;\n resumedFrom?: string;\n}\n\nexport interface SessionEndContext extends HookContext {\n sessionId: string;\n reason?: 'completed' | 'error' | 'timeout' | 'user_request';\n}\n\nexport interface GatewayStartContext extends HookContext {\n port: number;\n host: string;\n}\n\nexport interface GatewayStopContext extends HookContext {\n port: number;\n reason?: string;\n}\n\n// ============================================================================\n// Enhanced Hook Event Types\n// ============================================================================\n\nexport interface ContextEvent {\n messages: Array<{ role: string; content: string }>;\n agentId?: string;\n sessionKey?: string;\n}\n\nexport interface ContextResult {\n messages: Array<{ role: string; content: string }>;\n}\n\nexport interface InputEvent {\n text: string;\n images?: string[];\n channelId?: string;\n from?: string;\n}\n\nexport interface InputResult {\n action: 'continue' | 'handled' | 'blocked';\n text?: string;\n images?: string[];\n response?: string;\n skipAgent?: boolean;\n}\n\nexport interface TurnEvent {\n turnId: string;\n prompt?: string;\n agentId?: string;\n sessionKey?: string;\n}\n\nexport interface TurnResult {\n context?: string;\n skipTurn?: boolean;\n}\n"],"mappings":";;;;AAuEA,MAAa,uBAAsE;CAEjF,oBAAoB;CACpB,gBAAgB;CAChB,aAAa;CACb,aAAa;CACb,cAAc;CACd,mBAAmB;CACnB,iBAAiB;CACjB,eAAe;CACf,iBAAiB;CACjB,gBAAgB;CAChB,qBAAqB;CACrB,oBAAoB;CACpB,cAAc;CACd,YAAY;CACZ,wBAAwB;CACxB,yBAAyB;CACzB,sBAAsB;CACtB,gBAAgB;CAChB,kBAAkB;CAGlB,sBAAsB;CACtB,wBAAwB;CACxB,uBAAuB;CACvB,mBAAmB;CACnB,oBAAoB;CACpB,WAAW;CACX,SAAS;CACT,wBAAwB;CACxB,qBAAqB;CACrB,4BAA4B;CAG5B,iBAAiB;CAClB"}
@@ -18,6 +18,7 @@ import { removeSkillsLockEntry } from "../agent/skills/hub-lock.js";
18
18
  import { deleteManagedSkill, installSkillFromZip, listManagedSkillDirs } from "../agent/skills/managed-store.js";
19
19
  import { MessageBus, MessageBusShutdownError } from "../infra/bus/queue.js";
20
20
  import "../infra/bus/index.js";
21
+ import { prependEnvelopeTimestamp } from "../channels/envelope-timestamp.js";
21
22
  import { SessionManager } from "../session/manager.js";
22
23
  import "../session/index.js";
23
24
  import { registerClarifyBridge } from "./clarify-runtime.js";
@@ -25,7 +26,6 @@ import { CHAT_CHANNEL_ORDER } from "../channels/registry.js";
25
26
  import { ExtensionLoader } from "../extensions/loader.js";
26
27
  import "../extensions/index.js";
27
28
  import { AgentService } from "../agent/service.js";
28
- import { prependEnvelopeTimestamp } from "../channels/envelope-timestamp.js";
29
29
  import { ChannelManager } from "../channels/manager.js";
30
30
  import { ConfigHotReloader } from "../config/reload.js";
31
31
  import "../config/index.js";
@@ -5,5 +5,6 @@
5
5
  import type { ChannelPlugin } from '../channels/plugin-types.js';
6
6
  import { telegramPlugin } from '../../extensions/telegram/src/index.js';
7
7
  import { weixinPlugin } from '../../extensions/weixin/src/index.js';
8
- export { telegramPlugin, weixinPlugin };
8
+ import { feishuPlugin } from '../../extensions/feishu/src/index.js';
9
+ export { telegramPlugin, weixinPlugin, feishuPlugin };
9
10
  export declare const bundledChannelPlugins: ChannelPlugin[];
@@ -2,9 +2,15 @@ import { telegramPlugin } from "../../extensions/telegram/src/plugin.js";
2
2
  import "../../extensions/telegram/src/index.js";
3
3
  import { weixinPlugin } from "../../extensions/weixin/src/plugin.js";
4
4
  import "../../extensions/weixin/src/index.js";
5
+ import { feishuPlugin } from "../../extensions/feishu/src/plugin.js";
6
+ import "../../extensions/feishu/src/index.js";
5
7
  //#region src/generated/bundled-channel-plugins.ts
6
- const bundledChannelPlugins = [telegramPlugin, weixinPlugin];
8
+ const bundledChannelPlugins = [
9
+ telegramPlugin,
10
+ weixinPlugin,
11
+ feishuPlugin
12
+ ];
7
13
  //#endregion
8
- export { bundledChannelPlugins, telegramPlugin, weixinPlugin };
14
+ export { bundledChannelPlugins, feishuPlugin, telegramPlugin, weixinPlugin };
9
15
 
10
16
  //# sourceMappingURL=bundled-channel-plugins.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundled-channel-plugins.js","names":[],"sources":["../../../src/generated/bundled-channel-plugins.ts"],"sourcesContent":["/**\n * Built-in channel plugins: sources under extensions/*, compiled to dist/extensions/*.\n * Regenerate: pnpm run generate:bundled-channels\n */\n\nimport type { ChannelPlugin } from '../channels/plugin-types.js';\nimport { telegramPlugin } from '../../extensions/telegram/src/index.js';\nimport { weixinPlugin } from '../../extensions/weixin/src/index.js';\n\nexport { telegramPlugin, weixinPlugin };\nexport const bundledChannelPlugins: ChannelPlugin[] = [telegramPlugin, weixinPlugin];\n"],"mappings":";;;;;AAUA,MAAa,wBAAyC,CAAC,gBAAgB,aAAa"}
1
+ {"version":3,"file":"bundled-channel-plugins.js","names":[],"sources":["../../../src/generated/bundled-channel-plugins.ts"],"sourcesContent":["/**\n * Built-in channel plugins: sources under extensions/*, compiled to dist/extensions/*.\n * Regenerate: pnpm run generate:bundled-channels\n */\n\nimport type { ChannelPlugin } from '../channels/plugin-types.js';\nimport { telegramPlugin } from '../../extensions/telegram/src/index.js';\nimport { weixinPlugin } from '../../extensions/weixin/src/index.js';\nimport { feishuPlugin } from '../../extensions/feishu/src/index.js';\n\nexport { telegramPlugin, weixinPlugin, feishuPlugin };\nexport const bundledChannelPlugins: ChannelPlugin[] = [telegramPlugin, weixinPlugin, feishuPlugin];\n"],"mappings":";;;;;;;AAWA,MAAa,wBAAyC;CAAC;CAAgB;CAAc;CAAa"}
@@ -3,6 +3,7 @@ import { createLogger } from "../utils/logger/index.js";
3
3
  import { init_logger } from "../utils/logger.js";
4
4
  import { init_providers, resolveModel } from "../providers/index.js";
5
5
  import { stripInboundFileMetadataFromText } from "../channels/attachments/inbound-persist.js";
6
+ import { stripEnvelopeTimestampPrefix } from "../channels/envelope-timestamp.js";
6
7
  import { complete } from "@mariozechner/pi-ai";
7
8
  //#region src/session/session-title.ts
8
9
  init_session_key();
@@ -28,7 +29,7 @@ function extractTextFromMessage(m) {
28
29
  function firstUserText(messages) {
29
30
  const u = messages.find((m) => m.role === "user");
30
31
  if (!u) return "";
31
- return stripInboundFileMetadataFromText(extractTextFromMessage(u));
32
+ return stripInboundFileMetadataFromText(stripEnvelopeTimestampPrefix(extractTextFromMessage(u)));
32
33
  }
33
34
  /** First assistant message that has visible text (skips tool-only assistant rows). */
34
35
  function firstAssistantText(messages) {
@@ -1 +1 @@
1
- {"version":3,"file":"session-title.js","names":[],"sources":["../../../src/session/session-title.ts"],"sourcesContent":["/**\n * LLM-generated session titles (webchat and any path using SessionStore).\n */\n\nimport type { AgentMessage } from '@mariozechner/pi-agent-core';\nimport { complete, type UserMessage } from '@mariozechner/pi-ai';\n\nimport { stripInboundFileMetadataFromText } from '../channels/attachments/inbound-persist.js';\nimport { isCronSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { resolveModel } from '../providers/index.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { SessionStore } from './store.js';\n\nconst log = createLogger('SessionAutoTitle');\n\nconst MAX_TITLE_LEN = 80;\n\n/** Collect visible text from any content block that exposes `text` (pi-ai / OpenAI / Anthropic shapes). */\nfunction extractTextFromMessage(m: AgentMessage): string {\n if (typeof m.content === 'string') return m.content.trim();\n if (Array.isArray(m.content)) {\n const parts: string[] = [];\n for (const c of m.content) {\n if (c && typeof c === 'object') {\n const o = c as unknown as Record<string, unknown>;\n const type = typeof o.type === 'string' ? o.type : '';\n if (type === 'toolCall' || type === 'tool_use' || type === 'tool_result') continue;\n if (typeof o.text === 'string' && o.text.trim()) {\n parts.push(o.text.trim());\n }\n }\n }\n return parts.join(' ').trim();\n }\n return '';\n}\n\nfunction firstUserText(messages: AgentMessage[]): string {\n const u = messages.find((m) => m.role === 'user');\n if (!u) return '';\n const raw = extractTextFromMessage(u);\n // User turns include `formatInboundFileTextBlock` text blocks; do not feed [File:…] into title LLM / fallback.\n return stripInboundFileMetadataFromText(raw);\n}\n\n/** First assistant message that has visible text (skips tool-only assistant rows). */\nfunction firstAssistantText(messages: AgentMessage[]): string {\n for (const m of messages) {\n if (m.role === 'assistant') {\n const t = extractTextFromMessage(m);\n if (t.length > 0) return t;\n }\n }\n return '';\n}\n\nexport function isWebchatSessionKey(sessionKey: string): boolean {\n const p = parseSessionKey(sessionKey);\n if (p?.source === 'webchat') return true;\n return sessionKey.includes(':webchat:');\n}\n\n/** Whether to run LLM/fallback session naming for this key (excludes cron, heartbeat). */\nexport function shouldAutoTitleSessionKey(sessionKey: string): boolean {\n const raw = (sessionKey ?? '').trim();\n if (!raw) return false;\n if (isCronSessionKey(raw)) return false;\n if (raw.toLowerCase().startsWith('heartbeat:')) return false;\n return true;\n}\n\nexport function sanitizeSessionTitle(raw: string): string {\n let s = raw.trim();\n if ((s.startsWith('\"') && s.endsWith('\"')) || (s.startsWith(\"'\") && s.endsWith(\"'\"))) {\n s = s.slice(1, -1).trim();\n }\n const lineBreak = s.indexOf('\\n');\n if (lineBreak !== -1) s = s.slice(0, lineBreak).trim();\n if (s.length > MAX_TITLE_LEN) s = s.slice(0, MAX_TITLE_LEN - 1).trimEnd() + '…';\n return s;\n}\n\n/** Non-LLM title: first line of first user text, else first assistant line. */\nexport function fallbackTitleFromMessages(messages: AgentMessage[]): string | null {\n const u = firstUserText(messages);\n if (u) {\n const line = u.split(/\\n/)[0]?.trim();\n if (line) return sanitizeSessionTitle(line);\n }\n const a = firstAssistantText(messages);\n if (a) {\n const line = a.split(/\\n/)[0]?.trim();\n if (line) return sanitizeSessionTitle(line);\n }\n return null;\n}\n\n/**\n * Returns a title string, or null if generation should be skipped or failed.\n */\nexport async function generateSessionTitleFromMessages(\n modelRef: string,\n messages: AgentMessage[],\n signal?: AbortSignal,\n): Promise<string | null> {\n const userText = firstUserText(messages);\n const assistantText = firstAssistantText(messages);\n if (!userText && !assistantText) return null;\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(modelRef);\n } catch (err) {\n log.warn({ err, modelRef }, 'Cannot resolve model for session title');\n return null;\n }\n\n const prompt =\n userText && assistantText\n ? `You label chat sessions. Given the first user message and the start of the assistant reply, output ONE short title (max 8 words). No quotes. No punctuation at the end. Use the same language as the user when possible.\n\nUser: ${userText.slice(0, 2000)}\n\nAssistant: ${assistantText.slice(0, 2000)}\n\nTitle:`\n : userText\n ? `The assistant reply only used tools (no visible text yet). Output ONE short title (max 8 words) based only on the user's first message. No quotes. No punctuation at the end. Use the same language as the user.\n\nUser: ${userText.slice(0, 2000)}\n\nTitle:`\n : `Output ONE short title (max 8 words) for this assistant reply. No quotes. No punctuation at the end.\n\nAssistant: ${assistantText!.slice(0, 2000)}\n\nTitle:`;\n\n const userMsg: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n\n try {\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: 64,\n temperature: 0.35,\n signal: signal as AbortSignal,\n },\n );\n\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n\n const cleaned = sanitizeSessionTitle(text);\n return cleaned.length > 0 ? cleaned : null;\n } catch (err) {\n log.warn({ err }, 'Session title LLM call failed');\n return null;\n }\n}\n\n/**\n * If the session is still unnamed, set `name` (LLM when possible, else first-line fallback).\n * Skips cron/heartbeat keys. Ensures index row exists by re-saving when metadata is missing (fixes index lag).\n */\nexport async function maybeAutoTitleSessionStore(\n sessionStore: SessionStore,\n sessionKey: string,\n modelRef: string | undefined,\n): Promise<void> {\n if (!shouldAutoTitleSessionKey(sessionKey)) return;\n\n let messages = await sessionStore.load(sessionKey);\n if (!messages.length) return;\n\n let meta = await sessionStore.getMetadata(sessionKey);\n if (!meta) {\n await sessionStore.save(sessionKey, messages);\n meta = await sessionStore.getMetadata(sessionKey);\n }\n if (!meta) {\n log.warn({ sessionKey }, 'Session title: metadata missing after save');\n return;\n }\n if (meta.name && meta.name.trim().length > 0) return;\n\n let title: string | null = null;\n const ref = modelRef?.trim();\n if (ref) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 25_000);\n try {\n title = await generateSessionTitleFromMessages(ref, messages, controller.signal);\n } finally {\n clearTimeout(timeout);\n }\n }\n if (!title) {\n title = fallbackTitleFromMessages(messages);\n }\n if (!title) return;\n\n try {\n await sessionStore.updateMetadata(sessionKey, { name: title });\n } catch (err) {\n log.warn({ err, sessionKey }, 'Session title: updateMetadata failed');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-title.js","names":[],"sources":["../../../src/session/session-title.ts"],"sourcesContent":["/**\n * LLM-generated session titles (webchat and any path using SessionStore).\n */\n\nimport type { AgentMessage } from '@mariozechner/pi-agent-core';\nimport { complete, type UserMessage } from '@mariozechner/pi-ai';\n\nimport { stripInboundFileMetadataFromText } from '../channels/attachments/inbound-persist.js';\nimport { stripEnvelopeTimestampPrefix } from '../channels/envelope-timestamp.js';\nimport { isCronSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { resolveModel } from '../providers/index.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { SessionStore } from './store.js';\n\nconst log = createLogger('SessionAutoTitle');\n\nconst MAX_TITLE_LEN = 80;\n\n/** Collect visible text from any content block that exposes `text` (pi-ai / OpenAI / Anthropic shapes). */\nfunction extractTextFromMessage(m: AgentMessage): string {\n if (typeof m.content === 'string') return m.content.trim();\n if (Array.isArray(m.content)) {\n const parts: string[] = [];\n for (const c of m.content) {\n if (c && typeof c === 'object') {\n const o = c as unknown as Record<string, unknown>;\n const type = typeof o.type === 'string' ? o.type : '';\n if (type === 'toolCall' || type === 'tool_use' || type === 'tool_result') continue;\n if (typeof o.text === 'string' && o.text.trim()) {\n parts.push(o.text.trim());\n }\n }\n }\n return parts.join(' ').trim();\n }\n return '';\n}\n\nfunction firstUserText(messages: AgentMessage[]): string {\n const u = messages.find((m) => m.role === 'user');\n if (!u) return '';\n const raw = extractTextFromMessage(u);\n // User turns include `formatInboundFileTextBlock` text blocks; do not feed [File:…] into title LLM / fallback.\n // Inbound pipeline / webchat prepends `[YYYY-MM-DD HH:MM TZ]`; strip so titles are not timestamp-led.\n return stripInboundFileMetadataFromText(stripEnvelopeTimestampPrefix(raw));\n}\n\n/** First assistant message that has visible text (skips tool-only assistant rows). */\nfunction firstAssistantText(messages: AgentMessage[]): string {\n for (const m of messages) {\n if (m.role === 'assistant') {\n const t = extractTextFromMessage(m);\n if (t.length > 0) return t;\n }\n }\n return '';\n}\n\nexport function isWebchatSessionKey(sessionKey: string): boolean {\n const p = parseSessionKey(sessionKey);\n if (p?.source === 'webchat') return true;\n return sessionKey.includes(':webchat:');\n}\n\n/** Whether to run LLM/fallback session naming for this key (excludes cron, heartbeat). */\nexport function shouldAutoTitleSessionKey(sessionKey: string): boolean {\n const raw = (sessionKey ?? '').trim();\n if (!raw) return false;\n if (isCronSessionKey(raw)) return false;\n if (raw.toLowerCase().startsWith('heartbeat:')) return false;\n return true;\n}\n\nexport function sanitizeSessionTitle(raw: string): string {\n let s = raw.trim();\n if ((s.startsWith('\"') && s.endsWith('\"')) || (s.startsWith(\"'\") && s.endsWith(\"'\"))) {\n s = s.slice(1, -1).trim();\n }\n const lineBreak = s.indexOf('\\n');\n if (lineBreak !== -1) s = s.slice(0, lineBreak).trim();\n if (s.length > MAX_TITLE_LEN) s = s.slice(0, MAX_TITLE_LEN - 1).trimEnd() + '…';\n return s;\n}\n\n/** Non-LLM title: first line of first user text, else first assistant line. */\nexport function fallbackTitleFromMessages(messages: AgentMessage[]): string | null {\n const u = firstUserText(messages);\n if (u) {\n const line = u.split(/\\n/)[0]?.trim();\n if (line) return sanitizeSessionTitle(line);\n }\n const a = firstAssistantText(messages);\n if (a) {\n const line = a.split(/\\n/)[0]?.trim();\n if (line) return sanitizeSessionTitle(line);\n }\n return null;\n}\n\n/**\n * Returns a title string, or null if generation should be skipped or failed.\n */\nexport async function generateSessionTitleFromMessages(\n modelRef: string,\n messages: AgentMessage[],\n signal?: AbortSignal,\n): Promise<string | null> {\n const userText = firstUserText(messages);\n const assistantText = firstAssistantText(messages);\n if (!userText && !assistantText) return null;\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(modelRef);\n } catch (err) {\n log.warn({ err, modelRef }, 'Cannot resolve model for session title');\n return null;\n }\n\n const prompt =\n userText && assistantText\n ? `You label chat sessions. Given the first user message and the start of the assistant reply, output ONE short title (max 8 words). No quotes. No punctuation at the end. Use the same language as the user when possible.\n\nUser: ${userText.slice(0, 2000)}\n\nAssistant: ${assistantText.slice(0, 2000)}\n\nTitle:`\n : userText\n ? `The assistant reply only used tools (no visible text yet). Output ONE short title (max 8 words) based only on the user's first message. No quotes. No punctuation at the end. Use the same language as the user.\n\nUser: ${userText.slice(0, 2000)}\n\nTitle:`\n : `Output ONE short title (max 8 words) for this assistant reply. No quotes. No punctuation at the end.\n\nAssistant: ${assistantText!.slice(0, 2000)}\n\nTitle:`;\n\n const userMsg: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n\n try {\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: 64,\n temperature: 0.35,\n signal: signal as AbortSignal,\n },\n );\n\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n\n const cleaned = sanitizeSessionTitle(text);\n return cleaned.length > 0 ? cleaned : null;\n } catch (err) {\n log.warn({ err }, 'Session title LLM call failed');\n return null;\n }\n}\n\n/**\n * If the session is still unnamed, set `name` (LLM when possible, else first-line fallback).\n * Skips cron/heartbeat keys. Ensures index row exists by re-saving when metadata is missing (fixes index lag).\n */\nexport async function maybeAutoTitleSessionStore(\n sessionStore: SessionStore,\n sessionKey: string,\n modelRef: string | undefined,\n): Promise<void> {\n if (!shouldAutoTitleSessionKey(sessionKey)) return;\n\n let messages = await sessionStore.load(sessionKey);\n if (!messages.length) return;\n\n let meta = await sessionStore.getMetadata(sessionKey);\n if (!meta) {\n await sessionStore.save(sessionKey, messages);\n meta = await sessionStore.getMetadata(sessionKey);\n }\n if (!meta) {\n log.warn({ sessionKey }, 'Session title: metadata missing after save');\n return;\n }\n if (meta.name && meta.name.trim().length > 0) return;\n\n let title: string | null = null;\n const ref = modelRef?.trim();\n if (ref) {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 25_000);\n try {\n title = await generateSessionTitleFromMessages(ref, messages, controller.signal);\n } finally {\n clearTimeout(timeout);\n }\n }\n if (!title) {\n title = fallbackTitleFromMessages(messages);\n }\n if (!title) return;\n\n try {\n await sessionStore.updateMetadata(sessionKey, { name: title });\n } catch (err) {\n log.warn({ err, sessionKey }, 'Session title: updateMetadata failed');\n }\n}\n"],"mappings":";;;;;;;;kBAS8E;gBACzB;aACH;AAGlD,MAAM,MAAM,aAAa,mBAAmB;AAE5C,MAAM,gBAAgB;;AAGtB,SAAS,uBAAuB,GAAyB;AACvD,KAAI,OAAO,EAAE,YAAY,SAAU,QAAO,EAAE,QAAQ,MAAM;AAC1D,KAAI,MAAM,QAAQ,EAAE,QAAQ,EAAE;EAC5B,MAAM,QAAkB,EAAE;AAC1B,OAAK,MAAM,KAAK,EAAE,QAChB,KAAI,KAAK,OAAO,MAAM,UAAU;GAC9B,MAAM,IAAI;GACV,MAAM,OAAO,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AACnD,OAAI,SAAS,cAAc,SAAS,cAAc,SAAS,cAAe;AAC1E,OAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,MAAM,CAC7C,OAAM,KAAK,EAAE,KAAK,MAAM,CAAC;;AAI/B,SAAO,MAAM,KAAK,IAAI,CAAC,MAAM;;AAE/B,QAAO;;AAGT,SAAS,cAAc,UAAkC;CACvD,MAAM,IAAI,SAAS,MAAM,MAAM,EAAE,SAAS,OAAO;AACjD,KAAI,CAAC,EAAG,QAAO;AAIf,QAAO,iCAAiC,6BAH5B,uBAAuB,EAGqC,CAAC,CAAC;;;AAI5E,SAAS,mBAAmB,UAAkC;AAC5D,MAAK,MAAM,KAAK,SACd,KAAI,EAAE,SAAS,aAAa;EAC1B,MAAM,IAAI,uBAAuB,EAAE;AACnC,MAAI,EAAE,SAAS,EAAG,QAAO;;AAG7B,QAAO;;AAGT,SAAgB,oBAAoB,YAA6B;AAE/D,KADU,gBAAgB,WACrB,EAAE,WAAW,UAAW,QAAO;AACpC,QAAO,WAAW,SAAS,YAAY;;;AAIzC,SAAgB,0BAA0B,YAA6B;CACrE,MAAM,OAAO,cAAc,IAAI,MAAM;AACrC,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,iBAAiB,IAAI,CAAE,QAAO;AAClC,KAAI,IAAI,aAAa,CAAC,WAAW,aAAa,CAAE,QAAO;AACvD,QAAO;;AAGT,SAAgB,qBAAqB,KAAqB;CACxD,IAAI,IAAI,IAAI,MAAM;AAClB,KAAK,EAAE,WAAW,KAAI,IAAI,EAAE,SAAS,KAAI,IAAM,EAAE,WAAW,IAAI,IAAI,EAAE,SAAS,IAAI,CACjF,KAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM;CAE3B,MAAM,YAAY,EAAE,QAAQ,KAAK;AACjC,KAAI,cAAc,GAAI,KAAI,EAAE,MAAM,GAAG,UAAU,CAAC,MAAM;AACtD,KAAI,EAAE,SAAS,cAAe,KAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAAC,SAAS,GAAG;AAC5E,QAAO;;;AAIT,SAAgB,0BAA0B,UAAyC;CACjF,MAAM,IAAI,cAAc,SAAS;AACjC,KAAI,GAAG;EACL,MAAM,OAAO,EAAE,MAAM,KAAK,CAAC,IAAI,MAAM;AACrC,MAAI,KAAM,QAAO,qBAAqB,KAAK;;CAE7C,MAAM,IAAI,mBAAmB,SAAS;AACtC,KAAI,GAAG;EACL,MAAM,OAAO,EAAE,MAAM,KAAK,CAAC,IAAI,MAAM;AACrC,MAAI,KAAM,QAAO,qBAAqB,KAAK;;AAE7C,QAAO;;;;;AAMT,eAAsB,iCACpB,UACA,UACA,QACwB;CACxB,MAAM,WAAW,cAAc,SAAS;CACxC,MAAM,gBAAgB,mBAAmB,SAAS;AAClD,KAAI,CAAC,YAAY,CAAC,cAAe,QAAO;CAExC,IAAI;AACJ,KAAI;AACF,UAAQ,aAAa,SAAS;UACvB,KAAK;AACZ,MAAI,KAAK;GAAE;GAAK;GAAU,EAAE,yCAAyC;AACrE,SAAO;;CAwBT,MAAM,UAAuB;EAAE,MAAM;EAAQ,SApB3C,YAAY,gBACR;;QAEA,SAAS,MAAM,GAAG,IAAK,CAAC;;aAEnB,cAAc,MAAM,GAAG,IAAK,CAAC;;UAGlC,WACE;;QAEF,SAAS,MAAM,GAAG,IAAK,CAAC;;UAGtB;;aAEG,cAAe,MAAM,GAAG,IAAK,CAAC;;;EAIqB,WAAW,KAAK,KAAK;EAAE;AAErF,KAAI;EACF,MAAM,SAAS,MAAM,SACnB,OACA,EAAE,UAAU,CAAC,QAAQ,EAAE,EACvB;GACE,WAAW;GACX,aAAa;GACL;GACT,CACF;EAED,IAAI,OAAO;AACX,MAAI,MAAM,QAAQ,OAAO,QAAQ;QAC1B,MAAM,KAAK,OAAO,QACrB,KAAI,KAAK,OAAO,MAAM,YAAa,EAAwB,SAAS,OAClE,SAAQ,OAAQ,EAAwB,QAAQ,GAAG;;EAKzD,MAAM,UAAU,qBAAqB,KAAK;AAC1C,SAAO,QAAQ,SAAS,IAAI,UAAU;UAC/B,KAAK;AACZ,MAAI,KAAK,EAAE,KAAK,EAAE,gCAAgC;AAClD,SAAO;;;;;;;AAQX,eAAsB,2BACpB,cACA,YACA,UACe;AACf,KAAI,CAAC,0BAA0B,WAAW,CAAE;CAE5C,IAAI,WAAW,MAAM,aAAa,KAAK,WAAW;AAClD,KAAI,CAAC,SAAS,OAAQ;CAEtB,IAAI,OAAO,MAAM,aAAa,YAAY,WAAW;AACrD,KAAI,CAAC,MAAM;AACT,QAAM,aAAa,KAAK,YAAY,SAAS;AAC7C,SAAO,MAAM,aAAa,YAAY,WAAW;;AAEnD,KAAI,CAAC,MAAM;AACT,MAAI,KAAK,EAAE,YAAY,EAAE,6CAA6C;AACtE;;AAEF,KAAI,KAAK,QAAQ,KAAK,KAAK,MAAM,CAAC,SAAS,EAAG;CAE9C,IAAI,QAAuB;CAC3B,MAAM,MAAM,UAAU,MAAM;AAC5B,KAAI,KAAK;EACP,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,UAAU,iBAAiB,WAAW,OAAO,EAAE,KAAO;AAC5D,MAAI;AACF,WAAQ,MAAM,iCAAiC,KAAK,UAAU,WAAW,OAAO;YACxE;AACR,gBAAa,QAAQ;;;AAGzB,KAAI,CAAC,MACH,SAAQ,0BAA0B,SAAS;AAE7C,KAAI,CAAC,MAAO;AAEZ,KAAI;AACF,QAAM,aAAa,eAAe,YAAY,EAAE,MAAM,OAAO,CAAC;UACvD,KAAK;AACZ,MAAI,KAAK;GAAE;GAAK;GAAY,EAAE,uCAAuC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Personal AI assistant: CLI, gateway (HTTP/WebSocket + React console), Telegram and WeChat (Weixin) channels — TypeScript, 20+ LLM providers via pi-ai, extensions and skills.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -127,7 +127,7 @@
127
127
  "dev:web": "pnpm -C web run dev",
128
128
  "dev": "tsx src/cli/index.ts",
129
129
  "start": "node dist/src/cli/index.js",
130
- "test": "vitest run src extensions/telegram/src",
130
+ "test": "vitest run src extensions/telegram/src extensions/feishu/src",
131
131
  "test:skills": "vitest run src/agent/skills/__tests__",
132
132
  "test:skills:regression": "./scripts/test-skills.sh",
133
133
  "test:skills:all": "npm run test:skills && npm run test:skills:regression",