pulseed 0.4.13 → 0.4.15

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 (210) hide show
  1. package/assets/seedy.png +0 -0
  2. package/dist/adapters/types/mcp.d.ts +6 -6
  3. package/dist/base/config/global-config.d.ts.map +1 -1
  4. package/dist/base/config/global-config.js +2 -2
  5. package/dist/base/config/global-config.js.map +1 -1
  6. package/dist/base/llm/codex-llm-client.d.ts +1 -1
  7. package/dist/base/llm/codex-llm-client.d.ts.map +1 -1
  8. package/dist/base/llm/codex-llm-client.js +1 -1
  9. package/dist/base/llm/codex-llm-client.js.map +1 -1
  10. package/dist/base/llm/llm-client.d.ts +3 -2
  11. package/dist/base/llm/llm-client.d.ts.map +1 -1
  12. package/dist/base/llm/llm-client.js.map +1 -1
  13. package/dist/base/llm/provider-config.d.ts +6 -1
  14. package/dist/base/llm/provider-config.d.ts.map +1 -1
  15. package/dist/base/llm/provider-config.js +148 -94
  16. package/dist/base/llm/provider-config.js.map +1 -1
  17. package/dist/base/utils/json-io.d.ts +6 -1
  18. package/dist/base/utils/json-io.d.ts.map +1 -1
  19. package/dist/base/utils/json-io.js +16 -3
  20. package/dist/base/utils/json-io.js.map +1 -1
  21. package/dist/interface/chat/chat-history.d.ts +6 -1
  22. package/dist/interface/chat/chat-history.d.ts.map +1 -1
  23. package/dist/interface/chat/chat-history.js +16 -2
  24. package/dist/interface/chat/chat-history.js.map +1 -1
  25. package/dist/interface/chat/chat-runner.d.ts +27 -1
  26. package/dist/interface/chat/chat-runner.d.ts.map +1 -1
  27. package/dist/interface/chat/chat-runner.js +441 -13
  28. package/dist/interface/chat/chat-runner.js.map +1 -1
  29. package/dist/interface/chat/grounding.d.ts.map +1 -1
  30. package/dist/interface/chat/grounding.js +2 -1
  31. package/dist/interface/chat/grounding.js.map +1 -1
  32. package/dist/interface/chat/mutation-tool-defs.d.ts +0 -21
  33. package/dist/interface/chat/mutation-tool-defs.d.ts.map +1 -1
  34. package/dist/interface/chat/mutation-tool-defs.js +1 -164
  35. package/dist/interface/chat/mutation-tool-defs.js.map +1 -1
  36. package/dist/interface/cli/commands/daemon.d.ts.map +1 -1
  37. package/dist/interface/cli/commands/daemon.js +4 -14
  38. package/dist/interface/cli/commands/daemon.js.map +1 -1
  39. package/dist/interface/cli/commands/setup/import/apply.d.ts.map +1 -1
  40. package/dist/interface/cli/commands/setup/import/apply.js +4 -0
  41. package/dist/interface/cli/commands/setup/import/apply.js.map +1 -1
  42. package/dist/interface/cli/commands/setup/import/discovery.d.ts.map +1 -1
  43. package/dist/interface/cli/commands/setup/import/discovery.js +10 -2
  44. package/dist/interface/cli/commands/setup/import/discovery.js.map +1 -1
  45. package/dist/interface/cli/commands/setup/import/flow.d.ts.map +1 -1
  46. package/dist/interface/cli/commands/setup/import/flow.js +11 -1
  47. package/dist/interface/cli/commands/setup/import/flow.js.map +1 -1
  48. package/dist/interface/cli/commands/setup/import/types.d.ts +3 -0
  49. package/dist/interface/cli/commands/setup/import/types.d.ts.map +1 -1
  50. package/dist/interface/cli/commands/setup/steps-identity.d.ts.map +1 -1
  51. package/dist/interface/cli/commands/setup/steps-identity.js +1 -2
  52. package/dist/interface/cli/commands/setup/steps-identity.js.map +1 -1
  53. package/dist/interface/cli/commands/setup/steps-provider.d.ts +4 -0
  54. package/dist/interface/cli/commands/setup/steps-provider.d.ts.map +1 -1
  55. package/dist/interface/cli/commands/setup/steps-provider.js +26 -0
  56. package/dist/interface/cli/commands/setup/steps-provider.js.map +1 -1
  57. package/dist/interface/cli/commands/setup-wizard.d.ts.map +1 -1
  58. package/dist/interface/cli/commands/setup-wizard.js +65 -17
  59. package/dist/interface/cli/commands/setup-wizard.js.map +1 -1
  60. package/dist/interface/cli/commands/setup.d.ts.map +1 -1
  61. package/dist/interface/cli/commands/setup.js +12 -2
  62. package/dist/interface/cli/commands/setup.js.map +1 -1
  63. package/dist/interface/cli/setup.d.ts.map +1 -1
  64. package/dist/interface/cli/setup.js +6 -5
  65. package/dist/interface/cli/setup.js.map +1 -1
  66. package/dist/interface/mcp-server/index.d.ts.map +1 -1
  67. package/dist/interface/mcp-server/index.js +2 -1
  68. package/dist/interface/mcp-server/index.js.map +1 -1
  69. package/dist/orchestrator/execution/agent-loop/agent-loop-budget.d.ts +1 -1
  70. package/dist/orchestrator/execution/agent-loop/agent-loop-budget.d.ts.map +1 -1
  71. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client-factory.d.ts.map +1 -1
  72. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client-factory.js +3 -0
  73. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client-factory.js.map +1 -1
  74. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client.d.ts.map +1 -1
  75. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client.js +15 -6
  76. package/dist/orchestrator/execution/agent-loop/agent-loop-model-client.js.map +1 -1
  77. package/dist/orchestrator/execution/agent-loop/agent-loop-model.d.ts +1 -0
  78. package/dist/orchestrator/execution/agent-loop/agent-loop-model.d.ts.map +1 -1
  79. package/dist/orchestrator/execution/agent-loop/agent-loop-model.js.map +1 -1
  80. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.d.ts.map +1 -1
  81. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.js +0 -3
  82. package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.js.map +1 -1
  83. package/dist/orchestrator/execution/agent-loop/index.d.ts +1 -0
  84. package/dist/orchestrator/execution/agent-loop/index.d.ts.map +1 -1
  85. package/dist/orchestrator/execution/agent-loop/index.js +1 -0
  86. package/dist/orchestrator/execution/agent-loop/index.js.map +1 -1
  87. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.d.ts +16 -0
  88. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.d.ts.map +1 -0
  89. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.js +74 -0
  90. package/dist/orchestrator/execution/agent-loop/prompted-tool-protocol.js.map +1 -0
  91. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.d.ts +1 -1
  92. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.d.ts.map +1 -1
  93. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.js +3 -2
  94. package/dist/orchestrator/execution/agent-loop/task-agent-loop-factory.js.map +1 -1
  95. package/dist/orchestrator/execution/task/task-lifecycle.js +1 -1
  96. package/dist/orchestrator/execution/task/task-lifecycle.js.map +1 -1
  97. package/dist/platform/observation/observation-tools.d.ts.map +1 -1
  98. package/dist/platform/observation/observation-tools.js +4 -1
  99. package/dist/platform/observation/observation-tools.js.map +1 -1
  100. package/dist/platform/soil/config.d.ts.map +1 -1
  101. package/dist/platform/soil/config.js +1 -2
  102. package/dist/platform/soil/config.js.map +1 -1
  103. package/dist/platform/soil/display/index.d.ts +4 -0
  104. package/dist/platform/soil/display/index.d.ts.map +1 -0
  105. package/dist/platform/soil/display/index.js +4 -0
  106. package/dist/platform/soil/display/index.js.map +1 -0
  107. package/dist/platform/soil/display/materialize.d.ts +3 -0
  108. package/dist/platform/soil/display/materialize.d.ts.map +1 -0
  109. package/dist/platform/soil/display/materialize.js +381 -0
  110. package/dist/platform/soil/display/materialize.js.map +1 -0
  111. package/dist/platform/soil/display/registry.d.ts +4 -0
  112. package/dist/platform/soil/display/registry.d.ts.map +1 -0
  113. package/dist/platform/soil/display/registry.js +19 -0
  114. package/dist/platform/soil/display/registry.js.map +1 -0
  115. package/dist/platform/soil/display/types.d.ts +28 -0
  116. package/dist/platform/soil/display/types.d.ts.map +1 -0
  117. package/dist/platform/soil/display/types.js +2 -0
  118. package/dist/platform/soil/display/types.js.map +1 -0
  119. package/dist/platform/soil/index.d.ts +1 -0
  120. package/dist/platform/soil/index.d.ts.map +1 -1
  121. package/dist/platform/soil/index.js +1 -0
  122. package/dist/platform/soil/index.js.map +1 -1
  123. package/dist/platform/soil/open.d.ts.map +1 -1
  124. package/dist/platform/soil/open.js +2 -0
  125. package/dist/platform/soil/open.js.map +1 -1
  126. package/dist/platform/soil/publish/publisher.d.ts.map +1 -1
  127. package/dist/platform/soil/publish/publisher.js +2 -0
  128. package/dist/platform/soil/publish/publisher.js.map +1 -1
  129. package/dist/reflection/evening-catchup.d.ts.map +1 -1
  130. package/dist/reflection/evening-catchup.js +6 -41
  131. package/dist/reflection/evening-catchup.js.map +1 -1
  132. package/dist/reflection/morning-planning.d.ts.map +1 -1
  133. package/dist/reflection/morning-planning.js +6 -46
  134. package/dist/reflection/morning-planning.js.map +1 -1
  135. package/dist/reflection/reflection-utils.d.ts +16 -0
  136. package/dist/reflection/reflection-utils.d.ts.map +1 -0
  137. package/dist/reflection/reflection-utils.js +56 -0
  138. package/dist/reflection/reflection-utils.js.map +1 -0
  139. package/dist/reflection/weekly-review.d.ts.map +1 -1
  140. package/dist/reflection/weekly-review.js +3 -12
  141. package/dist/reflection/weekly-review.js.map +1 -1
  142. package/dist/reporting/report-formatters.d.ts +7 -0
  143. package/dist/reporting/report-formatters.d.ts.map +1 -1
  144. package/dist/reporting/report-formatters.js +22 -33
  145. package/dist/reporting/report-formatters.js.map +1 -1
  146. package/dist/reporting/reporting-engine.d.ts.map +1 -1
  147. package/dist/reporting/reporting-engine.js +34 -49
  148. package/dist/reporting/reporting-engine.js.map +1 -1
  149. package/dist/runtime/builtin-integrations.d.ts +4 -0
  150. package/dist/runtime/builtin-integrations.d.ts.map +1 -0
  151. package/dist/runtime/builtin-integrations.js +45 -0
  152. package/dist/runtime/builtin-integrations.js.map +1 -0
  153. package/dist/runtime/event/server.d.ts +1 -1
  154. package/dist/runtime/event/server.d.ts.map +1 -1
  155. package/dist/runtime/event/server.js +5 -27
  156. package/dist/runtime/event/server.js.map +1 -1
  157. package/dist/runtime/executor/loop-supervisor.d.ts.map +1 -1
  158. package/dist/runtime/executor/loop-supervisor.js +2 -2
  159. package/dist/runtime/executor/loop-supervisor.js.map +1 -1
  160. package/dist/runtime/foreign-plugins/compatibility.d.ts +7 -0
  161. package/dist/runtime/foreign-plugins/compatibility.d.ts.map +1 -0
  162. package/dist/runtime/foreign-plugins/compatibility.js +175 -0
  163. package/dist/runtime/foreign-plugins/compatibility.js.map +1 -0
  164. package/dist/runtime/foreign-plugins/index.d.ts +3 -0
  165. package/dist/runtime/foreign-plugins/index.d.ts.map +1 -0
  166. package/dist/runtime/foreign-plugins/index.js +2 -0
  167. package/dist/runtime/foreign-plugins/index.js.map +1 -0
  168. package/dist/runtime/foreign-plugins/types.d.ts +25 -0
  169. package/dist/runtime/foreign-plugins/types.d.ts.map +1 -0
  170. package/dist/runtime/foreign-plugins/types.js +2 -0
  171. package/dist/runtime/foreign-plugins/types.js.map +1 -0
  172. package/dist/runtime/schedule/engine.d.ts +12 -0
  173. package/dist/runtime/schedule/engine.d.ts.map +1 -1
  174. package/dist/runtime/schedule/engine.js +402 -243
  175. package/dist/runtime/schedule/engine.js.map +1 -1
  176. package/dist/runtime/types/builtin-integration.d.ts +13 -0
  177. package/dist/runtime/types/builtin-integration.d.ts.map +1 -0
  178. package/dist/runtime/types/builtin-integration.js +2 -0
  179. package/dist/runtime/types/builtin-integration.js.map +1 -0
  180. package/dist/tools/executor.js +2 -2
  181. package/dist/tools/executor.js.map +1 -1
  182. package/dist/tools/fs/ReadPulseedFileTool/ReadPulseedFileTool.d.ts.map +1 -1
  183. package/dist/tools/fs/ReadPulseedFileTool/ReadPulseedFileTool.js +21 -5
  184. package/dist/tools/fs/ReadPulseedFileTool/ReadPulseedFileTool.js.map +1 -1
  185. package/dist/tools/fs/WritePulseedFileTool/WritePulseedFileTool.d.ts.map +1 -1
  186. package/dist/tools/fs/WritePulseedFileTool/WritePulseedFileTool.js +61 -6
  187. package/dist/tools/fs/WritePulseedFileTool/WritePulseedFileTool.js.map +1 -1
  188. package/dist/tools/interaction/plan-utils.js +2 -2
  189. package/dist/tools/interaction/plan-utils.js.map +1 -1
  190. package/dist/tools/network/McpStdioTool/McpStdioTool.d.ts +10 -10
  191. package/dist/tools/query/ConfigTool/ConfigTool.d.ts.map +1 -1
  192. package/dist/tools/query/ConfigTool/ConfigTool.js +4 -3
  193. package/dist/tools/query/ConfigTool/ConfigTool.js.map +1 -1
  194. package/dist/tools/query/PluginStateTool/PluginStateTool.d.ts.map +1 -1
  195. package/dist/tools/query/PluginStateTool/PluginStateTool.js +13 -2
  196. package/dist/tools/query/PluginStateTool/PluginStateTool.js.map +1 -1
  197. package/dist/tools/system/ProcessSessionTool/ProcessSessionTool.d.ts +4 -4
  198. package/package.json +2 -1
  199. package/dist/interface/chat/self-knowledge-mutation-tools.d.ts +0 -12
  200. package/dist/interface/chat/self-knowledge-mutation-tools.d.ts.map +0 -1
  201. package/dist/interface/chat/self-knowledge-mutation-tools.js +0 -261
  202. package/dist/interface/chat/self-knowledge-mutation-tools.js.map +0 -1
  203. package/dist/interface/chat/self-knowledge-tools.d.ts +0 -30
  204. package/dist/interface/chat/self-knowledge-tools.d.ts.map +0 -1
  205. package/dist/interface/chat/self-knowledge-tools.js +0 -248
  206. package/dist/interface/chat/self-knowledge-tools.js.map +0 -1
  207. package/dist/prompt/index.d.ts +0 -12
  208. package/dist/prompt/index.d.ts.map +0 -1
  209. package/dist/prompt/index.js +0 -9
  210. package/dist/prompt/index.js.map +0 -1
