opencode-async-agent 1.0.0 → 1.0.2
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/README.md +25 -7
- package/dist/async-agent.js +1289 -0
- package/package.json +3 -1
- package/AGENTS.md +0 -119
- package/src/plugin/manager.ts +0 -630
- package/src/plugin/plugin.ts +0 -200
- package/src/plugin/rules.ts +0 -115
- package/src/plugin/tools.ts +0 -230
- package/src/plugin/types.ts +0 -80
- package/src/plugin/utils.ts +0 -51
|
@@ -0,0 +1,1289 @@
|
|
|
1
|
+
// src/plugin/utils.ts
|
|
2
|
+
function createLogger(client) {
|
|
3
|
+
const log = (level, message) => client.app.log({ body: { service: "async-agent", level, message } }).catch(() => {
|
|
4
|
+
});
|
|
5
|
+
return {
|
|
6
|
+
debug: (msg) => log("debug", msg),
|
|
7
|
+
info: (msg) => log("info", msg),
|
|
8
|
+
warn: (msg) => log("warn", msg),
|
|
9
|
+
error: (msg) => log("error", msg)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function showToast(client, title, message, variant = "info", duration = 3e3) {
|
|
13
|
+
const tuiClient = client;
|
|
14
|
+
if (!tuiClient.tui?.showToast) return;
|
|
15
|
+
tuiClient.tui.showToast({
|
|
16
|
+
body: { title, message, variant, duration }
|
|
17
|
+
}).catch(() => {
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function formatDuration(startedAt, completedAt) {
|
|
21
|
+
const end = completedAt || /* @__PURE__ */ new Date();
|
|
22
|
+
const diffMs = end.getTime() - startedAt.getTime();
|
|
23
|
+
const diffSec = Math.floor(diffMs / 1e3);
|
|
24
|
+
if (diffSec < 60) {
|
|
25
|
+
return `${diffSec}s`;
|
|
26
|
+
}
|
|
27
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
28
|
+
if (diffMin < 60) {
|
|
29
|
+
const secs = diffSec % 60;
|
|
30
|
+
return secs > 0 ? `${diffMin}m ${secs}s` : `${diffMin}m`;
|
|
31
|
+
}
|
|
32
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
33
|
+
const mins = diffMin % 60;
|
|
34
|
+
return mins > 0 ? `${diffHour}h ${mins}m` : `${diffHour}h`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/plugin/types.ts
|
|
38
|
+
var MAX_RUN_TIME_MS = 15 * 60 * 1e3;
|
|
39
|
+
|
|
40
|
+
// src/plugin/manager.ts
|
|
41
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
42
|
+
import { join } from "path";
|
|
43
|
+
function parseModel(model) {
|
|
44
|
+
const [providerID, ...rest] = model.split("/");
|
|
45
|
+
return { providerID, modelID: rest.join("/") };
|
|
46
|
+
}
|
|
47
|
+
var ANALYSIS_PROMPT = `You are a session analyst. Analyze the following AI task execution comprehensively so the main agent can make informed next decisions.
|
|
48
|
+
|
|
49
|
+
## Analysis Criteria
|
|
50
|
+
|
|
51
|
+
### 1. Anything AI Missed Based on Initial Prompt
|
|
52
|
+
- Compare the original user prompt against what was actually accomplished
|
|
53
|
+
- Identify any requirements, questions, or requests that were never addressed
|
|
54
|
+
- List promises made by the agent that were left unfulfilled
|
|
55
|
+
|
|
56
|
+
### 2. Wrong Doings
|
|
57
|
+
- Identify incorrect assumptions or bad approaches taken
|
|
58
|
+
- Note any factual errors or wrong technical decisions
|
|
59
|
+
- Call out misinterpretations of the original prompt
|
|
60
|
+
|
|
61
|
+
### 3. Gave Up / Shortcuts
|
|
62
|
+
- Did the agent abandon parts of the task prematurely?
|
|
63
|
+
- Were steps skipped or incomplete solutions used?
|
|
64
|
+
- Did the agent stop without exhausting reasonable options?
|
|
65
|
+
- Any signs of "good enough" attitude instead of thorough completion?
|
|
66
|
+
|
|
67
|
+
### 4. Messed Up
|
|
68
|
+
- Did the agent break existing functionality?
|
|
69
|
+
- Were new problems introduced during the task?
|
|
70
|
+
- Any destructive actions or unintended side effects?
|
|
71
|
+
|
|
72
|
+
### 5. Good Points / Choices
|
|
73
|
+
- What technical decisions were sound and should be replicated?
|
|
74
|
+
- What approaches worked well that future tasks should follow?
|
|
75
|
+
- Notable strengths in this session's execution
|
|
76
|
+
|
|
77
|
+
### 6. Session Ended Properly or Stream Cut Out
|
|
78
|
+
- **Proper finish:** Agent concluded with clear result or summary
|
|
79
|
+
- **Stream cut out:** Session interrupted mid-task with no conclusion
|
|
80
|
+
- **Ambiguous end:** Final state unclear or incomplete explanation
|
|
81
|
+
|
|
82
|
+
### 7. Overall Status on the Session
|
|
83
|
+
- Give the main agent a complete picture of what happened
|
|
84
|
+
- Was this session successful, partial, or a failure?
|
|
85
|
+
- Is the output reliable enough to base next decisions on?
|
|
86
|
+
|
|
87
|
+
## Output Format
|
|
88
|
+
|
|
89
|
+
Provide your analysis in **markdown** with these exact sections:
|
|
90
|
+
|
|
91
|
+
### Summary
|
|
92
|
+
[2-3 sentence summary of what happened]
|
|
93
|
+
|
|
94
|
+
### What the AI Missed Based on Initial Prompt
|
|
95
|
+
[List anything not covered from the original prompt]
|
|
96
|
+
|
|
97
|
+
### Wrong Doings
|
|
98
|
+
[Incorrect assumptions, bad approaches, factual errors]
|
|
99
|
+
|
|
100
|
+
### Gave Up / Shortcuts
|
|
101
|
+
[Premature abandonment, skipped steps, incomplete solutions]
|
|
102
|
+
|
|
103
|
+
### Messed Up
|
|
104
|
+
[Broke things, created new problems, unintended side effects]
|
|
105
|
+
|
|
106
|
+
### Good Points / Choices
|
|
107
|
+
[Sound decisions, approaches worth replicating, notable strengths]
|
|
108
|
+
|
|
109
|
+
### Session Completion
|
|
110
|
+
- **Status:** [Proper finish / Stream cut out / Ambiguous]
|
|
111
|
+
- **Details:** [explanation of how the session ended]
|
|
112
|
+
|
|
113
|
+
### Overall Status
|
|
114
|
+
[Complete assessment: Is this session's output reliable for next decisions? What's the verdict?]
|
|
115
|
+
|
|
116
|
+
### Next Action for Main Agent
|
|
117
|
+
[Specific recommendation on what the main agent should do next based on this session's outcome]
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Session Data
|
|
122
|
+
|
|
123
|
+
### Initial User Prompt
|
|
124
|
+
\`\`\`
|
|
125
|
+
\${initialPrompt}
|
|
126
|
+
\`\`\`
|
|
127
|
+
|
|
128
|
+
### Full Conversation
|
|
129
|
+
\`\`\`
|
|
130
|
+
\${formattedMessages}
|
|
131
|
+
\`\`\`
|
|
132
|
+
|
|
133
|
+
### Session Metadata
|
|
134
|
+
- Agent: \${agent}
|
|
135
|
+
- Model: \${model}
|
|
136
|
+
- Duration: \${duration}
|
|
137
|
+
- Status: \${status}
|
|
138
|
+
- Started: \${startTime}
|
|
139
|
+
- Completed: \${completedTime}
|
|
140
|
+
`;
|
|
141
|
+
var DelegationManager = class {
|
|
142
|
+
delegations = /* @__PURE__ */ new Map();
|
|
143
|
+
client;
|
|
144
|
+
log;
|
|
145
|
+
pendingByParent = /* @__PURE__ */ new Map();
|
|
146
|
+
constructor(client, log) {
|
|
147
|
+
this.client = client;
|
|
148
|
+
this.log = log;
|
|
149
|
+
}
|
|
150
|
+
calculateDuration(delegation) {
|
|
151
|
+
return formatDuration(delegation.startedAt, delegation.completedAt);
|
|
152
|
+
}
|
|
153
|
+
// ---- Parent session model ----
|
|
154
|
+
async getParentModel(parentSessionID) {
|
|
155
|
+
try {
|
|
156
|
+
const messagesResult = await this.client.session.messages({
|
|
157
|
+
path: { id: parentSessionID }
|
|
158
|
+
});
|
|
159
|
+
const messageData = messagesResult.data;
|
|
160
|
+
if (!messageData || messageData.length === 0) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const lastUserMessage = [...messageData].reverse().find((m) => m.info.role === "user");
|
|
164
|
+
if (!lastUserMessage || !lastUserMessage.info.model) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const model = lastUserMessage.info.model;
|
|
168
|
+
const modelString = `${model.providerID}/${model.modelID}`;
|
|
169
|
+
await this.debugLog(`Got parent model: ${modelString}`);
|
|
170
|
+
return modelString;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this.log.debug(`Failed to get parent model: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ---- Core operations ----
|
|
177
|
+
async delegate(input) {
|
|
178
|
+
await this.debugLog(`delegate() called`);
|
|
179
|
+
const agentsResult = await this.client.app.agents({});
|
|
180
|
+
const agents = agentsResult.data ?? [];
|
|
181
|
+
const validAgent = agents.find((a) => a.name === input.agent);
|
|
182
|
+
if (!validAgent) {
|
|
183
|
+
const available = agents.filter((a) => a.mode === "subagent" || a.mode === "all" || !a.mode).map((a) => `\u2022 ${a.name}${a.description ? ` - ${a.description}` : ""}`).join("\n");
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Agent "${input.agent}" not found.
|
|
186
|
+
|
|
187
|
+
Available agents:
|
|
188
|
+
${available || "(none)"}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const sessionResult = await this.client.session.create({
|
|
192
|
+
body: {
|
|
193
|
+
title: `Delegation: ${input.agent}`,
|
|
194
|
+
parentID: input.parentSessionID
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
await this.debugLog(`session.create result: ${JSON.stringify(sessionResult.data)}`);
|
|
198
|
+
if (!sessionResult.data?.id) {
|
|
199
|
+
throw new Error("Failed to create delegation session");
|
|
200
|
+
}
|
|
201
|
+
const sessionID = sessionResult.data.id;
|
|
202
|
+
const parentModel = await this.getParentModel(input.parentSessionID);
|
|
203
|
+
const delegation = {
|
|
204
|
+
id: sessionID,
|
|
205
|
+
sessionID,
|
|
206
|
+
parentSessionID: input.parentSessionID,
|
|
207
|
+
parentMessageID: input.parentMessageID,
|
|
208
|
+
parentAgent: input.parentAgent,
|
|
209
|
+
parentModel,
|
|
210
|
+
prompt: input.prompt,
|
|
211
|
+
agent: input.agent,
|
|
212
|
+
model: input.model,
|
|
213
|
+
status: "running",
|
|
214
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
215
|
+
progress: {
|
|
216
|
+
toolCalls: 0,
|
|
217
|
+
lastUpdate: /* @__PURE__ */ new Date()
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
await this.debugLog(`Created delegation ${delegation.id}`);
|
|
221
|
+
this.delegations.set(delegation.id, delegation);
|
|
222
|
+
const parentId = input.parentSessionID;
|
|
223
|
+
if (!this.pendingByParent.has(parentId)) {
|
|
224
|
+
this.pendingByParent.set(parentId, /* @__PURE__ */ new Set());
|
|
225
|
+
}
|
|
226
|
+
this.pendingByParent.get(parentId)?.add(delegation.id);
|
|
227
|
+
await this.debugLog(
|
|
228
|
+
`Tracking delegation ${delegation.id} for parent ${parentId}. Pending count: ${this.pendingByParent.get(parentId)?.size}`
|
|
229
|
+
);
|
|
230
|
+
setTimeout(() => {
|
|
231
|
+
const current = this.delegations.get(delegation.id);
|
|
232
|
+
if (current && current.status === "running") {
|
|
233
|
+
this.handleTimeout(delegation.id);
|
|
234
|
+
}
|
|
235
|
+
}, MAX_RUN_TIME_MS + 5e3);
|
|
236
|
+
showToast(this.client, "New Background Task", `${delegation.id} (${input.agent})`, "info", 3e3);
|
|
237
|
+
const promptBody = {
|
|
238
|
+
agent: input.agent,
|
|
239
|
+
parts: [{ type: "text", text: input.prompt }],
|
|
240
|
+
tools: {
|
|
241
|
+
task: false,
|
|
242
|
+
delegate: false,
|
|
243
|
+
todowrite: false,
|
|
244
|
+
plan_save: false
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
if (input.model) {
|
|
248
|
+
promptBody.model = parseModel(input.model);
|
|
249
|
+
}
|
|
250
|
+
this.client.session.prompt({
|
|
251
|
+
path: { id: delegation.sessionID },
|
|
252
|
+
body: promptBody
|
|
253
|
+
}).catch((error) => {
|
|
254
|
+
delegation.status = "error";
|
|
255
|
+
delegation.error = error.message;
|
|
256
|
+
delegation.completedAt = /* @__PURE__ */ new Date();
|
|
257
|
+
delegation.duration = this.calculateDuration(delegation);
|
|
258
|
+
this.notifyParent(delegation);
|
|
259
|
+
});
|
|
260
|
+
return delegation;
|
|
261
|
+
}
|
|
262
|
+
async resume(delegationId, newPrompt) {
|
|
263
|
+
const delegation = this.delegations.get(delegationId);
|
|
264
|
+
if (!delegation) {
|
|
265
|
+
throw new Error(`Delegation "${delegationId}" not found`);
|
|
266
|
+
}
|
|
267
|
+
if (delegation.status === "running") {
|
|
268
|
+
throw new Error(`Delegation is already running. Wait for it to complete or cancel it first.`);
|
|
269
|
+
}
|
|
270
|
+
delegation.status = "running";
|
|
271
|
+
delegation.completedAt = void 0;
|
|
272
|
+
delegation.error = void 0;
|
|
273
|
+
delegation.startedAt = /* @__PURE__ */ new Date();
|
|
274
|
+
delegation.progress = {
|
|
275
|
+
toolCalls: 0,
|
|
276
|
+
lastUpdate: /* @__PURE__ */ new Date()
|
|
277
|
+
};
|
|
278
|
+
const parentId = delegation.parentSessionID;
|
|
279
|
+
if (!this.pendingByParent.has(parentId)) {
|
|
280
|
+
this.pendingByParent.set(parentId, /* @__PURE__ */ new Set());
|
|
281
|
+
}
|
|
282
|
+
this.pendingByParent.get(parentId)?.add(delegation.id);
|
|
283
|
+
const prompt = newPrompt || "Continue from where you left off.";
|
|
284
|
+
const resumeBody = {
|
|
285
|
+
agent: delegation.agent,
|
|
286
|
+
parts: [{ type: "text", text: prompt }],
|
|
287
|
+
tools: {
|
|
288
|
+
task: false,
|
|
289
|
+
delegate: false,
|
|
290
|
+
todowrite: false,
|
|
291
|
+
plan_save: false
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
if (delegation.model) {
|
|
295
|
+
resumeBody.model = parseModel(delegation.model);
|
|
296
|
+
}
|
|
297
|
+
this.client.session.prompt({
|
|
298
|
+
path: { id: delegation.sessionID },
|
|
299
|
+
body: resumeBody
|
|
300
|
+
}).catch((error) => {
|
|
301
|
+
delegation.status = "error";
|
|
302
|
+
delegation.error = error.message;
|
|
303
|
+
delegation.completedAt = /* @__PURE__ */ new Date();
|
|
304
|
+
delegation.duration = this.calculateDuration(delegation);
|
|
305
|
+
this.notifyParent(delegation);
|
|
306
|
+
});
|
|
307
|
+
await this.debugLog(`Resumed delegation ${delegation.id}`);
|
|
308
|
+
return delegation;
|
|
309
|
+
}
|
|
310
|
+
async cancel(delegationId) {
|
|
311
|
+
const delegation = this.delegations.get(delegationId);
|
|
312
|
+
if (!delegation) return false;
|
|
313
|
+
if (delegation.status !== "running") return false;
|
|
314
|
+
delegation.status = "cancelled";
|
|
315
|
+
delegation.completedAt = /* @__PURE__ */ new Date();
|
|
316
|
+
delegation.duration = this.calculateDuration(delegation);
|
|
317
|
+
try {
|
|
318
|
+
await this.client.session.abort({
|
|
319
|
+
path: { id: delegation.sessionID }
|
|
320
|
+
});
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
const pendingSet = this.pendingByParent.get(delegation.parentSessionID);
|
|
324
|
+
if (pendingSet) {
|
|
325
|
+
pendingSet.delete(delegationId);
|
|
326
|
+
}
|
|
327
|
+
await this.notifyParent(delegation);
|
|
328
|
+
showToast(this.client, "Task Cancelled", `${delegation.id} cancelled (${delegation.duration})`, "info", 3e3);
|
|
329
|
+
await this.debugLog(`Cancelled delegation ${delegation.id}`);
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
async cancelAll(parentSessionID) {
|
|
333
|
+
const cancelled = [];
|
|
334
|
+
for (const delegation of this.delegations.values()) {
|
|
335
|
+
if (delegation.parentSessionID === parentSessionID && delegation.status === "running") {
|
|
336
|
+
const success = await this.cancel(delegation.id);
|
|
337
|
+
if (success) {
|
|
338
|
+
cancelled.push(delegation.id);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return cancelled;
|
|
343
|
+
}
|
|
344
|
+
// ---- Event handlers ----
|
|
345
|
+
async handleTimeout(delegationId) {
|
|
346
|
+
const delegation = this.delegations.get(delegationId);
|
|
347
|
+
if (!delegation || delegation.status !== "running") return;
|
|
348
|
+
await this.debugLog(`handleTimeout for delegation ${delegation.id}`);
|
|
349
|
+
delegation.status = "timeout";
|
|
350
|
+
delegation.completedAt = /* @__PURE__ */ new Date();
|
|
351
|
+
delegation.duration = this.calculateDuration(delegation);
|
|
352
|
+
delegation.error = `Delegation timed out after ${MAX_RUN_TIME_MS / 1e3}s`;
|
|
353
|
+
try {
|
|
354
|
+
await this.client.session.abort({
|
|
355
|
+
path: { id: delegation.sessionID }
|
|
356
|
+
});
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
359
|
+
await this.notifyParent(delegation);
|
|
360
|
+
}
|
|
361
|
+
async handleSessionIdle(sessionID) {
|
|
362
|
+
const delegation = this.findBySession(sessionID);
|
|
363
|
+
if (!delegation || delegation.status !== "running") return;
|
|
364
|
+
await this.debugLog(`handleSessionIdle for delegation ${delegation.id}`);
|
|
365
|
+
delegation.status = "completed";
|
|
366
|
+
delegation.completedAt = /* @__PURE__ */ new Date();
|
|
367
|
+
delegation.duration = this.calculateDuration(delegation);
|
|
368
|
+
try {
|
|
369
|
+
const messages = await this.client.session.messages({
|
|
370
|
+
path: { id: delegation.sessionID }
|
|
371
|
+
});
|
|
372
|
+
const messageData = messages.data;
|
|
373
|
+
if (messageData && messageData.length > 0) {
|
|
374
|
+
const firstUser = messageData.find((m) => m.info.role === "user");
|
|
375
|
+
if (firstUser) {
|
|
376
|
+
const textPart = firstUser.parts.find((p) => p.type === "text");
|
|
377
|
+
if (textPart) {
|
|
378
|
+
delegation.description = textPart.text.slice(0, 150);
|
|
379
|
+
delegation.title = textPart.text.split("\n")[0].slice(0, 50);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
showToast(
|
|
386
|
+
this.client,
|
|
387
|
+
"Task Completed",
|
|
388
|
+
`"${delegation.id}" finished in ${delegation.duration}`,
|
|
389
|
+
"success",
|
|
390
|
+
5e3
|
|
391
|
+
);
|
|
392
|
+
await this.notifyParent(delegation);
|
|
393
|
+
}
|
|
394
|
+
// ---- Read delegation results ----
|
|
395
|
+
async readDelegation(args) {
|
|
396
|
+
const delegation = this.delegations.get(args.id);
|
|
397
|
+
if (!delegation) {
|
|
398
|
+
throw new Error(`Delegation "${args.id}" not found.
|
|
399
|
+
|
|
400
|
+
Use delegation_list() to see available delegations.`);
|
|
401
|
+
}
|
|
402
|
+
if (delegation.status === "running") {
|
|
403
|
+
if (args.ai) {
|
|
404
|
+
return `Delegation "${args.id}" is still running.
|
|
405
|
+
|
|
406
|
+
Status: ${delegation.status}
|
|
407
|
+
Started: ${delegation.startedAt.toISOString()}
|
|
408
|
+
|
|
409
|
+
Wait for completion notification, then call delegation_read() again. AI analysis only available for completed sessions.`;
|
|
410
|
+
}
|
|
411
|
+
return `Delegation "${args.id}" is still running.
|
|
412
|
+
|
|
413
|
+
Status: ${delegation.status}
|
|
414
|
+
Started: ${delegation.startedAt.toISOString()}
|
|
415
|
+
|
|
416
|
+
Wait for completion notification, then call delegation_read() again.`;
|
|
417
|
+
}
|
|
418
|
+
if (delegation.status !== "completed") {
|
|
419
|
+
let statusMessage = `Delegation "${args.id}" ended with status: ${delegation.status}`;
|
|
420
|
+
if (delegation.error) statusMessage += `
|
|
421
|
+
|
|
422
|
+
Error: ${delegation.error}`;
|
|
423
|
+
if (delegation.duration) statusMessage += `
|
|
424
|
+
|
|
425
|
+
Duration: ${delegation.duration}`;
|
|
426
|
+
return statusMessage;
|
|
427
|
+
}
|
|
428
|
+
if (args.ai) {
|
|
429
|
+
let model = args.ai_model;
|
|
430
|
+
if (!model) {
|
|
431
|
+
model = delegation.parentModel || await this.getDefaultModel();
|
|
432
|
+
if (!model) {
|
|
433
|
+
return "\u274C ai_model required when ai=true and no default model configured (parent session has no model)";
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return await this.analyzeSessionWithAI(delegation, model);
|
|
437
|
+
}
|
|
438
|
+
if (!args.mode || args.mode === "simple") {
|
|
439
|
+
return await this.getSimpleResult(delegation);
|
|
440
|
+
}
|
|
441
|
+
if (args.mode === "full") {
|
|
442
|
+
return await this.getFullSession(delegation, args);
|
|
443
|
+
}
|
|
444
|
+
return "Invalid mode. Use 'simple' or 'full'.";
|
|
445
|
+
}
|
|
446
|
+
async getSimpleResult(delegation) {
|
|
447
|
+
try {
|
|
448
|
+
const messages = await this.client.session.messages({
|
|
449
|
+
path: { id: delegation.sessionID }
|
|
450
|
+
});
|
|
451
|
+
const messageData = messages.data;
|
|
452
|
+
if (!messageData || messageData.length === 0) {
|
|
453
|
+
return `Delegation "${delegation.id}" completed but produced no output.`;
|
|
454
|
+
}
|
|
455
|
+
const assistantMessages = messageData.filter(
|
|
456
|
+
(m) => m.info.role === "assistant"
|
|
457
|
+
);
|
|
458
|
+
if (assistantMessages.length === 0) {
|
|
459
|
+
return `Delegation "${delegation.id}" completed but produced no assistant response.`;
|
|
460
|
+
}
|
|
461
|
+
const lastMessage = assistantMessages[assistantMessages.length - 1];
|
|
462
|
+
const textParts = lastMessage.parts.filter((p) => p.type === "text");
|
|
463
|
+
if (textParts.length === 0) {
|
|
464
|
+
return `Delegation "${delegation.id}" completed but produced no text content.`;
|
|
465
|
+
}
|
|
466
|
+
const result = textParts.map((p) => p.text).join("\n");
|
|
467
|
+
const header = `# Task Result: ${delegation.id}
|
|
468
|
+
|
|
469
|
+
**Agent:** ${delegation.agent}
|
|
470
|
+
**Status:** ${delegation.status}
|
|
471
|
+
**Duration:** ${delegation.duration || "N/A"}
|
|
472
|
+
**Started:** ${delegation.startedAt.toISOString()}
|
|
473
|
+
${delegation.completedAt ? `**Completed:** ${delegation.completedAt.toISOString()}` : ""}
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
`;
|
|
478
|
+
return header + result;
|
|
479
|
+
} catch (error) {
|
|
480
|
+
return `Error retrieving result: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
async getFullSession(delegation, args) {
|
|
484
|
+
try {
|
|
485
|
+
const messages = await this.client.session.messages({
|
|
486
|
+
path: { id: delegation.sessionID }
|
|
487
|
+
});
|
|
488
|
+
const messageData = messages.data;
|
|
489
|
+
if (!messageData || messageData.length === 0) {
|
|
490
|
+
return `Delegation "${delegation.id}" has no messages.`;
|
|
491
|
+
}
|
|
492
|
+
const sortedMessages = [...messageData].sort((a, b) => {
|
|
493
|
+
const timeA = String(a.info.time || "");
|
|
494
|
+
const timeB = String(b.info.time || "");
|
|
495
|
+
return timeA.localeCompare(timeB);
|
|
496
|
+
});
|
|
497
|
+
let filteredMessages = sortedMessages;
|
|
498
|
+
if (args.since_message_id) {
|
|
499
|
+
const index = sortedMessages.findIndex((m) => m.info.id === args.since_message_id);
|
|
500
|
+
if (index !== -1) {
|
|
501
|
+
filteredMessages = sortedMessages.slice(index + 1);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const limit = args.limit ? Math.min(args.limit, 100) : void 0;
|
|
505
|
+
const hasMore = limit !== void 0 && filteredMessages.length > limit;
|
|
506
|
+
const visibleMessages = limit !== void 0 ? filteredMessages.slice(0, limit) : filteredMessages;
|
|
507
|
+
const lines = [];
|
|
508
|
+
lines.push(`# Full Session: ${delegation.id}`);
|
|
509
|
+
lines.push("");
|
|
510
|
+
lines.push(`**Agent:** ${delegation.agent}`);
|
|
511
|
+
lines.push(`**Status:** ${delegation.status}`);
|
|
512
|
+
lines.push(`**Duration:** ${delegation.duration || "N/A"}`);
|
|
513
|
+
lines.push(`**Total messages:** ${sortedMessages.length}`);
|
|
514
|
+
lines.push(`**Returned:** ${visibleMessages.length}`);
|
|
515
|
+
lines.push(`**Has more:** ${hasMore ? "true" : "false"}`);
|
|
516
|
+
lines.push("");
|
|
517
|
+
lines.push("## Messages");
|
|
518
|
+
lines.push("");
|
|
519
|
+
for (const message of visibleMessages) {
|
|
520
|
+
const role = message.info.role;
|
|
521
|
+
const time = message.info.time || "unknown";
|
|
522
|
+
const id = message.info.id || "unknown";
|
|
523
|
+
lines.push(`### [${role}] ${time} (id: ${id})`);
|
|
524
|
+
lines.push("");
|
|
525
|
+
for (const part of message.parts) {
|
|
526
|
+
if (part.type === "text" && part.text) {
|
|
527
|
+
lines.push(part.text.trim());
|
|
528
|
+
lines.push("");
|
|
529
|
+
}
|
|
530
|
+
if (args.include_thinking && (part.type === "thinking" || part.type === "reasoning")) {
|
|
531
|
+
const thinkingText = part.thinking || part.text || "";
|
|
532
|
+
if (thinkingText) {
|
|
533
|
+
lines.push(`[thinking] ${thinkingText.slice(0, 2e3)}`);
|
|
534
|
+
lines.push("");
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (args.include_tools && part.type === "tool_result") {
|
|
538
|
+
const content = part.content || part.output || "";
|
|
539
|
+
if (content) {
|
|
540
|
+
lines.push(`[tool result] ${content}`);
|
|
541
|
+
lines.push("");
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return lines.join("\n");
|
|
547
|
+
} catch (error) {
|
|
548
|
+
return `Error fetching session: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
// ---- Notification ----
|
|
552
|
+
async notifyParent(delegation) {
|
|
553
|
+
try {
|
|
554
|
+
const pendingSet = this.pendingByParent.get(delegation.parentSessionID);
|
|
555
|
+
if (pendingSet) {
|
|
556
|
+
pendingSet.delete(delegation.id);
|
|
557
|
+
}
|
|
558
|
+
const allComplete = !pendingSet || pendingSet.size === 0;
|
|
559
|
+
const remainingCount = pendingSet?.size || 0;
|
|
560
|
+
const statusText = delegation.status === "completed" ? "COMPLETED" : delegation.status === "cancelled" ? "CANCELLED" : delegation.status === "error" ? "ERROR" : delegation.status === "timeout" ? "TIMEOUT" : delegation.status.toUpperCase();
|
|
561
|
+
const duration = delegation.duration || "N/A";
|
|
562
|
+
const errorInfo = delegation.error ? `
|
|
563
|
+
**Error:** ${delegation.error}` : "";
|
|
564
|
+
let notification;
|
|
565
|
+
if (allComplete) {
|
|
566
|
+
const completedTasks = [];
|
|
567
|
+
for (const d of this.delegations.values()) {
|
|
568
|
+
if (d.parentSessionID === delegation.parentSessionID && d.status !== "running") {
|
|
569
|
+
completedTasks.push(d);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const completedList = completedTasks.map((t) => `- \`${t.id}\`: ${t.title || t.prompt.slice(0, 80)}`).join("\n");
|
|
573
|
+
const sessionHints = completedTasks.map((t) => `opencode -s ${t.id}`).join("\n");
|
|
574
|
+
notification = `<system-reminder>
|
|
575
|
+
[ALL BACKGROUND TASKS COMPLETE]
|
|
576
|
+
|
|
577
|
+
**Completed:**
|
|
578
|
+
${completedList || `- \`${delegation.id}\`: ${delegation.title || delegation.prompt.slice(0, 80)}`}
|
|
579
|
+
|
|
580
|
+
Use \`delegation_read(id="<id>")\` to retrieve each result.
|
|
581
|
+
</system-reminder>
|
|
582
|
+
To inspect session content(human): ${sessionHints || `opencode -s ${delegation.id}`}`;
|
|
583
|
+
} else {
|
|
584
|
+
notification = `<system-reminder>
|
|
585
|
+
[BACKGROUND TASK ${statusText}]
|
|
586
|
+
**ID:** \`${delegation.id}\`
|
|
587
|
+
**Agent:** ${delegation.agent}
|
|
588
|
+
**Duration:** ${duration}${errorInfo}
|
|
589
|
+
|
|
590
|
+
**${remainingCount} task${remainingCount === 1 ? "" : "s"} still in progress.** You WILL be notified when ALL complete.
|
|
591
|
+
Do NOT poll - continue productive work.
|
|
592
|
+
|
|
593
|
+
Use \`delegation_read(id="${delegation.id}")\` to retrieve this result when ready.
|
|
594
|
+
</system-reminder>
|
|
595
|
+
To inspect session content(human): opencode -s ${delegation.id}`;
|
|
596
|
+
}
|
|
597
|
+
this.client.session.prompt({
|
|
598
|
+
path: { id: delegation.parentSessionID },
|
|
599
|
+
body: {
|
|
600
|
+
noReply: !allComplete,
|
|
601
|
+
agent: delegation.parentAgent,
|
|
602
|
+
parts: [{ type: "text", text: notification }]
|
|
603
|
+
}
|
|
604
|
+
}).catch(() => {
|
|
605
|
+
});
|
|
606
|
+
await this.debugLog(
|
|
607
|
+
`Notified parent session ${delegation.parentSessionID} (status=${statusText}, remaining=${remainingCount})`
|
|
608
|
+
);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
await this.debugLog(
|
|
611
|
+
`Failed to notify parent: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
// ---- Queries ----
|
|
616
|
+
async listDelegations(parentSessionID) {
|
|
617
|
+
const results = [];
|
|
618
|
+
for (const delegation of this.delegations.values()) {
|
|
619
|
+
if (delegation.parentSessionID === parentSessionID) {
|
|
620
|
+
results.push({
|
|
621
|
+
id: delegation.id,
|
|
622
|
+
status: delegation.status,
|
|
623
|
+
title: delegation.title,
|
|
624
|
+
description: delegation.description,
|
|
625
|
+
agent: delegation.agent,
|
|
626
|
+
duration: delegation.duration,
|
|
627
|
+
startedAt: delegation.startedAt
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return results.sort((a, b) => (b.startedAt?.getTime() || 0) - (a.startedAt?.getTime() || 0));
|
|
632
|
+
}
|
|
633
|
+
listAllDelegations() {
|
|
634
|
+
const results = [];
|
|
635
|
+
for (const delegation of this.delegations.values()) {
|
|
636
|
+
results.push({
|
|
637
|
+
id: delegation.id,
|
|
638
|
+
status: delegation.status,
|
|
639
|
+
title: delegation.title,
|
|
640
|
+
description: delegation.description,
|
|
641
|
+
agent: delegation.agent,
|
|
642
|
+
duration: delegation.duration,
|
|
643
|
+
startedAt: delegation.startedAt
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
return results.sort((a, b) => (b.startedAt?.getTime() || 0) - (a.startedAt?.getTime() || 0));
|
|
647
|
+
}
|
|
648
|
+
findBySession(sessionID) {
|
|
649
|
+
return Array.from(this.delegations.values()).find((d) => d.sessionID === sessionID);
|
|
650
|
+
}
|
|
651
|
+
handleMessageEvent(sessionID, messageText) {
|
|
652
|
+
const delegation = this.findBySession(sessionID);
|
|
653
|
+
if (!delegation || delegation.status !== "running") return;
|
|
654
|
+
delegation.progress.lastUpdate = /* @__PURE__ */ new Date();
|
|
655
|
+
if (messageText) {
|
|
656
|
+
delegation.progress.lastMessage = messageText;
|
|
657
|
+
delegation.progress.lastMessageAt = /* @__PURE__ */ new Date();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
getPendingCount(parentSessionID) {
|
|
661
|
+
const pendingSet = this.pendingByParent.get(parentSessionID);
|
|
662
|
+
return pendingSet ? pendingSet.size : 0;
|
|
663
|
+
}
|
|
664
|
+
getRunningDelegations() {
|
|
665
|
+
return Array.from(this.delegations.values()).filter((d) => d.status === "running");
|
|
666
|
+
}
|
|
667
|
+
async debugLog(msg) {
|
|
668
|
+
this.log.debug(msg);
|
|
669
|
+
}
|
|
670
|
+
// ---- AI Analysis ----
|
|
671
|
+
async getDefaultModel() {
|
|
672
|
+
try {
|
|
673
|
+
const result = await this.client.config.get();
|
|
674
|
+
const config = result.data;
|
|
675
|
+
return config?.model || null;
|
|
676
|
+
} catch (error) {
|
|
677
|
+
this.log.debug(`Failed to get default model: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
getModelInfo(modelId) {
|
|
682
|
+
const [provider, ...rest] = modelId.split("/");
|
|
683
|
+
if (!provider) {
|
|
684
|
+
throw new Error(`Invalid model format: "${modelId}". Expected "provider/model"`);
|
|
685
|
+
}
|
|
686
|
+
const modelIdOnly = rest.join("/");
|
|
687
|
+
try {
|
|
688
|
+
const modelJsonPath = join(process.env.HOME || "", ".cache", "opencode", "models.json");
|
|
689
|
+
const content = readFileSync(modelJsonPath, "utf-8");
|
|
690
|
+
const modelsData = JSON.parse(content);
|
|
691
|
+
if (!modelsData[provider]) {
|
|
692
|
+
throw new Error(`Provider "${provider}" not found in models.json`);
|
|
693
|
+
}
|
|
694
|
+
const providerData = modelsData[provider];
|
|
695
|
+
const apiUrl = providerData.api || providerData.baseUrl;
|
|
696
|
+
if (!apiUrl) {
|
|
697
|
+
throw new Error(`No API URL found for provider "${provider}"`);
|
|
698
|
+
}
|
|
699
|
+
return { provider, apiUrl, modelId: modelIdOnly };
|
|
700
|
+
} catch (error) {
|
|
701
|
+
if (error instanceof Error) throw error;
|
|
702
|
+
throw new Error(`Failed to parse models.json: ${error}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
getApiKey(provider) {
|
|
706
|
+
try {
|
|
707
|
+
const authJsonPath = join(process.env.HOME || "", ".local", "share", "opencode", "auth.json");
|
|
708
|
+
const content = readFileSync(authJsonPath, "utf-8");
|
|
709
|
+
const authData = JSON.parse(content);
|
|
710
|
+
if (!authData[provider]) {
|
|
711
|
+
throw new Error(`Provider "${provider}" not found in auth.json`);
|
|
712
|
+
}
|
|
713
|
+
const providerAuth = authData[provider];
|
|
714
|
+
if (providerAuth.type !== "api") {
|
|
715
|
+
throw new Error(`Provider "${provider}" is not an API key type`);
|
|
716
|
+
}
|
|
717
|
+
return providerAuth.key;
|
|
718
|
+
} catch (error) {
|
|
719
|
+
if (error instanceof Error) throw error;
|
|
720
|
+
throw new Error(`Failed to parse auth.json: ${error}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
formatSessionForAI(messages, delegation) {
|
|
724
|
+
let initialPrompt = delegation.prompt;
|
|
725
|
+
const parts = [];
|
|
726
|
+
for (const msg of messages) {
|
|
727
|
+
const role = msg.info.role.toUpperCase();
|
|
728
|
+
const timestamp = msg.info.time?.created ? new Date(msg.info.time.created).toISOString() : "unknown";
|
|
729
|
+
parts.push(`[${role}] ${timestamp}`);
|
|
730
|
+
for (const part of msg.parts) {
|
|
731
|
+
switch (part.type) {
|
|
732
|
+
case "text":
|
|
733
|
+
if (part.text) {
|
|
734
|
+
parts.push(part.text.trim());
|
|
735
|
+
}
|
|
736
|
+
break;
|
|
737
|
+
case "reasoning":
|
|
738
|
+
case "thinking":
|
|
739
|
+
const thinkingText = part.thinking || part.text || "";
|
|
740
|
+
if (thinkingText) {
|
|
741
|
+
parts.push(`[REASONING] ${thinkingText.slice(0, 2e3)}`);
|
|
742
|
+
}
|
|
743
|
+
break;
|
|
744
|
+
case "tool":
|
|
745
|
+
if (part.state) {
|
|
746
|
+
const toolInput = part.state.status === "pending" || part.state.status === "running" ? JSON.stringify(part.state.input || {}) : JSON.stringify(part.state.input || {});
|
|
747
|
+
parts.push(`[TOOL CALL] ${part.tool}: ${toolInput}`);
|
|
748
|
+
}
|
|
749
|
+
break;
|
|
750
|
+
case "tool_result":
|
|
751
|
+
const content = part.content || part.output || "";
|
|
752
|
+
parts.push(`[TOOL RESULT] ${content}`);
|
|
753
|
+
break;
|
|
754
|
+
case "file":
|
|
755
|
+
parts.push(`[FILE] ${part.filename || "unknown file"} (${part.mime})`);
|
|
756
|
+
break;
|
|
757
|
+
case "patch":
|
|
758
|
+
parts.push(`[PATCH] Code diff applied`);
|
|
759
|
+
break;
|
|
760
|
+
case "snapshot":
|
|
761
|
+
parts.push(`[SNAPSHOT] State snapshot`);
|
|
762
|
+
break;
|
|
763
|
+
case "agent":
|
|
764
|
+
parts.push(`[AGENT] Switched to: ${part.name || "unknown"}`);
|
|
765
|
+
break;
|
|
766
|
+
default:
|
|
767
|
+
parts.push(`[${part.type}] ${JSON.stringify(part).slice(0, 200)}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
parts.push("");
|
|
771
|
+
}
|
|
772
|
+
return `# Full Conversation
|
|
773
|
+
|
|
774
|
+
${parts.join("\n")}`;
|
|
775
|
+
}
|
|
776
|
+
async callAIForAnalysis(apiUrl, apiKey, model, prompt, timeoutMs = 6e4) {
|
|
777
|
+
const controller = new AbortController();
|
|
778
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
779
|
+
try {
|
|
780
|
+
const url = apiUrl.endsWith("/") ? `${apiUrl}chat/completions` : `${apiUrl}/chat/completions`;
|
|
781
|
+
const response = await fetch(url, {
|
|
782
|
+
method: "POST",
|
|
783
|
+
headers: {
|
|
784
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
785
|
+
"Content-Type": "application/json"
|
|
786
|
+
},
|
|
787
|
+
body: JSON.stringify({
|
|
788
|
+
model,
|
|
789
|
+
messages: [{ role: "user", content: prompt }],
|
|
790
|
+
max_tokens: 4e3,
|
|
791
|
+
temperature: 0.3
|
|
792
|
+
}),
|
|
793
|
+
signal: controller.signal
|
|
794
|
+
});
|
|
795
|
+
clearTimeout(timeoutId);
|
|
796
|
+
if (!response.ok) {
|
|
797
|
+
const errorText = await response.text();
|
|
798
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}
|
|
799
|
+
${errorText}`);
|
|
800
|
+
}
|
|
801
|
+
const data = await response.json();
|
|
802
|
+
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
|
|
803
|
+
throw new Error("Invalid API response format");
|
|
804
|
+
}
|
|
805
|
+
return data.choices[0].message.content;
|
|
806
|
+
} catch (error) {
|
|
807
|
+
if (error.name === "AbortError") {
|
|
808
|
+
throw new Error("AI analysis timed out after 60 seconds");
|
|
809
|
+
}
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
logAnalysis(delegationId, model, result, error, duration) {
|
|
814
|
+
if (process.env.OC_ASYNC_DEBUG !== "true") {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
const logDir = join(process.env.HOME || "", ".cache", "opencode-delegation-ai");
|
|
819
|
+
if (!existsSync(logDir)) {
|
|
820
|
+
mkdirSync(logDir, { recursive: true });
|
|
821
|
+
}
|
|
822
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
823
|
+
const filename = `analysis-${delegationId}-${timestamp}.json`;
|
|
824
|
+
const logEntry = {
|
|
825
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
826
|
+
delegationId,
|
|
827
|
+
model,
|
|
828
|
+
status: error ? "error" : "success",
|
|
829
|
+
durationMs: duration,
|
|
830
|
+
result: error ? void 0 : result,
|
|
831
|
+
error: error || void 0
|
|
832
|
+
};
|
|
833
|
+
writeFileSync(join(logDir, filename), JSON.stringify(logEntry, null, 2));
|
|
834
|
+
} catch (err) {
|
|
835
|
+
this.log.debug(`Failed to log analysis: ${err}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
async analyzeSessionWithAI(delegation, model) {
|
|
839
|
+
const startTime = Date.now();
|
|
840
|
+
try {
|
|
841
|
+
const modelInfo = this.getModelInfo(model);
|
|
842
|
+
const apiKey = this.getApiKey(modelInfo.provider);
|
|
843
|
+
const messagesResult = await this.client.session.messages({
|
|
844
|
+
path: { id: delegation.sessionID }
|
|
845
|
+
});
|
|
846
|
+
const messageData = messagesResult.data;
|
|
847
|
+
if (!messageData || messageData.length === 0) {
|
|
848
|
+
return `Delegation "${delegation.id}" has no messages to analyze.`;
|
|
849
|
+
}
|
|
850
|
+
const formattedMessages = this.formatSessionForAI(messageData, delegation);
|
|
851
|
+
const initialPrompt = messageData.find((m) => m.info.role === "user")?.parts.filter((p) => p.type === "text").map((p) => p.text).join("\n") || delegation.prompt;
|
|
852
|
+
const sessionMetadata = `
|
|
853
|
+
### Session Metadata
|
|
854
|
+
- Agent: ${delegation.agent}
|
|
855
|
+
- Model: ${delegation.model || "unknown"}
|
|
856
|
+
- Duration: ${delegation.duration || "N/A"}
|
|
857
|
+
- Status: ${delegation.status}
|
|
858
|
+
- Started: ${delegation.startedAt.toISOString()}
|
|
859
|
+
- Completed: ${delegation.completedAt?.toISOString() || "N/A"}
|
|
860
|
+
`;
|
|
861
|
+
const fullPrompt = ANALYSIS_PROMPT.replace("${initialPrompt}", initialPrompt).replace("${formattedMessages}", formattedMessages).replace("${agent}", delegation.agent).replace("${model}", delegation.model || "unknown").replace("${duration}", delegation.duration || "N/A").replace("${status}", delegation.status).replace("${startTime}", delegation.startedAt.toISOString()).replace("${completedTime}", delegation.completedAt?.toISOString() || "N/A");
|
|
862
|
+
const analysis = await this.callAIForAnalysis(modelInfo.apiUrl, apiKey, modelInfo.modelId, fullPrompt);
|
|
863
|
+
const duration = Date.now() - startTime;
|
|
864
|
+
this.logAnalysis(delegation.id, model, analysis, void 0, duration);
|
|
865
|
+
const header = `# AI Analysis for Delegation: ${delegation.id}
|
|
866
|
+
|
|
867
|
+
**Agent:** ${delegation.agent}
|
|
868
|
+
**Analysis Model:** ${model}
|
|
869
|
+
**Duration:** ${delegation.duration || "N/A"}
|
|
870
|
+
**Analysis Time:** ${(duration / 1e3).toFixed(2)}s
|
|
871
|
+
|
|
872
|
+
---
|
|
873
|
+
|
|
874
|
+
`;
|
|
875
|
+
return header + analysis;
|
|
876
|
+
} catch (error) {
|
|
877
|
+
const duration = Date.now() - startTime;
|
|
878
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
879
|
+
this.logAnalysis(delegation.id, model, "", errorMessage, duration);
|
|
880
|
+
return `\u274C AI analysis failed:
|
|
881
|
+
|
|
882
|
+
${errorMessage}`;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
// src/plugin/tools.ts
|
|
888
|
+
import { tool } from "@opencode-ai/plugin";
|
|
889
|
+
function createDelegate(manager) {
|
|
890
|
+
return tool({
|
|
891
|
+
description: `Delegate a task to an agent. Returns immediately with the session ID.
|
|
892
|
+
|
|
893
|
+
Use this for:
|
|
894
|
+
- Research tasks (will be auto-saved)
|
|
895
|
+
- Parallel work that can run in background
|
|
896
|
+
- Any task where you want persistent, retrievable output
|
|
897
|
+
|
|
898
|
+
On completion, a notification will arrive with the session ID, status, duration.
|
|
899
|
+
Use \`delegation_read\` with the session ID to retrieve the result.`,
|
|
900
|
+
args: {
|
|
901
|
+
prompt: tool.schema.string().describe("The full detailed prompt for the agent. Must be in English."),
|
|
902
|
+
agent: tool.schema.string().describe(
|
|
903
|
+
'Agent to delegate to: "explore" (codebase search), "researcher" (external research), etc.'
|
|
904
|
+
),
|
|
905
|
+
model: tool.schema.string().optional().describe(
|
|
906
|
+
'Override model for this delegation. Format: "provider/model" (e.g. "minimax/MiniMax-M2.5"). If not set, uses the agent default.'
|
|
907
|
+
)
|
|
908
|
+
},
|
|
909
|
+
async execute(args, toolCtx) {
|
|
910
|
+
if (!toolCtx?.sessionID) {
|
|
911
|
+
return "\u274C delegate requires sessionID. This is a system error.";
|
|
912
|
+
}
|
|
913
|
+
if (!toolCtx?.messageID) {
|
|
914
|
+
return "\u274C delegate requires messageID. This is a system error.";
|
|
915
|
+
}
|
|
916
|
+
if (manager.findBySession(toolCtx.sessionID)) {
|
|
917
|
+
return "\u274C Sub-agents cannot delegate tasks. Only the main agent can use delegation tools.";
|
|
918
|
+
}
|
|
919
|
+
try {
|
|
920
|
+
const delegation = await manager.delegate({
|
|
921
|
+
parentSessionID: toolCtx.sessionID,
|
|
922
|
+
parentMessageID: toolCtx.messageID,
|
|
923
|
+
parentAgent: toolCtx.agent,
|
|
924
|
+
prompt: args.prompt,
|
|
925
|
+
agent: args.agent,
|
|
926
|
+
model: args.model
|
|
927
|
+
});
|
|
928
|
+
const pendingCount = manager.getPendingCount(toolCtx.sessionID);
|
|
929
|
+
let response = `Delegation started: ${delegation.id}
|
|
930
|
+
Agent: ${args.agent}`;
|
|
931
|
+
if (pendingCount > 1) {
|
|
932
|
+
response += `
|
|
933
|
+
|
|
934
|
+
${pendingCount} delegations now active.`;
|
|
935
|
+
}
|
|
936
|
+
response += `
|
|
937
|
+
|
|
938
|
+
You WILL be notified via <system-reminder> when complete. Do NOT poll delegation_list().`;
|
|
939
|
+
return response;
|
|
940
|
+
} catch (error) {
|
|
941
|
+
return `\u274C Delegation failed:
|
|
942
|
+
|
|
943
|
+
${error instanceof Error ? error.message : "Unknown error"}`;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
function createDelegationRead(manager) {
|
|
949
|
+
return tool({
|
|
950
|
+
description: `Read the output of a delegation by its ID.
|
|
951
|
+
|
|
952
|
+
Modes:
|
|
953
|
+
- simple (default): Returns just the final result
|
|
954
|
+
- full: Returns all messages in the session with timestamps
|
|
955
|
+
- ai (requires ai=true): Use AI to analyze and summarize the entire session execution
|
|
956
|
+
|
|
957
|
+
Use filters to get specific parts of the conversation.`,
|
|
958
|
+
args: {
|
|
959
|
+
id: tool.schema.string().describe("The delegation session ID"),
|
|
960
|
+
mode: tool.schema.enum(["simple", "full"]).optional().describe("Output mode: 'simple' for result only, 'full' for all messages"),
|
|
961
|
+
include_thinking: tool.schema.boolean().optional().describe("Include thinking/reasoning blocks in full mode"),
|
|
962
|
+
include_tools: tool.schema.boolean().optional().describe("Include tool results in full mode"),
|
|
963
|
+
since_message_id: tool.schema.string().optional().describe("Return only messages after this message ID (full mode only)"),
|
|
964
|
+
limit: tool.schema.number().optional().describe("Max messages to return, capped at 100 (full mode only)"),
|
|
965
|
+
ai: tool.schema.boolean().optional().describe("Use AI to analyze and summarize the session"),
|
|
966
|
+
ai_model: tool.schema.string().optional().describe("Model for AI analysis (e.g. 'minimax/MiniMax-M2.5'). Required when ai=true if no default model configured")
|
|
967
|
+
},
|
|
968
|
+
async execute(args, toolCtx) {
|
|
969
|
+
if (!toolCtx?.sessionID) {
|
|
970
|
+
return "\u274C delegation_read requires sessionID. This is a system error.";
|
|
971
|
+
}
|
|
972
|
+
try {
|
|
973
|
+
return await manager.readDelegation(args);
|
|
974
|
+
} catch (error) {
|
|
975
|
+
return `\u274C Error reading delegation: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
function createDelegationList(manager) {
|
|
981
|
+
return tool({
|
|
982
|
+
description: `List all delegations for the current session.
|
|
983
|
+
Shows running, completed, cancelled, and error tasks with metadata.`,
|
|
984
|
+
args: {},
|
|
985
|
+
async execute(_args, toolCtx) {
|
|
986
|
+
if (!toolCtx?.sessionID) {
|
|
987
|
+
return "\u274C delegation_list requires sessionID. This is a system error.";
|
|
988
|
+
}
|
|
989
|
+
const delegations = await manager.listDelegations(toolCtx.sessionID);
|
|
990
|
+
if (delegations.length === 0) {
|
|
991
|
+
return "No delegations found for this session.";
|
|
992
|
+
}
|
|
993
|
+
const lines = delegations.map((d) => {
|
|
994
|
+
const titlePart = d.title ? ` | ${d.title}` : "";
|
|
995
|
+
const durationPart = d.duration ? ` (${d.duration})` : "";
|
|
996
|
+
const descPart = d.description ? `
|
|
997
|
+
\u2192 ${d.description.slice(0, 100)}${d.description.length > 100 ? "..." : ""}` : "";
|
|
998
|
+
return `- **${d.id}**${titlePart} [${d.status}]${durationPart}${descPart}`;
|
|
999
|
+
});
|
|
1000
|
+
return `## Delegations
|
|
1001
|
+
|
|
1002
|
+
${lines.join("\n")}`;
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
function createDelegationCancel(manager) {
|
|
1007
|
+
return tool({
|
|
1008
|
+
description: `Cancel a running delegation by ID, or cancel all running delegations.
|
|
1009
|
+
|
|
1010
|
+
Cancelled tasks can be resumed later with delegation_resume().`,
|
|
1011
|
+
args: {
|
|
1012
|
+
id: tool.schema.string().optional().describe("Task ID to cancel"),
|
|
1013
|
+
all: tool.schema.boolean().optional().describe("Cancel ALL running delegations")
|
|
1014
|
+
},
|
|
1015
|
+
async execute(args, toolCtx) {
|
|
1016
|
+
if (!toolCtx?.sessionID) {
|
|
1017
|
+
return "\u274C delegation_cancel requires sessionID. This is a system error.";
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
if (args.all) {
|
|
1021
|
+
const cancelled = await manager.cancelAll(toolCtx.sessionID);
|
|
1022
|
+
if (cancelled.length === 0) {
|
|
1023
|
+
return "No running delegations to cancel.";
|
|
1024
|
+
}
|
|
1025
|
+
return `Cancelled ${cancelled.length} delegation(s):
|
|
1026
|
+
${cancelled.map((id) => `- ${id}`).join("\n")}`;
|
|
1027
|
+
}
|
|
1028
|
+
if (!args.id) {
|
|
1029
|
+
return "\u274C Must provide either 'id' or 'all=true'";
|
|
1030
|
+
}
|
|
1031
|
+
const success = await manager.cancel(args.id);
|
|
1032
|
+
if (!success) {
|
|
1033
|
+
return `\u274C Could not cancel "${args.id}". Task may not exist or is not running.`;
|
|
1034
|
+
}
|
|
1035
|
+
return `\u2705 Cancelled delegation: ${args.id}
|
|
1036
|
+
|
|
1037
|
+
You can resume it later with delegation_resume(id="${args.id}")`;
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
return `\u274C Error cancelling: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
function createDelegationResume(manager) {
|
|
1045
|
+
return tool({
|
|
1046
|
+
description: `Resume a cancelled or errored delegation by sending a new prompt to the same session.
|
|
1047
|
+
|
|
1048
|
+
The agent will have access to the previous conversation context.`,
|
|
1049
|
+
args: {
|
|
1050
|
+
id: tool.schema.string().describe("Task ID to resume"),
|
|
1051
|
+
prompt: tool.schema.string().optional().describe("Optional prompt to send (default: 'Continue from where you left off.')")
|
|
1052
|
+
},
|
|
1053
|
+
async execute(args, toolCtx) {
|
|
1054
|
+
if (!toolCtx?.sessionID) {
|
|
1055
|
+
return "\u274C delegation_resume requires sessionID. This is a system error.";
|
|
1056
|
+
}
|
|
1057
|
+
try {
|
|
1058
|
+
const delegation = await manager.resume(args.id, args.prompt);
|
|
1059
|
+
return `\u2705 Resumed delegation: ${delegation.id}
|
|
1060
|
+
Agent: ${delegation.agent}
|
|
1061
|
+
Status: ${delegation.status}`;
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
return `\u274C Error resuming: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/plugin/rules.ts
|
|
1070
|
+
var DELEGATION_RULES = `<system-reminder>
|
|
1071
|
+
<delegation-system>
|
|
1072
|
+
|
|
1073
|
+
## Async Delegation
|
|
1074
|
+
|
|
1075
|
+
You have tools for parallel background work:
|
|
1076
|
+
- \`delegate(prompt, agent)\` - Launch task, returns ID immediately
|
|
1077
|
+
- \`delegate(prompt, agent, model)\` - Launch task with specific model override
|
|
1078
|
+
- \`delegation_read(id)\` - Retrieve completed result
|
|
1079
|
+
- \`delegation_list()\` - List delegations (use sparingly)
|
|
1080
|
+
- \`delegation_cancel(id|all)\` - Cancel running task(s)
|
|
1081
|
+
- \`delegation_resume(id, prompt?)\` - Continue cancelled task (same session)
|
|
1082
|
+
|
|
1083
|
+
## How It Works
|
|
1084
|
+
|
|
1085
|
+
1. Call \`delegate()\` - Get task ID immediately, continue working
|
|
1086
|
+
2. Receive \`<system-reminder>\` notification when complete
|
|
1087
|
+
3. Call \`delegation_read(id)\` to get the actual result
|
|
1088
|
+
|
|
1089
|
+
## Model Override
|
|
1090
|
+
|
|
1091
|
+
The \`delegate\` tool accepts an optional \`model\` parameter in "provider/model" format.
|
|
1092
|
+
Example: \`delegate(prompt, agent, model="minimax/MiniMax-M2.5")\`
|
|
1093
|
+
If not specified, the agent's default model is used.
|
|
1094
|
+
|
|
1095
|
+
## Critical Constraints
|
|
1096
|
+
|
|
1097
|
+
**NEVER poll \`delegation_list\` to check completion.**
|
|
1098
|
+
You WILL be notified via \`<system-reminder>\`. Polling wastes tokens.
|
|
1099
|
+
|
|
1100
|
+
**NEVER wait idle.** Always have productive work while delegations run.
|
|
1101
|
+
|
|
1102
|
+
**Cancelled tasks can be resumed** with \`delegation_resume()\` - same session, full context.
|
|
1103
|
+
|
|
1104
|
+
</delegation-system>
|
|
1105
|
+
</system-reminder>`;
|
|
1106
|
+
async function readBgAgentsConfig() {
|
|
1107
|
+
const { homedir } = await import("os");
|
|
1108
|
+
const { readFile } = await import("fs/promises");
|
|
1109
|
+
const { join: join2 } = await import("path");
|
|
1110
|
+
const configDir = join2(homedir(), ".config", "opencode");
|
|
1111
|
+
for (const name of ["async-agents.md", "async-agent.md"]) {
|
|
1112
|
+
try {
|
|
1113
|
+
return await readFile(join2(configDir, name), "utf-8");
|
|
1114
|
+
} catch {
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return "";
|
|
1118
|
+
}
|
|
1119
|
+
function formatDelegationContext(running, completed) {
|
|
1120
|
+
const sections = ["<delegation-context>"];
|
|
1121
|
+
if (running.length > 0) {
|
|
1122
|
+
sections.push("## Running Delegations");
|
|
1123
|
+
sections.push("");
|
|
1124
|
+
for (const d of running) {
|
|
1125
|
+
sections.push(`### \`${d.id}\`${d.agent ? ` (${d.agent})` : ""}`);
|
|
1126
|
+
if (d.startedAt) {
|
|
1127
|
+
sections.push(`**Started:** ${d.startedAt.toISOString()}`);
|
|
1128
|
+
}
|
|
1129
|
+
sections.push("");
|
|
1130
|
+
}
|
|
1131
|
+
sections.push(
|
|
1132
|
+
"> **Note:** You WILL be notified via `<system-reminder>` when delegations complete."
|
|
1133
|
+
);
|
|
1134
|
+
sections.push("> Do NOT poll `delegation_list` - continue productive work.");
|
|
1135
|
+
sections.push("");
|
|
1136
|
+
}
|
|
1137
|
+
if (completed.length > 0) {
|
|
1138
|
+
sections.push("## Recent Completed Delegations");
|
|
1139
|
+
sections.push("");
|
|
1140
|
+
for (const d of completed) {
|
|
1141
|
+
const statusEmoji = d.status === "completed" ? "\u2705" : d.status === "error" ? "\u274C" : d.status === "timeout" ? "\u23F1\uFE0F" : "\u{1F6AB}";
|
|
1142
|
+
sections.push(`### ${statusEmoji} \`${d.id}\``);
|
|
1143
|
+
sections.push(`**Status:** ${d.status}`);
|
|
1144
|
+
if (d.duration) sections.push(`**Duration:** ${d.duration}`);
|
|
1145
|
+
sections.push("");
|
|
1146
|
+
}
|
|
1147
|
+
sections.push("> Use `delegation_list()` to see all delegations.");
|
|
1148
|
+
sections.push("");
|
|
1149
|
+
}
|
|
1150
|
+
sections.push("## Retrieval");
|
|
1151
|
+
sections.push('Use `delegation_read("id")` to access results.');
|
|
1152
|
+
sections.push('Use `delegation_read(id, mode="full")` for full conversation.');
|
|
1153
|
+
sections.push("</delegation-context>");
|
|
1154
|
+
return sections.join("\n");
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// src/plugin/plugin.ts
|
|
1158
|
+
var delegationCommand = "delegation";
|
|
1159
|
+
var AsyncAgentPlugin = async (ctx) => {
|
|
1160
|
+
const { client } = ctx;
|
|
1161
|
+
const log = createLogger(client);
|
|
1162
|
+
const manager = new DelegationManager(client, log);
|
|
1163
|
+
await manager.debugLog("AsyncAgentPlugin initialized");
|
|
1164
|
+
return {
|
|
1165
|
+
// Handle /delegation slash command execution
|
|
1166
|
+
"command.execute.before": async (input) => {
|
|
1167
|
+
if (input.command !== delegationCommand) return;
|
|
1168
|
+
const typedClient = client;
|
|
1169
|
+
let childSessions = [];
|
|
1170
|
+
try {
|
|
1171
|
+
const result = await typedClient.session.children({
|
|
1172
|
+
path: { id: input.sessionID }
|
|
1173
|
+
});
|
|
1174
|
+
childSessions = result.data ?? [];
|
|
1175
|
+
} catch {
|
|
1176
|
+
}
|
|
1177
|
+
const delegationSessions = childSessions.filter(
|
|
1178
|
+
(s) => s.title?.startsWith("Delegation:")
|
|
1179
|
+
);
|
|
1180
|
+
const inMemory = manager.listAllDelegations();
|
|
1181
|
+
const inMemoryMap = new Map(inMemory.map((d) => [d.id, d]));
|
|
1182
|
+
let message;
|
|
1183
|
+
if (delegationSessions.length === 0 && inMemory.length === 0) {
|
|
1184
|
+
message = "No delegations found for this session.";
|
|
1185
|
+
} else {
|
|
1186
|
+
const lines = [];
|
|
1187
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1188
|
+
const entries = [];
|
|
1189
|
+
for (const s of delegationSessions) {
|
|
1190
|
+
seen.add(s.id);
|
|
1191
|
+
const mem = inMemoryMap.get(s.id);
|
|
1192
|
+
const agent = mem?.agent ?? s.title?.replace("Delegation: ", "") ?? "unknown";
|
|
1193
|
+
const status = mem?.status?.toUpperCase() ?? "PERSISTED";
|
|
1194
|
+
const duration = mem?.duration ?? "\u2014";
|
|
1195
|
+
const created = s.time?.created ? new Date(s.time.created).toISOString() : mem?.startedAt?.toISOString() ?? "\u2014";
|
|
1196
|
+
entries.push({
|
|
1197
|
+
id: s.id,
|
|
1198
|
+
title: mem?.title ?? s.title ?? "\u2014",
|
|
1199
|
+
agent,
|
|
1200
|
+
status,
|
|
1201
|
+
duration,
|
|
1202
|
+
started: created
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
for (const d of inMemory) {
|
|
1206
|
+
if (seen.has(d.id)) continue;
|
|
1207
|
+
entries.push({
|
|
1208
|
+
id: d.id,
|
|
1209
|
+
title: d.title ?? d.description?.slice(0, 60) ?? "\u2014",
|
|
1210
|
+
agent: d.agent ?? "unknown",
|
|
1211
|
+
status: d.status?.toUpperCase() ?? "UNKNOWN",
|
|
1212
|
+
duration: d.duration ?? "\u2014",
|
|
1213
|
+
started: d.startedAt?.toISOString() ?? "\u2014"
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
lines.push(`## Delegations (${entries.length})
|
|
1217
|
+
`);
|
|
1218
|
+
for (const e of entries) {
|
|
1219
|
+
lines.push(`**[${e.id}]** ${e.title}`);
|
|
1220
|
+
lines.push(` Status: ${e.status} | Agent: ${e.agent} | Duration: ${e.duration}`);
|
|
1221
|
+
lines.push(` Started: ${e.started}`);
|
|
1222
|
+
lines.push(` \`opencode -s ${e.id}\``);
|
|
1223
|
+
lines.push("");
|
|
1224
|
+
}
|
|
1225
|
+
message = lines.join("\n");
|
|
1226
|
+
}
|
|
1227
|
+
await typedClient.session.prompt({
|
|
1228
|
+
path: { id: input.sessionID },
|
|
1229
|
+
body: {
|
|
1230
|
+
noReply: true,
|
|
1231
|
+
parts: [{ type: "text", text: message }]
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
throw new Error("Command handled by async-agent plugin");
|
|
1235
|
+
},
|
|
1236
|
+
tool: {
|
|
1237
|
+
delegate: createDelegate(manager),
|
|
1238
|
+
delegation_read: createDelegationRead(manager),
|
|
1239
|
+
delegation_list: createDelegationList(manager),
|
|
1240
|
+
delegation_cancel: createDelegationCancel(manager),
|
|
1241
|
+
delegation_resume: createDelegationResume(manager)
|
|
1242
|
+
},
|
|
1243
|
+
// Register /delegation slash command
|
|
1244
|
+
config: async (input) => {
|
|
1245
|
+
if (!input.command) input.command = {};
|
|
1246
|
+
input.command[delegationCommand] = {
|
|
1247
|
+
template: "Show all background delegation sessions with their status, IDs, agents, and metadata.",
|
|
1248
|
+
description: "List all background delegations"
|
|
1249
|
+
};
|
|
1250
|
+
},
|
|
1251
|
+
// Inject delegation rules + async-agent.md config into system prompt
|
|
1252
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
1253
|
+
output.system.push(DELEGATION_RULES);
|
|
1254
|
+
const bgConfig = await readBgAgentsConfig();
|
|
1255
|
+
if (bgConfig.trim()) {
|
|
1256
|
+
output.system.push(`<async-agent-config>
|
|
1257
|
+
${bgConfig}
|
|
1258
|
+
</async-agent-config>`);
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
// Inject active delegation context during session compaction
|
|
1262
|
+
"experimental.session.compacting": async (_input, output) => {
|
|
1263
|
+
const running = manager.getRunningDelegations().map((d) => ({
|
|
1264
|
+
id: d.id,
|
|
1265
|
+
agent: d.agent,
|
|
1266
|
+
status: d.status,
|
|
1267
|
+
startedAt: d.startedAt
|
|
1268
|
+
}));
|
|
1269
|
+
if (running.length > 0) {
|
|
1270
|
+
output.context.push(formatDelegationContext(running, []));
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
event: async (input) => {
|
|
1274
|
+
const { event } = input;
|
|
1275
|
+
if (event.type === "session.idle") {
|
|
1276
|
+
const sessionID = event.properties?.sessionID;
|
|
1277
|
+
const delegation = manager.findBySession(sessionID);
|
|
1278
|
+
if (delegation) {
|
|
1279
|
+
await manager.handleSessionIdle(sessionID);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
};
|
|
1285
|
+
var plugin_default = AsyncAgentPlugin;
|
|
1286
|
+
export {
|
|
1287
|
+
AsyncAgentPlugin,
|
|
1288
|
+
plugin_default as default
|
|
1289
|
+
};
|