@tyvm/knowhow 0.0.101 ā 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 +34 -6
- 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/cloudWorker.ts +48 -30
- package/src/config.ts +2 -0
- package/src/fileSync.ts +153 -9
- package/src/hashes.ts +52 -0
- package/src/services/AgentSyncFs.ts +24 -2
- package/src/services/AgentSyncKnowhowWeb.ts +27 -5
- package/src/services/KnowhowClient.ts +69 -3
- package/src/services/S3.ts +15 -5
- 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.d.ts +1 -0
- package/ts_build/src/agents/base/base.js +23 -4
- 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/cloudWorker.js +38 -25
- package/ts_build/src/cloudWorker.js.map +1 -1
- package/ts_build/src/config.js +1 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/fileSync.d.ts +3 -0
- package/ts_build/src/fileSync.js +104 -6
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/hashes.d.ts +4 -0
- package/ts_build/src/hashes.js +34 -0
- package/ts_build/src/hashes.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 +25 -1
- package/ts_build/src/services/KnowhowClient.js +14 -1
- 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/S3.js +14 -4
- package/ts_build/src/services/S3.js.map +1 -1
- 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
|
@@ -64,7 +64,13 @@ export abstract class BaseAgent implements IAgent {
|
|
|
64
64
|
protected compressMinMessages = 30;
|
|
65
65
|
|
|
66
66
|
protected threads = [] as Message[][];
|
|
67
|
+
|
|
68
|
+
// Message from users
|
|
67
69
|
protected pendingUserMessages = [] as Message[];
|
|
70
|
+
|
|
71
|
+
// Internal messages
|
|
72
|
+
protected pendingMessages = [] as Message[];
|
|
73
|
+
|
|
68
74
|
protected taskBreakdown = "";
|
|
69
75
|
protected summaries = [] as string[];
|
|
70
76
|
protected currentTaskId: string | null = null;
|
|
@@ -538,10 +544,14 @@ export abstract class BaseAgent implements IAgent {
|
|
|
538
544
|
|
|
539
545
|
async kill() {
|
|
540
546
|
this.log("Killing agent");
|
|
547
|
+
if (this.status === this.eventTypes.kill || this.status === this.eventTypes.done) {
|
|
548
|
+
this.log("Agent is already being killed or done, ignoring duplicate kill()", "warn");
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
541
551
|
this.agentEvents.emit(this.eventTypes.kill, this);
|
|
542
552
|
this.status = this.eventTypes.kill;
|
|
543
553
|
|
|
544
|
-
this.
|
|
554
|
+
this.addPendingMessage({
|
|
545
555
|
role: "user",
|
|
546
556
|
content: `<Workflow>The user has requested the task to end, please call ${this.requiredToolNames} with a report of your ending state</Workflow>`,
|
|
547
557
|
} as Message);
|
|
@@ -599,6 +609,11 @@ export abstract class BaseAgent implements IAgent {
|
|
|
599
609
|
this.pendingUserMessages = [];
|
|
600
610
|
}
|
|
601
611
|
|
|
612
|
+
if (this.pendingMessages.length) {
|
|
613
|
+
messages.push(...this.pendingMessages);
|
|
614
|
+
this.pendingMessages = [];
|
|
615
|
+
}
|
|
616
|
+
|
|
602
617
|
messages = this.formatInputMessages(messages);
|
|
603
618
|
this.updateCurrentThread(messages);
|
|
604
619
|
const isMissingTool = this.isRequiredToolMissing();
|
|
@@ -673,7 +688,7 @@ export abstract class BaseAgent implements IAgent {
|
|
|
673
688
|
this.updateCurrentThread(messages);
|
|
674
689
|
|
|
675
690
|
for (const toolCall of toolCalls) {
|
|
676
|
-
if(this.status === this.eventTypes.pause) {
|
|
691
|
+
if (this.status === this.eventTypes.pause) {
|
|
677
692
|
this.log(
|
|
678
693
|
"Agent was paused before tool call, waiting before processing tool calls"
|
|
679
694
|
);
|
|
@@ -716,7 +731,9 @@ export abstract class BaseAgent implements IAgent {
|
|
|
716
731
|
});
|
|
717
732
|
const doneMsg = finalMessage.content || "Done";
|
|
718
733
|
|
|
719
|
-
|
|
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);
|
|
720
737
|
this.agentEvents.emit(this.eventTypes.done, doneMsg);
|
|
721
738
|
this.status = this.eventTypes.done;
|
|
722
739
|
return doneMsg;
|
|
@@ -908,21 +925,32 @@ export abstract class BaseAgent implements IAgent {
|
|
|
908
925
|
});
|
|
909
926
|
}
|
|
910
927
|
|
|
928
|
+
// A new message from system, non blocking
|
|
911
929
|
addPendingMessage(message: Message) {
|
|
912
930
|
if (this.status === this.eventTypes.done) {
|
|
913
931
|
this.log("Agent is done, cannot take more messages", "warn");
|
|
914
932
|
} else {
|
|
915
|
-
const pendingMessages = this.
|
|
933
|
+
const pendingMessages = this.pendingMessages.map((m) => m.content);
|
|
916
934
|
if (pendingMessages.includes(message.content)) {
|
|
917
935
|
// Ignore messages we already have queue'd up
|
|
918
936
|
return;
|
|
919
937
|
}
|
|
920
|
-
this.
|
|
938
|
+
this.pendingMessages.push(message);
|
|
921
939
|
}
|
|
922
940
|
}
|
|
923
941
|
|
|
942
|
+
// A new message from users, blocks completion
|
|
924
943
|
addPendingUserMessage(message: Message) {
|
|
925
|
-
this.
|
|
944
|
+
if (this.status === this.eventTypes.done) {
|
|
945
|
+
this.log("Agent is done, cannot take more messages", "warn");
|
|
946
|
+
} else {
|
|
947
|
+
const pendingMessages = this.pendingUserMessages.map((m) => m.content);
|
|
948
|
+
if (pendingMessages.includes(message.content)) {
|
|
949
|
+
// Ignore messages we already have queue'd up
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
this.pendingUserMessages.push(message);
|
|
953
|
+
}
|
|
926
954
|
this.events.emit(this.eventTypes.userSay, message.content);
|
|
927
955
|
}
|
|
928
956
|
|
|
@@ -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
|
+
}
|