@tyvm/knowhow 0.0.82 → 0.0.84
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 +4 -2
- package/src/agents/base/base.ts +72 -62
- package/src/agents/index.ts +30 -14
- package/src/agents/tools/startAgentTask.ts +3 -1
- package/src/chat/CliChatService.ts +20 -4
- package/src/chat/modules/AgentModule.ts +399 -357
- package/src/chat/modules/CustomCommandsModule.ts +0 -1
- package/src/chat/modules/InternalChatModule.ts +18 -2
- package/src/chat/modules/RendererModule.ts +109 -0
- package/src/chat/modules/SessionsModule.ts +854 -0
- package/src/chat/modules/SetupModule.ts +6 -8
- package/src/chat/modules/index.ts +1 -0
- package/src/chat/renderer/CompactRenderer.ts +209 -0
- package/src/chat/renderer/ConsoleRenderer.ts +141 -0
- package/src/chat/renderer/FancyRenderer.ts +421 -0
- package/src/chat/renderer/index.ts +5 -0
- package/src/chat/renderer/loadRenderer.ts +314 -0
- package/src/chat/renderer/messagesToRenderEvents.ts +96 -0
- package/src/chat/renderer/types.ts +88 -0
- package/src/chat/types.ts +5 -0
- package/src/chat.ts +69 -5
- package/src/cli.ts +24 -5
- package/src/config.ts +15 -0
- package/src/plugins/AgentsMdPlugin.ts +1 -1
- package/src/plugins/GitPlugin.ts +20 -20
- package/src/plugins/PluginBase.ts +11 -0
- package/src/plugins/SkillsPlugin.ts +150 -0
- package/src/plugins/asana.ts +4 -4
- package/src/plugins/embedding.ts +3 -5
- package/src/plugins/exec.ts +3 -3
- package/src/plugins/figma.ts +3 -7
- package/src/plugins/github.ts +18 -29
- package/src/plugins/jira.ts +2 -2
- package/src/plugins/language.ts +4 -4
- package/src/plugins/linear.ts +4 -4
- package/src/plugins/notion.ts +6 -8
- package/src/plugins/plugins.ts +30 -4
- package/src/plugins/url.ts +2 -2
- package/src/plugins/vim.ts +4 -3
- package/src/services/AgentService.ts +17 -0
- package/src/services/AgentSyncFs.ts +3 -0
- package/src/services/EventService.ts +168 -27
- package/src/services/KnowhowClient.ts +1 -0
- package/src/services/SessionManager.ts +51 -1
- package/src/services/SyncedAgentWatcher.ts +397 -0
- package/src/services/SyncerService.ts +147 -0
- package/src/services/index.ts +2 -0
- package/src/services/modules/index.ts +14 -3
- package/src/types.ts +25 -0
- package/src/worker.ts +80 -2
- package/src/workers/auth/PasskeySetup.ts +185 -0
- package/src/workers/auth/WorkerPasskeyAuth.ts +190 -0
- package/src/workers/auth/types.ts +58 -0
- package/src/workers/tools/getChallenge.ts +33 -0
- package/src/workers/tools/index.ts +8 -0
- package/src/workers/tools/lock.ts +31 -0
- package/src/workers/tools/unlock.ts +116 -0
- package/tests/unit/modules/moduleLoading.test.ts +226 -0
- package/tests/unit/plugins/pluginLoading.test.ts +151 -0
- package/ts_build/package.json +4 -2
- package/ts_build/src/agents/base/base.d.ts +4 -3
- package/ts_build/src/agents/base/base.js +54 -30
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/index.d.ts +3 -0
- package/ts_build/src/agents/index.js +21 -11
- package/ts_build/src/agents/index.js.map +1 -1
- package/ts_build/src/agents/tools/startAgentTask.js +2 -1
- package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
- package/ts_build/src/chat/CliChatService.js +16 -5
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +34 -17
- package/ts_build/src/chat/modules/AgentModule.js +248 -258
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
- package/ts_build/src/chat/modules/InternalChatModule.d.ts +3 -0
- package/ts_build/src/chat/modules/InternalChatModule.js +16 -1
- package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
- package/ts_build/src/chat/modules/RendererModule.d.ts +16 -0
- package/ts_build/src/chat/modules/RendererModule.js +76 -0
- package/ts_build/src/chat/modules/RendererModule.js.map +1 -0
- package/ts_build/src/chat/modules/SessionsModule.d.ts +33 -0
- package/ts_build/src/chat/modules/SessionsModule.js +582 -0
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -0
- package/ts_build/src/chat/modules/SetupModule.d.ts +3 -3
- package/ts_build/src/chat/modules/SetupModule.js +4 -6
- package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
- package/ts_build/src/chat/modules/index.d.ts +1 -0
- package/ts_build/src/chat/modules/index.js +3 -1
- package/ts_build/src/chat/modules/index.js.map +1 -1
- package/ts_build/src/chat/renderer/CompactRenderer.d.ts +23 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js +167 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +22 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js +110 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/FancyRenderer.d.ts +23 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js +328 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/index.d.ts +5 -0
- package/ts_build/src/chat/renderer/index.js +29 -0
- package/ts_build/src/chat/renderer/index.js.map +1 -0
- package/ts_build/src/chat/renderer/loadRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/loadRenderer.js +246 -0
- package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/messagesToRenderEvents.d.ts +15 -0
- package/ts_build/src/chat/renderer/messagesToRenderEvents.js +72 -0
- package/ts_build/src/chat/renderer/messagesToRenderEvents.js.map +1 -0
- package/ts_build/src/chat/renderer/types.d.ts +75 -0
- package/ts_build/src/chat/renderer/types.js +3 -0
- package/ts_build/src/chat/renderer/types.js.map +1 -0
- package/ts_build/src/chat/types.d.ts +5 -0
- package/ts_build/src/chat.js +46 -4
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.js +18 -5
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/config.d.ts +1 -0
- package/ts_build/src/config.js +17 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/plugins/AgentsMdPlugin.js +1 -1
- package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -1
- package/ts_build/src/plugins/GitPlugin.js +20 -20
- package/ts_build/src/plugins/GitPlugin.js.map +1 -1
- package/ts_build/src/plugins/PluginBase.d.ts +1 -0
- package/ts_build/src/plugins/PluginBase.js +13 -0
- package/ts_build/src/plugins/PluginBase.js.map +1 -1
- package/ts_build/src/plugins/SkillsPlugin.d.ts +13 -0
- package/ts_build/src/plugins/SkillsPlugin.js +149 -0
- package/ts_build/src/plugins/SkillsPlugin.js.map +1 -0
- package/ts_build/src/plugins/asana.js +4 -4
- package/ts_build/src/plugins/asana.js.map +1 -1
- package/ts_build/src/plugins/embedding.js +3 -3
- package/ts_build/src/plugins/embedding.js.map +1 -1
- package/ts_build/src/plugins/exec.js +3 -3
- package/ts_build/src/plugins/exec.js.map +1 -1
- package/ts_build/src/plugins/figma.js +3 -3
- package/ts_build/src/plugins/figma.js.map +1 -1
- package/ts_build/src/plugins/github.js +18 -18
- package/ts_build/src/plugins/github.js.map +1 -1
- package/ts_build/src/plugins/jira.js +2 -2
- package/ts_build/src/plugins/jira.js.map +1 -1
- package/ts_build/src/plugins/language.js +4 -4
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/plugins/linear.js +4 -4
- package/ts_build/src/plugins/linear.js.map +1 -1
- package/ts_build/src/plugins/notion.js +6 -6
- package/ts_build/src/plugins/notion.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +3 -0
- package/ts_build/src/plugins/plugins.js +19 -4
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/url.js +2 -2
- package/ts_build/src/plugins/url.js.map +1 -1
- package/ts_build/src/plugins/vim.js +2 -2
- package/ts_build/src/plugins/vim.js.map +1 -1
- package/ts_build/src/services/AgentService.d.ts +3 -0
- package/ts_build/src/services/AgentService.js +7 -0
- package/ts_build/src/services/AgentService.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +1 -0
- package/ts_build/src/services/AgentSyncFs.js +2 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/EventService.d.ts +25 -2
- package/ts_build/src/services/EventService.js +92 -14
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +1 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/SessionManager.d.ts +6 -0
- package/ts_build/src/services/SessionManager.js +39 -1
- package/ts_build/src/services/SessionManager.js.map +1 -1
- package/ts_build/src/services/SyncedAgentWatcher.d.ts +101 -0
- package/ts_build/src/services/SyncedAgentWatcher.js +312 -0
- package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -0
- package/ts_build/src/services/SyncerService.d.ts +30 -0
- package/ts_build/src/services/SyncerService.js +72 -0
- package/ts_build/src/services/SyncerService.js.map +1 -0
- package/ts_build/src/services/index.d.ts +2 -0
- package/ts_build/src/services/index.js +2 -0
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/index.js +10 -2
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/types.d.ts +19 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.d.ts +2 -0
- package/ts_build/src/worker.js +59 -4
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/src/workers/auth/PasskeySetup.d.ts +10 -0
- package/ts_build/src/workers/auth/PasskeySetup.js +131 -0
- package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -0
- package/ts_build/src/workers/auth/WorkerPasskeyAuth.d.ts +35 -0
- package/ts_build/src/workers/auth/WorkerPasskeyAuth.js +129 -0
- package/ts_build/src/workers/auth/WorkerPasskeyAuth.js.map +1 -0
- package/ts_build/src/workers/auth/types.d.ts +36 -0
- package/ts_build/src/workers/auth/types.js +3 -0
- package/ts_build/src/workers/auth/types.js.map +1 -0
- package/ts_build/src/workers/tools/getChallenge.d.ts +9 -0
- package/ts_build/src/workers/tools/getChallenge.js +27 -0
- package/ts_build/src/workers/tools/getChallenge.js.map +1 -0
- package/ts_build/src/workers/tools/index.d.ts +6 -0
- package/ts_build/src/workers/tools/index.js +10 -0
- package/ts_build/src/workers/tools/index.js.map +1 -1
- package/ts_build/src/workers/tools/lock.d.ts +9 -0
- package/ts_build/src/workers/tools/lock.js +27 -0
- package/ts_build/src/workers/tools/lock.js.map +1 -0
- package/ts_build/src/workers/tools/unlock.d.ts +18 -0
- package/ts_build/src/workers/tools/unlock.js +78 -0
- package/ts_build/src/workers/tools/unlock.js.map +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.d.ts +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js +187 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -0
- package/ts_build/tests/unit/plugins/pluginLoading.test.d.ts +1 -0
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +123 -0
- package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -0
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions Chat Module - Handles session/attachment related commands
|
|
3
|
+
* Extracted from AgentModule to keep AgentModule focused on agent running logic.
|
|
4
|
+
*
|
|
5
|
+
* Command semantics:
|
|
6
|
+
* /attach <taskId> - Attach to a RUNNING session (in-memory, filesystem, or web).
|
|
7
|
+
* If the task is completed, the user is told to use /resume instead.
|
|
8
|
+
* /resume <taskId> - Resume a COMPLETED/saved session with optional additional context.
|
|
9
|
+
* /sessions - List sessions that can be attached to or resumed.
|
|
10
|
+
* /logs [N] - Show the last N messages from the currently attached agent.
|
|
11
|
+
*/
|
|
12
|
+
import { BaseChatModule } from "./BaseChatModule";
|
|
13
|
+
import { ChatCommand, ChatMode, ChatContext } from "../types";
|
|
14
|
+
import { AgentModule } from "./AgentModule";
|
|
15
|
+
import {
|
|
16
|
+
FsSyncedAgentWatcher,
|
|
17
|
+
WebSyncedAgentWatcher,
|
|
18
|
+
WatcherBackedAgent,
|
|
19
|
+
} from "../../services/index";
|
|
20
|
+
import { TaskInfo, ChatSession } from "../types";
|
|
21
|
+
import { agents } from "../../agents";
|
|
22
|
+
import { KnowhowSimpleClient } from "../../services/KnowhowClient";
|
|
23
|
+
import { messagesToRenderEvents } from "../renderer/messagesToRenderEvents";
|
|
24
|
+
import { Marked } from "../../utils/index";
|
|
25
|
+
import * as fs from "fs";
|
|
26
|
+
import * as path from "path";
|
|
27
|
+
|
|
28
|
+
export class SessionsModule extends BaseChatModule {
|
|
29
|
+
name = "sessions";
|
|
30
|
+
description = "Session and attachment management";
|
|
31
|
+
|
|
32
|
+
private agentModule: AgentModule;
|
|
33
|
+
|
|
34
|
+
constructor(agentModule: AgentModule) {
|
|
35
|
+
super();
|
|
36
|
+
this.agentModule = agentModule;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getCommands(): ChatCommand[] {
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
name: "attach",
|
|
43
|
+
description:
|
|
44
|
+
"Attach to a RUNNING session. Use --completed to also see completed sessions.",
|
|
45
|
+
handler: this.handleAttachCommand.bind(this),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "resume",
|
|
49
|
+
description:
|
|
50
|
+
"Resume a completed/saved session with optional additional context",
|
|
51
|
+
handler: this.handleResumeCommand.bind(this),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "sessions",
|
|
55
|
+
description:
|
|
56
|
+
"List running sessions. Use --completed to also show completed/saved sessions.",
|
|
57
|
+
handler: this.handleSessionsCommand.bind(this),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "logs",
|
|
61
|
+
description: "Show recent messages from attached agent [N=20]",
|
|
62
|
+
handler: this.handleLogsCommand.bind(this),
|
|
63
|
+
modes: ["agent:attached"],
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
69
|
+
// /logs [N]
|
|
70
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
async handleLogsCommand(args: string[]): Promise<void> {
|
|
73
|
+
const count = parseInt(args[0] || "20", 10) || 20;
|
|
74
|
+
const activeSyncedWatcher = this.agentModule.getActiveSyncedWatcher();
|
|
75
|
+
const renderer = this.agentModule.getRenderer();
|
|
76
|
+
const taskRegistry = this.agentModule.getTaskRegistry();
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Prefer the synced watcher (fs or web attach)
|
|
80
|
+
if (activeSyncedWatcher) {
|
|
81
|
+
const threads = await activeSyncedWatcher.getThreads();
|
|
82
|
+
const lastThread = threads[threads.length - 1] || [];
|
|
83
|
+
const events = messagesToRenderEvents(
|
|
84
|
+
lastThread,
|
|
85
|
+
activeSyncedWatcher.taskId,
|
|
86
|
+
activeSyncedWatcher.agentName
|
|
87
|
+
);
|
|
88
|
+
renderer.logMessages(events, count);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fall back to in-process task
|
|
93
|
+
const activeTaskId = renderer.getActiveTaskId();
|
|
94
|
+
if (activeTaskId && taskRegistry.has(activeTaskId)) {
|
|
95
|
+
const taskInfo = taskRegistry.get(activeTaskId);
|
|
96
|
+
const agent = taskInfo?.agent;
|
|
97
|
+
if (agent) {
|
|
98
|
+
const threads = agent.getThreads();
|
|
99
|
+
const lastThread = threads[threads.length - 1] || [];
|
|
100
|
+
const events = messagesToRenderEvents(
|
|
101
|
+
lastThread,
|
|
102
|
+
activeTaskId,
|
|
103
|
+
agent.name
|
|
104
|
+
);
|
|
105
|
+
renderer.logMessages(events, count);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(
|
|
111
|
+
"No active agent to show logs for. Use /attach <taskId> to attach to an agent first."
|
|
112
|
+
);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error("Error showing logs:", error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
119
|
+
// Numbered selection helper
|
|
120
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Shows a prompt and accepts a number (1-based index) to select from a list of IDs.
|
|
124
|
+
* Returns the resolved ID, or undefined if cancelled.
|
|
125
|
+
*/
|
|
126
|
+
private async selectByNumber(
|
|
127
|
+
prompt: string,
|
|
128
|
+
allIds: string[]
|
|
129
|
+
): Promise<string | undefined> {
|
|
130
|
+
const numbers = allIds.map((_, i) => String(i + 1));
|
|
131
|
+
const input = await this.chatService?.getInput(prompt, numbers);
|
|
132
|
+
if (!input || !input.trim()) return undefined;
|
|
133
|
+
const idx = parseInt(input.trim(), 10);
|
|
134
|
+
if (isNaN(idx) || idx < 1 || idx > allIds.length) return undefined;
|
|
135
|
+
return allIds[idx - 1];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
139
|
+
// /attach [taskId]
|
|
140
|
+
// Attaches to a RUNNING session only. Completed sessions → suggest /resume.
|
|
141
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
async handleAttachCommand(args: string[]): Promise<void> {
|
|
144
|
+
const taskRegistry = this.agentModule.getTaskRegistry();
|
|
145
|
+
const sessionManager = this.agentModule.getSessionManager();
|
|
146
|
+
const showCompleted = args.includes("--completed");
|
|
147
|
+
const filteredArgs = args.filter((a) => a !== "--completed");
|
|
148
|
+
|
|
149
|
+
if (filteredArgs.length === 0) {
|
|
150
|
+
// Build list of running-only sessions for interactive selection
|
|
151
|
+
const runningTasks = taskRegistry.getAll();
|
|
152
|
+
const fsAgents = await this.getFsAgents(runningTasks);
|
|
153
|
+
// If --completed flag, also fetch saved/completed sessions
|
|
154
|
+
const savedSessions = showCompleted
|
|
155
|
+
? sessionManager.listAvailableSessions()
|
|
156
|
+
: [];
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
runningTasks.length === 0 &&
|
|
160
|
+
fsAgents.length === 0 &&
|
|
161
|
+
savedSessions.length === 0
|
|
162
|
+
) {
|
|
163
|
+
console.log(
|
|
164
|
+
"No running sessions found to attach to.\n" +
|
|
165
|
+
"Use /attach --completed to also see completed sessions.\n" +
|
|
166
|
+
"Use /resume <taskId> to resume a completed session."
|
|
167
|
+
);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (showCompleted) {
|
|
172
|
+
this.logSessionsCompact(runningTasks, savedSessions, fsAgents);
|
|
173
|
+
} else {
|
|
174
|
+
this.printRunningTable(runningTasks, fsAgents);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const allIds = [
|
|
178
|
+
...runningTasks.map((t) => t.taskId),
|
|
179
|
+
...fsAgents.map((a) => a.taskId),
|
|
180
|
+
...(showCompleted ? savedSessions.map((s) => s.sessionId) : []),
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
const selectedId = await this.selectByNumber(
|
|
184
|
+
showCompleted
|
|
185
|
+
? "Enter number to attach/resume (or press Enter to cancel): "
|
|
186
|
+
: "Enter number to attach to (or press Enter to cancel): ",
|
|
187
|
+
allIds
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (selectedId) {
|
|
191
|
+
const trimmed = selectedId;
|
|
192
|
+
const isCompleted =
|
|
193
|
+
showCompleted && savedSessions.some((s) => s.sessionId === trimmed);
|
|
194
|
+
if (isCompleted) {
|
|
195
|
+
await this.resumeById(trimmed);
|
|
196
|
+
} else {
|
|
197
|
+
await this.attachById(trimmed);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const taskId = filteredArgs[0];
|
|
204
|
+
await this.attachById(taskId);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Core attach logic — only attaches to RUNNING sessions.
|
|
209
|
+
* Completed/saved sessions get a helpful message pointing to /resume.
|
|
210
|
+
*/
|
|
211
|
+
private async attachById(id: string): Promise<void> {
|
|
212
|
+
const taskRegistry = this.agentModule.getTaskRegistry();
|
|
213
|
+
const sessionManager = this.agentModule.getSessionManager();
|
|
214
|
+
|
|
215
|
+
// ── Case 1: in-memory running task ──────────────────────────────────────
|
|
216
|
+
if (taskRegistry.has(id)) {
|
|
217
|
+
const taskInfo = taskRegistry.get(id)!;
|
|
218
|
+
const renderer = this.agentModule.getRenderer();
|
|
219
|
+
const context = this.chatService?.getContext();
|
|
220
|
+
const allAgents = agents();
|
|
221
|
+
const selectedAgent = allAgents[taskInfo.agentName];
|
|
222
|
+
|
|
223
|
+
if (context && selectedAgent) {
|
|
224
|
+
context.selectedAgent = selectedAgent;
|
|
225
|
+
context.agentMode = true;
|
|
226
|
+
context.currentAgent = taskInfo.agentName;
|
|
227
|
+
context.activeAgentTaskId = id;
|
|
228
|
+
context.currentModel = selectedAgent.getModel();
|
|
229
|
+
context.currentProvider = selectedAgent.getProvider();
|
|
230
|
+
}
|
|
231
|
+
this.agentModule.setActiveAgentTaskId(id);
|
|
232
|
+
renderer.setActiveTaskId(id);
|
|
233
|
+
if (this.chatService) this.chatService.setMode("agent:attached");
|
|
234
|
+
|
|
235
|
+
console.log(`🔄 Attached to running task: ${id}`);
|
|
236
|
+
console.log(` Agent : ${taskInfo.agentName}`);
|
|
237
|
+
console.log(` Task : ${taskInfo.initialInput}`);
|
|
238
|
+
console.log(` Status: ${taskInfo.status}`);
|
|
239
|
+
console.log(
|
|
240
|
+
` Type /logs to see recent messages, or /detach to detach.`
|
|
241
|
+
);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ── Case 2: filesystem agent directory ──────────────────────────────────
|
|
246
|
+
const fsAgentPath = path.join(".knowhow", "processes", "agents", id);
|
|
247
|
+
if (fs.existsSync(fsAgentPath)) {
|
|
248
|
+
// Read status — only attach if running
|
|
249
|
+
const status = this.readFsAgentStatus(fsAgentPath);
|
|
250
|
+
if (status === "completed") {
|
|
251
|
+
console.log(
|
|
252
|
+
`⚠️ Task ${id} is completed.\n` +
|
|
253
|
+
` Use /resume ${id} to resume it with additional context.`
|
|
254
|
+
);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
await this.attachToFsAgent(id);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ── Case 3: saved session (completed) ───────────────────────────────────
|
|
262
|
+
try {
|
|
263
|
+
const session = sessionManager.loadSession(id);
|
|
264
|
+
if (session) {
|
|
265
|
+
if (session.status === "completed") {
|
|
266
|
+
console.log(
|
|
267
|
+
`⚠️ Session ${id} is completed.\n` +
|
|
268
|
+
` Use /resume ${id} to resume it with additional context.`
|
|
269
|
+
);
|
|
270
|
+
} else {
|
|
271
|
+
// Session exists but is not yet completed — treat as attach via fs watcher
|
|
272
|
+
// (the agent may be running in another process)
|
|
273
|
+
const fsPath = path.join(".knowhow", "processes", "agents", id);
|
|
274
|
+
if (fs.existsSync(fsPath)) {
|
|
275
|
+
await this.attachToFsAgent(id);
|
|
276
|
+
} else {
|
|
277
|
+
// Try web as last resort
|
|
278
|
+
await this.attachToWebAgent(id);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
// session not on disk, continue
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── Case 4: web task ────────────────────────────────────────────────────
|
|
288
|
+
try {
|
|
289
|
+
await this.attachToWebAgent(id);
|
|
290
|
+
return;
|
|
291
|
+
} catch {
|
|
292
|
+
// not found on web
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
console.log(
|
|
296
|
+
`Session/Task "${id}" not found among running tasks, filesystem agents, or web.\n` +
|
|
297
|
+
`Use /sessions to see all known sessions.`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
302
|
+
// /resume [taskId]
|
|
303
|
+
// Resumes a completed/saved session with optional additional context.
|
|
304
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
305
|
+
|
|
306
|
+
async handleResumeCommand(args: string[]): Promise<void> {
|
|
307
|
+
const sessionManager = this.agentModule.getSessionManager();
|
|
308
|
+
|
|
309
|
+
if (args.length === 0) {
|
|
310
|
+
// Interactive: show saved sessions for selection
|
|
311
|
+
const savedSessions = sessionManager.listAvailableSessions();
|
|
312
|
+
if (savedSessions.length === 0) {
|
|
313
|
+
console.log("No saved sessions found to resume.");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.printSavedSessionsTable(savedSessions);
|
|
318
|
+
|
|
319
|
+
const allIds = savedSessions.map((s) => s.sessionId);
|
|
320
|
+
const selectedId = await this.selectByNumber(
|
|
321
|
+
"Enter number to resume (or press Enter to cancel): ",
|
|
322
|
+
allIds
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
if (selectedId) {
|
|
326
|
+
await this.resumeById(selectedId);
|
|
327
|
+
}
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
await this.resumeById(args[0]);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private async resumeById(id: string): Promise<void> {
|
|
335
|
+
const sessionManager = this.agentModule.getSessionManager();
|
|
336
|
+
|
|
337
|
+
// Check saved sessions first
|
|
338
|
+
try {
|
|
339
|
+
const session = sessionManager.loadSession(id);
|
|
340
|
+
if (session) {
|
|
341
|
+
console.log(`\n📋 Session found: ${id}`);
|
|
342
|
+
console.log(` Agent : ${session.agentName}`);
|
|
343
|
+
console.log(` Task : ${session.initialInput}`);
|
|
344
|
+
console.log(` Status : ${session.status}`);
|
|
345
|
+
|
|
346
|
+
const additionalContext = await this.chatService?.getInput(
|
|
347
|
+
"Add any additional context for resuming this session (or press Enter to skip): "
|
|
348
|
+
);
|
|
349
|
+
await this.agentModule.resumeSession(
|
|
350
|
+
id,
|
|
351
|
+
additionalContext?.trim() || undefined
|
|
352
|
+
);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
// not found as a saved session
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Check filesystem agent (may have metadata with threads)
|
|
360
|
+
const fsAgentPath = path.join(".knowhow", "processes", "agents", id);
|
|
361
|
+
if (fs.existsSync(fsAgentPath)) {
|
|
362
|
+
console.log(
|
|
363
|
+
`⚠️ Task ${id} exists in the filesystem but has no saved session.\n` +
|
|
364
|
+
` Use /attach ${id} if it is still running.`
|
|
365
|
+
);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
console.log(
|
|
370
|
+
`Session "${id}" not found. Use /sessions to list available sessions.`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
375
|
+
// /sessions [--all] [--csv]
|
|
376
|
+
// Shows all sessions: running ones can be /attach'd, saved ones can be /resume'd
|
|
377
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
async handleSessionsCommand(args: string[]): Promise<void> {
|
|
380
|
+
try {
|
|
381
|
+
const showCompleted =
|
|
382
|
+
args.includes("--completed") || args.includes("--all");
|
|
383
|
+
const showCsv = args.includes("--csv");
|
|
384
|
+
|
|
385
|
+
const taskRegistry = this.agentModule.getTaskRegistry();
|
|
386
|
+
const sessionManager = this.agentModule.getSessionManager();
|
|
387
|
+
const runningTasks = taskRegistry.getAll();
|
|
388
|
+
const fsAgents = await this.getFsAgents(runningTasks);
|
|
389
|
+
// Only include saved/completed sessions when --completed (or --all) is passed
|
|
390
|
+
const savedSessions = showCompleted
|
|
391
|
+
? sessionManager.listAvailableSessions()
|
|
392
|
+
: [];
|
|
393
|
+
|
|
394
|
+
if (showCompleted) {
|
|
395
|
+
// Show running + completed together
|
|
396
|
+
await this.logSessionTable(true, showCsv, true);
|
|
397
|
+
} else {
|
|
398
|
+
// Only show running tasks + fs agents
|
|
399
|
+
if (runningTasks.length === 0 && fsAgents.length === 0) {
|
|
400
|
+
console.log(
|
|
401
|
+
"No running sessions. Use /sessions --completed to also see completed sessions."
|
|
402
|
+
);
|
|
403
|
+
} else if (showCsv) {
|
|
404
|
+
this.logSessionsCsv(runningTasks, [], fsAgents);
|
|
405
|
+
} else {
|
|
406
|
+
this.logSessionsCompact(runningTasks, [], fsAgents);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Interactive selection — running → attach, completed → resume
|
|
411
|
+
const allIds = [
|
|
412
|
+
...runningTasks.map((t) => t.taskId),
|
|
413
|
+
...fsAgents.map((a) => a.taskId),
|
|
414
|
+
...savedSessions.map((s) => s.sessionId),
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
if (allIds.length > 0) {
|
|
418
|
+
const selectedId = await this.selectByNumber(
|
|
419
|
+
showCompleted
|
|
420
|
+
? "Enter number to attach/resume (or press Enter to skip): "
|
|
421
|
+
: "Enter number to attach to (or press Enter to skip): ",
|
|
422
|
+
allIds
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
if (selectedId) {
|
|
426
|
+
const isRunning =
|
|
427
|
+
taskRegistry.has(selectedId) ||
|
|
428
|
+
fsAgents.some((a) => a.taskId === selectedId);
|
|
429
|
+
|
|
430
|
+
if (isRunning) {
|
|
431
|
+
await this.attachById(selectedId);
|
|
432
|
+
} else {
|
|
433
|
+
await this.resumeById(selectedId);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error("Error listing sessions and tasks:", error);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
443
|
+
// Table rendering helpers
|
|
444
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Main session table — shows running, saved, and fs agents.
|
|
448
|
+
*/
|
|
449
|
+
async logSessionTable(
|
|
450
|
+
all: boolean = false,
|
|
451
|
+
csv: boolean = false,
|
|
452
|
+
includeFs: boolean = false
|
|
453
|
+
) {
|
|
454
|
+
const taskRegistry = this.agentModule.getTaskRegistry();
|
|
455
|
+
const sessionManager = this.agentModule.getSessionManager();
|
|
456
|
+
const runningTasks = taskRegistry.getAll();
|
|
457
|
+
let savedSessions = sessionManager.listAvailableSessions();
|
|
458
|
+
|
|
459
|
+
if (!all) {
|
|
460
|
+
savedSessions = savedSessions.filter(
|
|
461
|
+
(s) => s.startTime >= this.agentModule.getProcessStartTime()
|
|
462
|
+
);
|
|
463
|
+
const filteredTasks = runningTasks.filter(
|
|
464
|
+
(t) => t.startTime >= this.agentModule.getProcessStartTime()
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
const fsAgents = includeFs ? await this.getFsAgents(runningTasks) : [];
|
|
468
|
+
if (
|
|
469
|
+
filteredTasks.length === 0 &&
|
|
470
|
+
savedSessions.length === 0 &&
|
|
471
|
+
fsAgents.length === 0
|
|
472
|
+
) {
|
|
473
|
+
console.log(
|
|
474
|
+
"No sessions from this process run. Use --all to see all historical sessions."
|
|
475
|
+
);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (csv) {
|
|
480
|
+
this.logSessionsCsv(filteredTasks, savedSessions, fsAgents);
|
|
481
|
+
} else {
|
|
482
|
+
this.logSessionsCompact(filteredTasks, savedSessions, fsAgents);
|
|
483
|
+
}
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const fsAgents = includeFs
|
|
488
|
+
? await this.getFsAgentsIncludingCompleted(runningTasks)
|
|
489
|
+
: [];
|
|
490
|
+
if (csv) {
|
|
491
|
+
this.logSessionsCsv(runningTasks, savedSessions, fsAgents);
|
|
492
|
+
} else {
|
|
493
|
+
this.logSessionsCompact(runningTasks, savedSessions, fsAgents);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Compact table of ONLY running tasks + fs agents (for /attach interactive) */
|
|
498
|
+
private printRunningTable(
|
|
499
|
+
runningTasks: TaskInfo[],
|
|
500
|
+
fsAgents: {
|
|
501
|
+
taskId: string;
|
|
502
|
+
agentName: string;
|
|
503
|
+
status: string;
|
|
504
|
+
totalCostUsd?: number;
|
|
505
|
+
}[]
|
|
506
|
+
): void {
|
|
507
|
+
const rows = [
|
|
508
|
+
...runningTasks.map((t) => ({
|
|
509
|
+
id: t.taskId,
|
|
510
|
+
agent: t.agentName,
|
|
511
|
+
status: t.status,
|
|
512
|
+
cost: `$${t.totalCost.toFixed(3)}`,
|
|
513
|
+
type: "running",
|
|
514
|
+
})),
|
|
515
|
+
...fsAgents.map((a) => ({
|
|
516
|
+
id: a.taskId,
|
|
517
|
+
agent: a.agentName,
|
|
518
|
+
status: a.status,
|
|
519
|
+
cost: a.totalCostUsd != null ? `$${a.totalCostUsd.toFixed(3)}` : "n/a",
|
|
520
|
+
type: "fs",
|
|
521
|
+
})),
|
|
522
|
+
];
|
|
523
|
+
|
|
524
|
+
console.log("\n🏃 Running sessions (attach-able):");
|
|
525
|
+
console.log("─".repeat(86));
|
|
526
|
+
console.log(
|
|
527
|
+
"#".padEnd(5) +
|
|
528
|
+
"taskId".padEnd(40) +
|
|
529
|
+
"agent".padEnd(14) +
|
|
530
|
+
"status".padEnd(12) +
|
|
531
|
+
"cost"
|
|
532
|
+
);
|
|
533
|
+
console.log("─".repeat(86));
|
|
534
|
+
for (const r of rows) {
|
|
535
|
+
const num = String(rows.indexOf(r) + 1).padEnd(5);
|
|
536
|
+
const shortId = r.id.length > 38 ? r.id.substring(0, 35) + "..." : r.id;
|
|
537
|
+
console.log(
|
|
538
|
+
num +
|
|
539
|
+
shortId.padEnd(40) +
|
|
540
|
+
r.agent.padEnd(14) +
|
|
541
|
+
r.status.padEnd(12) +
|
|
542
|
+
r.cost
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
console.log("─".repeat(86));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/** Compact table of ONLY saved sessions (for /resume interactive) */
|
|
549
|
+
private printSavedSessionsTable(savedSessions: ChatSession[]): void {
|
|
550
|
+
console.log("\n💾 Saved sessions (resumable):");
|
|
551
|
+
console.log("─".repeat(86));
|
|
552
|
+
console.log(
|
|
553
|
+
"#".padEnd(5) +
|
|
554
|
+
"taskId".padEnd(40) +
|
|
555
|
+
"agent".padEnd(14) +
|
|
556
|
+
"status".padEnd(12) +
|
|
557
|
+
"cost"
|
|
558
|
+
);
|
|
559
|
+
console.log("─".repeat(86));
|
|
560
|
+
for (let i = 0; i < savedSessions.length; i++) {
|
|
561
|
+
const s = savedSessions[i];
|
|
562
|
+
const num = String(i + 1).padEnd(5);
|
|
563
|
+
const shortId =
|
|
564
|
+
s.sessionId.length > 38
|
|
565
|
+
? s.sessionId.substring(0, 35) + "..."
|
|
566
|
+
: s.sessionId;
|
|
567
|
+
const cost = s.totalCost ? `$${s.totalCost.toFixed(3)}` : "$0.000";
|
|
568
|
+
console.log(
|
|
569
|
+
num +
|
|
570
|
+
shortId.padEnd(40) +
|
|
571
|
+
s.agentName.padEnd(14) +
|
|
572
|
+
s.status.padEnd(12) +
|
|
573
|
+
cost
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
console.log("─".repeat(86));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Full compact list: running + saved + fs agents, with type labels.
|
|
581
|
+
*/
|
|
582
|
+
private logSessionsCompact(
|
|
583
|
+
runningTasks: TaskInfo[],
|
|
584
|
+
savedSessions: ChatSession[],
|
|
585
|
+
fsAgents: {
|
|
586
|
+
taskId: string;
|
|
587
|
+
agentName: string;
|
|
588
|
+
status: string;
|
|
589
|
+
totalCostUsd?: number;
|
|
590
|
+
}[] = []
|
|
591
|
+
): void {
|
|
592
|
+
const runningTaskIds = new Set(runningTasks.map((t) => t.taskId));
|
|
593
|
+
const savedIds = new Set(savedSessions.map((s) => s.sessionId));
|
|
594
|
+
const dedupedSaved = savedSessions.filter(
|
|
595
|
+
(s) => !runningTaskIds.has(s.sessionId)
|
|
596
|
+
);
|
|
597
|
+
const allKnownIds = new Set([...runningTaskIds, ...savedIds]);
|
|
598
|
+
const dedupedFs = fsAgents.filter((a) => !allKnownIds.has(a.taskId));
|
|
599
|
+
|
|
600
|
+
const rows = [
|
|
601
|
+
...runningTasks.map((t) => ({
|
|
602
|
+
id: t.taskId,
|
|
603
|
+
agent: t.agentName,
|
|
604
|
+
status: t.status,
|
|
605
|
+
cost: `$${t.totalCost.toFixed(3)}`,
|
|
606
|
+
type: "running",
|
|
607
|
+
action: "/attach",
|
|
608
|
+
})),
|
|
609
|
+
...dedupedFs.map((a) => ({
|
|
610
|
+
id: a.taskId,
|
|
611
|
+
agent: a.agentName,
|
|
612
|
+
status: a.status,
|
|
613
|
+
cost: a.totalCostUsd != null ? `$${a.totalCostUsd.toFixed(3)}` : "n/a",
|
|
614
|
+
type: "fs",
|
|
615
|
+
action: a.status === "completed" ? "/resume" : "/attach",
|
|
616
|
+
})),
|
|
617
|
+
...dedupedSaved.map((s) => ({
|
|
618
|
+
id: s.sessionId,
|
|
619
|
+
agent: s.agentName,
|
|
620
|
+
status: s.status,
|
|
621
|
+
cost: s.totalCost ? `$${s.totalCost.toFixed(3)}` : "$0.000",
|
|
622
|
+
type: "saved",
|
|
623
|
+
action: "/resume",
|
|
624
|
+
})),
|
|
625
|
+
];
|
|
626
|
+
|
|
627
|
+
if (rows.length === 0) {
|
|
628
|
+
console.log("No sessions found.");
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
console.log("\n📋 Sessions:");
|
|
633
|
+
console.log("─".repeat(109));
|
|
634
|
+
console.log(
|
|
635
|
+
"#".padEnd(5) +
|
|
636
|
+
"taskId".padEnd(40) +
|
|
637
|
+
"agent".padEnd(14) +
|
|
638
|
+
"status".padEnd(12) +
|
|
639
|
+
"type".padEnd(10) +
|
|
640
|
+
"cost".padEnd(12) +
|
|
641
|
+
"action"
|
|
642
|
+
);
|
|
643
|
+
console.log("─".repeat(109));
|
|
644
|
+
for (let i = 0; i < rows.length; i++) {
|
|
645
|
+
const r = rows[i];
|
|
646
|
+
const num = String(i + 1).padEnd(5);
|
|
647
|
+
const shortId = r.id.length > 38 ? r.id.substring(0, 35) + "..." : r.id;
|
|
648
|
+
console.log(
|
|
649
|
+
num +
|
|
650
|
+
shortId.padEnd(40) +
|
|
651
|
+
r.agent.padEnd(14) +
|
|
652
|
+
r.status.padEnd(12) +
|
|
653
|
+
r.type.padEnd(10) +
|
|
654
|
+
r.cost.padEnd(12) +
|
|
655
|
+
r.action
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
console.log("─".repeat(109));
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* CSV output for sessions.
|
|
663
|
+
*/
|
|
664
|
+
private logSessionsCsv(
|
|
665
|
+
runningTasks: TaskInfo[],
|
|
666
|
+
savedSessions: ChatSession[],
|
|
667
|
+
fsAgents: {
|
|
668
|
+
taskId: string;
|
|
669
|
+
agentName: string;
|
|
670
|
+
status: string;
|
|
671
|
+
totalCostUsd?: number;
|
|
672
|
+
}[] = []
|
|
673
|
+
): void {
|
|
674
|
+
const lines = ["taskId,agent,status,type,cost,startTime,initialInput"];
|
|
675
|
+
const runningTaskIds = new Set(runningTasks.map((t) => t.taskId));
|
|
676
|
+
const dedupedSaved = savedSessions.filter(
|
|
677
|
+
(s) => !runningTaskIds.has(s.sessionId)
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
for (const t of runningTasks) {
|
|
681
|
+
const input = (t.initialInput || "")
|
|
682
|
+
.replace(/,/g, ";")
|
|
683
|
+
.replace(/\n/g, " ");
|
|
684
|
+
lines.push(
|
|
685
|
+
`${t.taskId},${t.agentName},${t.status},running,${
|
|
686
|
+
t.totalCost?.toFixed(3) || "0.000"
|
|
687
|
+
},${t.startTime},"${input}"`
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
for (const s of dedupedSaved) {
|
|
691
|
+
const input = (s.initialInput || "")
|
|
692
|
+
.replace(/,/g, ";")
|
|
693
|
+
.replace(/\n/g, " ");
|
|
694
|
+
lines.push(
|
|
695
|
+
`${s.sessionId},${s.agentName},${s.status},saved,${
|
|
696
|
+
s.totalCost?.toFixed(3) || "0.000"
|
|
697
|
+
},${s.startTime},"${input}"`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
const allKnownIds = new Set([
|
|
701
|
+
...runningTaskIds,
|
|
702
|
+
...savedSessions.map((s) => s.sessionId),
|
|
703
|
+
]);
|
|
704
|
+
for (const a of fsAgents) {
|
|
705
|
+
if (!allKnownIds.has(a.taskId)) {
|
|
706
|
+
lines.push(
|
|
707
|
+
`${a.taskId},${a.agentName},${a.status},fs,${
|
|
708
|
+
a.totalCostUsd != null ? a.totalCostUsd.toFixed(3) : "n/a"
|
|
709
|
+
},n/a,""`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
console.log(lines.join("\n"));
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
717
|
+
// Low-level attach helpers
|
|
718
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
719
|
+
|
|
720
|
+
private async attachToFsAgent(taskId: string): Promise<void> {
|
|
721
|
+
const existingWatcher = this.agentModule.getActiveSyncedWatcher();
|
|
722
|
+
if (existingWatcher) {
|
|
723
|
+
existingWatcher.stopWatching();
|
|
724
|
+
this.agentModule.setActiveSyncedWatcher(undefined);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const watcher = new FsSyncedAgentWatcher();
|
|
728
|
+
await watcher.startWatching(taskId);
|
|
729
|
+
this.agentModule.setActiveSyncedWatcher(watcher);
|
|
730
|
+
|
|
731
|
+
// Wire rendering via AgentModule utility (handles cleanup on detach)
|
|
732
|
+
this.agentModule.wireAgentRendering(taskId, watcher.agentEvents, watcher.eventTypes, watcher.agentName);
|
|
733
|
+
watcher.agentEvents.once(watcher.eventTypes.done, (output) => {
|
|
734
|
+
console.log(Marked.parse(output));
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
const context = this.chatService?.getContext();
|
|
738
|
+
if (context) context.activeAgentTaskId = taskId;
|
|
739
|
+
|
|
740
|
+
console.log(`📁 Attached to filesystem agent: ${taskId}`);
|
|
741
|
+
|
|
742
|
+
// Enter interactive loop — this sets mode to "agent:attached" and blocks until detach/done/kill
|
|
743
|
+
const fsWatcherAgent = new WatcherBackedAgent(watcher);
|
|
744
|
+
await this.agentModule.attachedAgentChatLoop(taskId, fsWatcherAgent);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private async attachToWebAgent(taskId: string): Promise<void> {
|
|
748
|
+
const client = new KnowhowSimpleClient();
|
|
749
|
+
// Verify the task exists — throws if not found
|
|
750
|
+
const details = await client.getTaskDetails(taskId);
|
|
751
|
+
|
|
752
|
+
// Check if it's already completed on the web
|
|
753
|
+
const webStatus = details?.data?.status;
|
|
754
|
+
if (webStatus === "completed" || webStatus === "killed") {
|
|
755
|
+
console.log(
|
|
756
|
+
`⚠️ Web task ${taskId} has status: ${webStatus}.\n` +
|
|
757
|
+
` Use /resume ${taskId} to resume it with additional context.`
|
|
758
|
+
);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const existingWatcher = this.agentModule.getActiveSyncedWatcher();
|
|
763
|
+
if (existingWatcher) {
|
|
764
|
+
existingWatcher.stopWatching();
|
|
765
|
+
this.agentModule.setActiveSyncedWatcher(undefined);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const watcher = new WebSyncedAgentWatcher(client);
|
|
769
|
+
await watcher.startWatching(taskId);
|
|
770
|
+
this.agentModule.setActiveSyncedWatcher(watcher);
|
|
771
|
+
|
|
772
|
+
// Wire rendering via AgentModule utility (handles cleanup on detach)
|
|
773
|
+
this.agentModule.wireAgentRendering(taskId, watcher.agentEvents, watcher.eventTypes, watcher.agentName);
|
|
774
|
+
|
|
775
|
+
const context = this.chatService?.getContext();
|
|
776
|
+
if (context) context.activeAgentTaskId = taskId;
|
|
777
|
+
|
|
778
|
+
console.log(`🌐 Attached to web agent: ${taskId}`);
|
|
779
|
+
|
|
780
|
+
// Enter interactive loop — this sets mode to "agent:attached" and blocks until detach/done/kill
|
|
781
|
+
const webWatcherAgent = new WatcherBackedAgent(watcher);
|
|
782
|
+
await this.agentModule.attachedAgentChatLoop(taskId, webWatcherAgent);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
786
|
+
// Utility helpers
|
|
787
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
788
|
+
|
|
789
|
+
private readFsAgentStatus(agentPath: string): string {
|
|
790
|
+
try {
|
|
791
|
+
const statusPath = path.join(agentPath, "status.txt");
|
|
792
|
+
if (fs.existsSync(statusPath)) {
|
|
793
|
+
return fs.readFileSync(statusPath, "utf8").trim();
|
|
794
|
+
}
|
|
795
|
+
// Fall back to metadata.json
|
|
796
|
+
const metaPath = path.join(agentPath, "metadata.json");
|
|
797
|
+
if (fs.existsSync(metaPath)) {
|
|
798
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, "utf8"));
|
|
799
|
+
return meta.status || "unknown";
|
|
800
|
+
}
|
|
801
|
+
} catch {
|
|
802
|
+
// ignore
|
|
803
|
+
}
|
|
804
|
+
return "unknown";
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private async getFsAgents(runningTasks: TaskInfo[]): Promise<
|
|
808
|
+
{
|
|
809
|
+
taskId: string;
|
|
810
|
+
agentName: string;
|
|
811
|
+
status: string;
|
|
812
|
+
totalCostUsd?: number;
|
|
813
|
+
}[]
|
|
814
|
+
> {
|
|
815
|
+
const sessionManager = this.agentModule.getSessionManager();
|
|
816
|
+
const registeredIds = new Set(runningTasks.map((t) => t.taskId));
|
|
817
|
+
return sessionManager.discoverFsAgents(registeredIds);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
private async getFsAgentsIncludingCompleted(
|
|
821
|
+
runningTasks: TaskInfo[]
|
|
822
|
+
): Promise<
|
|
823
|
+
{
|
|
824
|
+
taskId: string;
|
|
825
|
+
agentName: string;
|
|
826
|
+
status: string;
|
|
827
|
+
totalCostUsd?: number;
|
|
828
|
+
}[]
|
|
829
|
+
> {
|
|
830
|
+
const sessionManager = this.agentModule.getSessionManager();
|
|
831
|
+
const registeredIds = new Set(runningTasks.map((t) => t.taskId));
|
|
832
|
+
return sessionManager.discoverFsAgents(registeredIds, true);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
836
|
+
// Public helpers used by CLI (src/cli.ts)
|
|
837
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
838
|
+
|
|
839
|
+
public async listAvailableSessions(): Promise<ChatSession[]> {
|
|
840
|
+
return this.agentModule.getSessionManager().listAvailableSessions();
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
public async listSessionsAndTasks(): Promise<{
|
|
844
|
+
runningTasks: TaskInfo[];
|
|
845
|
+
savedSessions: ChatSession[];
|
|
846
|
+
}> {
|
|
847
|
+
const taskRegistry = this.agentModule.getTaskRegistry();
|
|
848
|
+
const sessionManager = this.agentModule.getSessionManager();
|
|
849
|
+
return {
|
|
850
|
+
runningTasks: taskRegistry.getAll(),
|
|
851
|
+
savedSessions: sessionManager.listAvailableSessions(),
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
}
|