@tyvm/knowhow 0.0.102 ā 0.0.103
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.
- package/package.json +2 -2
- package/src/agents/base/base.ts +3 -0
- package/src/chat/modules/AgentModule.ts +24 -10
- package/src/chat/modules/InternalChatModule.ts +6 -0
- package/src/chat/modules/RemoteSyncModule.ts +447 -0
- package/src/chat/types.ts +2 -0
- package/src/config.ts +2 -0
- package/src/services/AgentSyncFs.ts +24 -2
- package/src/services/AgentSyncKnowhowWeb.ts +27 -5
- package/src/services/KnowhowClient.ts +61 -0
- package/src/services/SessionManager.ts +2 -0
- package/src/services/script-execution/ScriptPolicy.ts +0 -44
- package/src/types.ts +3 -0
- package/src/worker.ts +70 -4
- package/ts_build/package.json +2 -2
- package/ts_build/src/agents/base/base.js +1 -0
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +2 -1
- package/ts_build/src/chat/modules/AgentModule.js +12 -7
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/InternalChatModule.d.ts +1 -0
- package/ts_build/src/chat/modules/InternalChatModule.js +6 -0
- package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
- package/ts_build/src/chat/modules/RemoteSyncModule.d.ts +27 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js +282 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -0
- package/ts_build/src/chat/types.d.ts +2 -0
- package/ts_build/src/config.js +1 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +1 -0
- package/ts_build/src/services/AgentSyncFs.js +13 -2
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +1 -0
- package/ts_build/src/services/AgentSyncKnowhowWeb.js +13 -2
- package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +21 -0
- package/ts_build/src/services/KnowhowClient.js +10 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/Mcp.d.ts +219 -406
- package/ts_build/src/services/SessionManager.js +2 -0
- package/ts_build/src/services/SessionManager.js.map +1 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -35
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -1
- package/ts_build/src/types.d.ts +2 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.js +51 -2
- 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.
|
|
3
|
+
"version": "0.0.103",
|
|
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
|
|
56
|
+
"glob": "11.1.0",
|
|
57
57
|
"isolated-vm": "^5.0.4",
|
|
58
58
|
"jiti": "^2.6.1",
|
|
59
59
|
"marked": "^10.0.0",
|
package/src/agents/base/base.ts
CHANGED
|
@@ -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
|
-
//
|
|
649
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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;
|
package/src/config.ts
CHANGED
|
@@ -29,6 +29,12 @@ export class AgentSyncFs {
|
|
|
29
29
|
private agent: BaseAgent | undefined;
|
|
30
30
|
private threadUpdateHandler: ((...args: any[]) => void) | undefined;
|
|
31
31
|
private doneHandler: ((...args: any[]) => void) | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Tracks the most recent in-flight filesystem metadata update.
|
|
34
|
+
* The done handler awaits this before finalizing, preventing a race where
|
|
35
|
+
* the completion call writes before the last thread sync finishes.
|
|
36
|
+
*/
|
|
37
|
+
private pendingThreadUpdatePromise: Promise<void> | null = null;
|
|
32
38
|
|
|
33
39
|
constructor() {
|
|
34
40
|
// Start cleanup process when created
|
|
@@ -294,8 +300,12 @@ export class AgentSyncFs {
|
|
|
294
300
|
if (!this.taskId) return;
|
|
295
301
|
|
|
296
302
|
try {
|
|
297
|
-
await
|
|
298
|
-
|
|
303
|
+
// Track the pending update so the done handler can await it.
|
|
304
|
+
this.pendingThreadUpdatePromise = (async () => {
|
|
305
|
+
await this.updateMetadata(agent, true);
|
|
306
|
+
await this.checkForChanges(agent);
|
|
307
|
+
})();
|
|
308
|
+
await this.pendingThreadUpdatePromise;
|
|
299
309
|
} catch (error) {
|
|
300
310
|
console.error(`ā Error during threadUpdate sync:`, error);
|
|
301
311
|
}
|
|
@@ -314,6 +324,17 @@ export class AgentSyncFs {
|
|
|
314
324
|
// Store finalization promise so callers can await it (same pattern as AgentSyncKnowhowWeb)
|
|
315
325
|
this.finalizationPromise = (async () => {
|
|
316
326
|
try {
|
|
327
|
+
// Flush any in-flight thread update before finalizing.
|
|
328
|
+
// This prevents the race where a pending "inProgress: true" metadata write
|
|
329
|
+
// overwrites the finalization write.
|
|
330
|
+
if (this.pendingThreadUpdatePromise) {
|
|
331
|
+
console.log(`ā³ [AgentSyncFs] Awaiting pending thread update before finalizing...`);
|
|
332
|
+
await this.pendingThreadUpdatePromise.catch(() => {
|
|
333
|
+
// Ignore errors in pending update ā we still want to finalize
|
|
334
|
+
});
|
|
335
|
+
this.pendingThreadUpdatePromise = null;
|
|
336
|
+
}
|
|
337
|
+
|
|
317
338
|
await this.updateMetadata(agent, false, result);
|
|
318
339
|
console.log(`ā
Completed filesystem sync for task: ${this.taskId}`);
|
|
319
340
|
await this.cleanup();
|
|
@@ -435,5 +456,6 @@ export class AgentSyncFs {
|
|
|
435
456
|
this.eventHandlersSetup = false;
|
|
436
457
|
this.lastInputContent = "";
|
|
437
458
|
this.finalizationPromise = null;
|
|
459
|
+
this.pendingThreadUpdatePromise = null;
|
|
438
460
|
}
|
|
439
461
|
}
|