@@ -3,6 +3,11 @@
3
3
  // Central coordinator for 1-shot chat execution (Tier 1).
4
4
  // Bypasses TaskLifecycle — calls adapter.execute() directly.
5
5
  import { execFile } from "node:child_process";
6
+ import * as fsp from "node:fs/promises";
7
+ import * as path from "node:path";
8
+ import { getPulseedDirPath } from "../../base/utils/paths.js";
9
+ import { loadProviderConfig } from "../../base/llm/provider-config.js";
10
+ import { TaskSchema } from "../../base/types/task.js";
6
11
  import { ChatHistory } from "./chat-history.js";
7
12
  import { ChatSessionCatalog, ChatSessionSelectorError, } from "./chat-session-store.js";
8
13
  import { buildChatContext, resolveGitRoot } from "../../platform/observation/context-provider.js";
@@ -11,6 +16,7 @@ import { verifyChatAction } from "./chat-verifier.js";
11
16
  import { toToolDefinitionsFiltered } from "../../tools/tool-definition-adapter.js";
12
17
  import { TendCommand } from "./tend-command.js";
13
18
  import { EventSubscriber } from "./event-subscriber.js";
19
+ import { buildPromptedToolProtocolSystemPrompt, extractPromptedToolCalls, } from "../../orchestrator/execution/agent-loop/prompted-tool-protocol.js";
14
20
  import { recognizeRuntimeControlIntent } from "../../runtime/control/index.js";
