@tyvm/knowhow 0.0.102 → 0.0.104

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 (62) hide show
  1. package/package.json +2 -2
  2. package/src/agents/base/base.ts +3 -0
  3. package/src/chat/modules/AgentModule.ts +24 -10
  4. package/src/chat/modules/InternalChatModule.ts +6 -0
  5. package/src/chat/modules/RemoteSyncModule.ts +447 -0
  6. package/src/chat/types.ts +2 -0
  7. package/src/clients/pricing/catalog.ts +287 -0
  8. package/src/clients/pricing/index.ts +2 -0
  9. package/src/config.ts +2 -0
  10. package/src/fileSync.ts +15 -2
  11. package/src/hashes.ts +33 -0
  12. package/src/services/AgentSyncFs.ts +44 -14
  13. package/src/services/AgentSyncKnowhowWeb.ts +27 -5
  14. package/src/services/KnowhowClient.ts +61 -0
  15. package/src/services/SessionManager.ts +2 -0
  16. package/src/services/script-execution/ScriptPolicy.ts +0 -44
  17. package/src/types.ts +3 -0
  18. package/src/worker.ts +70 -4
  19. package/ts_build/package.json +2 -2
  20. package/ts_build/src/agents/base/base.js +1 -0
  21. package/ts_build/src/agents/base/base.js.map +1 -1
  22. package/ts_build/src/chat/modules/AgentModule.d.ts +2 -1
  23. package/ts_build/src/chat/modules/AgentModule.js +12 -7
  24. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  25. package/ts_build/src/chat/modules/InternalChatModule.d.ts +1 -0
  26. package/ts_build/src/chat/modules/InternalChatModule.js +6 -0
  27. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  28. package/ts_build/src/chat/modules/RemoteSyncModule.d.ts +27 -0
  29. package/ts_build/src/chat/modules/RemoteSyncModule.js +282 -0
  30. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -0
  31. package/ts_build/src/chat/types.d.ts +2 -0
  32. package/ts_build/src/clients/pricing/catalog.d.ts +28 -0
  33. package/ts_build/src/clients/pricing/catalog.js +179 -0
  34. package/ts_build/src/clients/pricing/catalog.js.map +1 -0
  35. package/ts_build/src/clients/pricing/index.d.ts +2 -0
  36. package/ts_build/src/clients/pricing/index.js +8 -1
  37. package/ts_build/src/clients/pricing/index.js.map +1 -1
  38. package/ts_build/src/config.js +1 -0
  39. package/ts_build/src/config.js.map +1 -1
  40. package/ts_build/src/fileSync.js +8 -1
  41. package/ts_build/src/fileSync.js.map +1 -1
  42. package/ts_build/src/hashes.d.ts +2 -0
  43. package/ts_build/src/hashes.js +23 -0
  44. package/ts_build/src/hashes.js.map +1 -1
  45. package/ts_build/src/services/AgentSyncFs.d.ts +6 -3
  46. package/ts_build/src/services/AgentSyncFs.js +30 -13
  47. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  48. package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +1 -0
  49. package/ts_build/src/services/AgentSyncKnowhowWeb.js +13 -2
  50. package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -1
  51. package/ts_build/src/services/KnowhowClient.d.ts +21 -0
  52. package/ts_build/src/services/KnowhowClient.js +10 -0
  53. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  54. package/ts_build/src/services/Mcp.d.ts +219 -406
  55. package/ts_build/src/services/SessionManager.js +2 -0
  56. package/ts_build/src/services/SessionManager.js.map +1 -1
  57. package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -35
  58. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -1
  59. package/ts_build/src/types.d.ts +2 -0
  60. package/ts_build/src/types.js.map +1 -1
  61. package/ts_build/src/worker.js +51 -2
  62. package/ts_build/src/worker.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.102",
3
+ "version": "0.0.104",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -53,7 +53,7 @@
53
53
  "diff": "^5.2.0",
54
54
  "express": "^4.19.2",
55
55
  "gitignore-to-glob": "^0.3.0",
56
- "glob": "11.0.3",
56
+ "glob": "11.1.0",
57
57
  "isolated-vm": "^5.0.4",
58
58
  "jiti": "^2.6.1",
59
59
  "marked": "^10.0.0",
@@ -731,6 +731,9 @@ export abstract class BaseAgent implements IAgent {
731
731
  });
