opencode-claw 0.2.2 → 0.2.5
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/dist/channels/router.js +44 -15
- package/dist/cli.js +0 -0
- package/dist/sessions/manager.d.ts +1 -1
- package/dist/sessions/manager.js +9 -12
- package/package.json +1 -1
package/dist/channels/router.js
CHANGED
|
@@ -31,11 +31,13 @@ function parseCommand(text) {
|
|
|
31
31
|
return { name: trimmed.slice(1).toLowerCase(), args: "" };
|
|
32
32
|
return { name: trimmed.slice(1, space).toLowerCase(), args: trimmed.slice(space + 1).trim() };
|
|
33
33
|
}
|
|
34
|
+
const PAGE_SIZE = 10;
|
|
34
35
|
const HELP_TEXT = `Available commands:
|
|
35
36
|
/new [title] — Create a new session
|
|
36
37
|
/switch <id> — Switch to an existing session
|
|
37
|
-
/sessions — List your sessions
|
|
38
|
+
/sessions [page] — List your sessions (paginated)
|
|
38
39
|
/current — Show current session
|
|
40
|
+
/status — Show current agent run status
|
|
39
41
|
/fork — Fork current session into a new one
|
|
40
42
|
/cancel — Abort the currently running agent
|
|
41
43
|
/help — Show this help`;
|
|
@@ -43,9 +45,8 @@ const HELP_TEXT = `Available commands:
|
|
|
43
45
|
function peerKey(channel, peerId) {
|
|
44
46
|
return `${channel}:${peerId}`;
|
|
45
47
|
}
|
|
46
|
-
async function handleCommand(cmd, msg, deps, activeStreams) {
|
|
48
|
+
async function handleCommand(cmd, msg, deps, activeStreams, activeStreamsMeta) {
|
|
47
49
|
const key = buildSessionKey(msg.channel, msg.peerId, msg.threadId);
|
|
48
|
-
const prefix = `${msg.channel}:${msg.peerId}`;
|
|
49
50
|
switch (cmd.name) {
|
|
50
51
|
case "new": {
|
|
51
52
|
const id = await deps.sessions.newSession(key, cmd.args || undefined);
|
|
@@ -58,15 +59,21 @@ async function handleCommand(cmd, msg, deps, activeStreams) {
|
|
|
58
59
|
return `Switched to session: ${cmd.args}`;
|
|
59
60
|
}
|
|
60
61
|
case "sessions": {
|
|
61
|
-
const list = await deps.sessions.listSessions(
|
|
62
|
+
const list = await deps.sessions.listSessions(key);
|
|
62
63
|
if (list.length === 0)
|
|
63
64
|
return "No sessions found.";
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
const page = Math.max(1, Number.parseInt(cmd.args) || 1);
|
|
66
|
+
const totalPages = Math.ceil(list.length / PAGE_SIZE);
|
|
67
|
+
const clamped = Math.min(page, totalPages);
|
|
68
|
+
const slice = list.slice((clamped - 1) * PAGE_SIZE, clamped * PAGE_SIZE);
|
|
69
|
+
const lines = slice.map((s) => {
|
|
66
70
|
const marker = s.active ? " (active)" : "";
|
|
67
71
|
return `• ${s.id} — ${s.title}${marker}`;
|
|
68
|
-
})
|
|
69
|
-
|
|
72
|
+
});
|
|
73
|
+
if (totalPages > 1) {
|
|
74
|
+
lines.push(`\nPage ${clamped}/${totalPages}${clamped < totalPages ? ` — use /sessions ${clamped + 1} for next` : ""}`);
|
|
75
|
+
}
|
|
76
|
+
return lines.join("\n");
|
|
70
77
|
}
|
|
71
78
|
case "current": {
|
|
72
79
|
const id = deps.sessions.currentSession(key);
|
|
@@ -97,6 +104,19 @@ async function handleCommand(cmd, msg, deps, activeStreams) {
|
|
|
97
104
|
deps.logger.info("router: session aborted by user", { sessionId, aborted });
|
|
98
105
|
return aborted ? "Agent aborted." : "Abort request sent (agent may already be done).";
|
|
99
106
|
}
|
|
107
|
+
case "status": {
|
|
108
|
+
const pk = peerKey(msg.channel, msg.peerId);
|
|
109
|
+
const sessionId = activeStreams.get(pk);
|
|
110
|
+
if (!sessionId)
|
|
111
|
+
return "No agent is currently running.";
|
|
112
|
+
const meta = activeStreamsMeta.get(pk);
|
|
113
|
+
const elapsedSec = meta ? Math.floor((Date.now() - meta.startedAt) / 1000) : 0;
|
|
114
|
+
const mins = Math.floor(elapsedSec / 60);
|
|
115
|
+
const secs = elapsedSec % 60;
|
|
116
|
+
const elapsed = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
117
|
+
const tool = meta?.lastTool ? ` — last tool: ${humanizeToolName(meta.lastTool)}` : "";
|
|
118
|
+
return `⏳ Agent is running (${elapsed} elapsed${tool})`;
|
|
119
|
+
}
|
|
100
120
|
case "help": {
|
|
101
121
|
return HELP_TEXT;
|
|
102
122
|
}
|
|
@@ -111,7 +131,7 @@ function humanizeToolName(raw) {
|
|
|
111
131
|
return raw;
|
|
112
132
|
return raw.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
113
133
|
}
|
|
114
|
-
async function routeMessage(msg, deps, activeStreams, pendingQuestions) {
|
|
134
|
+
async function routeMessage(msg, deps, activeStreams, activeStreamsMeta, pendingQuestions) {
|
|
115
135
|
const adapter = deps.adapters.get(msg.channel);
|
|
116
136
|
if (!adapter) {
|
|
117
137
|
deps.logger.warn("router: no adapter for channel", { channel: msg.channel });
|
|
@@ -132,7 +152,7 @@ async function routeMessage(msg, deps, activeStreams, pendingQuestions) {
|
|
|
132
152
|
// Command interception
|
|
133
153
|
const cmd = parseCommand(msg.text);
|
|
134
154
|
if (cmd) {
|
|
135
|
-
const reply = await handleCommand(cmd, msg, deps, activeStreams);
|
|
155
|
+
const reply = await handleCommand(cmd, msg, deps, activeStreams, activeStreamsMeta);
|
|
136
156
|
await adapter.send(msg.peerId, { text: reply, replyToId: msg.replyToId });
|
|
137
157
|
return;
|
|
138
158
|
}
|
|
@@ -142,6 +162,7 @@ async function routeMessage(msg, deps, activeStreams, pendingQuestions) {
|
|
|
142
162
|
deps.logger.debug("router: prompting session", { sessionId, channel: msg.channel });
|
|
143
163
|
const pk = peerKey(msg.channel, msg.peerId);
|
|
144
164
|
activeStreams.set(pk, sessionId);
|
|
165
|
+
activeStreamsMeta.set(pk, { startedAt: Date.now(), lastTool: undefined });
|
|
145
166
|
// Start typing indicator
|
|
146
167
|
if (adapter.sendTyping) {
|
|
147
168
|
await adapter.sendTyping(msg.peerId).catch(() => { });
|
|
@@ -180,10 +201,15 @@ async function routeMessage(msg, deps, activeStreams, pendingQuestions) {
|
|
|
180
201
|
}
|
|
181
202
|
const progress = progressEnabled
|
|
182
203
|
? {
|
|
183
|
-
onToolRunning: (_tool, title) =>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
204
|
+
onToolRunning: (_tool, title) => {
|
|
205
|
+
const meta = activeStreamsMeta.get(pk);
|
|
206
|
+
if (meta)
|
|
207
|
+
meta.lastTool = title;
|
|
208
|
+
return adapter.send(msg.peerId, {
|
|
209
|
+
text: `🔧 ${humanizeToolName(title)}...`,
|
|
210
|
+
replyToId: msg.replyToId,
|
|
211
|
+
});
|
|
212
|
+
},
|
|
187
213
|
onHeartbeat: async () => {
|
|
188
214
|
if (adapter.sendTyping) {
|
|
189
215
|
await adapter.sendTyping(msg.peerId).catch(() => { });
|
|
@@ -220,6 +246,7 @@ async function routeMessage(msg, deps, activeStreams, pendingQuestions) {
|
|
|
220
246
|
}
|
|
221
247
|
finally {
|
|
222
248
|
activeStreams.delete(pk);
|
|
249
|
+
activeStreamsMeta.delete(pk);
|
|
223
250
|
pendingQuestions.delete(pk);
|
|
224
251
|
if (adapter.stopTyping) {
|
|
225
252
|
await adapter.stopTyping(msg.peerId).catch(() => { });
|
|
@@ -235,6 +262,8 @@ async function routeMessage(msg, deps, activeStreams, pendingQuestions) {
|
|
|
235
262
|
export function createRouter(deps) {
|
|
236
263
|
// Tracks which sessionId is currently streaming for each channel:peerId pair
|
|
237
264
|
const activeStreams = new Map();
|
|
265
|
+
// Tracks timing + last tool for each active stream
|
|
266
|
+
const activeStreamsMeta = new Map();
|
|
238
267
|
// Tracks pending question resolvers — when agent asks a question, user's next message resolves it
|
|
239
268
|
const pendingQuestions = new Map();
|
|
240
269
|
async function handler(msg) {
|
|
@@ -248,7 +277,7 @@ export function createRouter(deps) {
|
|
|
248
277
|
pending.resolve(msg.text);
|
|
249
278
|
return;
|
|
250
279
|
}
|
|
251
|
-
await routeMessage(msg, deps, activeStreams, pendingQuestions);
|
|
280
|
+
await routeMessage(msg, deps, activeStreams, activeStreamsMeta, pendingQuestions);
|
|
252
281
|
}
|
|
253
282
|
catch (err) {
|
|
254
283
|
deps.logger.error("router: unhandled error", {
|
package/dist/cli.js
CHANGED
|
File without changes
|
|
@@ -13,7 +13,7 @@ export declare function createSessionManager(client: OpencodeClient, config: Ses
|
|
|
13
13
|
resolveSession: (key: string, title?: string) => Promise<string>;
|
|
14
14
|
switchSession: (key: string, targetId: string) => Promise<void>;
|
|
15
15
|
newSession: (key: string, title?: string) => Promise<string>;
|
|
16
|
-
listSessions: (
|
|
16
|
+
listSessions: (key: string) => Promise<SessionInfo[]>;
|
|
17
17
|
currentSession: (key: string) => string | undefined;
|
|
18
18
|
persist: () => Promise<void>;
|
|
19
19
|
};
|
package/dist/sessions/manager.js
CHANGED
|
@@ -39,20 +39,17 @@ export function createSessionManager(client, config, map, logger) {
|
|
|
39
39
|
logger.info("sessions: created and switched to new session", { key, id: session.data.id });
|
|
40
40
|
return session.data.id;
|
|
41
41
|
}
|
|
42
|
-
async function listSessions(
|
|
42
|
+
async function listSessions(key) {
|
|
43
43
|
const all = await client.session.list();
|
|
44
44
|
const sessions = all.data ?? [];
|
|
45
|
-
const
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
createdAt: session?.time.created,
|
|
54
|
-
};
|
|
55
|
-
});
|
|
45
|
+
const activeId = map.get(key);
|
|
46
|
+
return sessions.map((s) => ({
|
|
47
|
+
id: s.id,
|
|
48
|
+
key: [...map.entries()].find(([, id]) => id === s.id)?.[0] ?? "(external)",
|
|
49
|
+
title: s.title ?? s.id,
|
|
50
|
+
active: s.id === activeId,
|
|
51
|
+
createdAt: s.time.created,
|
|
52
|
+
}));
|
|
56
53
|
}
|
|
57
54
|
function currentSession(key) {
|
|
58
55
|
return map.get(key);
|