pulseed 0.4.20 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/dist/adapters/types/mcp.d.ts +6 -6
  2. package/dist/base/config/global-config.d.ts +4 -4
  3. package/dist/base/llm/codex-llm-client.d.ts +8 -2
  4. package/dist/base/llm/codex-llm-client.d.ts.map +1 -1
  5. package/dist/base/llm/codex-llm-client.js +75 -30
  6. package/dist/base/llm/codex-llm-client.js.map +1 -1
  7. package/dist/base/llm/provider-config.d.ts +14 -0
  8. package/dist/base/llm/provider-config.d.ts.map +1 -1
  9. package/dist/base/llm/provider-config.js +56 -2
  10. package/dist/base/llm/provider-config.js.map +1 -1
  11. package/dist/base/llm/provider-factory.d.ts.map +1 -1
  12. package/dist/base/llm/provider-factory.js +3 -0
  13. package/dist/base/llm/provider-factory.js.map +1 -1
  14. package/dist/grounding/contracts.d.ts +160 -0
  15. package/dist/grounding/contracts.d.ts.map +1 -0
  16. package/dist/grounding/contracts.js +2 -0
  17. package/dist/grounding/contracts.js.map +1 -0
  18. package/dist/grounding/gateway.d.ts +12 -0
  19. package/dist/grounding/gateway.d.ts.map +1 -0
  20. package/dist/grounding/gateway.js +116 -0
  21. package/dist/grounding/gateway.js.map +1 -0
  22. package/dist/grounding/profiles.d.ts +4 -0
  23. package/dist/grounding/profiles.d.ts.map +1 -0
  24. package/dist/grounding/profiles.js +159 -0
  25. package/dist/grounding/profiles.js.map +1 -0
  26. package/dist/grounding/providers/agents-provider.d.ts +14 -0
  27. package/dist/grounding/providers/agents-provider.d.ts.map +1 -0
  28. package/dist/grounding/providers/agents-provider.js +129 -0
  29. package/dist/grounding/providers/agents-provider.js.map +1 -0
  30. package/dist/grounding/providers/goal-state-provider.d.ts +3 -0
  31. package/dist/grounding/providers/goal-state-provider.d.ts.map +1 -0
  32. package/dist/grounding/providers/goal-state-provider.js +34 -0
  33. package/dist/grounding/providers/goal-state-provider.js.map +1 -0
  34. package/dist/grounding/providers/helpers.d.ts +13 -0
  35. package/dist/grounding/providers/helpers.d.ts.map +1 -0
  36. package/dist/grounding/providers/helpers.js +93 -0
  37. package/dist/grounding/providers/helpers.js.map +1 -0
  38. package/dist/grounding/providers/knowledge-provider.d.ts +3 -0
  39. package/dist/grounding/providers/knowledge-provider.d.ts.map +1 -0
  40. package/dist/grounding/providers/knowledge-provider.js +49 -0
  41. package/dist/grounding/providers/knowledge-provider.js.map +1 -0
  42. package/dist/grounding/providers/lessons-provider.d.ts +3 -0
  43. package/dist/grounding/providers/lessons-provider.d.ts.map +1 -0
  44. package/dist/grounding/providers/lessons-provider.js +36 -0
  45. package/dist/grounding/providers/lessons-provider.js.map +1 -0
  46. package/dist/grounding/providers/plugins-provider.d.ts +3 -0
  47. package/dist/grounding/providers/plugins-provider.d.ts.map +1 -0
  48. package/dist/grounding/providers/plugins-provider.js +35 -0
  49. package/dist/grounding/providers/plugins-provider.js.map +1 -0
  50. package/dist/grounding/providers/progress-history-provider.d.ts +3 -0
  51. package/dist/grounding/providers/progress-history-provider.d.ts.map +1 -0
  52. package/dist/grounding/providers/progress-history-provider.js +30 -0
  53. package/dist/grounding/providers/progress-history-provider.js.map +1 -0
  54. package/dist/grounding/providers/provider-state-provider.d.ts +3 -0
  55. package/dist/grounding/providers/provider-state-provider.d.ts.map +1 -0
  56. package/dist/grounding/providers/provider-state-provider.js +32 -0
  57. package/dist/grounding/providers/provider-state-provider.js.map +1 -0
  58. package/dist/grounding/providers/session-history-provider.d.ts +3 -0
  59. package/dist/grounding/providers/session-history-provider.d.ts.map +1 -0
  60. package/dist/grounding/providers/session-history-provider.js +72 -0
  61. package/dist/grounding/providers/session-history-provider.js.map +1 -0
  62. package/dist/grounding/providers/soil-provider.d.ts +3 -0
  63. package/dist/grounding/providers/soil-provider.d.ts.map +1 -0
  64. package/dist/grounding/providers/soil-provider.js +70 -0
  65. package/dist/grounding/providers/soil-provider.js.map +1 -0
  66. package/dist/grounding/providers/static-policy-provider.d.ts +8 -0
  67. package/dist/grounding/providers/static-policy-provider.d.ts.map +1 -0
  68. package/dist/grounding/providers/static-policy-provider.js +81 -0
  69. package/dist/grounding/providers/static-policy-provider.js.map +1 -0
  70. package/dist/grounding/providers/task-state-provider.d.ts +3 -0
  71. package/dist/grounding/providers/task-state-provider.d.ts.map +1 -0
  72. package/dist/grounding/providers/task-state-provider.js +55 -0
  73. package/dist/grounding/providers/task-state-provider.js.map +1 -0
  74. package/dist/grounding/providers/trust-state-provider.d.ts +3 -0
  75. package/dist/grounding/providers/trust-state-provider.d.ts.map +1 -0
  76. package/dist/grounding/providers/trust-state-provider.js +25 -0
  77. package/dist/grounding/providers/trust-state-provider.js.map +1 -0
  78. package/dist/grounding/providers/workspace-facts-provider.d.ts +3 -0
  79. package/dist/grounding/providers/workspace-facts-provider.d.ts.map +1 -0
  80. package/dist/grounding/providers/workspace-facts-provider.js +22 -0
  81. package/dist/grounding/providers/workspace-facts-provider.js.map +1 -0
  82. package/dist/grounding/renderers/debug-renderer.d.ts +3 -0
  83. package/dist/grounding/renderers/debug-renderer.d.ts.map +1 -0
  84. package/dist/grounding/renderers/debug-renderer.js +11 -0
  85. package/dist/grounding/renderers/debug-renderer.js.map +1 -0
  86. package/dist/grounding/renderers/prompt-renderer.d.ts +10 -0
  87. package/dist/grounding/renderers/prompt-renderer.d.ts.map +1 -0
  88. package/dist/grounding/renderers/prompt-renderer.js +23 -0
  89. package/dist/grounding/renderers/prompt-renderer.js.map +1 -0
  90. package/dist/index.d.ts +4 -2
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +3 -2
  93. package/dist/index.js.map +1 -1
  94. package/dist/interface/chat/chat-event-state.d.ts +1 -0
  95. package/dist/interface/chat/chat-event-state.d.ts.map +1 -1
  96. package/dist/interface/chat/chat-event-state.js +12 -2
  97. package/dist/interface/chat/chat-event-state.js.map +1 -1
  98. package/dist/interface/chat/chat-runner.d.ts +18 -6
  99. package/dist/interface/chat/chat-runner.d.ts.map +1 -1
  100. package/dist/interface/chat/chat-runner.js +331 -148
  101. package/dist/interface/chat/chat-runner.js.map +1 -1
  102. package/dist/interface/chat/cross-platform-session-global.d.ts +5 -0
  103. package/dist/interface/chat/cross-platform-session-global.d.ts.map +1 -0
  104. package/dist/interface/chat/cross-platform-session-global.js +17 -0
  105. package/dist/interface/chat/cross-platform-session-global.js.map +1 -0
  106. package/dist/interface/chat/cross-platform-session.d.ts +22 -0
  107. package/dist/interface/chat/cross-platform-session.d.ts.map +1 -1
  108. package/dist/interface/chat/cross-platform-session.js +192 -61
  109. package/dist/interface/chat/cross-platform-session.js.map +1 -1
  110. package/dist/interface/chat/event-subscriber.d.ts +14 -0
  111. package/dist/interface/chat/event-subscriber.d.ts.map +1 -1
  112. package/dist/interface/chat/event-subscriber.js +138 -28
  113. package/dist/interface/chat/event-subscriber.js.map +1 -1
  114. package/dist/interface/chat/grounding.d.ts +22 -1
  115. package/dist/interface/chat/grounding.d.ts.map +1 -1
  116. package/dist/interface/chat/grounding.js +82 -127
  117. package/dist/interface/chat/grounding.js.map +1 -1
  118. package/dist/interface/chat/ingress-router.d.ts +110 -0
  119. package/dist/interface/chat/ingress-router.d.ts.map +1 -0
  120. package/dist/interface/chat/ingress-router.js +183 -0
  121. package/dist/interface/chat/ingress-router.js.map +1 -0
  122. package/dist/interface/cli/setup.d.ts.map +1 -1
  123. package/dist/interface/cli/setup.js +1 -0
  124. package/dist/interface/cli/setup.js.map +1 -1
  125. package/dist/interface/tui/app.d.ts +2 -2
  126. package/dist/interface/tui/app.d.ts.map +1 -1
  127. package/dist/interface/tui/chat/scroll.d.ts +1 -1
  128. package/dist/interface/tui/chat/scroll.d.ts.map +1 -1
  129. package/dist/interface/tui/chat/scroll.js +19 -4
  130. package/dist/interface/tui/chat/scroll.js.map +1 -1
  131. package/dist/interface/tui/chat/types.d.ts +2 -0
  132. package/dist/interface/tui/chat/types.d.ts.map +1 -1
  133. package/dist/interface/tui/chat/viewport.d.ts.map +1 -1
  134. package/dist/interface/tui/chat/viewport.js +1 -0
  135. package/dist/interface/tui/chat/viewport.js.map +1 -1
  136. package/dist/interface/tui/chat-surface.d.ts +20 -0
  137. package/dist/interface/tui/chat-surface.d.ts.map +1 -0
  138. package/dist/interface/tui/chat-surface.js +59 -0
  139. package/dist/interface/tui/chat-surface.js.map +1 -0
  140. package/dist/interface/tui/chat.d.ts +1 -1
  141. package/dist/interface/tui/chat.d.ts.map +1 -1
  142. package/dist/interface/tui/chat.js +9 -6
  143. package/dist/interface/tui/chat.js.map +1 -1
  144. package/dist/interface/tui/entry.d.ts.map +1 -1
  145. package/dist/interface/tui/entry.js +64 -8
  146. package/dist/interface/tui/entry.js.map +1 -1
  147. package/dist/interface/tui/flicker/MouseTracking.d.ts +1 -0
  148. package/dist/interface/tui/flicker/MouseTracking.d.ts.map +1 -1
  149. package/dist/interface/tui/flicker/MouseTracking.js +7 -0
  150. package/dist/interface/tui/flicker/MouseTracking.js.map +1 -1
  151. package/dist/interface/tui/flicker/index.d.ts +1 -1
  152. package/dist/interface/tui/flicker/index.d.ts.map +1 -1
  153. package/dist/interface/tui/flicker/index.js +1 -1
  154. package/dist/interface/tui/flicker/index.js.map +1 -1
  155. package/dist/interface/tui/fullscreen-chat.d.ts +28 -3
  156. package/dist/interface/tui/fullscreen-chat.d.ts.map +1 -1
  157. package/dist/interface/tui/fullscreen-chat.js +61 -45
  158. package/dist/interface/tui/fullscreen-chat.js.map +1 -1
  159. package/dist/interface/tui/markdown-renderer.d.ts +1 -1
  160. package/dist/interface/tui/markdown-renderer.d.ts.map +1 -1
  161. package/dist/interface/tui/markdown-renderer.js +137 -11
  162. package/dist/interface/tui/markdown-renderer.js.map +1 -1
  163. package/dist/interface/tui/test-entry.d.ts.map +1 -1
  164. package/dist/interface/tui/test-entry.js +3 -2
  165. package/dist/interface/tui/test-entry.js.map +1 -1
  166. package/dist/orchestrator/execution/agent-loop/agent-loop-context-assembler.d.ts +3 -1
  167. package/dist/orchestrator/execution/agent-loop/agent-loop-context-assembler.d.ts.map +1 -1
  168. package/dist/orchestrator/execution/agent-loop/agent-loop-context-assembler.js +76 -112
  169. package/dist/orchestrator/execution/agent-loop/agent-loop-context-assembler.js.map +1 -1
  170. package/dist/orchestrator/execution/agent-loop/agent-loop-default-profile.d.ts +11 -0
  171. package/dist/orchestrator/execution/agent-loop/agent-loop-default-profile.d.ts.map +1 -1
  172. package/dist/orchestrator/execution/agent-loop/agent-loop-default-profile.js +13 -0
  173. package/dist/orchestrator/execution/agent-loop/agent-loop-default-profile.js.map +1 -1
  174. package/dist/orchestrator/execution/agent-loop/agent-loop-events.d.ts +1 -0
  175. package/dist/orchestrator/execution/agent-loop/agent-loop-events.d.ts.map +1 -1
  176. package/dist/orchestrator/execution/agent-loop/agent-loop-events.js.map +1 -1
  177. package/dist/orchestrator/execution/agent-loop/agent-loop-prompts.d.ts.map +1 -1
  178. package/dist/orchestrator/execution/agent-loop/agent-loop-prompts.js +8 -0
  179. package/dist/orchestrator/execution/agent-loop/agent-loop-prompts.js.map +1 -1
  180. package/dist/orchestrator/execution/agent-loop/agent-loop-session-state.d.ts +1 -0
  181. package/dist/orchestrator/execution/agent-loop/agent-loop-session-state.d.ts.map +1 -1
  182. package/dist/orchestrator/execution/agent-loop/agent-loop-session-state.js +1 -0
  183. package/dist/orchestrator/execution/agent-loop/agent-loop-session-state.js.map +1 -1
  184. package/dist/orchestrator/execution/agent-loop/agent-loop-turn-context.d.ts +3 -1
  185. package/dist/orchestrator/execution/agent-loop/agent-loop-turn-context.d.ts.map +1 -1
  186. package/dist/orchestrator/execution/agent-loop/agent-loop-turn-context.js.map +1 -1
  187. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.d.ts +1 -1
  188. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.d.ts.map +1 -1
  189. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.js +60 -50
  190. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.js.map +1 -1
  191. package/dist/orchestrator/execution/agent-loop/chat-agent-loop-runner.d.ts +239 -10
  192. package/dist/orchestrator/execution/agent-loop/chat-agent-loop-runner.d.ts.map +1 -1
  193. package/dist/orchestrator/execution/agent-loop/chat-agent-loop-runner.js +298 -96
  194. package/dist/orchestrator/execution/agent-loop/chat-agent-loop-runner.js.map +1 -1
  195. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.d.ts.map +1 -1
  196. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.js +1 -0
  197. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.js.map +1 -1
  198. package/dist/orchestrator/execution/agent-loop/task-agent-loop-context.d.ts +2 -5
  199. package/dist/orchestrator/execution/agent-loop/task-agent-loop-context.d.ts.map +1 -1
  200. package/dist/orchestrator/execution/agent-loop/task-agent-loop-context.js +10 -12
  201. package/dist/orchestrator/execution/agent-loop/task-agent-loop-context.js.map +1 -1
  202. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.d.ts +2 -0
  203. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.d.ts.map +1 -1
  204. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.js +16 -9
  205. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.js.map +1 -1
  206. package/dist/orchestrator/execution/agent-loop/task-agent-loop-result.d.ts +6 -6
  207. package/dist/orchestrator/execution/agent-loop/task-agent-loop-runner.d.ts.map +1 -1
  208. package/dist/orchestrator/execution/agent-loop/task-agent-loop-runner.js +53 -36
  209. package/dist/orchestrator/execution/agent-loop/task-agent-loop-runner.js.map +1 -1
  210. package/dist/orchestrator/loop/core-loop/phase-policy.d.ts.map +1 -1
  211. package/dist/orchestrator/loop/core-loop/phase-policy.js +87 -34
  212. package/dist/orchestrator/loop/core-loop/phase-policy.js.map +1 -1
  213. package/dist/orchestrator/loop/core-loop.d.ts +1 -0
  214. package/dist/orchestrator/loop/core-loop.d.ts.map +1 -1
  215. package/dist/orchestrator/loop/core-loop.js +191 -176
  216. package/dist/orchestrator/loop/core-loop.js.map +1 -1
  217. package/dist/platform/dream/dream-types.d.ts +14 -14
  218. package/dist/platform/knowledge/index.d.ts +3 -0
  219. package/dist/platform/knowledge/index.d.ts.map +1 -0
  220. package/dist/platform/knowledge/index.js +3 -0
  221. package/dist/platform/knowledge/index.js.map +1 -0
  222. package/dist/platform/knowledge/knowledge-manager.d.ts +1 -4
  223. package/dist/platform/knowledge/knowledge-manager.d.ts.map +1 -1
  224. package/dist/platform/knowledge/knowledge-manager.js +1 -4
  225. package/dist/platform/knowledge/knowledge-manager.js.map +1 -1
  226. package/dist/platform/knowledge/public-api.d.ts +4 -0
  227. package/dist/platform/knowledge/public-api.d.ts.map +1 -0
  228. package/dist/platform/knowledge/public-api.js +4 -0
  229. package/dist/platform/knowledge/public-api.js.map +1 -0
  230. package/dist/platform/soil/contracts.d.ts +66 -66
  231. package/dist/runtime/daemon/runner-goal-cycle.d.ts.map +1 -1
  232. package/dist/runtime/daemon/runner-goal-cycle.js +36 -1
  233. package/dist/runtime/daemon/runner-goal-cycle.js.map +1 -1
  234. package/dist/runtime/plugin-loader.d.ts.map +1 -1
  235. package/dist/runtime/plugin-loader.js +3 -6
  236. package/dist/runtime/plugin-loader.js.map +1 -1
  237. package/dist/runtime/schedule/history.d.ts +3 -3
  238. package/dist/runtime/schedule/presets.d.ts +20 -20
  239. package/dist/runtime/schedule/source.d.ts +4 -4
  240. package/dist/runtime/store/runtime-operation-schemas.d.ts +2 -2
  241. package/dist/runtime/store/runtime-schemas.d.ts +2 -2
  242. package/dist/runtime/types/cron.d.ts +4 -4
  243. package/dist/runtime/types/schedule.d.ts +51 -51
  244. package/dist/runtime/types/trigger.d.ts +6 -6
  245. package/dist/tools/builtin/exports.d.ts +79 -0
  246. package/dist/tools/builtin/exports.d.ts.map +1 -0
  247. package/dist/tools/builtin/exports.js +78 -0
  248. package/dist/tools/builtin/exports.js.map +1 -0
  249. package/dist/tools/builtin/factory.d.ts +33 -0
  250. package/dist/tools/builtin/factory.d.ts.map +1 -0
  251. package/dist/tools/builtin/factory.js +182 -0
  252. package/dist/tools/builtin/factory.js.map +1 -0
  253. package/dist/tools/builtin/index.d.ts +3 -110
  254. package/dist/tools/builtin/index.d.ts.map +1 -1
  255. package/dist/tools/builtin/index.js +2 -262
  256. package/dist/tools/builtin/index.js.map +1 -1
  257. package/dist/tools/execution/SoilOpenTool/SoilOpenTool.d.ts +4 -4
  258. package/dist/tools/execution/SoilPublishTool/SoilPublishTool.d.ts +2 -2
  259. package/dist/tools/index.d.ts +5 -3
  260. package/dist/tools/index.d.ts.map +1 -1
  261. package/dist/tools/index.js +4 -2
  262. package/dist/tools/index.js.map +1 -1
  263. package/dist/tools/mutation/TaskCreateTool/TaskCreateTool.d.ts +4 -4
  264. package/dist/tools/network/GitHubCliTool/GitHubCliTool.d.ts +4 -4
  265. package/dist/tools/network/McpStdioTool/McpStdioTool.d.ts +8 -8
  266. package/dist/tools/schedule/CreateScheduleTool/CreateScheduleTool.d.ts +44 -44
  267. package/dist/tools/schedule/ListSchedulesTool/ListSchedulesTool.d.ts +4 -4
  268. package/dist/tools/schedule/UpdateScheduleTool/UpdateScheduleTool.d.ts +58 -58
  269. package/dist/tools/system/ProcessSessionTool/ProcessSessionTool.d.ts +6 -6
  270. package/dist/tools/types.d.ts +2 -2
  271. package/package.json +1 -1