732
732
  const doneMsg = finalMessage.content || "Done";
733
733
 
734
+ // Ensure the final thread state (including the finalAnswer result) is
735
+ // captured before emitting done, so syncers see the complete thread.
736
+ this.updateCurrentThread(messages);
734
737
  this.agentEvents.emit(this.eventTypes.done, doneMsg);
735
738
  this.status = this.eventTypes.done;
736
739
  return doneMsg;
@@ -48,7 +48,6 @@ export class AgentModule extends BaseChatModule {
48
48
  // Service instances for task management, session management, and synchronization
49
49
  private taskRegistry: TaskRegistry;
50
50
  private sessionManager: SessionManager;
51
- private syncer: SyncerService;
52
51
  /** Timestamp when this process started - used to filter sessions */
53
52
  private processStartTime: number = Date.now();
54
53
  /** Currently attached agent task ID */
@@ -60,6 +59,9 @@ export class AgentModule extends BaseChatModule {
60
59
  private _wireAgentEvents: EventService | undefined;
61
60
  private _wireTaskId: string | undefined;
62
61
  private _wireAgentName: string | undefined;
62
+ /** Optional reference to RemoteSyncModule for auto-sync on new tasks */
63
+ private remoteSyncModule: any | undefined;
64
+
63
65
  private _wireEventTypes:
64
66
  | { toolCall?: string; toolUsed?: string; agentSay?: string; done: string }
65
67
  | undefined;
@@ -68,9 +70,17 @@ export class AgentModule extends BaseChatModule {
68
70
  super();
69
71
  this.taskRegistry = new TaskRegistry();
70
72
  this.sessionManager = new SessionManager();
71
- this.syncer = new SyncerService();
72
73
  }
73
74
 
75
+ /**
76
+ * Set the RemoteSyncModule reference for auto-sync support.
77
+ * Called from InternalChatModule after both modules are created.
78
+ */
79
+ public setRemoteSyncModule(module: any): void {
80
+ this.remoteSyncModule = module;
81
+ }
82
+
83
+
74
84
  getCommands(): ChatCommand[] {
75
85
  return [
76
86
  {
@@ -645,11 +655,12 @@ Please continue from where you left off and complete the original request.
645
655
  // Save initial session
646
656
  this.saveSession(taskId, taskInfo, []);
647
657
 
648
- // Reset sync services before setting up new task (removes old listeners)
649
- this.syncer.reset();
658
+ // Create a fresh SyncerService per agent task so that detaching from one
659
+ // agent and starting another doesn't tear down the first agent's sync.
660
+ const syncer = new SyncerService();
650
661
 
651
662
  // Create sync task (SyncerService decides web vs fs internally)
652
- const syncTaskId = await this.syncer.createTask({
663
+ const syncTaskId = await syncer.createTask({
653
664
  taskId,
654
665
  prompt: input,
655
666
  messageId: options.messageId,
@@ -658,14 +669,12 @@ Please continue from where you left off and complete the original request.
658
669
  agentName,
659
670
  });
660
671
 
661
- // Update TaskInfo with the sync task ID
662
- const webTaskId = this.syncer.getCreatedWebTaskId();
672
+ const webTaskId = syncer.getCreatedWebTaskId();
663
673
  knowhowTaskId = webTaskId;
664
674
  taskInfo.knowhowTaskId = webTaskId || syncTaskId;
665
675
  this.taskRegistry.register(taskId, taskInfo);
666
676
 
667
- // Wire up event listeners on the agent
668
- await this.syncer.setupAgentSync(agent, syncTaskId);
677
+ await syncer.setupAgentSync(agent, syncTaskId);
669
678
 
670
679
  // Set up session update listener
671
680
  const threadUpdateHandler = async (threadState: any) => {
@@ -784,7 +793,7 @@ Please continue from where you left off and complete the original request.
784
793
  taskInfo = this.taskRegistry.get(taskId);
785
794
 
786
795
  // Wait for AgentSync to finish before resolving
787
- await this.syncer.waitForFinalization();
796
+ await syncer.waitForFinalization();
788
797
 
789
798
  if (taskInfo) {
790
799
  taskInfo.status = "completed";
@@ -1008,6 +1017,11 @@ Please continue from where you left off and complete the original request.
1008
1017
  run: false, // Don't run yet, we need to set up event listeners first
1009
1018
  });
1010
1019
 
1020
+ // If auto-sync is enabled, push this task to the remote KnowHow app
1021
+ if (this.remoteSyncModule?.isAutoSyncEnabled()) {
1022
+ await this.remoteSyncModule.syncTask(taskId);
1023
+ }
1024
+
1011
1025
  await this.attachedAgentChatLoop(taskId, agent, formattedPrompt);
1012
1026
 
1013
1027
  return { taskId };
@@ -11,6 +11,7 @@ import { CustomCommandsModule } from "./CustomCommandsModule";
11
11
  import { ShellCommandModule } from "./ShellCommandModule";
12
12
  import { RendererModule } from "./RendererModule";
13
13
  import { SessionsModule } from "./SessionsModule";
14
+ import { RemoteSyncModule } from "./RemoteSyncModule";
14
15
 
15
16
  export class InternalChatModule implements ChatModule {
16
17
  private chatService?: CliChatService;
@@ -28,11 +29,14 @@ export class InternalChatModule implements ChatModule {
28
29
  private customCommandsModule = new CustomCommandsModule();
29
30
  private shellCommandModule = new ShellCommandModule();
30
31
  private rendererModule: RendererModule;
32
+ private remoteSyncModule: RemoteSyncModule;
31
33
 
32
34
  constructor() {
33
35
  this.rendererModule = new RendererModule(this.agentModule);
34
36
  this.setupModule = new SetupModule(this.agentModule);
35
37
  this.sessionsModule = new SessionsModule(this.agentModule);
38
+ this.remoteSyncModule = new RemoteSyncModule(this.agentModule);
39
+ this.agentModule.setRemoteSyncModule(this.remoteSyncModule);
36
40
  }
37
41
 
38
42
  async initialize(chatService: CliChatService): Promise<void> {
@@ -51,6 +55,7 @@ export class InternalChatModule implements ChatModule {
51
55
  await this.systemModule.initialize(chatService);
52
56
  await this.setupModule.initialize(chatService);
53
57
  await this.customCommandsModule.initialize(chatService);
58
+ await this.remoteSyncModule.initialize(chatService);
54
59
  await this.shellCommandModule.initialize(chatService);
55
60
 
56
61
  // Register our own commands (exit and multi) - not duplicated by BaseChatModule
@@ -84,6 +89,7 @@ export class InternalChatModule implements ChatModule {
84
89
  ...this.customCommandsModule.getCommands(),
85
90
  ...this.shellCommandModule.getCommands(),
86
91
  ...this.rendererModule.getCommands(),
92
+ ...this.remoteSyncModule.getCommands(),
87
93
  {
88
94
  name: "exit",
89
95
  description: "Exit the chat",
@@ -0,0 +1,447 @@
1
+ /**
2
+ * RemoteSyncModule - Handles /sync:remote, /sync:remote:off, /sync:status commands
3
+ * Allows CLI users to push agent tasks to the remote KnowHow web app.
4
+ */
5
+ import { BaseChatModule } from "./BaseChatModule";
6
+ import { ChatCommand, ChatContext, ChatService } from "../types";
7
+ import {
8
+ KnowhowSimpleClient,
9
+ KNOWHOW_API_URL,
10
+ } from "../../services/KnowhowClient";
11
+ import { AgentSyncKnowhowWeb } from "../../services/AgentSyncKnowhowWeb";
12
+ import { AgentModule } from "./AgentModule";
13
+ import { TaskInfo } from "../types";
14
+ import { getConfig, updateConfig } from "../../config";
15
+
16
+ export class RemoteSyncModule extends BaseChatModule {
17
+ name = "remote-sync";
18
+ description = "Remote sync functionality (/sync:remote, /sync:status)";
19
+
20
+ /** Per-process remote session ID - created once, reused for all tasks in this terminal */
21
+ private remoteSessionId: string | undefined;
22
+
23
+ /** Worker ID from config (set when running as a worker) */
24
+ private workerId: string | undefined;
25
+
26
+ /** Whether remote auto-sync is enabled for new tasks */
27
+ private autoSync: boolean = false;
28
+
29
+ /** Count of messages synced in this terminal session */
30
+ private syncedMessageCount: number = 0;
31
+
32
+ /** Reference to AgentModule for accessing task registry */
33
+ private agentModule: AgentModule;
34
+
35
+ /** KnowHow API client */
36
+ private client: KnowhowSimpleClient;
37
+
38
+ constructor(agentModule: AgentModule) {
39
+ super();
40
+ this.agentModule = agentModule;
41
+ this.client = new KnowhowSimpleClient(KNOWHOW_API_URL);
42
+ }
43
+
44
+ /**
45
+ * On initialize, read config to set initial autoSync state.
46
+ */
47
+ async initialize(service: ChatService): Promise<void> {
48
+ await super.initialize(service);
49
+ const config = await getConfig();
50
+ if (config.syncRemote === true) {
51
+ this.autoSync = true;
52
+ console.log("šŸ“” Remote auto-sync enabled (from config syncRemote: true)");
53
+ }
54
+ if (config.worker?.workerId) {
55
+ this.workerId = config.worker.workerId;
56
+ console.log(`šŸ”— Worker ID loaded: ${this.workerId}`);
57
+ }
58
+ }
59
+
60
+ getCommands(): ChatCommand[] {
61
+ return [
62
+ {
63
+ name: "sync:remote",
64
+ description:
65
+ "Push the current agent task to the remote KnowHow app. Creates a remote session+message if needed.",
66
+ handler: this.handleSyncRemote.bind(this),
67
+ },
68
+ {
69
+ name: "share",
70
+ description: "Alias for /sync:remote",
71
+ handler: this.handleSyncRemote.bind(this),
72
+ },
73
+ {
74
+ name: "sync:remote:on",
75
+ description: "Enable remote auto-sync for all future agent tasks and save to config",
76
+ handler: this.handleSyncRemoteOn.bind(this),
77
+ },
78
+ {
79
+ name: "sync:remote:off",
80
+ description: "Disable auto remote sync for future tasks",
81
+ handler: this.handleSyncRemoteOff.bind(this),
82
+ },
83
+ {
84
+ name: "sync:status",
85
+ description: "Show current remote sync state",
86
+ handler: this.handleSyncStatus.bind(this),
87
+ },
88
+ ];
89
+ }
90
+
91
+ /**
92
+ * Get the active task: first checks context for activeAgentTaskId,
93
+ * then falls back to the most recently registered task.
94
+ */
95
+ private getActiveTask(
96
+ taskIdArg?: string
97
+ ): { taskId: string; taskInfo: TaskInfo } | undefined {
98
+ const registry = this.agentModule.getTaskRegistry();
99
+ const context = this.chatService?.getContext();
100
+
101
+ // If explicit taskId provided, look it up
102
+ if (taskIdArg) {
103
+ const taskInfo = registry.get(taskIdArg);
104
+ if (taskInfo) return { taskId: taskIdArg, taskInfo };
105
+ console.log(`āš ļø Task "${taskIdArg}" not found in registry.`);
106
+ return undefined;
107
+ }
108
+
109
+ // Use context's activeAgentTaskId
110
+ const activeId = context?.activeAgentTaskId;
111
+ if (activeId) {
112
+ const taskInfo = registry.get(activeId);
113
+ if (taskInfo) return { taskId: activeId, taskInfo };
114
+ }
115
+
116
+ // Fall back to most recently registered task
117
+ const allTasks = registry.getEntries();
118
+ if (allTasks.length > 0) {
119
+ const [taskId, taskInfo] = allTasks[allTasks.length - 1];
120
+ return { taskId, taskInfo };
121
+ }
122
+
123
+ return undefined;
124
+ }
125
+
126
+ /**
127
+ * Extract a remote session UUID from a URL or raw UUID string.
128
+ * e.g. "http://localhost:3000/chat/47838f91-e918-4f77-9122-0160531f7d2a"
129
+ * or "47838f91-e918-4f77-9122-0160531f7d2a"
130
+ */
131
+ private extractSessionId(input: string): string | undefined {
132
+ // Match a UUID pattern (8-4-4-4-12 hex chars)
133
+ const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
134
+ const match = input.match(uuidRegex);
135
+ if (match) {
136
+ return match[0];
137
+ }
138
+ return undefined;
139
+ }
140
+
141
+ /**
142
+ * Ensure we have a remote session for this terminal process.
143
+ * On first call, creates a new session. Subsequent calls reuse it.
144
+ */
145
+ private async ensureRemoteSession(
146
+ title: string,
147
+ prompt: string
148
+ ): Promise<string> {
149
+ if (this.remoteSessionId) {
150
+ console.log(
151
+ `šŸ“” Using existing remote session: ${this.remoteSessionId}`
152
+ );
153
+ return this.remoteSessionId;
154
+ }
155
+
156
+ console.log("šŸ“” Creating remote session for this terminal session...");
157
+ const result = await this.client.createSessionPlaceholder({
158
+ title: title.slice(0, 80) || "CLI Session",
159
+ workerId: this.workerId,
160
+ metadata: { source: "cli", createdAt: new Date().toISOString() },
161
+ });
162
+
163
+ this.remoteSessionId = result.sessionId;
164
+ console.log(`āœ… Remote session created: ${result.sessionId}`);
165
+ return result.sessionId;
166
+ }
167
+
168
+ /**
169
+ * Create a remote message placeholder for the given task prompt.
170
+ */
171
+ private async createRemoteMessagePlaceholder(
172
+ sessionId: string,
173
+ prompt: string,
174
+ agentName?: string,
175
+ modelName?: string
176
+ ): Promise<{ messageId: string; taskId: string | undefined }> {
177
+ const result = await this.client.createMessagePlaceholder(sessionId, {
178
+ content: prompt,
179
+ agentName,
180
+ modelName,
181
+ metadata: { source: "cli", createdAt: new Date().toISOString() },
182
+ });
183
+ return { messageId: result.messageId, taskId: result.taskId };
184
+ }
185
+
186
+ /**
187
+ * Handle /sync:remote [taskId]
188
+ */
189
+ private async handleSyncRemote(args: string[]): Promise<void> {
190
+ const argRaw = args[0];
191
+
192
+ // Check if the arg is a remote session ID (UUID) or a URL containing one
193
+ if (argRaw) {
194
+ const sessionId = this.extractSessionId(argRaw);
195
+ if (sessionId) {
196
+ // User passed a remote session URL/ID — use that session for future syncs
197
+ this.remoteSessionId = sessionId;
198
+ console.log(`šŸ“” Using remote session: ${sessionId}`);
199
+ console.log(
200
+ " Future agent interactions will be appended to this session.\n" +
201
+ " Use /sync:remote:off to disable, or /sync:remote:on to persist to config."
202
+ );
203
+ // Enable auto-sync for this session too
204
+ if (!this.autoSync) {
205
+ this.autoSync = true;
206
+ }
207
+ // If there's an active task, sync it to this session now
208
+ const taskEntry = this.getActiveTask(undefined);
209
+ if (taskEntry) {
210
+ await this.syncTask(taskEntry.taskId);
211
+ }
212
+ return;
213
+ }
214
+ }
215
+
216
+ const taskIdArg = argRaw;
217
+ const taskEntry = this.getActiveTask(taskIdArg);
218
+
219
+ if (!taskEntry) {
220
+ // No task yet — enable auto-sync for future interactions (session-only, no config write)
221
+ this.autoSync = true;
222
+ console.log(
223
+ "šŸ“” Remote auto-sync enabled for this session.\n" +
224
+ " Future agent interactions will be pushed to the remote KnowHow app.\n" +
225
+ " Use /sync:remote:off to disable, or /sync:remote:on to persist to config."
226
+ );
227
+ return;
228
+ }
229
+
230
+ await this.syncTask(taskEntry.taskId);
231
+
232
+ // Also enable auto-sync for future interactions in this session
233
+ if (!this.autoSync) {
234
+ this.autoSync = true;
235
+ console.log(
236
+ "šŸ“” Remote auto-sync enabled for future agent tasks this session.\n" +
237
+ " Use /sync:remote:on to persist this setting, or /sync:remote:off to disable."
238
+ );
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Sync a specific task by taskId to the remote KnowHow app.
244
+ * Called by AgentModule for auto-sync, or directly by handleSyncRemote.
245
+ */
246
+ public async syncTask(taskId: string): Promise<void> {
247
+ const registry = this.agentModule.getTaskRegistry();
248
+ const taskInfo = registry.get(taskId);
249
+
250
+ if (!taskInfo) {
251
+ console.log(
252
+ `āš ļø Task "${taskId}" not found in registry.`
253
+ );
254
+ return;
255
+ }
256
+
257
+ // Check if already synced
258
+ if (taskInfo.knowhowTaskId && taskInfo.chatSessionId) {
259
+ console.log(
260
+ `ā„¹ļø Task "${taskId}" is already synced to remote session ${taskInfo.chatSessionId}.\n` +
261
+ ` Remote task ID: ${taskInfo.knowhowTaskId}\n` +
262
+ ` Use /sync:status to see full sync state.`
263
+ );
264
+ return;
265
+ }
266
+
267
+ try {
268
+ // Step 1: Ensure remote session exists
269
+ console.log("\nšŸ” Checking KnowHow API credentials...");
270
+ const me = await this.client.me();
271
+ console.log(`āœ… Authenticated as ${me.data?.email || "unknown"}`);
272
+
273
+ const title =
274
+ taskInfo.initialInput?.slice(0, 80) || `CLI Session – ${new Date().toLocaleString()}`;
275
+ const sessionId = await this.ensureRemoteSession(
276
+ title,
277
+ taskInfo.initialInput
278
+ );
279
+
280
+ // Step 2: Create remote message placeholder
281
+ console.log(
282
+ `\nšŸ“Ø Creating remote message for task: ${taskId}`
283
+ );
284
+ const agentName = taskInfo.agentName;
285
+ const modelName = taskInfo.agent?.getModel();
286
+ const { messageId, taskId: placeholderTaskId } = await this.createRemoteMessagePlaceholder(
287
+ sessionId,
288
+ taskInfo.formattedPrompt || taskInfo.initialInput,
289
+ agentName,
290
+ modelName
291
+ );
292
+ console.log(`āœ… Remote message created: ${messageId}`);
293
+
294
+ // Step 3: Use the task stub created by createMessagePlaceholder (avoids creating a second OrgAgentTask)
295
+ const knowhowTaskId = placeholderTaskId;
296
+ if (!knowhowTaskId) {
297
+ console.log(
298
+ "āŒ Failed to create remote agent task. Aborting sync."
299
+ );
300
+ return;
301
+ }
302
+ console.log(`\nšŸ”— Using task from message placeholder: ${knowhowTaskId}`);
303
+ const webSync = new AgentSyncKnowhowWeb(KNOWHOW_API_URL);
304
+ console.log(`āœ… Remote task created: ${knowhowTaskId}`);
305
+
306
+ // Step 4: Update local task info with remote IDs
307
+ taskInfo.knowhowMessageId = messageId;
308
+ taskInfo.knowhowTaskId = knowhowTaskId;
309
+ taskInfo.chatSessionId = sessionId;
310
+
311
+ // Update session manager
312
+ const sessionManager = this.agentModule.getSessionManager();
313
+ sessionManager.updateSession(taskId, taskInfo, taskInfo.agent?.getThreads() || []);
314
+
315
+ // Step 5: Wire up live sync
316
+ console.log(`\nšŸš€ Syncing thread progress to remote...`);
317
+ const agent = taskInfo.agent;
318
+ if (agent) {
319
+ if (taskInfo.status === "completed" || taskInfo.status === "failed") {
320
+ // Task already done — push final state directly
321
+ await webSync.setupAgentSync(agent, knowhowTaskId);
322
+ await webSync.updateChatTask(knowhowTaskId, agent, false, "Synced from CLI");
323
+ console.log(`āœ… Sync complete!`);
324
+ } else {
325
+ // Task still running — attach live sync
326
+ await webSync.setupAgentSync(agent, knowhowTaskId);
327
+ console.log(
328
+ `āœ… Live sync attached! Thread updates will push as the agent runs.`
329
+ );
330
+ }
331
+ } else {
332
+ console.log("āš ļø Agent not found in task info; skipping live sync.");
333
+ }
334
+
335
+ this.syncedMessageCount++;
336
+
337
+ const baseUrl = KNOWHOW_API_URL.replace("/api", "").replace(
338
+ "api.",
339
+ ""
340
+ );
341
+ console.log(
342
+ `\nšŸ’¾ Local session updated with remote IDs.\n` +
343
+ `🌐 View your task at: ${baseUrl}/chat/${sessionId}`
344
+ );
345
+ } catch (error: any) {
346
+ console.error(
347
+ `āŒ Remote sync failed: ${error?.message || error}\n` +
348
+ ` Local state is unaffected. You can retry with /sync:remote.`
349
+ );
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Handle /sync:remote:off
355
+ */
356
+ private async handleSyncRemoteOff(_args: string[]): Promise<void> {
357
+ this.autoSync = false;
358
+ try {
359
+ const config = await getConfig();
360
+ await updateConfig({ ...config, syncRemote: false });
361
+ console.log("šŸ’¾ Config updated: syncRemote set to false");
362
+ } catch (e: any) {
363
+ console.log(`āš ļø Could not update config: ${e?.message}`);
364
+ }
365
+ console.log(
366
+ "šŸ”• Remote auto-sync disabled for future tasks.\n" +
367
+ " Existing synced tasks will continue to push updates.\n" +
368
+ " Use /sync:remote to manually sync a task."
369
+ );
370
+ }
371
+
372
+ /**
373
+ * Handle /sync:remote:on - enables auto-sync and persists to config
374
+ */
375
+ private async handleSyncRemoteOn(_args: string[]): Promise<void> {
376
+ this.autoSync = true;
377
+ try {
378
+ const config = await getConfig();
379
+ await updateConfig({ ...config, syncRemote: true });
380
+ console.log("šŸ’¾ Config updated: syncRemote set to true");
381
+ } catch (e: any) {
382
+ console.log(`āš ļø Could not update config: ${e?.message}`);
383
+ }
384
+ console.log(
385
+ "šŸ“” Remote auto-sync enabled for all future agent tasks.\n" +
386
+ " Every new agent interaction will be pushed to the remote KnowHow app.\n" +
387
+ " Use /sync:remote:off to disable."
388
+ );
389
+ }
390
+
391
+ /**
392
+ * Handle /sync:status
393
+ */
394
+ private async handleSyncStatus(_args: string[]): Promise<void> {
395
+ const lines: string[] = ["\nšŸ“Š Remote Sync Status"];
396
+ lines.push("─".repeat(50));
397
+
398
+ if (this.workerId) {
399
+ lines.push(`šŸ”§ Worker ID: ${this.workerId}`);
400
+ } else {
401
+ lines.push(`šŸ”§ Worker ID: not set (start worker to register)`);
402
+ }
403
+
404
+ if (this.remoteSessionId) {
405
+ lines.push(`āœ… Remote session active: ${this.remoteSessionId}`);
406
+ } else {
407
+ lines.push("āŒ No remote session (run /sync:remote to create one)");
408
+ }
409
+
410
+ lines.push(
411
+ `šŸ”„ Auto-sync: ${this.autoSync ? "enabled" : "disabled"}`
412
+ );
413
+ lines.push(`šŸ“Ø Messages synced this session: ${this.syncedMessageCount}`);
414
+
415
+ // Show active tasks that have been synced
416
+ const registry = this.agentModule.getTaskRegistry();
417
+ const syncedTasks = registry
418
+ .getAll()
419
+ .filter((t) => t.knowhowTaskId && t.chatSessionId);
420
+
421
+ if (syncedTasks.length > 0) {
422
+ lines.push("\nSynced tasks:");
423
+ syncedTasks.forEach((t) => {
424
+ lines.push(
425
+ ` • ${t.taskId.slice(0, 40)} → ${t.knowhowTaskId} (${t.status})`
426
+ );
427
+ });
428
+ }
429
+
430
+ lines.push("─".repeat(50));
431
+ console.log(lines.join("\n"));
432
+ }
433
+
434
+ /**
435
+ * Whether auto-sync is currently enabled.
436
+ */
437
+ public isAutoSyncEnabled(): boolean {
438
+ return this.autoSync;
439
+ }
440
+
441
+ /**
442
+ * Get the current remote session ID (if any).
443
+ */
444
+ public getRemoteSessionId(): string | undefined {
445
+ return this.remoteSessionId;
446
+ }
447
+ }
package/src/chat/types.ts CHANGED
@@ -84,6 +84,7 @@ export interface TaskInfo {
84
84
  taskId: string;
85
85
  knowhowMessageId?: string;
86
86
  knowhowTaskId?: string;
87
+ chatSessionId?: string;
87
88
  agentName: string;
88
89
  agent: BaseAgent;
89
90
  initialInput: string;
@@ -98,6 +99,7 @@ export interface TaskInfo {
98
99
  export interface ChatSession {
99
100
  knowhowMessageId?: string;
100
101
  knowhowTaskId?: string;
102
+ chatSessionId?: string;
101
103
  sessionId: string;
102
104
  taskId: string;
103
105
  agentName: string;