15
21
  const DEFAULT_TIMEOUT_MS = 120_000;
16
22
  const MAX_VERIFY_RETRIES = 2;
@@ -19,6 +25,7 @@ const ACTIVITY_PREVIEW_CHARS = 40;
19
25
  const DIRECT_ANSWER_MAX_TOKENS = 256;
20
26
  // ─── Command help text ───
21
27
  const COMMAND_HELP = `Available commands:
28
+ Session
22
29
  /help Show this help message
23
30
  /clear Clear conversation history
24
31
  /sessions List prior chat sessions
@@ -26,9 +33,25 @@ const COMMAND_HELP = `Available commands:
26
33
  /title <title> Rename the current session
27
34
  /resume [id|title] Resume native agentloop state for the current or selected session
28
35
  /cleanup [--dry-run] Clean up stale chat sessions
36
+ /compact Summarize older chat turns and keep the latest turns
29
37
  /exit Exit chat mode
38
+
39
+ Goals and tasks
40
+ /status [goal-id] Show active goal status, or one goal when an id is provided
41
+ /goals List goals
42
+ /tasks [goal-id] List tasks for a goal; uses the only active goal when unambiguous
43
+ /task <task-id> [goal-id]
44
+ Show one task; searches goals when no goal id is provided
30
45
  /track Promote session to Tier 2 goal pursuit (not yet implemented)
