@xopcai/xopc 0.0.28 → 0.0.29

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 (160) hide show
  1. package/dist/extensions/telegram/xopc.extension.json +1 -1
  2. package/dist/gateway/static/root/assets/agents-CkgFSiCY.js +216 -0
  3. package/dist/gateway/static/root/assets/agents-CkgFSiCY.js.map +1 -0
  4. package/dist/gateway/static/root/assets/{apps-page-Co95hLOJ.js → apps-page-Bmq19MS-.js} +2 -2
  5. package/dist/gateway/static/root/assets/{apps-page-Co95hLOJ.js.map → apps-page-Bmq19MS-.js.map} +1 -1
  6. package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js +9 -0
  7. package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js.map +1 -0
  8. package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js +2 -0
  9. package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js.map +1 -0
  10. package/dist/gateway/static/root/assets/{cron-utils-BmzF4m1y.js → cron-utils-N1PqD2DB.js} +2 -2
  11. package/dist/gateway/static/root/assets/{cron-utils-BmzF4m1y.js.map → cron-utils-N1PqD2DB.js.map} +1 -1
  12. package/dist/gateway/static/root/assets/{dist-Dn-ufXyc.js → dist--p2HQ2QF.js} +2 -2
  13. package/dist/gateway/static/root/assets/{dist-Dn-ufXyc.js.map → dist--p2HQ2QF.js.map} +1 -1
  14. package/dist/gateway/static/root/assets/{extension-debug-page-BZ8xQ74_.js → extension-debug-page-DwHCB_6T.js} +2 -2
  15. package/dist/gateway/static/root/assets/{extension-debug-page-BZ8xQ74_.js.map → extension-debug-page-DwHCB_6T.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-page-BlNgKxwW.js → extension-page-BsYwQIex.js} +2 -2
  17. package/dist/gateway/static/root/assets/{extension-page-BlNgKxwW.js.map → extension-page-BsYwQIex.js.map} +1 -1
  18. package/dist/gateway/static/root/assets/{extension-settings-page-CWTdW_oY.js → extension-settings-page-nsisEgjB.js} +2 -2
  19. package/dist/gateway/static/root/assets/{extension-settings-page-CWTdW_oY.js.map → extension-settings-page-nsisEgjB.js.map} +1 -1
  20. package/dist/gateway/static/root/assets/index-CR8zUHGR.js +4734 -0
  21. package/dist/gateway/static/root/assets/{index-lV8FGWlt.js.map → index-CR8zUHGR.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/index-Dnfha4O2.css +1 -0
  23. package/dist/gateway/static/root/assets/logs-page-CQwdV_Xw.js +2 -0
  24. package/dist/gateway/static/root/assets/{logs-page-DG31RpvG.js.map → logs-page-CQwdV_Xw.js.map} +1 -1
  25. package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js +2 -0
  26. package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js.map +1 -0
  27. package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js +2 -0
  28. package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js.map +1 -0
  29. package/dist/gateway/static/root/assets/skills-page-Clg8deH0.js +3 -0
  30. package/dist/gateway/static/root/assets/{skills-page-lb7vYtlP.js.map → skills-page-Clg8deH0.js.map} +1 -1
  31. package/dist/gateway/static/root/index.html +2 -2
  32. package/dist/package.js +1 -1
  33. package/dist/src/agent/lifecycle/hook-handler.d.ts +2 -0
  34. package/dist/src/agent/lifecycle/hook-handler.js +24 -0
  35. package/dist/src/agent/lifecycle/hook-handler.js.map +1 -1
  36. package/dist/src/agent/messaging/command-handler.js +10 -2
  37. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  38. package/dist/src/agent/service/process-direct-streaming.js +77 -20
  39. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  40. package/dist/src/agent/service.d.ts +15 -0
  41. package/dist/src/agent/service.js +21 -1
  42. package/dist/src/agent/service.js.map +1 -1
  43. package/dist/src/channels/index.js +2 -2
  44. package/dist/src/channels/manager.js +2 -2
  45. package/dist/src/cli/agent-chat-log-level-preset.d.ts +3 -2
  46. package/dist/src/cli/agent-chat-log-level-preset.js +6 -3
  47. package/dist/src/cli/agent-chat-log-level-preset.js.map +1 -1
  48. package/dist/src/cli/index.js +4 -3
  49. package/dist/src/cli/index.js.map +1 -1
  50. package/dist/src/config/schema.js +5 -2
  51. package/dist/src/config/schema.js.map +1 -1
  52. package/dist/src/extensions/hooks.js +5 -1
  53. package/dist/src/extensions/hooks.js.map +1 -1
  54. package/dist/src/extensions/loader.d.ts +1 -0
  55. package/dist/src/extensions/loader.js +3 -1
  56. package/dist/src/extensions/loader.js.map +1 -1
  57. package/dist/src/extensions/sdk/index.d.ts +1 -1
  58. package/dist/src/extensions/sdk/index.js.map +1 -1
  59. package/dist/src/extensions/types/core.d.ts +8 -0
  60. package/dist/src/extensions/types/hooks.d.ts +16 -1
  61. package/dist/src/extensions/types/hooks.js +1 -0
  62. package/dist/src/extensions/types/hooks.js.map +1 -1
  63. package/dist/src/gateway/agents-admin.d.ts +19 -1
  64. package/dist/src/gateway/agents-admin.js +164 -3
  65. package/dist/src/gateway/agents-admin.js.map +1 -1
  66. package/dist/src/gateway/hono/app.js +1 -0
  67. package/dist/src/gateway/hono/app.js.map +1 -1
  68. package/dist/src/gateway/hono/routes/agents.js +59 -5
  69. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  70. package/dist/src/gateway/hono/routes/config.js +2 -2
  71. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  72. package/dist/src/gateway/hono/routes/public-gateway.js +1 -0
  73. package/dist/src/gateway/hono/routes/public-gateway.js.map +1 -1
  74. package/dist/src/gateway/hono/routes/sessions.js +17 -0
  75. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  76. package/dist/src/gateway/service.d.ts +2 -0
  77. package/dist/src/gateway/service.js +31 -4
  78. package/dist/src/gateway/service.js.map +1 -1
  79. package/dist/src/session/client-history.d.ts +21 -0
  80. package/dist/src/session/client-history.js +89 -0
  81. package/dist/src/session/client-history.js.map +1 -0
  82. package/dist/src/session/index.d.ts +1 -0
  83. package/dist/src/session/index.js +2 -1
  84. package/dist/src/session/manager.d.ts +2 -0
  85. package/dist/src/session/manager.js +5 -0
  86. package/dist/src/session/manager.js.map +1 -1
  87. package/dist/src/session/thinking-resolve.js +1 -1
  88. package/dist/src/session/thinking-resolve.js.map +1 -1
  89. package/dist/src/tui/backends/embedded-backend.d.ts +1 -1
  90. package/dist/src/tui/backends/embedded-backend.js +15 -2
  91. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  92. package/dist/src/tui/backends/gateway-sse-backend.d.ts +4 -0
  93. package/dist/src/tui/backends/gateway-sse-backend.js +34 -4
  94. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  95. package/dist/src/tui/chat-history.d.ts +4 -0
  96. package/dist/src/tui/chat-history.js +29 -0
  97. package/dist/src/tui/chat-history.js.map +1 -0
  98. package/dist/src/tui/components/chat-log.d.ts +3 -1
  99. package/dist/src/tui/components/chat-log.js +17 -3
  100. package/dist/src/tui/components/chat-log.js.map +1 -1
  101. package/dist/src/tui/components/custom-editor.d.ts +1 -0
  102. package/dist/src/tui/components/custom-editor.js +8 -2
  103. package/dist/src/tui/components/custom-editor.js.map +1 -1
  104. package/dist/src/tui/components/fuzzy-filter.d.ts +17 -0
  105. package/dist/src/tui/components/fuzzy-filter.js +85 -0
  106. package/dist/src/tui/components/fuzzy-filter.js.map +1 -0
  107. package/dist/src/tui/components/searchable-select-list.d.ts +39 -0
  108. package/dist/src/tui/components/searchable-select-list.js +257 -0
  109. package/dist/src/tui/components/searchable-select-list.js.map +1 -0
  110. package/dist/src/tui/theme.d.ts +2 -0
  111. package/dist/src/tui/theme.js +7 -1
  112. package/dist/src/tui/theme.js.map +1 -1
  113. package/dist/src/tui/tui-agent-events.d.ts +7 -0
  114. package/dist/src/tui/tui-agent-events.js +103 -0
  115. package/dist/src/tui/tui-agent-events.js.map +1 -0
  116. package/dist/src/tui/tui-backend.d.ts +8 -12
  117. package/dist/src/tui/tui-commands.d.ts +23 -0
  118. package/dist/src/tui/tui-commands.js +165 -0
  119. package/dist/src/tui/tui-commands.js.map +1 -0
  120. package/dist/src/tui/tui-lifecycle.d.ts +26 -0
  121. package/dist/src/tui/tui-lifecycle.js +57 -0
  122. package/dist/src/tui/tui-lifecycle.js.map +1 -0
  123. package/dist/src/tui/tui-local-shell.d.ts +28 -0
  124. package/dist/src/tui/tui-local-shell.js +147 -0
  125. package/dist/src/tui/tui-local-shell.js.map +1 -0
  126. package/dist/src/tui/tui-overlays.d.ts +8 -0
  127. package/dist/src/tui/tui-overlays.js +22 -0
  128. package/dist/src/tui/tui-overlays.js.map +1 -0
  129. package/dist/src/tui/tui-picker-overlay.d.ts +26 -0
  130. package/dist/src/tui/tui-picker-overlay.js +69 -0
  131. package/dist/src/tui/tui-picker-overlay.js.map +1 -0
  132. package/dist/src/tui/tui-stdio-filter.d.ts +17 -0
  133. package/dist/src/tui/tui-stdio-filter.js +96 -0
  134. package/dist/src/tui/tui-stdio-filter.js.map +1 -0
  135. package/dist/src/tui/tui-submit.d.ts +25 -0
  136. package/dist/src/tui/tui-submit.js +102 -0
  137. package/dist/src/tui/tui-submit.js.map +1 -0
  138. package/dist/src/tui/tui-suspend.d.ts +10 -0
  139. package/dist/src/tui/tui-suspend.js +18 -0
  140. package/dist/src/tui/tui-suspend.js.map +1 -0
  141. package/dist/src/tui/tui-types.d.ts +1 -0
  142. package/dist/src/tui/tui-types.js.map +1 -1
  143. package/dist/src/tui/tui.d.ts +2 -0
  144. package/dist/src/tui/tui.js +175 -312
  145. package/dist/src/tui/tui.js.map +1 -1
  146. package/package.json +2 -6
  147. package/dist/gateway/static/root/assets/agents-DplaQYS2.js +0 -216
  148. package/dist/gateway/static/root/assets/agents-DplaQYS2.js.map +0 -1
  149. package/dist/gateway/static/root/assets/channels-settings-CkfSST0k.js +0 -9
  150. package/dist/gateway/static/root/assets/channels-settings-CkfSST0k.js.map +0 -1
  151. package/dist/gateway/static/root/assets/cron-page-D9q6KqL8.js +0 -2
  152. package/dist/gateway/static/root/assets/cron-page-D9q6KqL8.js.map +0 -1
  153. package/dist/gateway/static/root/assets/index-OT4cGzon.css +0 -1
  154. package/dist/gateway/static/root/assets/index-lV8FGWlt.js +0 -4734
  155. package/dist/gateway/static/root/assets/logs-page-DG31RpvG.js +0 -2
  156. package/dist/gateway/static/root/assets/sessions-page-CdmjxDEM.js +0 -2
  157. package/dist/gateway/static/root/assets/sessions-page-CdmjxDEM.js.map +0 -1
  158. package/dist/gateway/static/root/assets/settings-page-DU2XLf5s.js +0 -2
  159. package/dist/gateway/static/root/assets/settings-page-DU2XLf5s.js.map +0 -1
  160. package/dist/gateway/static/root/assets/skills-page-lb7vYtlP.js +0 -3
@@ -8,6 +8,10 @@ import "../../chat-commands/index.js";
8
8
  init_providers();
9
9
  init_logger();
10
10
  const log = createLogger("CommandHandler");
11
+ /** Gateway console direct stream uses SSE tokens; there is no ChannelPlugin outbound for `webchat`. */
12
+ function shouldSkipBusOutboundForChannel(channel) {
13
+ return channel === "webchat";
14
+ }
11
15
  var CommandHandler = class {
12
16
  config;
13
17
  bus;
@@ -61,6 +65,7 @@ var CommandHandler = class {
61
65
  sessionConfigStore: this.sessionConfigStore,
62
66
  applySessionThinkingLevel: this.applySessionThinkingLevel,
63
67
  replyHandler: async (text, _options) => {
68
+ if (shouldSkipBusOutboundForChannel(context.channel)) return;
64
69
  await this.bus.publishOutbound({
65
70
  channel: context.channel,
66
71
  chat_id: context.chatId,
@@ -69,6 +74,7 @@ var CommandHandler = class {
69
74
  });
70
75
  },
71
76
  typingHandler: async (typing) => {
77
+ if (shouldSkipBusOutboundForChannel(context.channel)) return;
72
78
  await this.bus.publishOutbound({
73
79
  channel: context.channel,
74
80
  chat_id: context.chatId,
@@ -118,7 +124,7 @@ var CommandHandler = class {
118
124
  getSessionContextReport: this.getSessionContextReport
119
125
  });
120
126
  const result = await commandRegistry.execute(commandName, cmdCtx, args);
121
- if (result.content) await this.bus.publishOutbound({
127
+ if (result.content && !shouldSkipBusOutboundForChannel(context.channel)) await this.bus.publishOutbound({
122
128
  channel: context.channel,
123
129
  chat_id: context.chatId,
124
130
  content: result.content,
@@ -161,6 +167,7 @@ var CommandHandler = class {
161
167
  applySessionThinkingLevel: this.applySessionThinkingLevel,
162
168
  replyHandler: async (text, _options) => {
163
169
  segments.push(text);
170
+ if (shouldSkipBusOutboundForChannel(context.channel)) return;
164
171
  await this.bus.publishOutbound({
165
172
  channel: context.channel,
166
173
  chat_id: context.chatId,
@@ -169,6 +176,7 @@ var CommandHandler = class {
169
176
  });
170
177
  },
171
178
  typingHandler: async (typing) => {
179
+ if (shouldSkipBusOutboundForChannel(context.channel)) return;
172
180
  await this.bus.publishOutbound({
173
181
  channel: context.channel,
174
182
  chat_id: context.chatId,
@@ -220,7 +228,7 @@ var CommandHandler = class {
220
228
  const result = await commandRegistry.execute(commandName, wrapped, args);
221
229
  if (result.content) {
222
230
  segments.push(result.content);
223
- await this.bus.publishOutbound({
231
+ if (!shouldSkipBusOutboundForChannel(context.channel)) await this.bus.publishOutbound({
224
232
  channel: context.channel,
225
233
  chat_id: context.chatId,
226
234
  content: result.content,
@@ -1 +1 @@
1
- {"version":3,"file":"command-handler.js","names":[],"sources":["../../../../src/agent/messaging/command-handler.ts"],"sourcesContent":["/**\n * Command Handler - Parses and executes commands\n *\n * Handles command execution using the unified command system.\n */\n\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { Config } from '../../config/schema.js';\nimport { isProviderConfiguredSync } from '../../providers/index.js';\nimport type { SessionConfigStore, SessionStore } from '../../session/index.js';\nimport type { ThinkLevel } from '../transcript/thinking-types.js';\nimport type { CompactionResult } from '../memory/compaction.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { commandRegistry, createCommandContext } from '../../chat-commands/index.js';\nimport { getAllProviders, getModelsByProvider, getProviderDisplayName } from '../../providers/index.js';\n\nconst log = createLogger('CommandHandler');\n\nexport interface CommandContext {\n sessionKey: string;\n channel: string;\n chatId: string;\n senderId: string;\n isGroup: boolean;\n}\n\nexport interface CommandHandlerConfig {\n config: Config;\n bus: MessageBus;\n sessionStore: SessionStore;\n sessionConfigStore?: SessionConfigStore;\n /** After /think persists, sync pi-agent */\n applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n getCurrentModel: () => string;\n switchModelForSession: (sessionKey: string, modelId: string) => Promise<boolean>;\n /** Drop in-memory agent after session file is cleared (e.g. /new) */\n invalidateAgentSession?: (sessionKey: string) => void;\n /** Cancel streaming preview + in-flight LLM work for this session (e.g. /abort) */\n abortSessionTurn?: (sessionKey: string) => Promise<void>;\n\n compactSession?: (\n sessionKey: string,\n options?: { instructions?: string; force?: boolean },\n ) => Promise<CompactionResult>;\n\n btwQuery?: (sessionKey: string, question: string) => Promise<{ text: string; error?: string }>;\n\n getSessionContextReport?: (\n sessionKey: string,\n mode: 'list' | 'detail' | 'json',\n ) => Promise<string>;\n}\n\nexport class CommandHandler {\n private config: Config;\n private bus: MessageBus;\n private sessionStore: SessionStore;\n private sessionConfigStore?: SessionConfigStore;\n private applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n private getCurrentModel: () => string;\n private switchModelForSession: (sessionKey: string, modelId: string) => Promise<boolean>;\n private invalidateAgentSession?: (sessionKey: string) => void;\n private abortSessionTurn?: (sessionKey: string) => Promise<void>;\n private compactSession?: CommandHandlerConfig['compactSession'];\n private btwQuery?: CommandHandlerConfig['btwQuery'];\n private getSessionContextReport?: CommandHandlerConfig['getSessionContextReport'];\n\n constructor(handlerConfig: CommandHandlerConfig) {\n this.config = handlerConfig.config;\n this.bus = handlerConfig.bus;\n this.sessionStore = handlerConfig.sessionStore;\n this.sessionConfigStore = handlerConfig.sessionConfigStore;\n this.applySessionThinkingLevel = handlerConfig.applySessionThinkingLevel;\n this.getCurrentModel = handlerConfig.getCurrentModel;\n this.switchModelForSession = handlerConfig.switchModelForSession;\n this.invalidateAgentSession = handlerConfig.invalidateAgentSession;\n this.abortSessionTurn = handlerConfig.abortSessionTurn;\n this.compactSession = handlerConfig.compactSession;\n this.btwQuery = handlerConfig.btwQuery;\n this.getSessionContextReport = handlerConfig.getSessionContextReport;\n }\n\n /** Replace config reference after hot reload or gateway PATCH so commands see current defaults. */\n updateAgentConfig(config: Config): void {\n this.config = config;\n }\n\n /**\n * Execute a command using the unified command system\n */\n async executeCommand(\n commandName: string,\n args: string,\n context: CommandContext\n ): Promise<boolean> {\n // Check if command exists\n if (!commandRegistry.has(commandName)) {\n return false;\n }\n\n log.info({ command: commandName, sessionKey: context.sessionKey }, 'Executing command via new system');\n\n // Create command context\n const cmdCtx = createCommandContext({\n sessionKey: context.sessionKey,\n source: context.channel as 'telegram' | 'webui' | 'cli' | 'api' | 'system' | 'gateway',\n channelId: context.channel,\n chatId: context.chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n config: this.config,\n bus: this.bus,\n sessionStore: this.sessionStore,\n sessionConfigStore: this.sessionConfigStore,\n applySessionThinkingLevel: this.applySessionThinkingLevel,\n\n replyHandler: async (text: string, _options?) => {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: text,\n type: 'message',\n });\n },\n\n typingHandler: async (typing: boolean) => {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n type: typing ? 'typing_on' : 'typing_off',\n });\n },\n\n supportedFeatures: ['markdown', 'typing'],\n\n getCurrentModel: this.getCurrentModel,\n\n switchModel: async (modelId: string) => {\n return this.switchModelForSession(context.sessionKey, modelId);\n },\n\n listModels: async () => {\n const providers = getAllProviders();\n const models: Array<{ id: string; name: string; provider: string }> = [];\n\n for (const providerId of providers) {\n if (isProviderConfiguredSync(providerId)) {\n const providerModels = getModelsByProvider(providerId);\n for (const m of providerModels) {\n models.push({\n id: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n provider: getProviderDisplayName(providerId),\n });\n }\n }\n }\n\n return models;\n },\n\n getUsage: async () => {\n const messages = await this.sessionStore.load(context.sessionKey);\n let promptTokens = 0;\n let completionTokens = 0;\n\n for (const msg of messages) {\n if ('usage' in msg && msg.usage) {\n const usage = msg.usage as any;\n promptTokens += usage.input || 0;\n completionTokens += usage.output || 0;\n }\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n messageCount: messages.length,\n };\n },\n\n invalidateAgentSession: this.invalidateAgentSession,\n\n abortCurrentTurn: this.abortSessionTurn\n ? async () => {\n await this.abortSessionTurn!(context.sessionKey);\n }\n : undefined,\n\n compactSession: this.compactSession,\n btwQuery: this.btwQuery,\n getSessionContextReport: this.getSessionContextReport,\n });\n\n const result = await commandRegistry.execute(commandName, cmdCtx, args);\n\n if (result.content) {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: result.content,\n type: 'message',\n });\n }\n\n return true;\n }\n\n /**\n * Run command and return all user-visible text (ctx.reply + result.content) for SSE/CLI.\n * Same bus side effects as {@link executeCommand}.\n */\n async executeCommandAndAggregateReply(\n commandName: string,\n args: string,\n context: CommandContext,\n ): Promise<{ handled: boolean; aggregatedText: string }> {\n if (!commandRegistry.has(commandName)) {\n return { handled: false, aggregatedText: '' };\n }\n\n log.info({ command: commandName, sessionKey: context.sessionKey }, 'Executing command (aggregate reply)');\n\n const { aggregatedText } = await this.runRegistryExecuteWithCapture(args, commandName, context);\n\n return { handled: true, aggregatedText };\n }\n\n private async runRegistryExecuteWithCapture(\n args: string,\n commandName: string,\n context: CommandContext,\n ): Promise<{ aggregatedText: string }> {\n const segments: string[] = [];\n\n const wrapped = createCommandContext({\n sessionKey: context.sessionKey,\n source: context.channel as 'telegram' | 'webui' | 'cli' | 'api' | 'system' | 'gateway',\n channelId: context.channel,\n chatId: context.chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n config: this.config,\n bus: this.bus,\n sessionStore: this.sessionStore,\n sessionConfigStore: this.sessionConfigStore,\n applySessionThinkingLevel: this.applySessionThinkingLevel,\n\n replyHandler: async (text: string, _options?) => {\n segments.push(text);\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: text,\n type: 'message',\n });\n },\n\n typingHandler: async (typing: boolean) => {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n type: typing ? 'typing_on' : 'typing_off',\n });\n },\n\n supportedFeatures: ['markdown', 'typing'],\n\n getCurrentModel: this.getCurrentModel,\n\n switchModel: async (modelId: string) => {\n return this.switchModelForSession(context.sessionKey, modelId);\n },\n\n listModels: async () => {\n const providers = getAllProviders();\n const models: Array<{ id: string; name: string; provider: string }> = [];\n\n for (const providerId of providers) {\n if (isProviderConfiguredSync(providerId)) {\n const providerModels = getModelsByProvider(providerId);\n for (const m of providerModels) {\n models.push({\n id: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n provider: getProviderDisplayName(providerId),\n });\n }\n }\n }\n\n return models;\n },\n\n getUsage: async () => {\n const messages = await this.sessionStore.load(context.sessionKey);\n let promptTokens = 0;\n let completionTokens = 0;\n\n for (const msg of messages) {\n if ('usage' in msg && msg.usage) {\n const usage = msg.usage as { input?: number; output?: number };\n promptTokens += usage.input || 0;\n completionTokens += usage.output || 0;\n }\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n messageCount: messages.length,\n };\n },\n\n invalidateAgentSession: this.invalidateAgentSession,\n\n abortCurrentTurn: this.abortSessionTurn\n ? async () => {\n await this.abortSessionTurn!(context.sessionKey);\n }\n : undefined,\n\n compactSession: this.compactSession,\n btwQuery: this.btwQuery,\n getSessionContextReport: this.getSessionContextReport,\n });\n\n const result = await commandRegistry.execute(commandName, wrapped, args);\n\n if (result.content) {\n segments.push(result.content);\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: result.content,\n type: 'message',\n });\n }\n\n const aggregatedText = segments.filter((s) => s && s.trim()).join('\\n\\n');\n return { aggregatedText };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"command-handler.js","names":[],"sources":["../../../../src/agent/messaging/command-handler.ts"],"sourcesContent":["/**\n * Command Handler - Parses and executes commands\n *\n * Handles command execution using the unified command system.\n */\n\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { Config } from '../../config/schema.js';\nimport { isProviderConfiguredSync } from '../../providers/index.js';\nimport type { SessionConfigStore, SessionStore } from '../../session/index.js';\nimport type { ThinkLevel } from '../transcript/thinking-types.js';\nimport type { CompactionResult } from '../memory/compaction.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { commandRegistry, createCommandContext } from '../../chat-commands/index.js';\nimport { getAllProviders, getModelsByProvider, getProviderDisplayName } from '../../providers/index.js';\n\nconst log = createLogger('CommandHandler');\n\n/** Gateway console direct stream uses SSE tokens; there is no ChannelPlugin outbound for `webchat`. */\nfunction shouldSkipBusOutboundForChannel(channel: string): boolean {\n return channel === 'webchat';\n}\n\nexport interface CommandContext {\n sessionKey: string;\n channel: string;\n chatId: string;\n senderId: string;\n isGroup: boolean;\n}\n\nexport interface CommandHandlerConfig {\n config: Config;\n bus: MessageBus;\n sessionStore: SessionStore;\n sessionConfigStore?: SessionConfigStore;\n /** After /think persists, sync pi-agent */\n applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n getCurrentModel: () => string;\n switchModelForSession: (sessionKey: string, modelId: string) => Promise<boolean>;\n /** Drop in-memory agent after session file is cleared (e.g. /new) */\n invalidateAgentSession?: (sessionKey: string) => void;\n /** Cancel streaming preview + in-flight LLM work for this session (e.g. /abort) */\n abortSessionTurn?: (sessionKey: string) => Promise<void>;\n\n compactSession?: (\n sessionKey: string,\n options?: { instructions?: string; force?: boolean },\n ) => Promise<CompactionResult>;\n\n btwQuery?: (sessionKey: string, question: string) => Promise<{ text: string; error?: string }>;\n\n getSessionContextReport?: (\n sessionKey: string,\n mode: 'list' | 'detail' | 'json',\n ) => Promise<string>;\n}\n\nexport class CommandHandler {\n private config: Config;\n private bus: MessageBus;\n private sessionStore: SessionStore;\n private sessionConfigStore?: SessionConfigStore;\n private applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n private getCurrentModel: () => string;\n private switchModelForSession: (sessionKey: string, modelId: string) => Promise<boolean>;\n private invalidateAgentSession?: (sessionKey: string) => void;\n private abortSessionTurn?: (sessionKey: string) => Promise<void>;\n private compactSession?: CommandHandlerConfig['compactSession'];\n private btwQuery?: CommandHandlerConfig['btwQuery'];\n private getSessionContextReport?: CommandHandlerConfig['getSessionContextReport'];\n\n constructor(handlerConfig: CommandHandlerConfig) {\n this.config = handlerConfig.config;\n this.bus = handlerConfig.bus;\n this.sessionStore = handlerConfig.sessionStore;\n this.sessionConfigStore = handlerConfig.sessionConfigStore;\n this.applySessionThinkingLevel = handlerConfig.applySessionThinkingLevel;\n this.getCurrentModel = handlerConfig.getCurrentModel;\n this.switchModelForSession = handlerConfig.switchModelForSession;\n this.invalidateAgentSession = handlerConfig.invalidateAgentSession;\n this.abortSessionTurn = handlerConfig.abortSessionTurn;\n this.compactSession = handlerConfig.compactSession;\n this.btwQuery = handlerConfig.btwQuery;\n this.getSessionContextReport = handlerConfig.getSessionContextReport;\n }\n\n /** Replace config reference after hot reload or gateway PATCH so commands see current defaults. */\n updateAgentConfig(config: Config): void {\n this.config = config;\n }\n\n /**\n * Execute a command using the unified command system\n */\n async executeCommand(\n commandName: string,\n args: string,\n context: CommandContext\n ): Promise<boolean> {\n // Check if command exists\n if (!commandRegistry.has(commandName)) {\n return false;\n }\n\n log.info({ command: commandName, sessionKey: context.sessionKey }, 'Executing command via new system');\n\n // Create command context\n const cmdCtx = createCommandContext({\n sessionKey: context.sessionKey,\n source: context.channel as 'telegram' | 'webui' | 'cli' | 'api' | 'system' | 'gateway',\n channelId: context.channel,\n chatId: context.chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n config: this.config,\n bus: this.bus,\n sessionStore: this.sessionStore,\n sessionConfigStore: this.sessionConfigStore,\n applySessionThinkingLevel: this.applySessionThinkingLevel,\n\n replyHandler: async (text: string, _options?) => {\n if (shouldSkipBusOutboundForChannel(context.channel)) return;\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: text,\n type: 'message',\n });\n },\n\n typingHandler: async (typing: boolean) => {\n if (shouldSkipBusOutboundForChannel(context.channel)) return;\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n type: typing ? 'typing_on' : 'typing_off',\n });\n },\n\n supportedFeatures: ['markdown', 'typing'],\n\n getCurrentModel: this.getCurrentModel,\n\n switchModel: async (modelId: string) => {\n return this.switchModelForSession(context.sessionKey, modelId);\n },\n\n listModels: async () => {\n const providers = getAllProviders();\n const models: Array<{ id: string; name: string; provider: string }> = [];\n\n for (const providerId of providers) {\n if (isProviderConfiguredSync(providerId)) {\n const providerModels = getModelsByProvider(providerId);\n for (const m of providerModels) {\n models.push({\n id: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n provider: getProviderDisplayName(providerId),\n });\n }\n }\n }\n\n return models;\n },\n\n getUsage: async () => {\n const messages = await this.sessionStore.load(context.sessionKey);\n let promptTokens = 0;\n let completionTokens = 0;\n\n for (const msg of messages) {\n if ('usage' in msg && msg.usage) {\n const usage = msg.usage as any;\n promptTokens += usage.input || 0;\n completionTokens += usage.output || 0;\n }\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n messageCount: messages.length,\n };\n },\n\n invalidateAgentSession: this.invalidateAgentSession,\n\n abortCurrentTurn: this.abortSessionTurn\n ? async () => {\n await this.abortSessionTurn!(context.sessionKey);\n }\n : undefined,\n\n compactSession: this.compactSession,\n btwQuery: this.btwQuery,\n getSessionContextReport: this.getSessionContextReport,\n });\n\n const result = await commandRegistry.execute(commandName, cmdCtx, args);\n\n if (result.content && !shouldSkipBusOutboundForChannel(context.channel)) {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: result.content,\n type: 'message',\n });\n }\n\n return true;\n }\n\n /**\n * Run command and return all user-visible text (ctx.reply + result.content) for SSE/CLI.\n * Same bus side effects as {@link executeCommand}.\n */\n async executeCommandAndAggregateReply(\n commandName: string,\n args: string,\n context: CommandContext,\n ): Promise<{ handled: boolean; aggregatedText: string }> {\n if (!commandRegistry.has(commandName)) {\n return { handled: false, aggregatedText: '' };\n }\n\n log.info({ command: commandName, sessionKey: context.sessionKey }, 'Executing command (aggregate reply)');\n\n const { aggregatedText } = await this.runRegistryExecuteWithCapture(args, commandName, context);\n\n return { handled: true, aggregatedText };\n }\n\n private async runRegistryExecuteWithCapture(\n args: string,\n commandName: string,\n context: CommandContext,\n ): Promise<{ aggregatedText: string }> {\n const segments: string[] = [];\n\n const wrapped = createCommandContext({\n sessionKey: context.sessionKey,\n source: context.channel as 'telegram' | 'webui' | 'cli' | 'api' | 'system' | 'gateway',\n channelId: context.channel,\n chatId: context.chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n config: this.config,\n bus: this.bus,\n sessionStore: this.sessionStore,\n sessionConfigStore: this.sessionConfigStore,\n applySessionThinkingLevel: this.applySessionThinkingLevel,\n\n replyHandler: async (text: string, _options?) => {\n segments.push(text);\n if (shouldSkipBusOutboundForChannel(context.channel)) return;\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: text,\n type: 'message',\n });\n },\n\n typingHandler: async (typing: boolean) => {\n if (shouldSkipBusOutboundForChannel(context.channel)) return;\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n type: typing ? 'typing_on' : 'typing_off',\n });\n },\n\n supportedFeatures: ['markdown', 'typing'],\n\n getCurrentModel: this.getCurrentModel,\n\n switchModel: async (modelId: string) => {\n return this.switchModelForSession(context.sessionKey, modelId);\n },\n\n listModels: async () => {\n const providers = getAllProviders();\n const models: Array<{ id: string; name: string; provider: string }> = [];\n\n for (const providerId of providers) {\n if (isProviderConfiguredSync(providerId)) {\n const providerModels = getModelsByProvider(providerId);\n for (const m of providerModels) {\n models.push({\n id: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n provider: getProviderDisplayName(providerId),\n });\n }\n }\n }\n\n return models;\n },\n\n getUsage: async () => {\n const messages = await this.sessionStore.load(context.sessionKey);\n let promptTokens = 0;\n let completionTokens = 0;\n\n for (const msg of messages) {\n if ('usage' in msg && msg.usage) {\n const usage = msg.usage as { input?: number; output?: number };\n promptTokens += usage.input || 0;\n completionTokens += usage.output || 0;\n }\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n messageCount: messages.length,\n };\n },\n\n invalidateAgentSession: this.invalidateAgentSession,\n\n abortCurrentTurn: this.abortSessionTurn\n ? async () => {\n await this.abortSessionTurn!(context.sessionKey);\n }\n : undefined,\n\n compactSession: this.compactSession,\n btwQuery: this.btwQuery,\n getSessionContextReport: this.getSessionContextReport,\n });\n\n const result = await commandRegistry.execute(commandName, wrapped, args);\n\n if (result.content) {\n segments.push(result.content);\n if (!shouldSkipBusOutboundForChannel(context.channel)) {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: result.content,\n type: 'message',\n });\n }\n }\n\n const aggregatedText = segments.filter((s) => s && s.trim()).join('\\n\\n');\n return { aggregatedText };\n }\n}\n"],"mappings":"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"}
@@ -20,7 +20,7 @@ async function* runProcessDirectStreaming(deps, input) {
20
20
  let agentDone = false;
21
21
  let lastSentContent = "";
22
22
  let lastSentThinking = "";
23
- let reasoningLevel = "off";
23
+ let reasoningLevel = "stream";
24
24
  const enqueueSseEvent = (event) => {
25
25
  eventQueue.push(event);
26
26
  if (resolveWaiting) {
@@ -44,18 +44,22 @@ async function* runProcessDirectStreaming(deps, input) {
44
44
  switch (event.type) {
45
45
  case "tool_execution_start": {
46
46
  const toolEvent = event;
47
+ const toolName = typeof toolEvent.toolName === "string" && toolEvent.toolName.trim() ? toolEvent.toolName.trim() : "unknown";
47
48
  pushEvent({
48
49
  type: "tool_start",
49
- toolName: typeof toolEvent.toolName === "string" && toolEvent.toolName.trim() ? toolEvent.toolName.trim() : "unknown",
50
+ toolCallId: toolEvent.toolCallId,
51
+ toolName,
50
52
  args: toolEvent.args
51
53
  });
52
54
  break;
53
55
  }
54
56
  case "tool_execution_end": {
55
57
  const toolEvent = event;
58
+ const toolName = typeof toolEvent.toolName === "string" && toolEvent.toolName.trim() ? toolEvent.toolName.trim() : "unknown";
56
59
  pushEvent({
57
60
  type: "tool_end",
58
- toolName: typeof toolEvent.toolName === "string" && toolEvent.toolName.trim() ? toolEvent.toolName.trim() : "unknown",
61
+ toolCallId: toolEvent.toolCallId,
62
+ toolName,
59
63
  isError: toolEvent.isError,
60
64
  result: serializeAgentToolResultForSse(toolEvent.result)
61
65
  });
@@ -145,7 +149,7 @@ async function* runProcessDirectStreaming(deps, input) {
145
149
  agent.state.messages = prepareLoadedSessionMessages(sessionKey, loaded);
146
150
  await modelManager.applyModelForSession(agent, sessionKey);
147
151
  await applyResolvedThinkingLevel(sessionKey, input.thinking);
148
- reasoningLevel = await resolveEffectiveReasoningLevel(sessionConfigStore, sessionKey, getConfig()?.agents?.defaults?.reasoningDefault ?? "off");
152
+ reasoningLevel = await resolveEffectiveReasoningLevel(sessionConfigStore, sessionKey, getConfig()?.agents?.defaults?.reasoningDefault ?? "stream");
149
153
  const sttCfg = mergeSttConfigFromAppConfig(getConfig()?.stt);
150
154
  const { text: mergedUserText, inboundVoice } = await mergeVoiceTranscriptsIntoUserText(attachmentRootsForSession(sessionKey), prepared, input.content, sttCfg);
151
155
  const armAbort = () => {
@@ -160,21 +164,57 @@ async function* runProcessDirectStreaming(deps, input) {
160
164
  else signal.addEventListener("abort", armAbort, { once: true });
161
165
  const commandInfo = parseSlashCommand(mergedUserText);
162
166
  let ranSlashCommand = false;
163
- if (!abortHandled && commandInfo && commandRegistry.has(commandInfo.command)) {
164
- ranSlashCommand = true;
165
- const { aggregatedText } = await commandHandler.executeCommandAndAggregateReply(commandInfo.command, commandInfo.args, {
166
- sessionKey,
167
- channel,
168
- chatId,
169
- senderId: context.senderId,
170
- isGroup: context.isGroup
171
- });
172
- if (aggregatedText) pushEvent({
173
- type: "token",
174
- content: aggregatedText
175
- });
176
- pushEvent({ type: "__done__" });
177
- agentDone = true;
167
+ /** Plain text to persist for webchat slash turns (SSE can be dropped by the UI; disk is source of truth). */
168
+ let webchatSlashReceipt;
169
+ if (!abortHandled && commandInfo) {
170
+ if (commandRegistry.has(commandInfo.command)) {
171
+ ranSlashCommand = true;
172
+ try {
173
+ const { aggregatedText } = await commandHandler.executeCommandAndAggregateReply(commandInfo.command, commandInfo.args, {
174
+ sessionKey,
175
+ channel,
176
+ chatId,
177
+ senderId: context.senderId,
178
+ isGroup: context.isGroup
179
+ });
180
+ if (aggregatedText?.trim()) {
181
+ webchatSlashReceipt = aggregatedText.trim();
182
+ pushEvent({
183
+ type: "token",
184
+ content: webchatSlashReceipt
185
+ });
186
+ } else if (channel === "webchat") {
187
+ webchatSlashReceipt = "Command finished with no assistant text. For `/goal`, that usually means the goal was saved; the assistant will not run until your next normal message (or an auto-continuation after an assistant reply).";
188
+ pushEvent({
189
+ type: "token",
190
+ content: webchatSlashReceipt
191
+ });
192
+ }
193
+ } catch (cmdErr) {
194
+ const em = cmdErr instanceof Error ? cmdErr.message : String(cmdErr);
195
+ log.warn({
196
+ err: cmdErr,
197
+ sessionKey,
198
+ command: commandInfo.command
199
+ }, `Slash command failed: ${em}`);
200
+ webchatSlashReceipt = `Command error: ${em}`;
201
+ pushEvent({
202
+ type: "token",
203
+ content: webchatSlashReceipt
204
+ });
205
+ }
206
+ pushEvent({ type: "__done__" });
207
+ agentDone = true;
208
+ } else if (commandInfo.command === "goal") {
209
+ ranSlashCommand = true;
210
+ webchatSlashReceipt = "The /goal command is not available: the `standing-goal` extension is not loaded.\n\nFix: add `\"standing-goal\"` to `extensions.enabled` in your xopc config (or remove it from `extensions.disabled`), then restart the gateway. If you use a strict allowlist, include `standing-goal` in `extensions.enabled`.";
211
+ pushEvent({
212
+ type: "token",
213
+ content: webchatSlashReceipt
214
+ });
215
+ pushEvent({ type: "__done__" });
216
+ agentDone = true;
217
+ }
178
218
  }
179
219
  let messageContent;
180
220
  if (!abortHandled && !ranSlashCommand) messageContent = await buildMessageContent(mergedUserText.trimStart().startsWith("/skill:") ? agentManager.expandSkillUserText(mergedUserText) : mergedUserText, prepared, sessionKey);
@@ -222,7 +262,24 @@ async function* runProcessDirectStreaming(deps, input) {
222
262
  if (event.type === "__done__") continue;
223
263
  yield event;
224
264
  }
225
- await persistAgentSessionMessages(sessionKey);
265
+ if (channel === "webchat" && ranSlashCommand && webchatSlashReceipt?.trim()) try {
266
+ const loaded = await sessionStore.load(sessionKey);
267
+ const assistantMsg = {
268
+ role: "assistant",
269
+ content: [{
270
+ type: "text",
271
+ text: webchatSlashReceipt.trim()
272
+ }],
273
+ timestamp: Date.now()
274
+ };
275
+ await sessionStore.save(sessionKey, [...loaded, assistantMsg]);
276
+ } catch (err) {
277
+ log.warn({
278
+ err,
279
+ sessionKey
280
+ }, "Failed to persist webchat slash command receipt");
281
+ }
282
+ if (!ranSlashCommand) await persistAgentSessionMessages(sessionKey);
226
283
  if (!userAborted) {
227
284
  const ttsAudioEvent = await maybeEmitWebchatTts(sessionKey, inboundVoice);
228
285
  if (ttsAudioEvent) yield ttsAudioEvent;
@@ -1 +1 @@
1
- {"version":3,"file":"process-direct-streaming.js","names":["msgEvent"],"sources":["../../../../src/agent/service/process-direct-streaming.ts"],"sourcesContent":["import type { AgentEvent, AgentMessage } from '@mariozechner/pi-agent-core';\n\nimport type { Config } from '../../config/schema.js';\nimport type { InternalAttachmentRoots } from '../../channels/attachments/inbound-persist.js';\nimport { commandRegistry } from '../../chat-commands/index.js';\nimport { parseSlashCommand } from '../../chat-commands/command-parse.js';\nimport {\n mergeVoiceTranscriptsIntoUserText,\n mergeSttConfigFromAppConfig,\n} from '../../channels/attachments/voice-stt-webchat.js';\nimport {\n resolveEffectiveReasoningLevel,\n type SessionConfigStore,\n type SessionStore,\n} from '../../session/index.js';\nimport type { SessionContext } from '../session/index.js';\nimport {\n extractTextContent,\n extractThinkingContent,\n extractThinkingFromAssistantMessage,\n} from '../context/workspace.js';\nimport { extractAgentUserPlainText } from '../memory/user-message-text.js';\nimport { runAgentTurnWithModelFallbacks } from '../orchestration/run-agent-turn-with-fallbacks.js';\nimport { applyReasoningVisibilityToSseEvent } from '../streaming/reasoning-visibility-sse.js';\nimport type { ReasoningLevel } from '../transcript/thinking-types.js';\nimport { serializeAgentToolResultForSse } from '../service-inbound-utils.js';\nimport type { AgentManager } from '../agent-manager.js';\nimport type { AgentEventHandler } from '../orchestration/agent-event-handler.js';\nimport type { AgentOrchestrator } from '../orchestration/agent-orchestrator.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { ModelManager } from '../models/index.js';\n\nexport type DirectStreamInboundAttachment = {\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n};\n\n/** Logger shape expected by {@link runAgentTurnWithModelFallbacks}. */\nexport type ProcessDirectStreamLog = {\n info: (obj: Record<string, unknown>, msg: string) => void;\n warn: (obj: Record<string, unknown>, msg: string) => void;\n debug?: (obj: Record<string, unknown>, msg: string) => void;\n};\n\n/**\n * Explicit collaborators for {@link runProcessDirectStreaming} (gateway webchat / CLI direct stream).\n * Bound by `AgentService` from its private fields and methods.\n */\nexport interface ProcessDirectStreamingDeps {\n log: ProcessDirectStreamLog;\n parseSessionKey: (sessionKey: string) => { channel: string; chatId: string };\n initDirectStreamingSession: (\n sessionKey: string,\n channel: string,\n chatId: string,\n ) => SessionContext;\n registerWebchatSsePublisher: (\n sessionKey: string,\n publisher: (event: { type: string; [key: string]: unknown }) => void,\n ) => void;\n unregisterWebchatSsePublisher: (sessionKey: string) => void;\n agentManager: AgentManager;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n agentEventHandler: AgentEventHandler;\n sessionStore: SessionStore;\n prepareLoadedSessionMessages: (sessionKey: string, messages: AgentMessage[]) => AgentMessage[];\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n getConfig: () => Config | undefined;\n sessionConfigStore: SessionConfigStore;\n attachmentRootsForSession: (sessionKey: string) => InternalAttachmentRoots;\n agentOrchestrator: Pick<AgentOrchestrator, 'abort'>;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: DirectStreamInboundAttachment[],\n ) => Promise<DirectStreamInboundAttachment[] | undefined>;\n buildMessageContent: (\n content: string,\n attachments: DirectStreamInboundAttachment[] | undefined,\n sessionKey: string,\n ) => Promise<Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }>>;\n persistAgentSessionMessages: (sessionKey: string) => Promise<void>;\n maybeEmitWebchatTts: (\n sessionKey: string,\n hadInboundVoice: boolean,\n ) => Promise<{ type: 'tts_audio'; workspaceRelativePath: string; mimeType: string; name: string } | null>;\n endDirectRequestContext: () => void;\n}\n\nexport interface ProcessDirectStreamingInput {\n content: string;\n sessionKey?: string;\n attachments?: DirectStreamInboundAttachment[];\n thinking?: string;\n signal?: AbortSignal;\n}\n\nexport type ProcessDirectStreamingSseEvent = { type: string; [key: string]: unknown };\n\nexport async function* runProcessDirectStreaming(\n deps: ProcessDirectStreamingDeps,\n input: ProcessDirectStreamingInput,\n): AsyncGenerator<ProcessDirectStreamingSseEvent, void, unknown> {\n const {\n log,\n parseSessionKey,\n initDirectStreamingSession,\n registerWebchatSsePublisher,\n unregisterWebchatSsePublisher,\n agentManager,\n hydrateSessionWorkspaceFromStore,\n hydrateSessionModelFromStore,\n agentEventHandler,\n sessionStore,\n prepareLoadedSessionMessages,\n modelManager,\n applyResolvedThinkingLevel,\n getConfig,\n sessionConfigStore,\n attachmentRootsForSession,\n agentOrchestrator,\n commandHandler,\n prepareInboundAttachments,\n buildMessageContent,\n persistAgentSessionMessages,\n maybeEmitWebchatTts,\n endDirectRequestContext,\n } = deps;\n\n const sessionKey = input.sessionKey ?? 'cli:direct';\n const { channel, chatId } = parseSessionKey(sessionKey);\n const context = initDirectStreamingSession(sessionKey, channel, chatId);\n\n const eventQueue: ProcessDirectStreamingSseEvent[] = [];\n let resolveWaiting: (() => void) | null = null;\n let agentDone = false;\n\n let lastSentContent = '';\n let lastSentThinking = '';\n let reasoningLevel: ReasoningLevel = 'off';\n\n const enqueueSseEvent = (event: ProcessDirectStreamingSseEvent) => {\n eventQueue.push(event);\n if (resolveWaiting) {\n resolveWaiting();\n resolveWaiting = null;\n }\n };\n\n const pushEvent = (event: ProcessDirectStreamingSseEvent) => {\n const visible = applyReasoningVisibilityToSseEvent(event, reasoningLevel);\n if (visible !== null) {\n enqueueSseEvent(visible);\n }\n };\n\n if (channel === 'webchat') {\n registerWebchatSsePublisher(sessionKey, (e) => pushEvent(e));\n }\n\n const signal = input.signal;\n let userAborted = false;\n let abortHandled = false;\n\n await hydrateSessionWorkspaceFromStore(sessionKey);\n const agent = agentManager.getOrCreateAgent(sessionKey);\n await hydrateSessionModelFromStore(sessionKey);\n const unsubscribeStreaming = agent.subscribe((event: AgentEvent) => {\n agentEventHandler.handle(event, context);\n\n switch (event.type) {\n case 'tool_execution_start': {\n const toolEvent = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n const toolName =\n typeof toolEvent.toolName === 'string' && toolEvent.toolName.trim()\n ? toolEvent.toolName.trim()\n : 'unknown';\n pushEvent({\n type: 'tool_start',\n toolName,\n args: toolEvent.args,\n });\n break;\n }\n case 'tool_execution_end': {\n const toolEvent = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const toolName =\n typeof toolEvent.toolName === 'string' && toolEvent.toolName.trim()\n ? toolEvent.toolName.trim()\n : 'unknown';\n pushEvent({\n type: 'tool_end',\n toolName,\n isError: toolEvent.isError,\n result: serializeAgentToolResultForSse(toolEvent.result),\n });\n break;\n }\n case 'message_update': {\n const msgEvent = event as Extract<AgentEvent, { type: 'message_update' }>;\n if (msgEvent.message?.role === 'assistant') {\n const msgContent = msgEvent.message.content;\n const blocks = Array.isArray(msgContent)\n ? (msgContent as Array<{ type: string; text?: string }>)\n : undefined;\n const fullText = blocks ? extractTextContent(blocks) : String(msgContent);\n const thinkingFromBlocks = blocks ? extractThinkingContent(blocks) : '';\n const thinkingFromReasoning = extractThinkingFromAssistantMessage(msgEvent.message);\n const thinkingText =\n thinkingFromReasoning.length >= thinkingFromBlocks.length\n ? thinkingFromReasoning\n : thinkingFromBlocks;\n\n if (fullText.length > lastSentContent.length) {\n const delta = fullText.slice(lastSentContent.length);\n if (delta) {\n pushEvent({ type: 'token', content: delta });\n lastSentContent = fullText;\n }\n } else if (fullText.length < lastSentContent.length) {\n pushEvent({ type: 'token', content: fullText });\n lastSentContent = fullText;\n }\n\n if (thinkingText.length > lastSentThinking.length) {\n const thDelta = thinkingText.slice(lastSentThinking.length);\n if (thDelta) {\n pushEvent({ type: 'thinking', content: thDelta, delta: true });\n lastSentThinking = thinkingText;\n }\n } else if (thinkingText.length < lastSentThinking.length) {\n pushEvent({ type: 'thinking', content: thinkingText, delta: false });\n lastSentThinking = thinkingText;\n }\n }\n break;\n }\n case 'message_start': {\n const msgEvent = event as Extract<AgentEvent, { type: 'message_start' }>;\n if (msgEvent.message?.role === 'assistant') {\n lastSentContent = '';\n lastSentThinking = '';\n pushEvent({ type: 'thinking', status: 'started' });\n }\n break;\n }\n case 'message_end': {\n pushEvent({ type: 'message_end' });\n break;\n }\n case 'agent_start': {\n pushEvent({ type: 'progress', stage: 'thinking', message: 'Thinking...' });\n break;\n }\n case 'agent_end': {\n pushEvent({ type: 'progress', stage: 'idle', message: 'Done' });\n break;\n }\n default:\n break;\n }\n });\n\n try {\n const prepared = await prepareInboundAttachments(sessionKey, input.attachments);\n let loaded = await sessionStore.load(sessionKey);\n const lastMsg = loaded[loaded.length - 1] as { role?: string; webchatEarlySave?: boolean } | undefined;\n if (lastMsg?.role === 'user' && lastMsg.webchatEarlySave === true) {\n loaded = loaded.slice(0, -1);\n }\n agent.state.messages = prepareLoadedSessionMessages(sessionKey, loaded);\n\n await modelManager.applyModelForSession(agent, sessionKey);\n await applyResolvedThinkingLevel(sessionKey, input.thinking);\n {\n const defReason = (getConfig()?.agents?.defaults?.reasoningDefault ?? 'off') as ReasoningLevel;\n reasoningLevel = await resolveEffectiveReasoningLevel(sessionConfigStore, sessionKey, defReason);\n }\n\n const sttCfg = mergeSttConfigFromAppConfig(getConfig()?.stt);\n const { text: mergedUserText, inboundVoice } = await mergeVoiceTranscriptsIntoUserText(\n attachmentRootsForSession(sessionKey),\n prepared,\n input.content,\n sttCfg,\n );\n\n const armAbort = () => {\n if (abortHandled) {\n return;\n }\n abortHandled = true;\n userAborted = true;\n agentOrchestrator.abort(sessionKey);\n agentDone = true;\n pushEvent({ type: '__done__' });\n };\n if (signal) {\n if (signal.aborted) {\n armAbort();\n } else {\n signal.addEventListener('abort', armAbort, { once: true });\n }\n }\n\n const commandInfo = parseSlashCommand(mergedUserText);\n let ranSlashCommand = false;\n if (!abortHandled && commandInfo && commandRegistry.has(commandInfo.command)) {\n ranSlashCommand = true;\n const { aggregatedText } = await commandHandler.executeCommandAndAggregateReply(\n commandInfo.command,\n commandInfo.args,\n {\n sessionKey,\n channel,\n chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n },\n );\n if (aggregatedText) {\n pushEvent({ type: 'token', content: aggregatedText });\n }\n pushEvent({ type: '__done__' });\n agentDone = true;\n }\n\n let messageContent:\n | Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }>\n | undefined;\n if (!abortHandled && !ranSlashCommand) {\n const textForAgent = mergedUserText.trimStart().startsWith('/skill:')\n ? agentManager.expandSkillUserText(mergedUserText)\n : mergedUserText;\n messageContent = await buildMessageContent(textForAgent, prepared, sessionKey);\n }\n\n if (!abortHandled && !ranSlashCommand && messageContent !== undefined) {\n const agentPromise = (async () => {\n const userMessage = {\n role: 'user' as const,\n content: messageContent,\n timestamp: Date.now(),\n };\n const userPlain = extractAgentUserPlainText(userMessage);\n const userMessageForModel = await agentManager.applyMemoryPrefetchToUserMessage(\n userMessage,\n sessionKey,\n );\n await runAgentTurnWithModelFallbacks({\n agent,\n sessionKey,\n modelManager,\n userMessage: userMessageForModel,\n log,\n getConfig,\n beforeUserPrompt: () => agentManager.beginBackgroundReviewUserTurn(sessionKey),\n });\n agentManager.afterAgentTurn(sessionKey, userPlain);\n agentManager.scheduleBackgroundReviewAfterUserTurn(sessionKey);\n })();\n\n agentPromise\n .then(() => {\n if (abortHandled) {\n return;\n }\n agentDone = true;\n pushEvent({ type: '__done__' });\n })\n .catch((err) => {\n if (abortHandled) {\n return;\n }\n agentDone = true;\n pushEvent({ type: 'error', content: err instanceof Error ? err.message : String(err) });\n pushEvent({ type: '__done__' });\n });\n }\n\n while (true) {\n if (eventQueue.length > 0) {\n const event = eventQueue.shift()!;\n if (event.type === '__done__') break;\n yield event;\n } else if (agentDone) {\n break;\n } else {\n await new Promise<void>((resolve) => {\n resolveWaiting = resolve;\n });\n }\n }\n\n while (eventQueue.length > 0) {\n const event = eventQueue.shift()!;\n if (event.type === '__done__') continue;\n yield event;\n }\n\n await persistAgentSessionMessages(sessionKey);\n\n if (!userAborted) {\n const ttsAudioEvent = await maybeEmitWebchatTts(sessionKey, inboundVoice);\n if (ttsAudioEvent) {\n yield ttsAudioEvent;\n }\n }\n } finally {\n if (channel === 'webchat') {\n unregisterWebchatSsePublisher(sessionKey);\n }\n unsubscribeStreaming();\n endDirectRequestContext();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"process-direct-streaming.js","names":["msgEvent"],"sources":["../../../../src/agent/service/process-direct-streaming.ts"],"sourcesContent":["import type { AgentEvent, AgentMessage } from '@mariozechner/pi-agent-core';\n\nimport type { Config } from '../../config/schema.js';\nimport type { InternalAttachmentRoots } from '../../channels/attachments/inbound-persist.js';\nimport { commandRegistry } from '../../chat-commands/index.js';\nimport { parseSlashCommand } from '../../chat-commands/command-parse.js';\nimport {\n mergeVoiceTranscriptsIntoUserText,\n mergeSttConfigFromAppConfig,\n} from '../../channels/attachments/voice-stt-webchat.js';\nimport {\n resolveEffectiveReasoningLevel,\n type SessionConfigStore,\n type SessionStore,\n} from '../../session/index.js';\nimport type { SessionContext } from '../session/index.js';\nimport {\n extractTextContent,\n extractThinkingContent,\n extractThinkingFromAssistantMessage,\n} from '../context/workspace.js';\nimport { extractAgentUserPlainText } from '../memory/user-message-text.js';\nimport { runAgentTurnWithModelFallbacks } from '../orchestration/run-agent-turn-with-fallbacks.js';\nimport { applyReasoningVisibilityToSseEvent } from '../streaming/reasoning-visibility-sse.js';\nimport type { ReasoningLevel } from '../transcript/thinking-types.js';\nimport { serializeAgentToolResultForSse } from '../service-inbound-utils.js';\nimport type { AgentManager } from '../agent-manager.js';\nimport type { AgentEventHandler } from '../orchestration/agent-event-handler.js';\nimport type { AgentOrchestrator } from '../orchestration/agent-orchestrator.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { ModelManager } from '../models/index.js';\n\nexport type DirectStreamInboundAttachment = {\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n};\n\n/** Logger shape expected by {@link runAgentTurnWithModelFallbacks}. */\nexport type ProcessDirectStreamLog = {\n info: (obj: Record<string, unknown>, msg: string) => void;\n warn: (obj: Record<string, unknown>, msg: string) => void;\n debug?: (obj: Record<string, unknown>, msg: string) => void;\n};\n\n/**\n * Explicit collaborators for {@link runProcessDirectStreaming} (gateway webchat / CLI direct stream).\n * Bound by `AgentService` from its private fields and methods.\n */\nexport interface ProcessDirectStreamingDeps {\n log: ProcessDirectStreamLog;\n parseSessionKey: (sessionKey: string) => { channel: string; chatId: string };\n initDirectStreamingSession: (\n sessionKey: string,\n channel: string,\n chatId: string,\n ) => SessionContext;\n registerWebchatSsePublisher: (\n sessionKey: string,\n publisher: (event: { type: string; [key: string]: unknown }) => void,\n ) => void;\n unregisterWebchatSsePublisher: (sessionKey: string) => void;\n agentManager: AgentManager;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n agentEventHandler: AgentEventHandler;\n sessionStore: SessionStore;\n prepareLoadedSessionMessages: (sessionKey: string, messages: AgentMessage[]) => AgentMessage[];\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n getConfig: () => Config | undefined;\n sessionConfigStore: SessionConfigStore;\n attachmentRootsForSession: (sessionKey: string) => InternalAttachmentRoots;\n agentOrchestrator: Pick<AgentOrchestrator, 'abort'>;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: DirectStreamInboundAttachment[],\n ) => Promise<DirectStreamInboundAttachment[] | undefined>;\n buildMessageContent: (\n content: string,\n attachments: DirectStreamInboundAttachment[] | undefined,\n sessionKey: string,\n ) => Promise<Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }>>;\n persistAgentSessionMessages: (sessionKey: string) => Promise<void>;\n maybeEmitWebchatTts: (\n sessionKey: string,\n hadInboundVoice: boolean,\n ) => Promise<{ type: 'tts_audio'; workspaceRelativePath: string; mimeType: string; name: string } | null>;\n endDirectRequestContext: () => void;\n}\n\nexport interface ProcessDirectStreamingInput {\n content: string;\n sessionKey?: string;\n attachments?: DirectStreamInboundAttachment[];\n thinking?: string;\n signal?: AbortSignal;\n}\n\nexport type ProcessDirectStreamingSseEvent = { type: string; [key: string]: unknown };\n\nexport async function* runProcessDirectStreaming(\n deps: ProcessDirectStreamingDeps,\n input: ProcessDirectStreamingInput,\n): AsyncGenerator<ProcessDirectStreamingSseEvent, void, unknown> {\n const {\n log,\n parseSessionKey,\n initDirectStreamingSession,\n registerWebchatSsePublisher,\n unregisterWebchatSsePublisher,\n agentManager,\n hydrateSessionWorkspaceFromStore,\n hydrateSessionModelFromStore,\n agentEventHandler,\n sessionStore,\n prepareLoadedSessionMessages,\n modelManager,\n applyResolvedThinkingLevel,\n getConfig,\n sessionConfigStore,\n attachmentRootsForSession,\n agentOrchestrator,\n commandHandler,\n prepareInboundAttachments,\n buildMessageContent,\n persistAgentSessionMessages,\n maybeEmitWebchatTts,\n endDirectRequestContext,\n } = deps;\n\n const sessionKey = input.sessionKey ?? 'cli:direct';\n const { channel, chatId } = parseSessionKey(sessionKey);\n const context = initDirectStreamingSession(sessionKey, channel, chatId);\n\n const eventQueue: ProcessDirectStreamingSseEvent[] = [];\n let resolveWaiting: (() => void) | null = null;\n let agentDone = false;\n\n let lastSentContent = '';\n let lastSentThinking = '';\n let reasoningLevel: ReasoningLevel = 'stream';\n\n const enqueueSseEvent = (event: ProcessDirectStreamingSseEvent) => {\n eventQueue.push(event);\n if (resolveWaiting) {\n resolveWaiting();\n resolveWaiting = null;\n }\n };\n\n const pushEvent = (event: ProcessDirectStreamingSseEvent) => {\n const visible = applyReasoningVisibilityToSseEvent(event, reasoningLevel);\n if (visible !== null) {\n enqueueSseEvent(visible);\n }\n };\n\n if (channel === 'webchat') {\n registerWebchatSsePublisher(sessionKey, (e) => pushEvent(e));\n }\n\n const signal = input.signal;\n let userAborted = false;\n let abortHandled = false;\n\n await hydrateSessionWorkspaceFromStore(sessionKey);\n const agent = agentManager.getOrCreateAgent(sessionKey);\n await hydrateSessionModelFromStore(sessionKey);\n const unsubscribeStreaming = agent.subscribe((event: AgentEvent) => {\n agentEventHandler.handle(event, context);\n\n switch (event.type) {\n case 'tool_execution_start': {\n const toolEvent = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n const toolName =\n typeof toolEvent.toolName === 'string' && toolEvent.toolName.trim()\n ? toolEvent.toolName.trim()\n : 'unknown';\n pushEvent({\n type: 'tool_start',\n toolCallId: toolEvent.toolCallId,\n toolName,\n args: toolEvent.args,\n });\n break;\n }\n case 'tool_execution_end': {\n const toolEvent = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const toolName =\n typeof toolEvent.toolName === 'string' && toolEvent.toolName.trim()\n ? toolEvent.toolName.trim()\n : 'unknown';\n pushEvent({\n type: 'tool_end',\n toolCallId: toolEvent.toolCallId,\n toolName,\n isError: toolEvent.isError,\n result: serializeAgentToolResultForSse(toolEvent.result),\n });\n break;\n }\n case 'message_update': {\n const msgEvent = event as Extract<AgentEvent, { type: 'message_update' }>;\n if (msgEvent.message?.role === 'assistant') {\n const msgContent = msgEvent.message.content;\n const blocks = Array.isArray(msgContent)\n ? (msgContent as Array<{ type: string; text?: string }>)\n : undefined;\n const fullText = blocks ? extractTextContent(blocks) : String(msgContent);\n const thinkingFromBlocks = blocks ? extractThinkingContent(blocks) : '';\n const thinkingFromReasoning = extractThinkingFromAssistantMessage(msgEvent.message);\n const thinkingText =\n thinkingFromReasoning.length >= thinkingFromBlocks.length\n ? thinkingFromReasoning\n : thinkingFromBlocks;\n\n if (fullText.length > lastSentContent.length) {\n const delta = fullText.slice(lastSentContent.length);\n if (delta) {\n pushEvent({ type: 'token', content: delta });\n lastSentContent = fullText;\n }\n } else if (fullText.length < lastSentContent.length) {\n pushEvent({ type: 'token', content: fullText });\n lastSentContent = fullText;\n }\n\n if (thinkingText.length > lastSentThinking.length) {\n const thDelta = thinkingText.slice(lastSentThinking.length);\n if (thDelta) {\n pushEvent({ type: 'thinking', content: thDelta, delta: true });\n lastSentThinking = thinkingText;\n }\n } else if (thinkingText.length < lastSentThinking.length) {\n pushEvent({ type: 'thinking', content: thinkingText, delta: false });\n lastSentThinking = thinkingText;\n }\n }\n break;\n }\n case 'message_start': {\n const msgEvent = event as Extract<AgentEvent, { type: 'message_start' }>;\n if (msgEvent.message?.role === 'assistant') {\n lastSentContent = '';\n lastSentThinking = '';\n pushEvent({ type: 'thinking', status: 'started' });\n }\n break;\n }\n case 'message_end': {\n pushEvent({ type: 'message_end' });\n break;\n }\n case 'agent_start': {\n pushEvent({ type: 'progress', stage: 'thinking', message: 'Thinking...' });\n break;\n }\n case 'agent_end': {\n pushEvent({ type: 'progress', stage: 'idle', message: 'Done' });\n break;\n }\n default:\n break;\n }\n });\n\n try {\n const prepared = await prepareInboundAttachments(sessionKey, input.attachments);\n let loaded = await sessionStore.load(sessionKey);\n const lastMsg = loaded[loaded.length - 1] as { role?: string; webchatEarlySave?: boolean } | undefined;\n if (lastMsg?.role === 'user' && lastMsg.webchatEarlySave === true) {\n loaded = loaded.slice(0, -1);\n }\n agent.state.messages = prepareLoadedSessionMessages(sessionKey, loaded);\n\n await modelManager.applyModelForSession(agent, sessionKey);\n await applyResolvedThinkingLevel(sessionKey, input.thinking);\n {\n const defReason = (getConfig()?.agents?.defaults?.reasoningDefault ?? 'stream') as ReasoningLevel;\n reasoningLevel = await resolveEffectiveReasoningLevel(sessionConfigStore, sessionKey, defReason);\n }\n\n const sttCfg = mergeSttConfigFromAppConfig(getConfig()?.stt);\n const { text: mergedUserText, inboundVoice } = await mergeVoiceTranscriptsIntoUserText(\n attachmentRootsForSession(sessionKey),\n prepared,\n input.content,\n sttCfg,\n );\n\n const armAbort = () => {\n if (abortHandled) {\n return;\n }\n abortHandled = true;\n userAborted = true;\n agentOrchestrator.abort(sessionKey);\n agentDone = true;\n pushEvent({ type: '__done__' });\n };\n if (signal) {\n if (signal.aborted) {\n armAbort();\n } else {\n signal.addEventListener('abort', armAbort, { once: true });\n }\n }\n\n const commandInfo = parseSlashCommand(mergedUserText);\n let ranSlashCommand = false;\n /** Plain text to persist for webchat slash turns (SSE can be dropped by the UI; disk is source of truth). */\n let webchatSlashReceipt: string | undefined;\n if (!abortHandled && commandInfo) {\n if (commandRegistry.has(commandInfo.command)) {\n ranSlashCommand = true;\n try {\n const { aggregatedText } = await commandHandler.executeCommandAndAggregateReply(\n commandInfo.command,\n commandInfo.args,\n {\n sessionKey,\n channel,\n chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n },\n );\n if (aggregatedText?.trim()) {\n webchatSlashReceipt = aggregatedText.trim();\n pushEvent({ type: 'token', content: webchatSlashReceipt });\n } else if (channel === 'webchat') {\n webchatSlashReceipt =\n 'Command finished with no assistant text. For `/goal`, that usually means the goal was saved; the assistant will not run until your next normal message (or an auto-continuation after an assistant reply).';\n pushEvent({ type: 'token', content: webchatSlashReceipt });\n }\n } catch (cmdErr) {\n const em = cmdErr instanceof Error ? cmdErr.message : String(cmdErr);\n log.warn({ err: cmdErr, sessionKey, command: commandInfo.command }, `Slash command failed: ${em}`);\n webchatSlashReceipt = `Command error: ${em}`;\n pushEvent({ type: 'token', content: webchatSlashReceipt });\n }\n pushEvent({ type: '__done__' });\n agentDone = true;\n } else if (commandInfo.command === 'goal') {\n // `/goal` is provided by the `standing-goal` bundled extension; avoid silent LLM fallback.\n ranSlashCommand = true;\n webchatSlashReceipt =\n 'The /goal command is not available: the `standing-goal` extension is not loaded.\\n\\n' +\n 'Fix: add `\"standing-goal\"` to `extensions.enabled` in your xopc config (or remove it from `extensions.disabled`), then restart the gateway. ' +\n 'If you use a strict allowlist, include `standing-goal` in `extensions.enabled`.';\n pushEvent({\n type: 'token',\n content: webchatSlashReceipt,\n });\n pushEvent({ type: '__done__' });\n agentDone = true;\n }\n }\n\n let messageContent:\n | Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }>\n | undefined;\n if (!abortHandled && !ranSlashCommand) {\n const textForAgent = mergedUserText.trimStart().startsWith('/skill:')\n ? agentManager.expandSkillUserText(mergedUserText)\n : mergedUserText;\n messageContent = await buildMessageContent(textForAgent, prepared, sessionKey);\n }\n\n if (!abortHandled && !ranSlashCommand && messageContent !== undefined) {\n const agentPromise = (async () => {\n const userMessage = {\n role: 'user' as const,\n content: messageContent,\n timestamp: Date.now(),\n };\n const userPlain = extractAgentUserPlainText(userMessage);\n const userMessageForModel = await agentManager.applyMemoryPrefetchToUserMessage(\n userMessage,\n sessionKey,\n );\n await runAgentTurnWithModelFallbacks({\n agent,\n sessionKey,\n modelManager,\n userMessage: userMessageForModel,\n log,\n getConfig,\n beforeUserPrompt: () => agentManager.beginBackgroundReviewUserTurn(sessionKey),\n });\n agentManager.afterAgentTurn(sessionKey, userPlain);\n agentManager.scheduleBackgroundReviewAfterUserTurn(sessionKey);\n })();\n\n agentPromise\n .then(() => {\n if (abortHandled) {\n return;\n }\n agentDone = true;\n pushEvent({ type: '__done__' });\n })\n .catch((err) => {\n if (abortHandled) {\n return;\n }\n agentDone = true;\n pushEvent({ type: 'error', content: err instanceof Error ? err.message : String(err) });\n pushEvent({ type: '__done__' });\n });\n }\n\n while (true) {\n if (eventQueue.length > 0) {\n const event = eventQueue.shift()!;\n if (event.type === '__done__') break;\n yield event;\n } else if (agentDone) {\n break;\n } else {\n await new Promise<void>((resolve) => {\n resolveWaiting = resolve;\n });\n }\n }\n\n while (eventQueue.length > 0) {\n const event = eventQueue.shift()!;\n if (event.type === '__done__') continue;\n yield event;\n }\n\n // Persist slash command text to the session file so the gateway console can show a receipt even when\n // the browser drops SSE tokens (e.g. session key mismatch during hydration).\n if (channel === 'webchat' && ranSlashCommand && webchatSlashReceipt?.trim()) {\n try {\n const loaded = await sessionStore.load(sessionKey);\n const text = webchatSlashReceipt.trim();\n // Minimal assistant row for session JSON; full AssistantMessage metadata is LLM-turn specific.\n const assistantMsg = {\n role: 'assistant' as const,\n content: [{ type: 'text' as const, text }],\n timestamp: Date.now(),\n } as AgentMessage;\n await sessionStore.save(sessionKey, [...loaded, assistantMsg]);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Failed to persist webchat slash command receipt');\n }\n }\n\n // Slash-only turns never hydrate the early-saved user row into pi-agent state; persisting here would\n // write an empty/stale transcript over the gateway `SessionManager` copy (and break e.g. `/goal`).\n if (!ranSlashCommand) {\n await persistAgentSessionMessages(sessionKey);\n }\n\n if (!userAborted) {\n const ttsAudioEvent = await maybeEmitWebchatTts(sessionKey, inboundVoice);\n if (ttsAudioEvent) {\n yield ttsAudioEvent;\n }\n }\n } finally {\n if (channel === 'webchat') {\n unregisterWebchatSsePublisher(sessionKey);\n }\n unsubscribeStreaming();\n endDirectRequestContext();\n }\n}\n"],"mappings":"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"}
@@ -2,6 +2,7 @@ import type { AgentMessage, ThinkingLevel } from '@mariozechner/pi-agent-core';
2
2
  import { type MessageBus } from '../infra/bus/index.js';
3
3
  import { type Config } from '../config/schema.js';
4
4
  import type { ChannelManager } from '../channels/manager.js';
5
+ import type { SessionDetail } from '../session/types.js';
5
6
  import { type ReasoningLevel } from './transcript/thinking-types.js';
6
7
  import { type SkillCatalogEntry } from './agent-manager.js';
7
8
  import type { AgentServiceConfig, StreamHandle } from './service.types.js';
@@ -74,6 +75,18 @@ export declare class AgentService {
74
75
  private hydrateSessionModelFromStore;
75
76
  setStreamHandle(handle: StreamHandle): void;
76
77
  clearStreamHandle(): void;
78
+ /** Last assistant visible plain text for a session (e.g. after a webchat stream). */
79
+ getLastAssistantPlainText(sessionKey: string): string;
80
+ /**
81
+ * Notify extensions after a webchat direct stream ends (Gateway calls after session run lock is released).
82
+ */
83
+ emitWebchatTurnComplete(payload: {
84
+ sessionKey: string;
85
+ inboundUserText: string;
86
+ assistantPlainText: string;
87
+ aborted: boolean;
88
+ streamError?: string;
89
+ }): Promise<void>;
77
90
  start(): Promise<void>;
78
91
  stop(): Promise<void>;
79
92
  /**
@@ -116,6 +129,8 @@ export declare class AgentService {
116
129
  workspaceRelativePath?: string;
117
130
  }> | undefined>;
118
131
  private endDirectRequestContext;
132
+ /** Full session snapshot (metadata + API-shaped messages), e.g. embedded TUI history. */
133
+ loadSessionDetail(sessionKey: string): Promise<SessionDetail | null>;
119
134
  compactSession(sessionKey: string, options?: {
120
135
  instructions?: string;
121
136
  force?: boolean;
@@ -400,6 +400,22 @@ var AgentService = class {
400
400
  this.streamManager.clearHandle();
401
401
  this.feedbackCoordinator.endTask();
402
402
  }
403
+ /** Last assistant visible plain text for a session (e.g. after a webchat stream). */
404
+ getLastAssistantPlainText(sessionKey) {
405
+ return this.agentManager.getLastAssistantContent(sessionKey) ?? "";
406
+ }
407
+ /**
408
+ * Notify extensions after a webchat direct stream ends (Gateway calls after session run lock is released).
409
+ */
410
+ async emitWebchatTurnComplete(payload) {
411
+ await this.hookHandler.triggerWithSessionKey(payload.sessionKey, "webchat_turn_complete", {
412
+ sessionKey: payload.sessionKey,
413
+ inboundUserText: payload.inboundUserText,
414
+ assistantPlainText: payload.assistantPlainText,
415
+ aborted: payload.aborted,
416
+ ...payload.streamError !== void 0 ? { streamError: payload.streamError } : {}
417
+ });
418
+ }
403
419
  async start() {
404
420
  this.running = true;
405
421
  await this.sessionConfigStore.initialize();
@@ -685,6 +701,10 @@ var AgentService = class {
685
701
  this.feedbackCoordinator.clearContext();
686
702
  this.contextMiddleware.onResponse();
687
703
  }
704
+ /** Full session snapshot (metadata + API-shaped messages), e.g. embedded TUI history. */
705
+ async loadSessionDetail(sessionKey) {
706
+ return this.sessionStore.get(sessionKey);
707
+ }
688
708
  async compactSession(sessionKey, options) {
689
709
  const messages = await this.sessionStore.load(sessionKey);
690
710
  const contextWindow = this.getContextWindow();
@@ -856,7 +876,7 @@ var AgentService = class {
856
876
  if (profileModelRef) this.modelManager.setSessionProfileDefault(sessionKey, profileModelRef);
857
877
  const defThink = cfg.agents?.defaults?.thinkingDefault ?? "medium";
858
878
  const level = await resolveEffectiveThinkingLevel(this.sessionConfigStore, sessionKey, null, defThink);
859
- const defReason = cfg.agents?.defaults?.reasoningDefault ?? "off";
879
+ const defReason = cfg.agents?.defaults?.reasoningDefault ?? "stream";
860
880
  const reasoningLevel = await resolveEffectiveReasoningLevel(this.sessionConfigStore, sessionKey, defReason);
861
881
  return {
862
882
  thinkingLevel: level,