@@ -5,25 +5,25 @@
5
5
  import { execFile } from "node:child_process";
6
6
  import * as fsp from "node:fs/promises";
7
7
  import * as path from "node:path";
8
+ import { getPulseedDirPath } from "../../base/utils/paths.js";
8
9
  import { loadProviderConfig } from "../../base/llm/provider-config.js";
10
+ import { TaskSchema } from "../../base/types/task.js";
9
11
  import { ChatHistory } from "./chat-history.js";
10
12
  import { ChatSessionCatalog, ChatSessionSelectorError, } from "./chat-session-store.js";
11
13
  import { buildChatContext, resolveGitRoot } from "../../platform/observation/context-provider.js";
12
- import { buildDynamicContextPrompt, buildStaticSystemPrompt } from "./grounding.js";
14
+ import { buildChatAgentLoopSystemPrompt, buildStaticSystemPrompt, createChatGroundingGateway } from "./grounding.js";
13
15
  import { verifyChatAction } from "./chat-verifier.js";
14
16
  import { toToolDefinitionsFiltered } from "../../tools/tool-definition-adapter.js";
15
17
  import { TendCommand } from "./tend-command.js";
16
18
  import { EventSubscriber } from "./event-subscriber.js";
17
19
  import { buildPromptedToolProtocolSystemPrompt, extractPromptedToolCalls, } from "../../orchestrator/execution/agent-loop/prompted-tool-protocol.js";
