@agnishc/edb-bridge 0.14.2 → 0.15.1
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 +1 -1
- package/src/index.ts +38 -87
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -92,64 +92,7 @@ export default function edbBridgeExtension(pi: ExtensionAPI): void {
|
|
|
92
92
|
|
|
93
93
|
const pendingAsks = new Map<string, PendingAsk>();
|
|
94
94
|
|
|
95
|
-
// Per-session outbound ask waiters: keyed by session ID
|
|
96
|
-
const outboundAskWaiters = new Map<
|
|
97
|
-
string,
|
|
98
|
-
{
|
|
99
|
-
replyTo: string;
|
|
100
|
-
resolve: (text: string) => void;
|
|
101
|
-
reject: (err: Error) => void;
|
|
102
|
-
}
|
|
103
|
-
>();
|
|
104
|
-
|
|
105
|
-
function waitForReply(sessionId: string, messageId: string, signal?: AbortSignal): Promise<string> {
|
|
106
|
-
if (outboundAskWaiters.has(sessionId)) {
|
|
107
|
-
return Promise.reject(new Error("Already waiting for a reply from supervisor in this session"));
|
|
108
|
-
}
|
|
109
|
-
if (signal?.aborted) return Promise.reject(new Error("Cancelled"));
|
|
110
|
-
|
|
111
|
-
return new Promise((resolve, reject) => {
|
|
112
|
-
const timeout = setTimeout(() => {
|
|
113
|
-
outboundAskWaiters.delete(sessionId);
|
|
114
|
-
reject(new Error("No reply from supervisor within 10 minutes"));
|
|
115
|
-
}, ASK_TIMEOUT_MS);
|
|
116
|
-
|
|
117
|
-
const onAbort = () => {
|
|
118
|
-
clearTimeout(timeout);
|
|
119
|
-
outboundAskWaiters.delete(sessionId);
|
|
120
|
-
reject(new Error("Cancelled"));
|
|
121
|
-
};
|
|
122
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
123
|
-
|
|
124
|
-
outboundAskWaiters.set(sessionId, {
|
|
125
|
-
replyTo: messageId,
|
|
126
|
-
resolve: (text) => {
|
|
127
|
-
clearTimeout(timeout);
|
|
128
|
-
signal?.removeEventListener("abort", onAbort);
|
|
129
|
-
outboundAskWaiters.delete(sessionId);
|
|
130
|
-
resolve(text);
|
|
131
|
-
},
|
|
132
|
-
reject: (err) => {
|
|
133
|
-
clearTimeout(timeout);
|
|
134
|
-
signal?.removeEventListener("abort", onAbort);
|
|
135
|
-
outboundAskWaiters.delete(sessionId);
|
|
136
|
-
reject(err);
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
95
|
function handleIncoming(from: SessionInfo, message: BridgeMessage): void {
|
|
143
|
-
// Check if this is a reply to an outbound ask
|
|
144
|
-
if (message.replyTo) {
|
|
145
|
-
for (const [, waiter] of outboundAskWaiters) {
|
|
146
|
-
if (waiter.replyTo === message.replyTo) {
|
|
147
|
-
waiter.resolve(message.content.text);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
96
|
// Task update notification — trigger widget refresh
|
|
154
97
|
if (message.type === "task_updated") {
|
|
155
98
|
pi.events.emit(EV_TASK_UPDATED, message.content.data ?? {});
|
|
@@ -353,12 +296,6 @@ export default function edbBridgeExtension(pi: ExtensionAPI): void {
|
|
|
353
296
|
|
|
354
297
|
// Clean up per-session state regardless
|
|
355
298
|
if (currentSessionId) sessionBridgeCtx.delete(currentSessionId);
|
|
356
|
-
// Clean up outbound waiters for this session only
|
|
357
|
-
const sessionWaiter = outboundAskWaiters.get(currentSessionId ?? "");
|
|
358
|
-
if (sessionWaiter) {
|
|
359
|
-
sessionWaiter.reject(new Error("Session shutting down"));
|
|
360
|
-
outboundAskWaiters.delete(currentSessionId ?? "");
|
|
361
|
-
}
|
|
362
299
|
|
|
363
300
|
if (isMainSession) {
|
|
364
301
|
// Full teardown — this is the orchestrator shutting down
|
|
@@ -372,10 +309,6 @@ export default function edbBridgeExtension(pi: ExtensionAPI): void {
|
|
|
372
309
|
ask.reject(new Error("Session shutting down"));
|
|
373
310
|
}
|
|
374
311
|
pendingAsks.clear();
|
|
375
|
-
for (const waiter of outboundAskWaiters.values()) {
|
|
376
|
-
waiter.reject(new Error("Session shutting down"));
|
|
377
|
-
}
|
|
378
|
-
outboundAskWaiters.clear();
|
|
379
312
|
if (client) {
|
|
380
313
|
await client.disconnect();
|
|
381
314
|
client = null;
|
|
@@ -432,6 +365,12 @@ export default function edbBridgeExtension(pi: ExtensionAPI): void {
|
|
|
432
365
|
pi.events.emit("bridge:supervisor_answered", { taskId: ask.taskId });
|
|
433
366
|
}
|
|
434
367
|
|
|
368
|
+
// Resume the suspended sub-agent session with the answer.
|
|
369
|
+
// edb-subagents listens for this event and calls manager.resumeInBackground.
|
|
370
|
+
if (ask.agentId) {
|
|
371
|
+
pi.events.emit("bridge:resume_agent", { agentId: ask.agentId, answer: params.answer });
|
|
372
|
+
}
|
|
373
|
+
|
|
435
374
|
if (!result.delivered) {
|
|
436
375
|
return textResult(
|
|
437
376
|
`Answer could not reach sub-agent (${result.reason ?? "disconnected"}). The ask was resolved locally.`,
|
|
@@ -453,26 +392,28 @@ export default function edbBridgeExtension(pi: ExtensionAPI): void {
|
|
|
453
392
|
},
|
|
454
393
|
});
|
|
455
394
|
|
|
456
|
-
// ── ask_supervisor (sub-agent sessions with bridge context)
|
|
395
|
+
// ── ask_supervisor (sub-agent sessions with bridge context) ———————————
|
|
457
396
|
|
|
458
397
|
pi.registerTool({
|
|
459
398
|
name: "ask_supervisor",
|
|
460
399
|
label: "Ask Supervisor",
|
|
461
400
|
description:
|
|
462
|
-
"Ask the orchestrator a
|
|
463
|
-
"Use when blocked, uncertain, or facing a decision
|
|
401
|
+
"Ask the orchestrator a question. Returns immediately — your session will be automatically resumed with the answer.\n\n" +
|
|
402
|
+
"Use when blocked, uncertain, or facing a decision that requires supervisor input.\n" +
|
|
464
403
|
"Do NOT use for routine completion — return results normally.",
|
|
465
|
-
promptSnippet:
|
|
404
|
+
promptSnippet:
|
|
405
|
+
"Ask the orchestrator a question. Returns immediately — session resumes automatically with the answer.",
|
|
466
406
|
promptGuidelines: [
|
|
467
407
|
"Use ask_supervisor when blocked by a decision or missing critical information.",
|
|
468
408
|
"Do not use for routine task completion.",
|
|
409
|
+
"After calling ask_supervisor, write a brief status of your progress then stop all tool calls. Your session resumes automatically when the supervisor answers.",
|
|
469
410
|
],
|
|
470
411
|
parameters: Type.Object({
|
|
471
412
|
question: Type.String({ description: "The question for the supervisor." }),
|
|
472
413
|
task_id: Type.Optional(Type.String({ description: "Optional: linked task ID." })),
|
|
473
414
|
}),
|
|
474
415
|
|
|
475
|
-
async execute(_toolCallId, params,
|
|
416
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
476
417
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
477
418
|
const bridgeCtx = sessionBridgeCtx.get(sessionId);
|
|
478
419
|
|
|
@@ -487,8 +428,12 @@ export default function edbBridgeExtension(pi: ExtensionAPI): void {
|
|
|
487
428
|
});
|
|
488
429
|
|
|
489
430
|
const messageId = randomUUID();
|
|
490
|
-
|
|
491
|
-
|
|
431
|
+
|
|
432
|
+
// Signal edb-subagents to mark this agent as suspending BEFORE we send
|
|
433
|
+
// the message — ensures the flag is set before runAgent() can return.
|
|
434
|
+
if (bridgeCtx.agentId) {
|
|
435
|
+
pi.events.emit("bridge:agent_suspending", { agentId: bridgeCtx.agentId });
|
|
436
|
+
}
|
|
492
437
|
|
|
493
438
|
try {
|
|
494
439
|
const result = await c.send(bridgeCtx.parentSessionId, {
|
|
@@ -500,20 +445,27 @@ export default function edbBridgeExtension(pi: ExtensionAPI): void {
|
|
|
500
445
|
});
|
|
501
446
|
|
|
502
447
|
if (!result.delivered) {
|
|
503
|
-
|
|
504
|
-
|
|
448
|
+
if (bridgeCtx.agentId) {
|
|
449
|
+
pi.events.emit("bridge:agent_suspend_cancelled", { agentId: bridgeCtx.agentId });
|
|
450
|
+
}
|
|
451
|
+
return textResult(
|
|
452
|
+
`Supervisor not reachable: ${result.reason ?? "session not found"}. Continuing without answer.`,
|
|
453
|
+
);
|
|
505
454
|
}
|
|
506
455
|
} catch (err) {
|
|
507
|
-
|
|
508
|
-
|
|
456
|
+
if (bridgeCtx.agentId) {
|
|
457
|
+
pi.events.emit("bridge:agent_suspend_cancelled", { agentId: bridgeCtx.agentId });
|
|
458
|
+
}
|
|
459
|
+
return textResult(`Failed to send question: ${getError(err)}. Continuing without answer.`);
|
|
509
460
|
}
|
|
510
461
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
462
|
+
// Fire-and-forget: return immediately. The supervisor answers via
|
|
463
|
+
// answer_subagent, which emits bridge:resume_agent to restart this session.
|
|
464
|
+
return textResult(
|
|
465
|
+
"Question sent to supervisor. " +
|
|
466
|
+
"Write a brief summary of your progress so far, then stop all tool calls. " +
|
|
467
|
+
"Your session will be resumed automatically with the supervisor's answer.",
|
|
468
|
+
);
|
|
517
469
|
},
|
|
518
470
|
|
|
519
471
|
renderCall(args, theme) {
|
|
@@ -525,10 +477,9 @@ export default function edbBridgeExtension(pi: ExtensionAPI): void {
|
|
|
525
477
|
);
|
|
526
478
|
},
|
|
527
479
|
|
|
528
|
-
renderResult(result,
|
|
529
|
-
if (isPartial) return new Text(theme.fg("warning", "⏸ waiting for supervisor..."), 0, 0);
|
|
480
|
+
renderResult(result, _opts, theme) {
|
|
530
481
|
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
531
|
-
return new Text(theme.fg("
|
|
482
|
+
return new Text(theme.fg("warning", "⏸ ") + theme.fg("muted", text.slice(0, 120)), 0, 0);
|
|
532
483
|
},
|
|
533
484
|
});
|
|
534
485
|
|