31
- /tend Generate a goal from chat history and start autonomous daemon execution`;
46
+ /tend Generate a goal from chat history and start autonomous daemon execution
47
+
48
+ Configuration
49
+ /config Show provider configuration with secrets masked
50
+ /model Show the active provider/model/adapter
51
+ /plugins List installed plugins when plugin metadata is available
52
+
53
+ Deferred
54
+ /retry, /undo, and /usage are intentionally not supported yet.`;
32
55
  // ─── Helpers ───
33
56
  function checkGitChanges(cwd) {
34
57
  return new Promise((resolve) => {
@@ -164,6 +187,352 @@ export class ChatRunner {
164
187
  });
165
188
  return `Session ${session.id}${title} (${session.cwd})\n${lines.join("\n")}`;
166
189
  }
190
+ async loadGoals() {
191
+ const goalIds = await this.deps.stateManager.listGoalIds();
192
+ const goals = await Promise.all(goalIds.map((id) => this.deps.stateManager.loadGoal(id)));
193
+ return goals.filter((goal) => goal !== null);
194
+ }
195
+ async listAllGoalIds() {
196
+ const activeIds = await this.deps.stateManager.listGoalIds();
197
+ const archivedIds = await this.deps.stateManager.listArchivedGoals();
198
+ const recoverableArchivedIds = await this.listRecoverableArchivedGoalIds();
199
+ return [...new Set([...activeIds, ...archivedIds, ...recoverableArchivedIds])];
200
+ }
201
+ resolveStatePath(baseDir, ...segments) {
202
+ const base = path.resolve(baseDir);
203
+ const resolved = path.resolve(base, ...segments);
204
+ if (!resolved.startsWith(base + path.sep))
205
+ return null;
206
+ return resolved;
207
+ }
208
+ async listRecoverableArchivedGoalIds() {
209
+ const stateManager = this.deps.stateManager;
210
+ if (typeof stateManager.getBaseDir !== "function")
211
+ return [];
212
+ const archiveDir = this.resolveStatePath(stateManager.getBaseDir(), "archive");
213
+ if (archiveDir === null)
214
+ return [];
215
+ let entries = [];
216
+ try {
217
+ entries = await fsp.readdir(archiveDir, { withFileTypes: true });
218
+ }
219
+ catch {
220
+ return [];
221
+ }
222
+ const goalIds = [];
223
+ for (const entry of entries) {
224
+ if (!entry.isDirectory() || entry.name === ".staging")
225
+ continue;
226
+ try {
227
+ await fsp.access(path.join(archiveDir, entry.name, "goal", "goal.json"));
228
+ goalIds.push(entry.name);
229
+ }
230
+ catch {
231
+ continue;
232
+ }
233
+ }
234
+ return goalIds;
235
+ }
236
+ activeGoals(goals) {
237
+ return goals.filter((goal) => goal.status === "active" || goal.status === "waiting" || goal.loop_status === "running");
238
+ }
239
+ formatGoalLine(goal) {
240
+ const dimensions = goal.dimensions.length === 0
241
+ ? "no dimensions"
242
+ : goal.dimensions
243
+ .slice(0, 3)
244
+ .map((dimension) => `${dimension.name}: ${String(dimension.current_value)} target ${JSON.stringify(dimension.threshold)}`)
245
+ .join("; ");
246
+ return `${goal.id} - ${goal.title} [${goal.status}, loop ${goal.loop_status}] ${dimensions}`;
247
+ }
248
+ async handleStatus(args, start) {
249
+ if (args) {
250
+ const goal = await this.deps.stateManager.loadGoal(args);
251
+ if (!goal) {
252
+ return { success: false, output: `Goal not found: ${args}`, elapsed_ms: Date.now() - start };
253
+ }
254
+ const lines = [
255
+ `Goal status: ${goal.title}`,
256
+ `ID: ${goal.id}`,
257
+ `Status: ${goal.status}`,
258
+ `Loop: ${goal.loop_status}`,
259
+ `Updated: ${goal.updated_at}`,
260
+ `Children: ${goal.children_ids.length}`,
261
+ `Dimensions:`,
262
+ ...goal.dimensions.map((dimension) => `- ${dimension.name}: current=${String(dimension.current_value)}, threshold=${JSON.stringify(dimension.threshold)}, confidence=${dimension.confidence}`),
263
+ ];
264
+ return { success: true, output: lines.join("\n"), elapsed_ms: Date.now() - start };
265
+ }
266
+ const goals = await this.loadGoals();
267
+ const active = this.activeGoals(goals);
268
+ if (active.length === 0) {
269
+ return { success: true, output: "No active goals found.", elapsed_ms: Date.now() - start };
270
+ }
271
+ return {
272
+ success: true,
273
+ output: `Active goals:\n${active.map((goal) => this.formatGoalLine(goal)).join("\n")}`,
274
+ elapsed_ms: Date.now() - start,
275
+ };
276
+ }
277
+ async handleGoals(start) {
278
+ const goals = await this.loadGoals();
279
+ if (goals.length === 0) {
280
+ return { success: true, output: "No goals found.", elapsed_ms: Date.now() - start };
281
+ }
282
+ return {
283
+ success: true,
284
+ output: `Goals:\n${goals.map((goal) => this.formatGoalLine(goal)).join("\n")}`,
285
+ elapsed_ms: Date.now() - start,
286
+ };
287
+ }
288
+ async readTasksFromDir(tasksDir) {
289
+ let entries = [];
290
+ try {
291
+ entries = await fsp.readdir(tasksDir);
292
+ }
293
+ catch {
294
+ return [];
295
+ }
296
+ const tasks = [];
297
+ for (const entry of entries) {
298
+ if (!entry.endsWith(".json") || entry === "task-history.json" || entry === "last-failure-context.json")
299
+ continue;
300
+ let raw;
301
+ try {
302
+ raw = JSON.parse(await fsp.readFile(path.join(tasksDir, entry), "utf-8"));
303
+ }
304
+ catch {
305
+ continue;
306
+ }
307
+ const parsed = TaskSchema.safeParse(raw);
308
+ if (parsed.success)
309
+ tasks.push(parsed.data);
310
+ }
311
+ return tasks.sort((a, b) => (a.created_at < b.created_at ? 1 : -1));
312
+ }
313
+ async readTasksForGoal(goalId) {
314
+ const stateManager = this.deps.stateManager;
315
+ if (typeof stateManager.getBaseDir !== "function")
316
+ return [];
317
+ const baseDir = stateManager.getBaseDir();
318
+ const activeTasksDir = this.resolveStatePath(baseDir, "tasks", goalId);
319
+ const archiveTasksDir = this.resolveStatePath(baseDir, "archive", goalId, "tasks");
320
+ if (activeTasksDir === null || archiveTasksDir === null)
321
+ return [];
322
+ const activeTasks = await this.readTasksFromDir(activeTasksDir);
323
+ if (activeTasks.length > 0)
324
+ return activeTasks;
325
+ return this.readTasksFromDir(archiveTasksDir);
326
+ }
327
+ async resolveGoalForTasks(selector) {
328
+ if (selector)
329
+ return { goalId: selector };
330
+ const active = this.activeGoals(await this.loadGoals());
331
+ if (active.length === 1)
332
+ return { goalId: active[0].id };
333
+ if (active.length === 0)
334
+ return { error: "No active goals found. Use /tasks <goal-id>." };
335
+ return { error: "Multiple active goals found. Use /tasks <goal-id>." };
336
+ }
337
+ formatTaskLine(task) {
338
+ const verdict = task.verification_verdict ? `, verdict ${task.verification_verdict}` : "";
339
+ return `${task.id} - ${task.status}${verdict}: ${task.work_description}`;
340
+ }
341
+ async handleTasks(args, start) {
342
+ const resolved = await this.resolveGoalForTasks(args);
343
+ if (resolved.error || !resolved.goalId) {
344
+ return { success: false, output: resolved.error ?? "Usage: /tasks <goal-id>", elapsed_ms: Date.now() - start };
345
+ }
346
+ const tasks = await this.readTasksForGoal(resolved.goalId);
347
+ if (tasks.length === 0) {
348
+ return { success: true, output: `No tasks found for goal "${resolved.goalId}".`, elapsed_ms: Date.now() - start };
349
+ }
350
+ return {
351
+ success: true,
352
+ output: `Tasks for goal ${resolved.goalId}:\n${tasks.map((task) => this.formatTaskLine(task)).join("\n")}`,
353
+ elapsed_ms: Date.now() - start,
354
+ };
355
+ }
356
+ parseTaskArgs(args) {
357
+ const parts = args.split(/\s+/).filter(Boolean);
358
+ const goalFlagIndex = parts.indexOf("--goal");
359
+ if (goalFlagIndex >= 0) {
360
+ const goalId = parts[goalFlagIndex + 1];
361
+ parts.splice(goalFlagIndex, goalId ? 2 : 1);
362
+ return { taskId: parts[0], goalId };
363
+ }
364
+ return { taskId: parts[0], goalId: parts[1] };
365
+ }
366
+ async findTask(taskId, goalId) {
367
+ const goalIds = goalId ? [goalId] : await this.listAllGoalIds();
368
+ const matches = [];
369
+ for (const candidateGoalId of goalIds) {
370
+ let raw = null;
371
+ try {
372
+ raw = await this.deps.stateManager.readRaw(`tasks/${candidateGoalId}/${taskId}.json`);
373
+ }
374
+ catch {
375
+ raw = null;
376
+ }
377
+ if (!raw) {
378
+ const tasks = await this.readTasksForGoal(candidateGoalId);
379
+ const matched = tasks.find((task) => task.id === taskId || task.id.startsWith(taskId));
380
+ if (matched)
381
+ matches.push({ goalId: candidateGoalId, task: matched });
382
+ continue;
383
+ }
384
+ const parsed = TaskSchema.safeParse(raw);
385
+ if (parsed.success)
386
+ matches.push({ goalId: candidateGoalId, task: parsed.data });
387
+ }
388
+ return { task: matches.length === 1 ? matches[0].task : undefined, matches };
389
+ }
390
+ formatTask(task) {
391
+ const lines = [
392
+ `Task: ${task.id}`,
393
+ `Goal: ${task.goal_id}`,
394
+ `Status: ${task.status}`,
395
+ `Category: ${task.task_category}`,
396
+ `Created: ${task.created_at}`,
397
+ `Work: ${task.work_description}`,
398
+ `Approach: ${task.approach}`,
399
+ ];
400
+ if (task.started_at)
401
+ lines.push(`Started: ${task.started_at}`);
402
+ if (task.completed_at)
403
+ lines.push(`Completed: ${task.completed_at}`);
404
+ if (task.verification_verdict)
405
+ lines.push(`Verification: ${task.verification_verdict}`);
406
+ if (task.verification_evidence?.length)
407
+ lines.push(`Evidence: ${task.verification_evidence.join("; ")}`);
408
+ if (task.success_criteria.length > 0) {
409
+ lines.push("Success criteria:");
410
+ lines.push(...task.success_criteria.map((criterion) => `- ${criterion.description}`));
411
+ }
412
+ return lines.join("\n");
413
+ }
414
+ async handleTask(args, start) {
415
+ const { taskId, goalId } = this.parseTaskArgs(args);
416
+ if (!taskId) {
417
+ return { success: false, output: "Usage: /task <task-id> [goal-id]", elapsed_ms: Date.now() - start };
418
+ }
419
+ const found = await this.findTask(taskId, goalId);
420
+ if (found.matches.length > 1) {
421
+ return {
422
+ success: false,
423
+ output: `Task selector "${taskId}" matched multiple goals. Use /task ${taskId} <goal-id>.\n${found.matches.map((match) => `- ${match.goalId}`).join("\n")}`,
424
+ elapsed_ms: Date.now() - start,
425
+ };
426
+ }
427
+ if (!found.task) {
428
+ const suffix = goalId ? ` for goal "${goalId}"` : "";
429
+ return { success: false, output: `Task not found: ${taskId}${suffix}`, elapsed_ms: Date.now() - start };
430
+ }
431
+ return { success: true, output: this.formatTask(found.task), elapsed_ms: Date.now() - start };
432
+ }
433
+ providerConfigBaseDir() {
434
+ const stateManager = this.deps.stateManager;
435
+ return typeof stateManager.getBaseDir === "function" ? stateManager.getBaseDir() : getPulseedDirPath();
436
+ }
437
+ async readProviderConfigSummary() {
438
+ const config = await loadProviderConfig({
439
+ baseDir: this.providerConfigBaseDir(),
440
+ saveMigration: false,
441
+ });
442
+ return {
443
+ provider: config.provider,
444
+ model: config.model,
445
+ adapter: config.adapter,
446
+ light_model: config.light_model,
447
+ base_url: config.base_url,
448
+ codex_cli_path: config.codex_cli_path,
449
+ has_api_key: Boolean(config.api_key),
450
+ };
451
+ }
452
+ formatConfig(config) {
453
+ return Object.entries(config)
454
+ .filter(([, value]) => value !== undefined)
455
+ .map(([key, value]) => `${key}: ${typeof value === "string" && /key|token|secret/i.test(key) ? "[masked]" : String(value)}`)
456
+ .join("\n");
457
+ }
458
+ async handleConfig(start) {
459
+ const config = await this.readProviderConfigSummary();
460
+ return { success: true, output: `Provider configuration:\n${this.formatConfig(config)}`, elapsed_ms: Date.now() - start };
461
+ }
462
+ async handleModel(start) {
463
+ const config = await this.readProviderConfigSummary();
464
+ return {
465
+ success: true,
466
+ output: `Model: ${config.model}\nProvider: ${config.provider}\nAdapter: ${config.adapter}`,
467
+ elapsed_ms: Date.now() - start,
468
+ };
469
+ }
470
+ async handlePlugins(start) {
471
+ if (!this.deps.pluginLoader) {
472
+ return { success: true, output: "Plugin information is not available in this chat session.", elapsed_ms: Date.now() - start };
473
+ }
474
+ try {
475
+ const plugins = await this.deps.pluginLoader.loadAll();
476
+ if (plugins.length === 0) {
477
+ return { success: true, output: "No plugins found.", elapsed_ms: Date.now() - start };
478
+ }
479
+ return {
480
+ success: true,
481
+ output: `Plugins:\n${plugins.map((plugin) => `${plugin.name} - ${plugin.type ?? "unknown"} - ${plugin.enabled === false ? "disabled" : "enabled"}`).join("\n")}`,
482
+ elapsed_ms: Date.now() - start,
483
+ };
484
+ }
485
+ catch (err) {
486
+ const message = err instanceof Error ? err.message : String(err);
487
+ return { success: true, output: `Plugin information is unavailable: ${message}`, elapsed_ms: Date.now() - start };
488
+ }
489
+ }
490
+ deterministicChatSummary(messages) {
491
+ const lines = messages.map((message) => `${message.role}: ${message.content.replace(/\s+/g, " ").trim()}`);
492
+ return lines.join("\n").slice(0, 4_000);
493
+ }
494
+ async summarizeChatForCompaction(messages, existingSummary) {
495
+ const content = [
496
+ existingSummary ? `Previous summary:\n${existingSummary}` : "",
497
+ `Messages to summarize:\n${messages.map((message) => `${message.role}: ${message.content}`).join("\n")}`,
498
+ ].filter(Boolean).join("\n\n");
499
+ if (this.deps.llmClient) {
500
+ try {
501
+ const response = await this.deps.llmClient.sendMessage([
502
+ { role: "user", content: `Summarize this chat history for later continuation. Preserve decisions, open tasks, constraints, and user preferences. Keep it concise.\n\n${content}` },
503
+ ], { max_tokens: 700, model_tier: "light" });
504
+ if (response.content.trim())
505
+ return { summary: response.content.trim(), usedLlm: true };
506
+ }
507
+ catch {
508
+ // Fall back to deterministic summary below.
509
+ }
510
+ }
511
+ const fallback = [
512
+ existingSummary ? `Previous summary:\n${existingSummary}` : "",
513
+ "Extractive summary:",
514
+ this.deterministicChatSummary(messages),
515
+ ].filter(Boolean).join("\n\n");
516
+ return { summary: fallback, usedLlm: false };
517
+ }
518
+ async handleCompact(start) {
519
+ if (!this.history) {
520
+ return { success: false, output: "No active chat session to compact.", elapsed_ms: Date.now() - start };
521
+ }
522
+ const session = this.history.getSessionData();
523
+ if (session.messages.length <= 4) {
524
+ return { success: true, output: "Chat history is already compact. No messages were removed.", elapsed_ms: Date.now() - start };
525
+ }
526
+ const olderMessages = session.messages.slice(0, -4);
527
+ const { summary, usedLlm } = await this.summarizeChatForCompaction(olderMessages, session.compactionSummary);
528
+ const { before, after } = await this.history.compact(summary, 4);
529
+ const method = usedLlm ? "LLM summary" : "deterministic summary";
530
+ return {
531
+ success: true,
532
+ output: `Compacted chat history with ${method}. Persisted ${before} message(s) down to ${after}; the latest user/assistant turns were kept.`,
533
+ elapsed_ms: Date.now() - start,
534
+ };
535
+ }
167
536
  async handleCommand(input) {
168
537
  const trimmed = input.trim();
169
538
  if (!trimmed.startsWith("/"))
@@ -174,7 +543,7 @@ export class ChatRunner {
174
543
  return { success: true, output: COMMAND_HELP, elapsed_ms: Date.now() - start };
175
544
  }
176
545
  if (cmd === "/clear") {
177
- this.history?.clear();
546
+ await this.history?.clear();
178
547
  return { success: true, output: "Conversation history cleared.", elapsed_ms: Date.now() - start };
179
548
  }
180
549
  if (cmd === "/sessions") {
@@ -223,6 +592,30 @@ export class ChatRunner {
223
592
  elapsed_ms: Date.now() - start,
224
593
  };
225
594
  }
595
+ if (cmd === "/compact") {
596
+ return this.handleCompact(start);
597
+ }
598
+ if (cmd === "/status") {
599
+ return this.handleStatus(trimmed.slice("/status".length).trim(), start);
600
+ }
601
+ if (cmd === "/goals") {
602
+ return this.handleGoals(start);
603
+ }
604
+ if (cmd === "/tasks") {
605
+ return this.handleTasks(trimmed.slice("/tasks".length).trim(), start);
606
+ }
607
+ if (cmd === "/task") {
608
+ return this.handleTask(trimmed.slice("/task".length).trim(), start);
609
+ }
610
+ if (cmd === "/config") {
611
+ return this.handleConfig(start);
612
+ }
613
+ if (cmd === "/model") {
614
+ return this.handleModel(start);
615
+ }
616
+ if (cmd === "/plugins") {
617
+ return this.handlePlugins(start);
618
+ }
226
619
  if (cmd === "/exit") {
227
620
  return { success: true, output: "Exiting chat mode.", elapsed_ms: Date.now() - start };
228
621
  }
@@ -495,15 +888,23 @@ export class ChatRunner {
495
888
  this.cachedStaticSystemPrompt = "";
496
889
  }
497
890
  }
498
- // Build conversation history from prior turns (last 10)
891
+ // Build conversation history from prior turns (last 10), including any manual compaction summary.
499
892
  const messages = history.getMessages();
893
+ const compactionSummary = history.getSessionData().compactionSummary;
500
894
  const priorTurns = resumeOnly ? messages.slice(-10) : messages.slice(0, -1).slice(-10);
501
895
  let historyBlock = "";
896
+ const historySections = [];
897
+ if (compactionSummary) {
898
+ historySections.push(`Compacted previous conversation summary:\n${compactionSummary}`);
899
+ }
502
900
  if (priorTurns.length > 0) {
503
901
  const lines = priorTurns.map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.content}`).join("\n");
504
- historyBlock = `Previous conversation:\n${lines}\n\nCurrent message:\n`;
902
+ historySections.push(`Previous conversation:\n${lines}`);
505
903
  }
506
- const directAnswerRoute = !resumeOnly && this.deps.llmClient !== undefined && shouldUseDirectAnswerRoute(input);
904
+ if (historySections.length > 0) {
905
+ historyBlock = `${historySections.join("\n\n")}\n\nCurrent message:\n`;
906
+ }
907
+ const directAnswerRoute = !resumeOnly && !this.deps.chatAgentLoopRunner && this.deps.llmClient !== undefined && shouldUseDirectAnswerRoute(input);
507
908
  const directPrompt = historyBlock ? `${historyBlock}${input}` : input;
508
909
  const start = Date.now();
509
910
  const assistantBuffer = { text: "" };
@@ -569,6 +970,13 @@ export class ChatRunner {
569
970
  .filter((section) => section && section.trim().length > 0)
570
971
  .join("\n\n")
571
972
  .trim();
973
+ const agentLoopSystemPrompt = [
974
+ systemPrompt,
975
+ compactionSummary ? `## Compacted Chat Summary\n${compactionSummary}` : "",
976
+ ]
977
+ .filter((section) => section && section.trim().length > 0)
978
+ .join("\n\n")
979
+ .trim();
572
980
  const context = resumeOnly ? "" : await buildChatContext(input, gitRoot);