18
- import { recognizeRuntimeControlIntent } from "../../runtime/control/index.js";
19
- import { formatAgentLoopResolvedProfileSummary, resolveAgentLoopDefaultProfile, summarizeAgentLoopResolvedProfile, } from "../../orchestrator/execution/agent-loop/agent-loop-default-profile.js";
20
- import { summarizeExecutionPolicy, withExecutionPolicyOverrides, } from "../../orchestrator/execution/agent-loop/execution-policy.js";
21
- import { ChatStateService } from "./chat-state-service.js";
20
+ import { resolveExecutionPolicy, summarizeExecutionPolicy, withExecutionPolicyOverrides, } from "../../orchestrator/execution/agent-loop/execution-policy.js";
21
+ import { buildStandaloneIngressMessage, createIngressRouter, } from "./ingress-router.js";
22
22
  const DEFAULT_TIMEOUT_MS = 120_000;
23
23
  const MAX_VERIFY_RETRIES = 2;
24
24
  const MAX_TOOL_LOOPS = 5;
25
25
  const ACTIVITY_PREVIEW_CHARS = 40;
26
- const DIRECT_ANSWER_MAX_TOKENS = 256;
26
+ const standaloneIngressRouter = createIngressRouter();
27
27
  // ─── Command help text ───
28
28
  const COMMAND_HELP = `Available commands:
29
29
  Session
@@ -76,32 +76,10 @@ function formatToolActivity(action, toolName, detail) {
76
76
  const preview = detail ? previewActivityText(detail) : "";
77
77
  return preview ? `${action} tool: ${toolName} - ${preview}` : `${action} tool: ${toolName}`;
78
78
  }
79
- function shouldUseDirectAnswerRoute(input) {
80
- const normalized = input.trim();
81
- if (!normalized)
82
- return false;
83
- const lowered = normalized.toLowerCase();
84
- const questionSignals = [
85
- /[??]/,
86
- /\b(what|why|how|when|where|who|which|is|are|can|could|would|should|tell me|explain|describe|help me understand)\b/,
87
- /(教えて|説明して|教えてください|説明してください|どう思う|なんで|なぜ|どうして|いつ|どこ|だれ|誰|何|どれ|どっち)/,
88
- ];
89
- if (!questionSignals.some((pattern) => pattern.test(lowered))) {
90
- return false;
91
- }
92
- const workSignals = [
93
- /\b(fix|implement|change|changed|add|remove|delete|update|refactor|patch|debug|diagnose|investigate|review|write|create|build|run|execute|test|verify|confirm|check|inspect|search|open|read|edit|modify|commit|push|merge|release|deploy|start|stop|restart|resume|compare|convert|migrate|optimize|improve|configure|setup|set up)\b/,
94
- /(修正|実装|変更|追加|削除|更新|リファクタ|デバッグ|調査|確認|レビュー|書いて|作って|作成|実行|走らせ|テスト|検証|調べて|開いて|読んで|編集|コミット|プッシュ|マージ|デプロイ|再起動|再開|設定)/,
95
- /\b(git|repo|repository|branch|commit|diff|pull request|pr|issue|ticket|adapter|agentloop|tool|tools|code)\b|コード|src\//,
96
- /\b(latest|most recent|current|today|now|recent|news|web|internet|api|docs|github|release|version)\b|最新|最新版|今日|現在|最近|今|外部|ネット/,
97
- /\bwhat\s+(files?\s+)?changed\b|\bwhich\s+files?\s+(changed|were\s+(modified|edited))\b/,
98
- /(\.(ts|tsx|js|jsx|json|md|yml|yaml|toml|py|go|rs|sh|sql)\b|\/[^/\s]+\.[A-Za-z0-9]+$)/,
99
- ];
100
- return !workSignals.some((pattern) => pattern.test(lowered));
101
- }
102
79
  // ─── ChatRunner ───
103
80
  export class ChatRunner {
104
81
  deps;
82
+ groundingGateway;
105
83
  history = null;
106
84
  sessionCwd = null;
107
85
  /** True when startSession() has been called — enables session persistence across execute() calls. */
@@ -123,10 +101,12 @@ export class ChatRunner {
123
101
  nativeAgentLoopStatePath = null;
124
102
  runtimeControlContext = null;
125
103
  sessionExecutionPolicy = null;
126
- stateView;
127
104
  constructor(deps) {
128
105
  this.deps = deps;
129
- this.stateView = new ChatStateService(deps.stateManager);
106
+ this.groundingGateway = createChatGroundingGateway({
107
+ stateManager: deps.stateManager,
108
+ pluginLoader: deps.pluginLoader,
109
+ });
130
110
  }
131
111
  /**
132
112
  * Initialize a persistent session for interactive (multi-turn) mode.
@@ -161,6 +141,69 @@ export class ChatRunner {
161
141
  setRuntimeControlContext(context) {
162
142
  this.runtimeControlContext = context;
163
143
  }
144
+ async executeIngressMessage(ingress, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, selectedRoute) {
145
+ if (!selectedRoute) {
146
+ throw new Error("executeIngressMessage requires selectedRoute; use CrossPlatformChatSessionManager for ingress route selection.");
147
+ }
148
+ const runtimeControlContext = this.buildRuntimeControlContextFromIngress(ingress);
149
+ return this.execute(ingress.text, cwd, timeoutMs, { selectedRoute, runtimeControlContext });
150
+ }
151
+ resolveRouteFromIngress(ingress) {
152
+ return standaloneIngressRouter.selectRoute(ingress, this.getRouteCapabilities());
153
+ }
154
+ resolveRouteFromInput(input, runtimeControlContext) {
155
+ return this.resolveRouteFromIngress(this.buildStandaloneIngressMessage(input, runtimeControlContext));
156
+ }
157
+ getRouteCapabilities() {
158
+ return {
159
+ hasLightweightLlm: this.deps.llmClient !== undefined,
160
+ hasAgentLoop: this.deps.chatAgentLoopRunner !== undefined,
161
+ hasToolLoop: this.deps.llmClient !== undefined,
162
+ hasRuntimeControlService: this.deps.runtimeControlService !== undefined,
163
+ };
164
+ }
165
+ buildStandaloneIngressMessage(input, runtimeControlContext) {
166
+ const channel = runtimeControlContext?.replyTarget?.surface === "tui"
167
+ ? "tui"
168
+ : runtimeControlContext?.replyTarget?.surface === "cli"
169
+ ? "cli"
170
+ : runtimeControlContext?.replyTarget?.surface === "gateway"
171
+ ? "plugin_gateway"
172
+ : "cli";
173
+ const runtimeApprovalFn = runtimeControlContext?.approvalFn
174
+ ?? this.deps.runtimeControlApprovalFn
175
+ ?? this.deps.approvalFn;
176
+ return buildStandaloneIngressMessage({
177
+ text: input,
178
+ channel,
179
+ platform: runtimeControlContext?.replyTarget?.platform ?? this.deps.runtimeReplyTarget?.platform,
180
+ identity_key: runtimeControlContext?.replyTarget?.identity_key ?? this.deps.runtimeReplyTarget?.identity_key,
181
+ conversation_id: runtimeControlContext?.replyTarget?.conversation_id ?? this.deps.runtimeReplyTarget?.conversation_id,
182
+ user_id: runtimeControlContext?.replyTarget?.user_id ?? this.deps.runtimeReplyTarget?.user_id,
183
+ actor: runtimeControlContext?.actor ?? this.deps.runtimeControlActor,
184
+ replyTarget: runtimeControlContext?.replyTarget ?? this.deps.runtimeReplyTarget,
185
+ runtimeControl: {
186
+ allowed: true,
187
+ approvalMode: "interactive",
188
+ },
189
+ });
190
+ }
191
+ buildRuntimeControlContextFromIngress(ingress) {
192
+ if (!ingress.actor && !ingress.replyTarget)
193
+ return null;
194
+ const interactiveApproval = this.runtimeControlContext?.approvalFn
195
+ ?? this.deps.runtimeControlApprovalFn
196
+ ?? this.deps.approvalFn;
197
+ return {
198
+ actor: ingress.actor,
199
+ replyTarget: ingress.replyTarget,
200
+ approvalFn: ingress.runtimeControl.approvalMode === "preapproved"
201
+ ? async () => true
202
+ : ingress.runtimeControl.approvalMode === "interactive"
203
+ ? interactiveApproval
204
+ : undefined,
205
+ };
206
+ }
164
207
  loadedSessionToChatSession(session) {
165
208
  return {
166
209
  id: session.id,
@@ -177,7 +220,6 @@ export class ChatRunner {
177
220
  ...(session.agentLoopResumable ? { agentLoopResumable: true } : {}),
178
221
  ...(session.agentLoopUpdatedAt ? { agentLoopUpdatedAt: session.agentLoopUpdatedAt } : {}),
179
222
  ...(session.agentLoop ? { agentLoop: session.agentLoop } : {}),
180
- ...(session.usage ? { usage: session.usage } : {}),
181
223
  };
182
224
  }
183
225
  formatSessionsList(entries) {
@@ -202,13 +244,53 @@ export class ChatRunner {
202
244
  return `Session ${session.id}${title} (${session.cwd})\n${lines.join("\n")}`;
203
245
  }
204
246
  async loadGoals() {
205
- return this.stateView.loadGoals();
247
+ const goalIds = await this.deps.stateManager.listGoalIds();
248
+ const goals = await Promise.all(goalIds.map((id) => this.deps.stateManager.loadGoal(id)));
249
+ return goals.filter((goal) => goal !== null);
206
250
  }
207
251
  async listAllGoalIds() {
208
- return this.stateView.listAllGoalIds();
252
+ const activeIds = await this.deps.stateManager.listGoalIds();
253
+ const archivedIds = await this.deps.stateManager.listArchivedGoals();
254
+ const recoverableArchivedIds = await this.listRecoverableArchivedGoalIds();
255
+ return [...new Set([...activeIds, ...archivedIds, ...recoverableArchivedIds])];
256
+ }
257
+ resolveStatePath(baseDir, ...segments) {
258
+ const base = path.resolve(baseDir);
259
+ const resolved = path.resolve(base, ...segments);
260
+ if (!resolved.startsWith(base + path.sep))
261
+ return null;
262
+ return resolved;
263
+ }
264
+ async listRecoverableArchivedGoalIds() {
265
+ const stateManager = this.deps.stateManager;
266
+ if (typeof stateManager.getBaseDir !== "function")
267
+ return [];
268
+ const archiveDir = this.resolveStatePath(stateManager.getBaseDir(), "archive");
269
+ if (archiveDir === null)
270
+ return [];
271
+ let entries = [];
272
+ try {
273
+ entries = await fsp.readdir(archiveDir, { withFileTypes: true });
274
+ }
275
+ catch {
276
+ return [];
277
+ }
278
+ const goalIds = [];
279
+ for (const entry of entries) {
280
+ if (!entry.isDirectory() || entry.name === ".staging")
281
+ continue;
282
+ try {
283
+ await fsp.access(path.join(archiveDir, entry.name, "goal", "goal.json"));
284
+ goalIds.push(entry.name);
285
+ }
286
+ catch {
287
+ continue;
288
+ }
289
+ }
290
+ return goalIds;
209
291
  }
210
292
  activeGoals(goals) {
211
- return this.stateView.activeGoals(goals);
293
+ return goals.filter((goal) => goal.status === "active" || goal.status === "waiting" || goal.loop_status === "running");
212
294
  }
213
295
  formatGoalLine(goal) {
214
296
  const dimensions = goal.dimensions.length === 0
@@ -259,8 +341,44 @@ export class ChatRunner {
259
341
  elapsed_ms: Date.now() - start,
260
342
  };
261
343
  }
344
+ async readTasksFromDir(tasksDir) {
345
+ let entries = [];
346
+ try {
347
+ entries = await fsp.readdir(tasksDir);
348
+ }
349
+ catch {
350
+ return [];
351
+ }
352
+ const tasks = [];
353
+ for (const entry of entries) {
354
+ if (!entry.endsWith(".json") || entry === "task-history.json" || entry === "last-failure-context.json")
355
+ continue;
356
+ let raw;
357
+ try {
358
+ raw = JSON.parse(await fsp.readFile(path.join(tasksDir, entry), "utf-8"));
359
+ }
360
+ catch {
361
+ continue;
362
+ }
363
+ const parsed = TaskSchema.safeParse(raw);
364
+ if (parsed.success)
365
+ tasks.push(parsed.data);
366
+ }
367
+ return tasks.sort((a, b) => (a.created_at < b.created_at ? 1 : -1));
368
+ }
262
369
  async readTasksForGoal(goalId) {
263
- return this.stateView.readTasksForGoal(goalId);
370
+ const stateManager = this.deps.stateManager;
371
+ if (typeof stateManager.getBaseDir !== "function")
372
+ return [];
373
+ const baseDir = stateManager.getBaseDir();
374
+ const activeTasksDir = this.resolveStatePath(baseDir, "tasks", goalId);
375
+ const archiveTasksDir = this.resolveStatePath(baseDir, "archive", goalId, "tasks");
376
+ if (activeTasksDir === null || archiveTasksDir === null)
377
+ return [];
378
+ const activeTasks = await this.readTasksFromDir(activeTasksDir);
379
+ if (activeTasks.length > 0)
380
+ return activeTasks;
381
+ return this.readTasksFromDir(archiveTasksDir);
264
382
  }
265
383
  async resolveGoalForTasks(selector) {
266
384
  if (selector)
@@ -302,7 +420,28 @@ export class ChatRunner {
302
420
  return { taskId: parts[0], goalId: parts[1] };
303
421
  }
304
422
  async findTask(taskId, goalId) {
305
- return this.stateView.findTask(taskId, goalId);
423
+ const goalIds = goalId ? [goalId] : await this.listAllGoalIds();
424
+ const matches = [];
425
+ for (const candidateGoalId of goalIds) {
426
+ let raw = null;
427
+ try {
428
+ raw = await this.deps.stateManager.readRaw(`tasks/${candidateGoalId}/${taskId}.json`);
429
+ }
430
+ catch {
431
+ raw = null;
432
+ }
433
+ if (!raw) {
434
+ const tasks = await this.readTasksForGoal(candidateGoalId);
435
+ const matched = tasks.find((task) => task.id === taskId || task.id.startsWith(taskId));
436
+ if (matched)
437
+ matches.push({ goalId: candidateGoalId, task: matched });
438
+ continue;
439
+ }
440
+ const parsed = TaskSchema.safeParse(raw);
441
+ if (parsed.success)
442
+ matches.push({ goalId: candidateGoalId, task: parsed.data });
443
+ }
444
+ return { task: matches.length === 1 ? matches[0].task : undefined, matches };
306
445
  }
307
446
  formatTask(task) {
308
447
  const lines = [
@@ -347,11 +486,30 @@ export class ChatRunner {
347
486
  }
348
487
  return { success: true, output: this.formatTask(found.task), elapsed_ms: Date.now() - start };
349
488
  }
489
+ providerConfigBaseDir() {
490
+ const stateManager = this.deps.stateManager;
491
+ return typeof stateManager.getBaseDir === "function" ? stateManager.getBaseDir() : getPulseedDirPath();
492
+ }
350
493
  async readProviderConfigSummary() {
351
- return this.stateView.readProviderConfigSummary();
494
+ const config = await loadProviderConfig({
495
+ baseDir: this.providerConfigBaseDir(),
496
+ saveMigration: false,
497
+ });
498
+ return {
499
+ provider: config.provider,
500
+ model: config.model,
501
+ adapter: config.adapter,
502
+ light_model: config.light_model,
503
+ base_url: config.base_url,
504
+ codex_cli_path: config.codex_cli_path,
505
+ has_api_key: Boolean(config.api_key),
506
+ };
352
507
  }
353
508
  formatConfig(config) {
354
- return this.stateView.formatConfig(config);
509
+ return Object.entries(config)
510
+ .filter(([, value]) => value !== undefined)
511
+ .map(([key, value]) => `${key}: ${typeof value === "string" && /key|token|secret/i.test(key) ? "[masked]" : String(value)}`)
512
+ .join("\n");
355
513
  }
356
514
  async handleConfig(start) {
357
515
  const config = await this.readProviderConfigSummary();
@@ -394,11 +552,7 @@ export class ChatRunner {
394
552
  const totalTokens = Number.isFinite(usage.totalTokens)
395
553
  ? Math.max(0, Math.floor(usage.totalTokens))
396
554
  : inputTokens + outputTokens;
397
- return {
398
- inputTokens,
399
- outputTokens,
400
- totalTokens,
401
- };
555
+ return { inputTokens, outputTokens, totalTokens };
402
556
  }
403
557
  usageFromLLMResponse(response) {
404
558
  const inputTokens = response.usage?.input_tokens ?? 0;
@@ -776,7 +930,7 @@ export class ChatRunner {
776
930
  if (!args) {
777
931
  return {
778
932
  success: true,
779
- output: this.formatExecutionPolicyOutput(summarizeExecutionPolicy(policy), "Profile", await this.getAgentLoopProfileSummary("chat", policy)),
933
+ output: summarizeExecutionPolicy(policy),
780
934
  elapsed_ms: Date.now() - start,
781
935
  };
782
936
  }
@@ -818,15 +972,17 @@ export class ChatRunner {
818
972
  this.sessionExecutionPolicy = nextPolicy;
819
973
  return {
820
974
  success: true,
821
- output: this.formatExecutionPolicyOutput(summarizeExecutionPolicy(nextPolicy), "Profile", await this.getAgentLoopProfileSummary("chat", nextPolicy)),
975
+ output: summarizeExecutionPolicy(nextPolicy),
822
976
  elapsed_ms: Date.now() - start,
823
977
  };
824
978
  }
825
979
  async handleReview(start) {
826
980
  const cwd = this.sessionCwd ?? process.cwd();
827
981
  const diffStat = await checkGitChanges(cwd);
828
- const reviewProfile = await this.getAgentLoopDefaultProfile("review");
829
- const reviewPolicy = reviewProfile.executionPolicy ?? await this.getSessionExecutionPolicy();
982
+ const reviewPolicy = withExecutionPolicyOverrides(await this.getSessionExecutionPolicy(), {
983
+ sandboxMode: "read_only",
984
+ approvalPolicy: "never",
985
+ });
830
986
  if (this.deps.reviewAgentLoopRunner) {
831
987
  const review = await this.deps.reviewAgentLoopRunner.execute({
832
988
  cwd,
@@ -841,9 +997,6 @@ export class ChatRunner {
841
997
  "",
842
998
  "Execution policy",
843
999
  summarizeExecutionPolicy(reviewPolicy),
844
- "",
845
- "Review profile",
846
- formatAgentLoopResolvedProfileSummary(summarizeAgentLoopResolvedProfile(reviewProfile, reviewPolicy)),
847
1000
  ].join("\n");
848
1001
  return { success: true, output, elapsed_ms: Date.now() - start };
849
1002
  }
@@ -957,32 +1110,47 @@ export class ChatRunner {
957
1110
  elapsed_ms: Date.now() - start,
958
1111
  };
959
1112
  }
960
- try {
961
- await this.deps.daemonClient.startGoal(pending.goalId);
962
- }
963
- catch (err) {
964
- const msg = err instanceof Error ? err.message : String(err);
965
- return {
966
- success: false,
967
- output: `Daemon unavailable: ${msg}. Start the daemon with 'pulseed daemon start' first.`,
968
- elapsed_ms: Date.now() - start,
969
- };
970
- }
971
- // Subscribe to EventServer progress notifications (non-blocking)
972
1113
  const { goalId, maxIterations } = pending;
1114
+ let subscriber = null;
973
1115
  if (this.deps.daemonBaseUrl && !this.activeSubscribers.has(goalId)) {
974
- const subscriber = new EventSubscriber(this.deps.daemonBaseUrl, goalId, "normal");
1116
+ subscriber = new EventSubscriber(this.deps.daemonBaseUrl, goalId, "normal");
975
1117
  this.activeSubscribers.set(goalId, subscriber);
976
1118
  subscriber.on("notification", (notification) => {
977
1119
  const n = notification;
978
- // Invoke both the deps callback (wired at construction) and the public
979
- // onNotification property (wired post-construction, e.g. from React useEffect)
980
1120
  this.deps.onNotification?.(n.message);
981
1121
  this.onNotification?.(n.message);
982
1122
  });
983
- subscriber.subscribe().catch(() => {
984
- // Connection failures are handled inside EventSubscriber
1123
+ subscriber.on("chat_event", (event) => {
1124
+ this.emitEvent(event);
985
1125
  });
1126
+ try {
1127
+ await subscriber.subscribeReady();
1128
+ }
1129
+ catch (err) {
1130
+ subscriber.unsubscribe();
1131
+ this.activeSubscribers.delete(goalId);
1132
+ const msg = err instanceof Error ? err.message : String(err);
1133
+ return {
1134
+ success: false,
1135
+ output: `Daemon event stream unavailable: ${msg}. Goal was not started.`,
1136
+ elapsed_ms: Date.now() - start,
1137
+ };
1138
+ }
1139
+ }
1140
+ try {
1141
+ await this.deps.daemonClient.startGoal(goalId);
1142
+ }
1143
+ catch (err) {
1144
+ if (subscriber) {
1145
+ subscriber.unsubscribe();
1146
+ this.activeSubscribers.delete(goalId);
1147
+ }
1148
+ const msg = err instanceof Error ? err.message : String(err);
1149
+ return {
1150
+ success: false,
1151
+ output: `Daemon unavailable: ${msg}. Start the daemon with 'pulseed daemon start' first.`,
1152
+ elapsed_ms: Date.now() - start,
1153
+ };
986
1154
  }
987
1155
  const iterNote = maxIterations !== undefined ? ` (max ${maxIterations} iterations)` : "";
988
1156
  const shortId = goalId.length > 12 ? goalId.slice(0, 12) : goalId;
@@ -1004,10 +1172,11 @@ export class ChatRunner {
1004
1172
  * 6. Verify changes (git diff + tests); retry up to MAX_VERIFY_RETRIES if tests fail
1005
1173
  * 7. Persist assistant response only after the final assistant text is complete
1006
1174
  */
1007
- async execute(input, cwd, timeoutMs = DEFAULT_TIMEOUT_MS) {
1175
+ async execute(input, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, options = {}) {
1008
1176
  const eventContext = this.createEventContext();
1009
1177
  const resumeCommand = this.parseResumeCommand(input);
1010
1178
  const resumeOnly = resumeCommand !== null;
1179
+ const runtimeControlContext = options.runtimeControlContext ?? this.runtimeControlContext;
1011
1180
  // Intercept commands before any adapter call
1012
1181
  const commandResult = resumeOnly ? null : await this.handleCommand(input);
1013
1182
  if (commandResult !== null) {
@@ -1036,21 +1205,6 @@ export class ChatRunner {
1036
1205
  this.emitLifecycleEndEvent(confirmationResult.success ? "completed" : "error", confirmationResult.elapsed_ms, eventContext, false);
1037
1206
  return confirmationResult;
1038
1207
  }
1039
- const runtimeControlResult = resumeOnly
1040
- ? null
1041
- : await this.handleRuntimeControlIntent(input, cwd, Date.now());
1042
- if (runtimeControlResult !== null) {
1043
- if (runtimeControlResult.output) {
1044
- this.emitEvent({
1045
- type: "assistant_final",
1046
- text: runtimeControlResult.output,
1047
- persisted: false,
1048
- ...this.eventBase(eventContext),
1049
- });
1050
- }
1051
- this.emitLifecycleEndEvent(runtimeControlResult.success ? "completed" : "error", runtimeControlResult.elapsed_ms, eventContext, false);
1052
- return runtimeControlResult;
1053
- }
1054
1208
  if (resumeOnly && resumeCommand.selector) {
1055
1209
  try {
1056
1210
  const catalog = new ChatSessionCatalog(this.deps.stateManager);
@@ -1127,18 +1281,39 @@ export class ChatRunner {
1127
1281
  if (historySections.length > 0) {
1128
1282
  historyBlock = `${historySections.join("\n\n")}\n\nCurrent message:\n`;
1129
1283
  }
1130
- const directAnswerRoute = !resumeOnly && !this.deps.chatAgentLoopRunner && this.deps.llmClient !== undefined && shouldUseDirectAnswerRoute(input);
1284
+ const selectedRoute = resumeOnly
1285
+ ? null
1286
+ : (options.selectedRoute ?? this.resolveRouteFromInput(input, runtimeControlContext));
1131
1287
  const directPrompt = historyBlock ? `${historyBlock}${input}` : input;
1132
1288
  const start = Date.now();
1133
1289
  const assistantBuffer = { text: "" };
1134
1290
  const turnUsage = this.zeroUsageCounter();
1135
- if (directAnswerRoute) {
1291
+ if (selectedRoute?.kind === "runtime_control") {
1292
+ const runtimeControlResult = await this.executeRuntimeControlRoute(selectedRoute, runtimeControlContext, cwd, start);
1293
+ if (runtimeControlResult.success) {
1294
+ await history.appendAssistantMessage(runtimeControlResult.output);
1295
+ this.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
1296
+ this.emitEvent({
1297
+ type: "assistant_final",
1298
+ text: runtimeControlResult.output,
1299
+ persisted: true,
1300
+ ...this.eventBase(eventContext),
1301
+ });
1302
+ this.emitLifecycleEndEvent("completed", runtimeControlResult.elapsed_ms, eventContext, true);
1303
+ }
1304
+ else {
1305
+ this.emitLifecycleErrorEvent(runtimeControlResult.output, assistantBuffer.text, eventContext);
1306
+ this.emitLifecycleEndEvent("error", runtimeControlResult.elapsed_ms, eventContext, false);
1307
+ }
1308
+ return runtimeControlResult;
1309
+ }
1310
+ if (selectedRoute?.kind === "direct_answer") {
1136
1311
  try {
1137
1312
  this.emitActivity("lifecycle", "Calling model...", eventContext, "lifecycle:model");
1138
1313
  const directResponse = await this.sendLLMMessage(this.deps.llmClient, [{ role: "user", content: directPrompt }], {
1139
1314
  ...(this.cachedStaticSystemPrompt ? { system: this.cachedStaticSystemPrompt } : {}),
1140
- model_tier: "light",
1141
- max_tokens: DIRECT_ANSWER_MAX_TOKENS,
1315
+ model_tier: selectedRoute.modelTier,
1316
+ max_tokens: selectedRoute.maxTokens,
1142
1317
  }, assistantBuffer, eventContext);
1143
1318
  this.addUsageCounter(turnUsage, this.usageFromLLMResponse(directResponse));
1144
1319
  const elapsed_ms = Date.now() - start;
@@ -1161,9 +1336,9 @@ export class ChatRunner {
1161
1336
  elapsed_ms,
1162
1337
  diagnostics: {
1163
1338
  route: "direct",
1164
- reason: "simple_question",
1165
- modelTier: "light",
1166
- maxTokens: DIRECT_ANSWER_MAX_TOKENS,
1339
+ reason: selectedRoute.reason,
1340
+ modelTier: selectedRoute.modelTier,
1341
+ maxTokens: selectedRoute.maxTokens,
1167
1342
  },
1168
1343
  };
1169
1344
  }
@@ -1179,25 +1354,49 @@ export class ChatRunner {
1179
1354
  elapsed_ms: Date.now() - start,
1180
1355
  diagnostics: {
1181
1356
  route: "direct",
1182
- reason: "simple_question",
1183
- modelTier: "light",
1184
- maxTokens: DIRECT_ANSWER_MAX_TOKENS,
1357
+ reason: selectedRoute.reason,
1358
+ modelTier: selectedRoute.modelTier,
1359
+ maxTokens: selectedRoute.maxTokens,
1185
1360
  },
1186
1361
  };
1187
1362
  }
1188
1363
  }
1189
- let dynamicSystemPrompt = "";
1190
- try {
1191
- this.emitActivity("lifecycle", "Preparing context...", eventContext, "lifecycle:context");
1192
- dynamicSystemPrompt = await buildDynamicContextPrompt({ stateManager: this.deps.stateManager });
1193
- }
1194
- catch {
1195
- dynamicSystemPrompt = "";
1364
+ const usesNativeAgentLoop = resumeOnly || selectedRoute?.kind === "agent_loop";
1365
+ const groundingWorkspaceContext = !resumeOnly && usesNativeAgentLoop
1366
+ ? await buildChatContext(input, cwd)
1367
+ : undefined;
1368
+ let systemPrompt = this.cachedStaticSystemPrompt ?? "";
1369
+ if (!resumeOnly) {
1370
+ try {
1371
+ this.emitActivity("lifecycle", "Preparing context...", eventContext, "lifecycle:context");
1372
+ if (usesNativeAgentLoop) {
1373
+ systemPrompt = await buildChatAgentLoopSystemPrompt({
1374
+ stateManager: this.deps.stateManager,
1375
+ pluginLoader: this.deps.pluginLoader,
1376
+ workspaceRoot: cwd,
1377
+ goalId: this.deps.goalId,
1378
+ userMessage: input,
1379
+ trustProjectInstructions: this.sessionExecutionPolicy?.trustProjectInstructions ?? true,
1380
+ workspaceContext: groundingWorkspaceContext,
1381
+ });
1382
+ }
1383
+ else {
1384
+ const groundingBundle = await this.groundingGateway.build({
1385
+ surface: "chat",
1386
+ purpose: "general_turn",
1387
+ workspaceRoot: cwd,
1388
+ goalId: this.deps.goalId,
1389
+ userMessage: input,
1390
+ query: input,
1391
+ trustProjectInstructions: this.sessionExecutionPolicy?.trustProjectInstructions ?? true,
1392
+ });
1393
+ systemPrompt = String(groundingBundle.render("prompt"));
1394
+ }
1395
+ }
1396
+ catch {
1397
+ systemPrompt = this.cachedStaticSystemPrompt ?? "";
1398
+ }
1196
1399
  }
1197
- const systemPrompt = [this.cachedStaticSystemPrompt, dynamicSystemPrompt]
1198
- .filter((section) => section && section.trim().length > 0)
1199
- .join("\n\n")
1200
- .trim();
1201
1400
  const agentLoopSystemPrompt = [
1202
1401
  systemPrompt,
1203
1402
  compactionSummary ? `## Compacted Chat Summary\n${compactionSummary}` : "",
