@tyvm/knowhow 0.0.146 → 0.0.147

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 (214) hide show
  1. package/autodoc/cli-reference.md +36 -7
  2. package/bin/knowhow.js +8 -6
  3. package/package.json +1 -1
  4. package/src/agents/base/base.ts +24 -0
  5. package/src/agents/historyRollback.ts +36 -0
  6. package/src/agents/tools/startAgentTask.ts +41 -9
  7. package/src/auth/browserLogin.ts +9 -3
  8. package/src/auth/keyAuth.ts +115 -0
  9. package/src/auth/keyManager.ts +166 -0
  10. package/src/chat/modules/AgentModule.ts +114 -33
  11. package/src/chat/modules/AskModule.ts +2 -0
  12. package/src/chat/modules/BaseChatModule.ts +0 -1
  13. package/src/chat/modules/CustomCommandsModule.ts +2 -0
  14. package/src/chat/modules/InternalChatModule.ts +2 -0
  15. package/src/chat/modules/RemoteSyncModule.ts +4 -0
  16. package/src/chat/modules/RendererModule.ts +2 -0
  17. package/src/chat/modules/SearchModule.ts +2 -0
  18. package/src/chat/modules/SessionsModule.ts +103 -34
  19. package/src/chat/modules/SetupModule.ts +2 -0
  20. package/src/chat/modules/ShellCommandModule.ts +2 -0
  21. package/src/chat/modules/SystemModule.ts +2 -0
  22. package/src/chat/types.ts +2 -0
  23. package/src/cli.ts +1 -1
  24. package/src/clients/index.ts +2 -0
  25. package/src/commands/agent.ts +1 -38
  26. package/src/commands/agents.ts +403 -53
  27. package/src/commands/misc.ts +3 -2
  28. package/src/login.ts +89 -28
  29. package/src/services/AgentService.ts +2 -0
  30. package/src/services/AgentSyncFs.ts +65 -0
  31. package/src/services/AgentSyncKnowhowWeb.ts +22 -4
  32. package/src/services/BehaviorsService.ts +2 -0
  33. package/src/services/DockerService.ts +2 -0
  34. package/src/services/EmbeddingService.ts +2 -0
  35. package/src/services/EmbeddingsService.ts +3 -0
  36. package/src/services/KnowhowClient.ts +68 -7
  37. package/src/services/LazyToolsService.ts +9 -0
  38. package/src/services/Mcp.ts +2 -0
  39. package/src/services/McpServer.ts +2 -0
  40. package/src/services/MessageProcessor.ts +2 -0
  41. package/src/services/MinimalToolsService.ts +2 -0
  42. package/src/services/SessionManager.ts +5 -0
  43. package/src/services/SkillsService.ts +2 -0
  44. package/src/services/SyncedAgentWatcher.ts +2 -0
  45. package/src/services/SyncerService.ts +23 -8
  46. package/src/services/TaskRegistry.ts +2 -0
  47. package/src/services/Tools.ts +2 -0
  48. package/src/services/index.ts +2 -0
  49. package/src/services/modules/index.ts +4 -1
  50. package/src/types.ts +2 -0
  51. package/src/util/Trace.ts +1 -1
  52. package/src/utils/http.ts +18 -2
  53. package/tests/unit/agents/historyRollback.test.ts +53 -0
  54. package/tests/unit/auth/keyAuth.test.ts +102 -0
  55. package/tests/unit/auth/keyManager.test.ts +67 -0
  56. package/tests/unit/chat/historyCommandArgs.test.ts +37 -0
  57. package/tests/unit/commands/agents.test.ts +68 -0
  58. package/tests/unit/login.test.ts +119 -0
  59. package/tests/unit/services/AgentSyncKnowhowWeb.test.ts +38 -0
  60. package/tests/unit/services/LazyToolsService.test.ts +29 -0
  61. package/tests/unit/services/SessionManager.test.ts +43 -0
  62. package/tests/unit/services/SyncerService.test.ts +45 -0
  63. package/tests/unit/util/Trace.test.ts +119 -0
  64. package/tests/unit/utils/http.test.ts +43 -0
  65. package/ts_build/package.json +1 -1
  66. package/ts_build/src/agents/base/base.d.ts +1 -0
  67. package/ts_build/src/agents/base/base.js +31 -2
  68. package/ts_build/src/agents/base/base.js.map +1 -1
  69. package/ts_build/src/agents/historyRollback.d.ts +2 -0
  70. package/ts_build/src/agents/historyRollback.js +22 -0
  71. package/ts_build/src/agents/historyRollback.js.map +1 -0
  72. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -0
  73. package/ts_build/src/agents/tools/startAgentTask.js +36 -9
  74. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  75. package/ts_build/src/auth/browserLogin.d.ts +1 -0
  76. package/ts_build/src/auth/browserLogin.js +5 -2
  77. package/ts_build/src/auth/browserLogin.js.map +1 -1
  78. package/ts_build/src/auth/keyAuth.d.ts +5 -0
  79. package/ts_build/src/auth/keyAuth.js +79 -0
  80. package/ts_build/src/auth/keyAuth.js.map +1 -0
  81. package/ts_build/src/auth/keyManager.d.ts +14 -0
  82. package/ts_build/src/auth/keyManager.js +145 -0
  83. package/ts_build/src/auth/keyManager.js.map +1 -0
  84. package/ts_build/src/chat/modules/AgentModule.d.ts +13 -1
  85. package/ts_build/src/chat/modules/AgentModule.js +83 -24
  86. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  87. package/ts_build/src/chat/modules/AskModule.js +12 -2
  88. package/ts_build/src/chat/modules/AskModule.js.map +1 -1
  89. package/ts_build/src/chat/modules/BaseChatModule.js.map +1 -1
  90. package/ts_build/src/chat/modules/CustomCommandsModule.js +12 -2
  91. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
  92. package/ts_build/src/chat/modules/InternalChatModule.js +16 -2
  93. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  94. package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -2
  95. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
  96. package/ts_build/src/chat/modules/RendererModule.js +17 -2
  97. package/ts_build/src/chat/modules/RendererModule.js.map +1 -1
  98. package/ts_build/src/chat/modules/SearchModule.js +12 -2
  99. package/ts_build/src/chat/modules/SearchModule.js.map +1 -1
  100. package/ts_build/src/chat/modules/SessionsModule.d.ts +6 -0
  101. package/ts_build/src/chat/modules/SessionsModule.js +113 -18
  102. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  103. package/ts_build/src/chat/modules/SetupModule.js +17 -2
  104. package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
  105. package/ts_build/src/chat/modules/ShellCommandModule.js +12 -2
  106. package/ts_build/src/chat/modules/ShellCommandModule.js.map +1 -1
  107. package/ts_build/src/chat/modules/SystemModule.js +12 -2
  108. package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
  109. package/ts_build/src/chat/types.d.ts +2 -0
  110. package/ts_build/src/cli.js +1 -1
  111. package/ts_build/src/cli.js.map +1 -1
  112. package/ts_build/src/clients/index.js +19 -4
  113. package/ts_build/src/clients/index.js.map +1 -1
  114. package/ts_build/src/commands/agent.js +1 -21
  115. package/ts_build/src/commands/agent.js.map +1 -1
  116. package/ts_build/src/commands/agents.d.ts +1 -1
  117. package/ts_build/src/commands/agents.js +174 -16
  118. package/ts_build/src/commands/agents.js.map +1 -1
  119. package/ts_build/src/commands/misc.js +3 -2
  120. package/ts_build/src/commands/misc.js.map +1 -1
  121. package/ts_build/src/login.d.ts +1 -1
  122. package/ts_build/src/login.js +53 -15
  123. package/ts_build/src/login.js.map +1 -1
  124. package/ts_build/src/services/AgentService.js +18 -2
  125. package/ts_build/src/services/AgentService.js.map +1 -1
  126. package/ts_build/src/services/AgentSyncFs.d.ts +3 -0
  127. package/ts_build/src/services/AgentSyncFs.js +62 -15
  128. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  129. package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +2 -0
  130. package/ts_build/src/services/AgentSyncKnowhowWeb.js +28 -6
  131. package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -1
  132. package/ts_build/src/services/BehaviorsService.js +12 -2
  133. package/ts_build/src/services/BehaviorsService.js.map +1 -1
  134. package/ts_build/src/services/DockerService.js +20 -8
  135. package/ts_build/src/services/DockerService.js.map +1 -1
  136. package/ts_build/src/services/EmbeddingService.js +16 -2
  137. package/ts_build/src/services/EmbeddingService.js.map +1 -1
  138. package/ts_build/src/services/EmbeddingsService.js +12 -2
  139. package/ts_build/src/services/EmbeddingsService.js.map +1 -1
  140. package/ts_build/src/services/KnowhowClient.d.ts +7 -2
  141. package/ts_build/src/services/KnowhowClient.js +108 -10
  142. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  143. package/ts_build/src/services/LazyToolsService.d.ts +1 -0
  144. package/ts_build/src/services/LazyToolsService.js +21 -2
  145. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  146. package/ts_build/src/services/Mcp.js +12 -2
  147. package/ts_build/src/services/Mcp.js.map +1 -1
  148. package/ts_build/src/services/McpServer.js +17 -2
  149. package/ts_build/src/services/McpServer.js.map +1 -1
  150. package/ts_build/src/services/MessageProcessor.js +16 -2
  151. package/ts_build/src/services/MessageProcessor.js.map +1 -1
  152. package/ts_build/src/services/MinimalToolsService.js +16 -2
  153. package/ts_build/src/services/MinimalToolsService.js.map +1 -1
  154. package/ts_build/src/services/SessionManager.js +19 -2
  155. package/ts_build/src/services/SessionManager.js.map +1 -1
  156. package/ts_build/src/services/SkillsService.js +16 -2
  157. package/ts_build/src/services/SkillsService.js.map +1 -1
  158. package/ts_build/src/services/SyncedAgentWatcher.js +16 -2
  159. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
  160. package/ts_build/src/services/SyncerService.d.ts +1 -0
  161. package/ts_build/src/services/SyncerService.js +27 -5
  162. package/ts_build/src/services/SyncerService.js.map +1 -1
  163. package/ts_build/src/services/TaskRegistry.js +12 -2
  164. package/ts_build/src/services/TaskRegistry.js.map +1 -1
  165. package/ts_build/src/services/Tools.js +16 -2
  166. package/ts_build/src/services/Tools.js.map +1 -1
  167. package/ts_build/src/services/index.d.ts +2 -0
  168. package/ts_build/src/services/index.js +4 -2
  169. package/ts_build/src/services/index.js.map +1 -1
  170. package/ts_build/src/services/modules/index.d.ts +2 -1
  171. package/ts_build/src/services/modules/index.js +1 -1
  172. package/ts_build/src/services/modules/index.js.map +1 -1
  173. package/ts_build/src/types.d.ts +1 -0
  174. package/ts_build/src/types.js.map +1 -1
  175. package/ts_build/src/util/Trace.d.ts +1 -3
  176. package/ts_build/src/utils/http.d.ts +6 -2
  177. package/ts_build/src/utils/http.js +8 -2
  178. package/ts_build/src/utils/http.js.map +1 -1
  179. package/ts_build/tests/unit/agents/historyRollback.test.d.ts +1 -0
  180. package/ts_build/tests/unit/agents/historyRollback.test.js +42 -0
  181. package/ts_build/tests/unit/agents/historyRollback.test.js.map +1 -0
  182. package/ts_build/tests/unit/auth/keyAuth.test.d.ts +1 -0
  183. package/ts_build/tests/unit/auth/keyAuth.test.js +77 -0
  184. package/ts_build/tests/unit/auth/keyAuth.test.js.map +1 -0
  185. package/ts_build/tests/unit/auth/keyManager.test.d.ts +1 -0
  186. package/ts_build/tests/unit/auth/keyManager.test.js +52 -0
  187. package/ts_build/tests/unit/auth/keyManager.test.js.map +1 -0
  188. package/ts_build/tests/unit/chat/historyCommandArgs.test.d.ts +1 -0
  189. package/ts_build/tests/unit/chat/historyCommandArgs.test.js +29 -0
  190. package/ts_build/tests/unit/chat/historyCommandArgs.test.js.map +1 -0
  191. package/ts_build/tests/unit/commands/agents.test.d.ts +1 -0
  192. package/ts_build/tests/unit/commands/agents.test.js +91 -0
  193. package/ts_build/tests/unit/commands/agents.test.js.map +1 -0
  194. package/ts_build/tests/unit/login.test.d.ts +1 -0
  195. package/ts_build/tests/unit/login.test.js +97 -0
  196. package/ts_build/tests/unit/login.test.js.map +1 -0
  197. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.d.ts +1 -0
  198. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js +32 -0
  199. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js.map +1 -0
  200. package/ts_build/tests/unit/services/LazyToolsService.test.d.ts +1 -0
  201. package/ts_build/tests/unit/services/LazyToolsService.test.js +27 -0
  202. package/ts_build/tests/unit/services/LazyToolsService.test.js.map +1 -0
  203. package/ts_build/tests/unit/services/SessionManager.test.d.ts +1 -0
  204. package/ts_build/tests/unit/services/SessionManager.test.js +72 -0
  205. package/ts_build/tests/unit/services/SessionManager.test.js.map +1 -0
  206. package/ts_build/tests/unit/services/SyncerService.test.d.ts +1 -0
  207. package/ts_build/tests/unit/services/SyncerService.test.js +40 -0
  208. package/ts_build/tests/unit/services/SyncerService.test.js.map +1 -0
  209. package/ts_build/tests/unit/util/Trace.test.d.ts +1 -0
  210. package/ts_build/tests/unit/util/Trace.test.js +107 -0
  211. package/ts_build/tests/unit/util/Trace.test.js.map +1 -0
  212. package/ts_build/tests/unit/utils/http.test.d.ts +1 -0
  213. package/ts_build/tests/unit/utils/http.test.js +69 -0
  214. package/ts_build/tests/unit/utils/http.test.js.map +1 -0