573
981
  const basePrompt = resumeOnly ? "" : (context ? `${context}\n\n${input}` : input);
574
982
  const prompt = historyBlock ? `${historyBlock}${basePrompt}` : basePrompt;
@@ -626,7 +1034,7 @@ export class ChatRunner {
626
1034
  ...(this.nativeAgentLoopStatePath ? { resumeStatePath: this.nativeAgentLoopStatePath } : {}),
627
1035
  ...(resumeState ? { resumeState } : {}),
628
1036
  ...(resumeOnly ? { resumeOnly: true } : {}),
629
- ...(systemPrompt ? { systemPrompt } : {}),
1037
+ ...(agentLoopSystemPrompt ? { systemPrompt: agentLoopSystemPrompt } : {}),
630
1038
  });
631
1039
  const elapsed_ms = Date.now() - start;
632
1040
  if (result.output) {
@@ -666,9 +1074,8 @@ export class ChatRunner {
666
1074
  };
667
1075
  }
668
1076
  }
669
- // Use llmClient with self-knowledge tools when available (function calling path)
670
- // Skip executeWithTools for clients that don't support tool calling (e.g. CodexLLMClient)
671
- if (this.deps.llmClient && this.deps.llmClient.supportsToolCalling?.() !== false) {
1077
+ // Prefer the local LLM/tool loop over the external adapter fallback whenever a client is available.
1078
+ if (this.deps.llmClient) {
672
1079
  try {
673
1080
  const toolResult = await this.executeWithTools(prompt, eventContext, assistantBuffer, systemPrompt || undefined);
674
1081
  const elapsed_ms = Date.now() - start;
@@ -812,12 +1219,14 @@ export class ChatRunner {
812
1219
  const tools = this.deps.registry
813
1220
  ? toToolDefinitionsFiltered(this.deps.registry.listAll(), { activatedTools: this.activatedTools })
814
1221
  : [];
1222
+ const supportsNativeToolCalling = llmClient.supportsToolCalling?.() !== false;
815
1223
  let response;
816
1224
  try {
817
1225
  this.emitActivity("lifecycle", "Calling model...", eventContext, "lifecycle:model");
818
1226
  response = await this.sendLLMMessage(llmClient, messages, {
819
- tools,
820
- ...(systemPrompt ? { system: systemPrompt } : {}),
1227
+ ...(supportsNativeToolCalling
1228
+ ? { tools, ...(systemPrompt ? { system: systemPrompt } : {}) }
1229
+ : { system: buildPromptedToolProtocolSystemPrompt({ systemPrompt, tools }) }),
821
1230
  }, assistantBuffer, eventContext);
822
1231
  }
823
1232
  catch (err) {
@@ -825,13 +1234,32 @@ export class ChatRunner {
825
1234
  const hint = err instanceof Error ? `: ${err.message}` : "";
826
1235
  throw new Error(`Sorry, I encountered an error processing your request${hint}.`);
827
1236
  }
1237
+ const toolCalls = response.tool_calls?.length
1238
+ ? response.tool_calls
1239
+ : supportsNativeToolCalling
1240
+ ? []
1241
+ : extractPromptedToolCalls({
1242
+ content: response.content,
1243
+ tools,
1244
+ createId: () => `prompted-${loop}-${crypto.randomUUID()}`,
1245
+ }).map((call) => ({
1246
+ id: call.id,
1247
+ type: "function",
1248
+ function: {
1249
+ name: call.name,
1250
+ arguments: JSON.stringify(call.input ?? {}),
1251
+ },
1252
+ }));
1253
+ if (!supportsNativeToolCalling && toolCalls.length > 0) {
1254
+ assistantBuffer.text = "";
1255
+ }
828
1256
  // No tool calls — return the text content
829
- if (!response.tool_calls || response.tool_calls.length === 0) {
1257
+ if (toolCalls.length === 0) {
830
1258
  return assistantBuffer.text || response.content || "(no response)";
831
1259
  }
832
1260
  // Append assistant message, then process tool calls
833
1261
  messages.push({ role: "assistant", content: response.content || "" });
834
- for (const tc of response.tool_calls) {
1262
+ for (const tc of toolCalls) {
835
1263
  let args = {};
836
1264
  try {
837
1265
  args = JSON.parse(tc.function.arguments || "{}");