@@ -1205,7 +1404,7 @@ export class ChatRunner {
1205
1404
  .filter((section) => section && section.trim().length > 0)
1206
1405
  .join("\n\n")
1207
1406
  .trim();
1208
- const context = resumeOnly ? "" : await buildChatContext(input, gitRoot);
1407
+ const context = resumeOnly || usesNativeAgentLoop ? "" : await buildChatContext(input, gitRoot);
1209
1408
  const basePrompt = resumeOnly ? "" : (context ? `${context}\n\n${input}` : input);
1210
1409
  const prompt = historyBlock ? `${historyBlock}${basePrompt}` : basePrompt;
1211
1410
  if (resumeOnly && !this.deps.chatAgentLoopRunner) {
@@ -1224,7 +1423,8 @@ export class ChatRunner {
1224
1423
  elapsed_ms,
1225
1424
  };
1226
1425
  }
1227
- if (this.deps.chatAgentLoopRunner) {
1426
+ const chatAgentLoopRunner = this.deps.chatAgentLoopRunner;
1427
+ if (resumeOnly || selectedRoute?.kind === "agent_loop") {
1228
1428
  try {
1229
1429
  const resumeState = resumeOnly ? await this.loadResumableAgentLoopState() : null;
1230
1430
  if (resumeOnly && !resumeState) {
@@ -1244,7 +1444,7 @@ export class ChatRunner {
1244
1444
  };
1245
1445
  }
1246
1446
  this.emitActivity("lifecycle", "Calling model...", eventContext, "lifecycle:model");
1247
- const result = await this.deps.chatAgentLoopRunner.execute({
1447
+ const result = await chatAgentLoopRunner.execute({
1248
1448
  message: basePrompt,
1249
1449
  cwd,
1250
1450
  goalId: this.deps.goalId,
@@ -1268,7 +1468,9 @@ export class ChatRunner {
1268
1468
  ...(agentLoopSystemPrompt ? { systemPrompt: agentLoopSystemPrompt } : {}),
1269
1469
  });
1270
1470
  const elapsed_ms = Date.now() - start;
1271
- const agentLoopUsage = this.normalizeUsageCounter(result.agentLoop?.usage ?? this.zeroUsageCounter());
1471
+ const agentLoopUsage = result.agentLoop?.usage
1472
+ ? this.normalizeUsageCounter(result.agentLoop.usage)
1473
+ : this.zeroUsageCounter();
1272
1474
  if (this.hasUsage(agentLoopUsage)) {
1273
1475
  history.recordUsage("agentloop", agentLoopUsage);
1274
1476
  }
@@ -1287,9 +1489,6 @@ export class ChatRunner {
1287
1489
  this.emitLifecycleEndEvent("completed", elapsed_ms, eventContext, true);
1288
1490
  }
1289
1491
  else {
1290
- if (this.hasUsage(agentLoopUsage)) {
1291
- await history.persist();
1292
- }
1293
1492
  this.emitLifecycleErrorEvent(result.output || result.error || "Unknown error", assistantBuffer.text, eventContext);
1294
1493
  this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
1295
1494
  }
@@ -1313,7 +1512,7 @@ export class ChatRunner {
1313
1512
  }
1314
1513
  }
1315
1514
  // Prefer the local LLM/tool loop over the external adapter fallback whenever a client is available.
1316
- if (this.deps.llmClient) {
1515
+ if (selectedRoute?.kind === "tool_loop") {
1317
1516
  try {
1318
1517
  const toolResult = await this.executeWithTools(prompt, eventContext, assistantBuffer, systemPrompt || undefined);
1319
1518
  const elapsed_ms = Date.now() - start;
@@ -1344,6 +1543,17 @@ export class ChatRunner {
1344
1543
  };
1345
1544
  }
1346
1545
  }
1546
+ if (!resumeOnly && selectedRoute && selectedRoute.kind !== "adapter") {
1547
+ const elapsed_ms = Date.now() - start;
1548
+ const output = `Unsupported chat route: ${selectedRoute.kind}`;
1549
+ this.emitLifecycleErrorEvent(output, assistantBuffer.text, eventContext);
1550
+ this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
1551
+ return {
1552
+ success: false,
1553
+ output,
1554
+ elapsed_ms,
1555
+ };
1556
+ }
1347
1557
  const task = {
1348
1558
  prompt,
1349
1559
  timeout_ms: timeoutMs,
@@ -1413,10 +1623,7 @@ export class ChatRunner {
1413
1623
  elapsed_ms,
1414
1624
  };
1415
1625
  }
1416
- async handleRuntimeControlIntent(input, cwd, start) {
1417
- const intent = recognizeRuntimeControlIntent(input);
1418
- if (intent === null)
1419
- return null;
1626
+ async executeRuntimeControlRoute(route, runtimeControlContext, cwd, start) {
1420
1627
  if (!this.deps.runtimeControlService) {
1421
1628
  return {
1422
1629
  success: false,
@@ -1424,10 +1631,10 @@ export class ChatRunner {
1424
1631
  elapsed_ms: Date.now() - start,
1425
1632
  };
1426
1633
  }
1427
- const replyTarget = this.runtimeControlContext?.replyTarget ?? this.deps.runtimeReplyTarget;
1428
- const actor = this.runtimeControlContext?.actor ?? this.deps.runtimeControlActor;
1634
+ const replyTarget = runtimeControlContext?.replyTarget ?? this.deps.runtimeReplyTarget;
1635
+ const actor = runtimeControlContext?.actor ?? this.deps.runtimeControlActor;
1429
1636
  const result = await this.deps.runtimeControlService.request({
1430
- intent,
1637
+ intent: route.intent,
1431
1638
  cwd,
1432
1639
  requestedBy: actor ?? {
1433
1640
  surface: replyTarget?.surface ?? "chat",
@@ -1437,7 +1644,7 @@ export class ChatRunner {
1437
1644
  user_id: replyTarget?.user_id,
1438
1645
  },
1439
1646
  replyTarget: replyTarget ?? { surface: "chat" },
1440
- approvalFn: this.runtimeControlContext?.approvalFn
1647
+ approvalFn: runtimeControlContext?.approvalFn
1441
1648
  ?? this.deps.runtimeControlApprovalFn
1442
1649
  ?? this.deps.approvalFn,
1443
1650
  });
@@ -1470,13 +1677,13 @@ export class ChatRunner {
1470
1677
  ? { tools, ...(systemPrompt ? { system: systemPrompt } : {}) }
1471
1678
  : { system: buildPromptedToolProtocolSystemPrompt({ systemPrompt, tools }) }),
1472
1679
  }, assistantBuffer, eventContext);
1473
- this.addUsageCounter(usage, this.usageFromLLMResponse(response));
1474
1680
  }
1475
1681
  catch (err) {
1476
1682
  console.error("[chat-runner] executeWithTools error:", err);
1477
1683
  const hint = err instanceof Error ? `: ${err.message}` : "";
1478
1684
  throw new Error(`Sorry, I encountered an error processing your request${hint}.`);
1479
1685
  }
1686
+ this.addUsageCounter(usage, this.usageFromLLMResponse(response));
1480
1687
  const toolCalls = response.tool_calls?.length
1481
1688
  ? response.tool_calls
1482
1689
  : supportsNativeToolCalling
@@ -1676,19 +1883,10 @@ export class ChatRunner {
1676
1883
  return null;
1677
1884
  if (raw.status === "completed")
1678
1885
  return null;
1679
- const usageCandidate = raw.usage;
1680
- const usage = usageCandidate
1681
- ? this.normalizeUsageCounter({
1682
- inputTokens: typeof usageCandidate.inputTokens === "number" ? usageCandidate.inputTokens : 0,
1683
- outputTokens: typeof usageCandidate.outputTokens === "number" ? usageCandidate.outputTokens : 0,
1684
- totalTokens: typeof usageCandidate.totalTokens === "number" ? usageCandidate.totalTokens : 0,
1685
- })
1686
- : this.zeroUsageCounter();
1687
1886
  return {
1688
1887
  ...raw,
1689
1888
  messages: [...raw.messages],
1690
1889
  calledTools: [...raw.calledTools],
1691
- usage,
1692
1890
  };
1693
1891
  }
1694
1892
  isAgentLoopSessionState(value) {
@@ -1928,27 +2126,12 @@ export class ChatRunner {
1928
2126
  async getSessionExecutionPolicy() {
1929
2127
  if (this.sessionExecutionPolicy)
1930
2128
  return this.sessionExecutionPolicy;
1931
- const profile = await this.getAgentLoopDefaultProfile("chat");
1932
- if (!profile.executionPolicy) {
1933
- throw new Error("Chat profile did not resolve an execution policy.");
1934
- }
1935
- this.sessionExecutionPolicy = profile.executionPolicy;
1936
- return this.sessionExecutionPolicy;
1937
- }
1938
- async getAgentLoopDefaultProfile(surface) {
1939
2129
  const config = await loadProviderConfig({ saveMigration: false });
1940
- return resolveAgentLoopDefaultProfile({
1941
- surface,
2130
+ this.sessionExecutionPolicy = resolveExecutionPolicy({
1942
2131
  workspaceRoot: this.sessionCwd ?? process.cwd(),
1943
2132
  security: config.agent_loop?.security,
1944
2133
  });
1945
- }
1946
- async getAgentLoopProfileSummary(surface, executionPolicy) {
1947
- const profile = await this.getAgentLoopDefaultProfile(surface);
1948
- return formatAgentLoopResolvedProfileSummary(summarizeAgentLoopResolvedProfile(profile, executionPolicy));
1949
- }
1950
- formatExecutionPolicyOutput(policySummary, profileHeading, profileSummary) {
1951
- return [policySummary, "", profileHeading, profileSummary].join("\n");
2134
+ return this.sessionExecutionPolicy;
1952
2135
  }
1953
2136
  async buildToolCallContext() {
1954
2137
  const executionPolicy = await this.getSessionExecutionPolicy();