@@ -39,8 +39,11 @@ import {
39
39
  import { TaskInfo } from "../types";
40
40
  import { createAgent, agentConstructors, AgentName } from "../../agents";
41
41
  import { ToolCallEvent } from "../../agents/base/base";
42
+ import { rollbackAgentInteractions } from "../../agents/historyRollback";
42
43
  import { KnowhowSimpleClient } from "../../services/KnowhowClient";
44
+ import { TraceAll } from "../../util/Trace";
43
45
 
46
+ @TraceAll()
44
47
  export class AgentModule extends BaseChatModule {
45
48
  name = "agent";
46
49
  description = "Agent interaction functionality";
@@ -370,6 +373,7 @@ export class AgentModule extends BaseChatModule {
370
373
  }
371
374
  }
372
375
 
376
+
373
377
  async handleAgentCommand(args: string[]): Promise<void> {
374
378
  const context = this.chatService?.getContext();
375
379
 
@@ -639,7 +643,8 @@ export class AgentModule extends BaseChatModule {
639
643
  */
640
644
  public async resumeSession(
641
645
  sessionId: string,
642
- resumeReason?: string
646
+ resumeReason?: string,
647
+ rollback: number = 0
643
648
  ): Promise<void> {
644
649
  try {
645
650
  const session = this.sessionManager.loadSession(sessionId);
@@ -670,21 +675,18 @@ export class AgentModule extends BaseChatModule {
670
675
  threads.length > 0 && !Array.isArray(threads[0])
671
676
  ? [threads as unknown as Message[]]
672
677
  : (threads as Message[][]);
673
- const lastThread =
674
- normalizedThreads.length > 0
675
- ? normalizedThreads[normalizedThreads.length - 1]
676
- : [];
677
- const resumeMessages = [...lastThread];
678
+ const rolledBackThreads = rollbackAgentInteractions(normalizedThreads, rollback);
679
+ const resumeMessages = rolledBackThreads[rolledBackThreads.length - 1] || [];
678
680
 
679
681
  // Append the resume prompt to the last user message (or add a new one)
680
- const reversedIndex = [...lastThread]
682
+ const reversedIndex = [...resumeMessages]
681
683
  .reverse()
682
684
  .findIndex((e) => e.role === "user" && typeof e.content === "string");
683
685
 
684
686
  if (reversedIndex === -1) {
685
687
  resumeMessages.push({ role: "user", content: resumePrompt });
686
688
  } else {
687
- const actualIndex = lastThread.length - 1 - reversedIndex;
689
+ const actualIndex = resumeMessages.length - 1 - reversedIndex;
688
690
  resumeMessages[actualIndex] = {
689
691
  ...resumeMessages[actualIndex],
690
692
  content:
@@ -704,10 +706,17 @@ export class AgentModule extends BaseChatModule {
704
706
  }
705
707
 
706
708
  // Start agent with Knowhow task context and restored message history
709
+ const remoteTaskId = asRemoteTaskId(session.knowhowTaskId);
707
710
  const { agent, taskId } = await this.setupAgent({
708
711
  agentName,
709
712
  input: resumePrompt,
710
- messageId: session.knowhowMessageId,
713
+ // Resume attaches to the persisted remote task; it must not create a
714
+ // new task from the original message placeholder.
715
+ remoteTaskId,
716
+ remoteMessageId: session.knowhowMessageId,
717
+ chatSessionId: session.chatSessionId,
718
+ syncRemote: !!remoteTaskId,
719
+ taskId: session.taskId || sessionId,
711
720
  chatHistory: [],
712
721
  run: false, // Don't run yet, we need to set up event listeners first
713
722
  // Restore the exact model/provider/reasoning the original run used.
@@ -715,8 +724,15 @@ export class AgentModule extends BaseChatModule {
715
724
  provider: session.provider,
716
725
  reasoningEffort: session.reasoningEffort,
717
726
  summarizeReasoning: session.summarizeReasoning,
727
+ enabledTools: session.enabledTools,
718
728
  });
719
729
 
730
+ // Match new interactive threads: local-only sessions get a fresh remote
731
+ // task when auto-sync is enabled.
732
+ if (!remoteTaskId && this.remoteSyncModule?.isAutoSyncEnabled()) {
733
+ await this.remoteSyncModule.syncTask(taskId);
734
+ }
735
+
720
736
  // After resume finishes, revert to normal chat (non-agent mode) so the
721
737
  // user can start a fresh conversation instead of staying locked in agent mode.
722
738
  agent.agentEvents.once(agent.eventTypes.done, () => {
@@ -780,6 +796,9 @@ export class AgentModule extends BaseChatModule {
780
796
  public async setupAgent(options: {
781
797
  agentName: string;
782
798
  input: string;
799
+ remoteTaskId?: string;
800
+ remoteMessageId?: string;
801
+ chatSessionId?: string;
783
802
  messageId?: string;
784
803
  syncFs?: boolean;
785
804
  syncRemote?: boolean;
@@ -795,6 +814,7 @@ export class AgentModule extends BaseChatModule {
795
814
  // precedence over the module-level CLI defaults.
796
815
  reasoningEffort?: CompletionOptions["reasoning_effort"];
797
816
  summarizeReasoning?: boolean;
817
+ enabledTools?: string[];
798
818
  }) {
799
819
  const { input, chatHistory = [], agentName } = options;
800
820
  const agentContext = services().Agents.getAgentContext();
@@ -815,9 +835,18 @@ export class AgentModule extends BaseChatModule {
815
835
  }
816
836
  }
817
837
 
838
+ if (options.enabledTools) {
839
+ agent.restoreEnabledTools(options.enabledTools);
840
+ }
841
+
818
842
  let done = false;
819
843
  let output = "Done";
820
844
  const taskId = options.taskId || this.sessionManager.generateTaskId(input);
845
+ // Backwards compatibility for callers that historically supplied a remote
846
+ // UUID as taskId with syncRemote. Timestamped local slugs never qualify.
847
+ const remoteTaskId =
848
+ asRemoteTaskId(options.remoteTaskId) ||
849
+ (options.syncRemote ? asRemoteTaskId(taskId) : undefined);
821
850
  let knowhowTaskId: string | undefined;
822
851
 
823
852
  try {
@@ -835,10 +864,9 @@ export class AgentModule extends BaseChatModule {
835
864
  // Create task info object
836
865
  let taskInfo: TaskInfo = {
837
866
  taskId,
838
- knowhowMessageId: options.messageId,
839
- // When pushing to a remote task via --sync-remote, the taskId IS the
840
- // remote task ID. Otherwise it will be set after creating a chat task.
841
- knowhowTaskId: options.syncRemote ? taskId : undefined,
867
+ knowhowMessageId: options.remoteMessageId || options.messageId,
868
+ knowhowTaskId: remoteTaskId,
869
+ chatSessionId: options.chatSessionId,
842
870
  agentName,
843
871
  agent,
844
872
  initialInput: input,
@@ -846,6 +874,7 @@ export class AgentModule extends BaseChatModule {
846
874
  status: "running",
847
875
  startTime: Date.now(),
848
876
  totalCost: 0,
877
+ enabledTools: agent.getEnabledToolNames(),
849
878
  };
850
879
 
851
880
  // Add to task registry
@@ -862,6 +891,7 @@ export class AgentModule extends BaseChatModule {
862
891
  const syncTaskId = await syncer.createTask({
863
892
  taskId,
864
893
  prompt: input,
894
+ remoteTaskId,
865
895
  messageId: options.messageId,
866
896
  syncFs: options.syncFs,
867
897
  syncRemote: options.syncRemote,
@@ -870,8 +900,7 @@ export class AgentModule extends BaseChatModule {
870
900
  });
871
901
 
872
902
  const webTaskId = syncer.getCreatedWebTaskId();
873
- knowhowTaskId = webTaskId;
874
- taskInfo.knowhowTaskId = webTaskId || syncTaskId;
903
+ taskInfo.knowhowTaskId = webTaskId;
875
904
  this.taskRegistry.register(taskId, taskInfo);
876
905
 
877
906
  await syncer.setupAgentSync(agent, syncTaskId);
@@ -885,6 +914,7 @@ export class AgentModule extends BaseChatModule {
885
914
  taskInfo.provider = agent.getProvider();
886
915
  taskInfo.reasoningEffort = agent.getReasoningEffort?.();
887
916
  taskInfo.summarizeReasoning = agent.getSummarizeReasoning?.();
917
+ taskInfo.enabledTools = agent.getEnabledToolNames();
888
918
  // updateSession pulls the (mutated) taskInfo from the registry.
889
919
  this.updateSession(taskId, agent.getThreads());
890
920
  };
@@ -1203,6 +1233,8 @@ export class AgentModule extends BaseChatModule {
1203
1233
  reasoningEffort?: CompletionOptions["reasoning_effort"];
1204
1234
  summarizeReasoning?: boolean;
1205
1235
  agentName?: string;
1236
+ remoteTaskId?: string;
1237
+ enabledTools?: string[];
1206
1238
  }> {
1207
1239
  const localMetadataPath = path.join(
1208
1240
  ".knowhow",
@@ -1222,6 +1254,12 @@ export class AgentModule extends BaseChatModule {
1222
1254
  reasoningEffort: metadata.reasoningEffort,
1223
1255
  summarizeReasoning: metadata.summarizeReasoning,
1224
1256
  agentName: metadata.agentName,
1257
+ remoteTaskId: asRemoteTaskId(
1258
+ metadata.remoteTaskId || metadata.knowhowTaskId
1259
+ ),
1260
+ enabledTools: Array.isArray(metadata.enabledTools)
1261
+ ? metadata.enabledTools
1262
+ : undefined,
1225
1263
  };
1226
1264
  } catch (e) {
1227
1265
  console.warn(`⚠️ Failed to parse local metadata for settings: ${e.message}`);
@@ -1233,7 +1271,7 @@ export class AgentModule extends BaseChatModule {
1233
1271
 
1234
1272
  /**
1235
1273
  * Resume an agent from a set of existing message threads
1236
- * Used by the CLI --resume flag to continue crashed/failed tasks
1274
+ * Used by `knowhow agents resume` and interactive history commands.
1237
1275
  */
1238
1276
  public async resumeFromMessages(options: {
1239
1277
  agentName: string;
@@ -1241,15 +1279,22 @@ export class AgentModule extends BaseChatModule {
1241
1279
  threads: Message[][];
1242
1280
  messageId?: string;
1243
1281
  taskId?: string;
1282
+ remoteTaskId?: string;
1283
+ syncFs?: boolean;
1244
1284
  interactive?: boolean;
1245
- // Restore the exact model/provider/reasoning the original run used.
1285
+ // Restore the exact model/provider/reasoning/tools the original run used.
1246
1286
  model?: string;
1247
1287
  provider?: string;
1248
1288
  reasoningEffort?: CompletionOptions["reasoning_effort"];
1249
1289
  summarizeReasoning?: boolean;
1250
- }): Promise<{ taskCompleted: Promise<string> }> {
1290
+ enabledTools?: string[];
1291
+ rollback?: number;
1292
+ fork?: boolean;
1293
+ }): Promise<{ taskCompleted: Promise<string>; taskId: string }> {
1251
1294
  const { agentName, input, threads, messageId, taskId } = options;
1295
+ const action = options.fork ? "forking" : "resuming";
1252
1296
 
1297
+ const remoteTaskId = asRemoteTaskId(options.remoteTaskId);
1253
1298
  // Try to extract the original request from the first user message in threads
1254
1299
  let originalRequest = "";
1255
1300
  if (threads && threads.length > 0) {
@@ -1269,7 +1314,7 @@ export class AgentModule extends BaseChatModule {
1269
1314
 
1270
1315
  // Build the resume prompt
1271
1316
  const resumePrompt = [
1272
- "You are resuming a previously started task.",
1317
+ `You are ${action} a previously started task.`,
1273
1318
  originalRequest ? `ORIGINAL REQUEST: ${originalRequest}` : "",
1274
1319
  "Please continue from where you left off.",
1275
1320
  input ? input : "",
@@ -1277,13 +1322,20 @@ export class AgentModule extends BaseChatModule {
1277
1322
  .filter(Boolean)
1278
1323
  .join("\n");
1279
1324
 
1280
- // Flatten threads into a single messages array for the agent
1325
+ // Roll back at assistant boundaries so tool calls and their results are
1326
+ // removed together, never leaving an invalid orphaned tool message.
1327
+ const rolledBackThreads = rollbackAgentInteractions(
1328
+ threads || [],
1329
+ options.rollback || 0
1330
+ );
1281
1331
  const lastThread =
1282
- threads && threads.length > 0 ? threads[threads.length - 1] : [];
1332
+ rolledBackThreads.length > 0
1333
+ ? rolledBackThreads[rolledBackThreads.length - 1]
1334
+ : [];
1283
1335
  const resumeMessages = [...lastThread];
1284
1336
 
1285
1337
  // find last user message index
1286
- const resumeIndex = lastThread
1338
+ const resumeIndex = [...lastThread]
1287
1339
  .reverse()
1288
1340
  .findIndex((e) => e.role === "user" && typeof e.content === "string");
1289
1341
 
@@ -1293,41 +1345,60 @@ export class AgentModule extends BaseChatModule {
1293
1345
  content: resumePrompt,
1294
1346
  });
1295
1347
  } else {
1296
- const actualIndex = lastThread.length - 1 - resumeIndex;
1297
- const lastUserMessage = resumeMessages[actualIndex];
1298
- lastUserMessage.content += `\n\n<Workflow>[RESUME CONTEXT]: ${resumePrompt}</Workflow>`;
1348
+ const actualIndex = resumeMessages.length - 1 - resumeIndex;
1349
+ resumeMessages[actualIndex] = {
1350
+ ...resumeMessages[actualIndex],
1351
+ content: `${resumeMessages[actualIndex].content}\n\n<Workflow>[RESUME CONTEXT]: ${resumePrompt}</Workflow>`,
1352
+ };
1299
1353
  }
1300
1354
 
1301
1355
  const result = await this.setupAgent({
1302
1356
  agentName,
1303
1357
  input: resumePrompt,
1304
- messageId,
1358
+ // An existing task UUID always wins. messageId is only the create-new
1359
+ // path when there is no remote task to reattach to.
1360
+ messageId: remoteTaskId ? undefined : messageId,
1361
+ remoteMessageId: remoteTaskId ? messageId : undefined,
1305
1362
  taskId,
1306
- // When resuming a remote task (no messageId), keep pushing work to the
1307
- // remote task identified by taskId.
1308
- syncRemote: !messageId && !!taskId,
1363
+ syncFs: options.syncFs,
1364
+ remoteTaskId,
1309
1365
  run: false,
1310
- // Restore the model/provider/reasoning captured from the task metadata.
1366
+ syncRemote: !options.fork && !!remoteTaskId,
1311
1367
  model: options.model,
1312
1368
  provider: options.provider,
1313
1369
  reasoningEffort: options.reasoningEffort,
1314
1370
  summarizeReasoning: options.summarizeReasoning,
1371
+ enabledTools: options.enabledTools,
1315
1372
  });
1316
1373
 
1317
1374
  // Interactive (chat REPL) resume: route through the attached chat loop so the
1318
1375
  // renderer is wired the same way as a normal chat interaction / /attach, and
1319
1376
  // the user gets agent:attached mode commands (/poke, /detach, /kill, /logs).
1320
1377
  if (options.interactive) {
1378
+ // A local-only historical task has no remote UUID to reattach to. When
1379
+ // auto-sync is enabled, create the remote task through the exact same
1380
+ // path used for a newly started interactive thread.
1381
+ if (
1382
+ !options.fork &&
1383
+ !remoteTaskId &&
1384
+ this.remoteSyncModule?.isAutoSyncEnabled()
1385
+ ) {
1386
+ await this.remoteSyncModule.syncTask(result.taskId);
1387
+ }
1388
+
1321
1389
  await this.attachedAgentChatLoop(
1322
1390
  result.taskId,
1323
1391
  result.agent,
1324
1392
  resumePrompt,
1325
1393
  resumeMessages
1326
1394
  );
1327
- return { taskCompleted: result.taskCompleted };
1395
+ return {
1396
+ taskCompleted: result.taskCompleted,
1397
+ taskId: result.taskId,
1398
+ };
1328
1399
  }
1329
1400
 
1330
- // Non-interactive (headless CLI `knowhow agent --resume`) path: setupAgent was
1401
+ // Non-interactive (headless CLI `knowhow agents resume`) path: setupAgent was
1331
1402
  // called with run:false, so no inline render listeners were registered. Wire
1332
1403
  // rendering here (same keyed listeners as attachedAgentChatLoop) so the CLI
1333
1404
  // still shows the agent's messages / tool calls, then start the agent.
@@ -1339,7 +1410,7 @@ export class AgentModule extends BaseChatModule {
1339
1410
  );
1340
1411
  result.agent.call(resumePrompt, resumeMessages);
1341
1412
 
1342
- return { taskCompleted: result.taskCompleted };
1413
+ return { taskCompleted: result.taskCompleted, taskId: result.taskId };
1343
1414
  }
1344
1415
 
1345
1416
  /**
@@ -1493,3 +1564,13 @@ export class AgentModule extends BaseChatModule {
1493
1564
  }
1494
1565
  }
1495
1566
  }
1567
+
1568
+ /** Accept only backend task UUIDs; local task slugs intentionally fail. */
1569
+ function asRemoteTaskId(value: string | undefined): string | undefined {
1570
+ return value &&
1571
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
1572
+ value
1573
+ )
1574
+ ? value
1575
+ : undefined;
1576
+ }
@@ -3,12 +3,14 @@
3
3
  */
4
4
 
5
5
  import { BaseChatModule } from "./BaseChatModule";
6
+ import { TraceAll } from "../../util/Trace";
6
7
  import { ChatCommand, ChatMode, ChatContext } from "../types";
7
8
  import { ChatInteraction } from "../../types";
8
9
  import { Models } from "../../ai";
9
10
  import { services } from "../../services/index";
10
11
  import { Marked } from "../../utils/index";
11
12
 
13
+ @TraceAll()
12
14
  export class AskModule extends BaseChatModule {
13
15
  name = "ask";
14
16
  description = "Basic AI question/answer functionality";
@@ -9,7 +9,6 @@ import {
9
9
  ChatMode,
10
10
  ChatContext,
11
11
  } from "../types";
12
-
13
12
  export abstract class BaseChatModule implements ChatModule {
14
13
  public abstract name: string;
15
14
  public abstract description: string;
@@ -1,12 +1,14 @@
1
1
  import { BaseChatModule } from "./BaseChatModule";
2
2
  import { ChatCommand, ChatService, CommandResult } from "../types";
3
3
  import { getLanguageConfig } from "../../config";
4
+ import { TraceAll } from "../../util/Trace";
4
5
 
5
6
  /**
6
7
  * CustomCommandsModule - Loads `/command`-style keys from the language config
7
8
  * and registers them as chat commands. When invoked, the resolved language
8
9
  * sources are sent to the active agent via addPendingUserMessage.
9
10
  */
11
+ @TraceAll()
10
12
  export class CustomCommandsModule extends BaseChatModule {
11
13
  name = "custom-commands";
12
14
  description =
@@ -1,4 +1,5 @@
1
1
  import { ChatModule } from "../types";
2
+ import { TraceAll } from "../../util/Trace";
2
3
  import { CliChatService } from "../CliChatService";
3
4
  import { AgentModule } from "./AgentModule";
4
5
  import { ChatCommand, ChatMode, ChatContext } from "../types";
@@ -14,6 +15,7 @@ import { SessionsModule } from "./SessionsModule";
14
15
  import { RemoteSyncModule } from "./RemoteSyncModule";
15
16
  import { ClipboardImageModule } from "./ClipboardImageModule";
16
17
 
18
+ @TraceAll()
17
19
  export class InternalChatModule implements ChatModule {
18
20
  private clipboardImageModule = new ClipboardImageModule();
19
21
  private chatService?: CliChatService;
@@ -3,16 +3,19 @@
3
3
  * Allows CLI users to push agent tasks to the remote KnowHow web app.
4
4
  */
5
5
  import { BaseChatModule } from "./BaseChatModule";
6
+ import { TraceAll } from "../../util/Trace";
6
7
  import { ChatCommand, ChatContext, ChatService } from "../types";
7
8
  import {
8
9
  KnowhowSimpleClient,
9
10
  KNOWHOW_API_URL,
10
11
  } from "../../services/KnowhowClient";
11
12
  import { AgentSyncKnowhowWeb } from "../../services/AgentSyncKnowhowWeb";
13
+ import { AgentSyncFs } from "../../services/AgentSyncFs";
12
14
  import { AgentModule } from "./AgentModule";
13
15
  import { TaskInfo } from "../types";
14
16
  import { getConfig, updateConfig } from "../../config";
15
17
 
18
+ @TraceAll()
16
19
  export class RemoteSyncModule extends BaseChatModule {
17
20
  name = "remote-sync";
18
21
  description = "Remote sync functionality (/sync:remote, /sync:status)";
@@ -310,6 +313,7 @@ export class RemoteSyncModule extends BaseChatModule {
310
313
  taskInfo.knowhowMessageId = messageId;
311
314
  taskInfo.knowhowTaskId = knowhowTaskId;
312
315
  taskInfo.chatSessionId = sessionId;
316
+ await AgentSyncFs.persistRemoteIdentity(taskId, knowhowTaskId, messageId);
313
317
 
314
318
  // Update session manager
315
319
  const sessionManager = this.agentModule.getSessionManager();
@@ -2,6 +2,7 @@
2
2
  * RendererModule - Allows switching the active renderer mid-session via /render command
3
3
  */
4
4
  import { BaseChatModule } from "./BaseChatModule";
5
+ import { TraceAll } from "../../util/Trace";
5
6
  import { ChatCommand, ChatMode, ChatContext, ChatService } from "../types";
6
7
  import { loadRenderer } from "../renderer/loadRenderer";
7
8
  import { ConsoleRenderer } from "../renderer";
@@ -10,6 +11,7 @@ import { getConfig, updateConfig } from "../../config";
10
11
 
11
12
  const BUILTIN_RENDERERS = ["basic", "compact", "fancy"];
12
13
 
14
+ @TraceAll()
13
15
  export class RendererModule extends BaseChatModule {
14
16
  name = "renderer";
15
17
  description = "Renderer switching functionality";
@@ -1,10 +1,12 @@
1
1
  import { BaseChatModule } from "./BaseChatModule";
2
+ import { TraceAll } from "../../util/Trace";
2
3
  import { ChatContext, ChatCommand } from "../types";
3
4
  import { getConfiguredEmbeddingMap, queryEmbedding } from "../../embeddings";
4
5
  import { getConfig } from "../../config";
5
6
  import { ask } from "../../utils";
6
7
  import { marked } from "marked";
7
8
 
9
+ @TraceAll()
8
10
  export class SearchModule extends BaseChatModule {
9
11
  name = "Search";
10
12
  description = "Search through embeddings for relevant information";