@rrr2010/opencode-roundtable 0.2.0 → 0.3.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 +244 -198
- package/dist/index.js +139 -100
- package/dist/index.js.map +8 -8
- package/docs/SPEC.md +974 -979
- package/package.json +60 -53
- package/src/tui/tui.tsx +89 -0
package/dist/index.js
CHANGED
|
@@ -28,7 +28,8 @@ var DEFAULT_CONFIG = {
|
|
|
28
28
|
"5. **Suggested next steps**"
|
|
29
29
|
].join(`
|
|
30
30
|
`),
|
|
31
|
-
maxRounds: 10
|
|
31
|
+
maxRounds: 10,
|
|
32
|
+
navigation: "link"
|
|
32
33
|
};
|
|
33
34
|
var config = { ...DEFAULT_CONFIG };
|
|
34
35
|
function getConfig() {
|
|
@@ -40,7 +41,8 @@ function validateConfig(raw) {
|
|
|
40
41
|
loopSimilarityThreshold: typeof raw.loopSimilarityThreshold === "number" && raw.loopSimilarityThreshold >= 0 && raw.loopSimilarityThreshold <= 1 ? raw.loopSimilarityThreshold : DEFAULT_CONFIG.loopSimilarityThreshold,
|
|
41
42
|
toolOutputPreviewMax: typeof raw.toolOutputPreviewMax === "number" && raw.toolOutputPreviewMax >= 100 ? raw.toolOutputPreviewMax : DEFAULT_CONFIG.toolOutputPreviewMax,
|
|
42
43
|
defaultObserverPrompt: typeof raw.defaultObserverPrompt === "string" && raw.defaultObserverPrompt.length > 0 ? raw.defaultObserverPrompt : DEFAULT_CONFIG.defaultObserverPrompt,
|
|
43
|
-
maxRounds: typeof raw.maxRounds === "number" && raw.maxRounds >= 1 ? raw.maxRounds : DEFAULT_CONFIG.maxRounds
|
|
44
|
+
maxRounds: typeof raw.maxRounds === "number" && raw.maxRounds >= 1 ? raw.maxRounds : DEFAULT_CONFIG.maxRounds,
|
|
45
|
+
navigation: raw.navigation === "selectSession" || raw.navigation === "none" || raw.navigation === "auto" ? raw.navigation : DEFAULT_CONFIG.navigation
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
async function loadConfig(ctx) {
|
|
@@ -91,43 +93,31 @@ async function loadConfig(ctx) {
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
// src/state.ts
|
|
96
|
+
import { readFile as readFile2, writeFile as writeFile2, unlink, readdir, mkdir } from "fs/promises";
|
|
97
|
+
import { join as join2 } from "path";
|
|
98
|
+
import os2 from "os";
|
|
99
|
+
var statesDir = (() => {
|
|
100
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
101
|
+
const base = xdgConfigHome ? join2(xdgConfigHome, "opencode") : join2(os2.homedir(), ".config", "opencode");
|
|
102
|
+
return join2(base, "roundtable-states");
|
|
103
|
+
})();
|
|
94
104
|
var states = new Map;
|
|
95
105
|
var timeoutHandles = new Map;
|
|
96
106
|
var pendingResults = new Map;
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
return `[ROUNDTABLE META — INTERNAL ORCHESTRATION DATA — IGNORE FOR ANALYSIS] ${json} [/ROUNDTABLE META]`;
|
|
107
|
+
function stateFilePath(sessionID) {
|
|
108
|
+
return join2(statesDir, `${sessionID}.json`);
|
|
100
109
|
}
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (part.type === "text" && part.text.startsWith("[ROUNDTABLE META")) {
|
|
108
|
-
last = part.text;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return last;
|
|
110
|
+
async function saveStateFile(state) {
|
|
111
|
+
try {
|
|
112
|
+
await mkdir(statesDir, { recursive: true });
|
|
113
|
+
} catch {}
|
|
114
|
+
const filePath = stateFilePath(state.sessionID);
|
|
115
|
+
await writeFile2(filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
113
116
|
}
|
|
114
|
-
function
|
|
117
|
+
async function loadStateFile(sessionID) {
|
|
115
118
|
try {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
if (startIdx === -1)
|
|
119
|
-
return null;
|
|
120
|
-
const closeBracket = raw.indexOf("]", startIdx);
|
|
121
|
-
if (closeBracket === -1)
|
|
122
|
-
return null;
|
|
123
|
-
const contentStart = closeBracket + 1;
|
|
124
|
-
const endIdx = raw.indexOf(endTag, contentStart);
|
|
125
|
-
if (endIdx === -1)
|
|
126
|
-
return null;
|
|
127
|
-
const json = raw.slice(contentStart, endIdx).trim();
|
|
128
|
-
if (!json)
|
|
129
|
-
return null;
|
|
130
|
-
const parsed = JSON.parse(json);
|
|
119
|
+
const content = await readFile2(stateFilePath(sessionID), "utf-8");
|
|
120
|
+
const parsed = JSON.parse(content);
|
|
131
121
|
if (typeof parsed !== "object" || !parsed)
|
|
132
122
|
return null;
|
|
133
123
|
if (typeof parsed.sessionID !== "string")
|
|
@@ -161,6 +151,19 @@ function deserializeState(raw) {
|
|
|
161
151
|
return null;
|
|
162
152
|
}
|
|
163
153
|
}
|
|
154
|
+
async function deleteStateFile(sessionID) {
|
|
155
|
+
try {
|
|
156
|
+
await unlink(stateFilePath(sessionID));
|
|
157
|
+
} catch {}
|
|
158
|
+
}
|
|
159
|
+
async function listStateFiles() {
|
|
160
|
+
try {
|
|
161
|
+
const entries = await readdir(statesDir);
|
|
162
|
+
return entries.filter((e) => e.endsWith(".json")).map((e) => e.replace(/\.json$/, ""));
|
|
163
|
+
} catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
164
167
|
|
|
165
168
|
// src/utils.ts
|
|
166
169
|
function generateDefaultTitle(args) {
|
|
@@ -334,13 +337,44 @@ Continuation: ${extendPrompt}`;
|
|
|
334
337
|
].join(`
|
|
335
338
|
`);
|
|
336
339
|
}
|
|
340
|
+
async function navigateToSession(ctx, targetID, _parentID) {
|
|
341
|
+
try {
|
|
342
|
+
if (typeof ctx.client.tui.selectSession === "function") {
|
|
343
|
+
await ctx.client.tui.selectSession({ sessionID: targetID });
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
await ctx.client.tui.publish({
|
|
347
|
+
body: {
|
|
348
|
+
type: "tui.session.select",
|
|
349
|
+
properties: { sessionID: targetID }
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
return true;
|
|
353
|
+
} catch {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
337
357
|
async function scanOrphanRoundtables(ctx) {
|
|
358
|
+
const sessionIDs = await listStateFiles();
|
|
359
|
+
let loaded = 0;
|
|
360
|
+
let errors = 0;
|
|
361
|
+
for (const sid of sessionIDs) {
|
|
362
|
+
try {
|
|
363
|
+
const state = await loadStateFile(sid);
|
|
364
|
+
if (state) {
|
|
365
|
+
states.set(sid, state);
|
|
366
|
+
loaded++;
|
|
367
|
+
}
|
|
368
|
+
} catch {
|
|
369
|
+
errors++;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
338
372
|
await ctx.client.app.log({
|
|
339
373
|
body: {
|
|
340
374
|
service: "roundtable",
|
|
341
375
|
level: "debug",
|
|
342
|
-
message:
|
|
343
|
-
extra: {
|
|
376
|
+
message: `scanOrphanRoundtables: ${loaded} state(s) loaded, ${errors} error(s)`,
|
|
377
|
+
extra: { loaded, errors, total: sessionIDs.length }
|
|
344
378
|
}
|
|
345
379
|
});
|
|
346
380
|
}
|
|
@@ -352,7 +386,6 @@ function buildAgentPrompt(state, agent) {
|
|
|
352
386
|
const roundInfo = `Round ${state.currentRound + 1}/${state.totalRounds} | Current agent: ${agent}`;
|
|
353
387
|
if (state.currentRound === 0 && state.currentAgentIndex === 0) {
|
|
354
388
|
lines.push(`[System] You are participating in a roundtable debate. Agents (${agentList}) will discuss the topic below in ${state.totalRounds} round(s).`);
|
|
355
|
-
lines.push(`[System] The [ROUNDTABLE META] block above is internal routing data — ignore it for all analysis. Focus only on the topic given in [Topic] below.`);
|
|
356
389
|
lines.push(`[Topic] ${state.prompt}`);
|
|
357
390
|
lines.push("");
|
|
358
391
|
}
|
|
@@ -366,11 +399,11 @@ function buildAgentPrompt(state, agent) {
|
|
|
366
399
|
`);
|
|
367
400
|
}
|
|
368
401
|
function buildObserverPrompt(state, observer) {
|
|
369
|
-
const observerPrompt = getConfig().defaultObserverPrompt;
|
|
402
|
+
const observerPrompt = state.observerPrompt ?? getConfig().defaultObserverPrompt;
|
|
370
403
|
if (observer === "built-in")
|
|
371
404
|
return observerPrompt;
|
|
372
405
|
return `You are an impartial roundtable observer.
|
|
373
|
-
` + `Your role: ${observer}.
|
|
406
|
+
` + `Your role: ${observer}.
|
|
374
407
|
|
|
375
408
|
` + observerPrompt;
|
|
376
409
|
}
|
|
@@ -379,7 +412,10 @@ function buildObserverPrompt(state, observer) {
|
|
|
379
412
|
async function startNewRoundtable(ctx, args, toolCtx) {
|
|
380
413
|
const agents = args.agents;
|
|
381
414
|
const newSession = await ctx.client.session.create({
|
|
382
|
-
body: {
|
|
415
|
+
body: {
|
|
416
|
+
title: generateDefaultTitle({ ...args, agents }),
|
|
417
|
+
parentID: toolCtx.sessionID
|
|
418
|
+
}
|
|
383
419
|
});
|
|
384
420
|
const sessionID = newSession.data.id;
|
|
385
421
|
const parentSessionID = toolCtx.sessionID;
|
|
@@ -397,14 +433,12 @@ async function startNewRoundtable(ctx, args, toolCtx) {
|
|
|
397
433
|
errors: [],
|
|
398
434
|
createdAt: Date.now(),
|
|
399
435
|
currentGeneration: 0,
|
|
400
|
-
userInterjections: []
|
|
436
|
+
userInterjections: [],
|
|
437
|
+
observerPrompt: args.observerPrompt
|
|
401
438
|
};
|
|
402
439
|
states.set(sessionID, state);
|
|
403
440
|
try {
|
|
404
|
-
await
|
|
405
|
-
path: { id: sessionID },
|
|
406
|
-
body: { noReply: true, parts: [{ type: "text", text: serializeState(state) }] }
|
|
407
|
-
});
|
|
441
|
+
await saveStateFile(state);
|
|
408
442
|
await sendToAgent(ctx, state);
|
|
409
443
|
await ctx.client.session.prompt({
|
|
410
444
|
path: { id: parentSessionID },
|
|
@@ -412,10 +446,20 @@ async function startNewRoundtable(ctx, args, toolCtx) {
|
|
|
412
446
|
noReply: true,
|
|
413
447
|
parts: [{
|
|
414
448
|
type: "text",
|
|
415
|
-
text:
|
|
449
|
+
text: `⚙ Roundtable started — #${sessionID} • ${agents.join(" → ")} • ${args.rounds} round(s)`
|
|
416
450
|
}]
|
|
417
451
|
}
|
|
418
452
|
});
|
|
453
|
+
await ctx.client.session.prompt({
|
|
454
|
+
path: { id: sessionID },
|
|
455
|
+
body: {
|
|
456
|
+
noReply: true,
|
|
457
|
+
parts: [{ type: "text", text: `⚙ Parent: #${parentSessionID}` }]
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
if (getConfig().navigation === "auto") {
|
|
461
|
+
await navigateToSession(ctx, sessionID, parentSessionID);
|
|
462
|
+
}
|
|
419
463
|
await ctx.client.tui.showToast({
|
|
420
464
|
body: {
|
|
421
465
|
message: `Roundtable started in #${sessionID} (${agents.join(" → ")} · ${args.rounds} round(s))`,
|
|
@@ -473,10 +517,13 @@ async function sendToAgent(ctx, state) {
|
|
|
473
517
|
}
|
|
474
518
|
}
|
|
475
519
|
async function updateSessionTitle(ctx, state) {
|
|
476
|
-
const summary = state.prompt.length >
|
|
520
|
+
const summary = state.prompt.length > 40 ? state.prompt.slice(0, 37) + "..." : state.prompt;
|
|
521
|
+
const agentList = state.agents.join("→");
|
|
522
|
+
const roundInfo = `R${state.currentRound + 1}/${state.totalRounds}`;
|
|
523
|
+
const title = `⚡ "${summary}" · ${agentList} (${roundInfo} · ↑ #${state.parentSessionID})`;
|
|
477
524
|
await ctx.client.session.update({
|
|
478
525
|
path: { id: state.sessionID },
|
|
479
|
-
body: { title
|
|
526
|
+
body: { title }
|
|
480
527
|
});
|
|
481
528
|
}
|
|
482
529
|
async function sendObserverPrompt(ctx, state) {
|
|
@@ -522,17 +569,17 @@ async function finalizeRoundtable(ctx, state) {
|
|
|
522
569
|
pendingResults.delete(sessionID);
|
|
523
570
|
}
|
|
524
571
|
await injectRoundtableDelimiter(ctx, sessionID);
|
|
525
|
-
const shortPrompt = state.prompt.length >
|
|
572
|
+
const shortPrompt = state.prompt.length > 40 ? state.prompt.slice(0, 37) + "..." : state.prompt;
|
|
526
573
|
await ctx.client.session.update({
|
|
527
574
|
path: { id: sessionID },
|
|
528
|
-
body: { title:
|
|
575
|
+
body: { title: `⚡ "${shortPrompt}" · ${state.agents.join("→")} ✓` }
|
|
529
576
|
});
|
|
530
577
|
try {
|
|
531
|
-
await
|
|
532
|
-
path: { id: sessionID },
|
|
533
|
-
body: { noReply: true, parts: [{ type: "text", text: serializeState(state) }] }
|
|
534
|
-
});
|
|
578
|
+
await saveStateFile(state);
|
|
535
579
|
} catch {}
|
|
580
|
+
if (getConfig().navigation === "auto") {
|
|
581
|
+
await navigateToSession(ctx, state.parentSessionID, sessionID);
|
|
582
|
+
}
|
|
536
583
|
await ctx.client.tui.showToast({
|
|
537
584
|
body: { message: "Roundtable concluded", variant: "success" }
|
|
538
585
|
});
|
|
@@ -569,7 +616,7 @@ async function processNextTurn(ctx, state) {
|
|
|
569
616
|
const userMsgs = messages.filter((m) => m.info.role === "user");
|
|
570
617
|
for (const msg of userMsgs) {
|
|
571
618
|
for (const part of msg.parts) {
|
|
572
|
-
if (part.type === "text"
|
|
619
|
+
if (part.type === "text") {
|
|
573
620
|
if (!state.userInterjections.includes(part.text)) {
|
|
574
621
|
state.userInterjections.push(part.text);
|
|
575
622
|
}
|
|
@@ -597,6 +644,7 @@ async function processNextTurn(ctx, state) {
|
|
|
597
644
|
hasError: response === null
|
|
598
645
|
};
|
|
599
646
|
state.history.push(entry);
|
|
647
|
+
await saveStateFile(state);
|
|
600
648
|
await ctx.client.app.log({
|
|
601
649
|
body: {
|
|
602
650
|
service: "roundtable",
|
|
@@ -657,6 +705,7 @@ async function processNextTurn(ctx, state) {
|
|
|
657
705
|
toolCalls: [],
|
|
658
706
|
hasError: summary === null
|
|
659
707
|
});
|
|
708
|
+
await saveStateFile(state);
|
|
660
709
|
state.phase = "done";
|
|
661
710
|
await finalizeRoundtable(ctx, state);
|
|
662
711
|
}
|
|
@@ -664,6 +713,7 @@ async function processNextTurn(ctx, state) {
|
|
|
664
713
|
async function handleAgentError(ctx, state, event) {
|
|
665
714
|
if (state.phase === "observing" || state.phase === "done") {
|
|
666
715
|
state.phase = "aborted";
|
|
716
|
+
await saveStateFile(state);
|
|
667
717
|
await finalizeRoundtable(ctx, state);
|
|
668
718
|
return;
|
|
669
719
|
}
|
|
@@ -694,6 +744,7 @@ async function handleAgentError(ctx, state, event) {
|
|
|
694
744
|
}
|
|
695
745
|
});
|
|
696
746
|
} catch {}
|
|
747
|
+
await saveStateFile(state);
|
|
697
748
|
await sendToAgent(ctx, state);
|
|
698
749
|
} else if (state.currentRound + 1 < state.totalRounds) {
|
|
699
750
|
state.currentRound++;
|
|
@@ -708,6 +759,7 @@ async function handleAgentError(ctx, state, event) {
|
|
|
708
759
|
}
|
|
709
760
|
});
|
|
710
761
|
} catch {}
|
|
762
|
+
await saveStateFile(state);
|
|
711
763
|
await sendToAgent(ctx, state);
|
|
712
764
|
} else {
|
|
713
765
|
state.phase = "aborted";
|
|
@@ -716,6 +768,7 @@ async function handleAgentError(ctx, state, event) {
|
|
|
716
768
|
body: { message: "All agents failed — roundtable aborted", variant: "error" }
|
|
717
769
|
});
|
|
718
770
|
} catch {}
|
|
771
|
+
await saveStateFile(state);
|
|
719
772
|
await finalizeRoundtable(ctx, state);
|
|
720
773
|
}
|
|
721
774
|
}
|
|
@@ -725,6 +778,7 @@ async function handleSessionDeleted(ctx, state, deletedSessionID) {
|
|
|
725
778
|
clearTimeout(handle);
|
|
726
779
|
timeoutHandles.delete(state.sessionID);
|
|
727
780
|
}
|
|
781
|
+
await deleteStateFile(state.sessionID);
|
|
728
782
|
if (deletedSessionID === state.sessionID) {
|
|
729
783
|
state.phase = "aborted";
|
|
730
784
|
const partialSummary = buildConsolidatedSummary(state);
|
|
@@ -780,47 +834,15 @@ async function extendRoundtable(ctx, args, _toolCtx) {
|
|
|
780
834
|
} catch {
|
|
781
835
|
return `Error: Session #${sessionID} not found or inaccessible. Cannot extend.`;
|
|
782
836
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
messagesData = await ctx.client.session.messages({ path: { id: sessionID } });
|
|
786
|
-
} catch {
|
|
787
|
-
return `Error: Could not read messages from session #${sessionID}`;
|
|
788
|
-
}
|
|
789
|
-
const serializedRaw = findSerializedState(messagesData.data);
|
|
790
|
-
if (!serializedRaw) {
|
|
837
|
+
const originalState = await loadStateFile(sessionID);
|
|
838
|
+
if (!originalState) {
|
|
791
839
|
return [
|
|
792
|
-
`Error: No roundtable state found
|
|
793
|
-
"This session is not a roundtable or the state was
|
|
840
|
+
`Error: No roundtable state found for session #${sessionID}.`,
|
|
841
|
+
"This session is not a roundtable or the state file was deleted."
|
|
794
842
|
].join(`
|
|
795
843
|
`);
|
|
796
844
|
}
|
|
797
|
-
|
|
798
|
-
if (!originalState) {
|
|
799
|
-
return `Error: Corrupt roundtable state in session #${sessionID}. Cannot extend.`;
|
|
800
|
-
}
|
|
801
|
-
if (originalState.phase === "done") {} else if (originalState.phase === "active") {
|
|
802
|
-
const assistantCount = messagesData.data.filter((m) => m.info.role === "assistant").length;
|
|
803
|
-
const expectedAssistantCount = originalState.totalRounds * originalState.agents.length + 1;
|
|
804
|
-
if (assistantCount >= expectedAssistantCount) {
|
|
805
|
-
originalState.phase = "done";
|
|
806
|
-
try {
|
|
807
|
-
await ctx.client.app.log({
|
|
808
|
-
body: {
|
|
809
|
-
service: "roundtable",
|
|
810
|
-
level: "info",
|
|
811
|
-
message: `Recovered stuck session #${sessionID}: phase was active but ${assistantCount} assistant msgs suggest debate completed.`,
|
|
812
|
-
extra: { sessionID, assistantCount, expectedAssistantCount }
|
|
813
|
-
}
|
|
814
|
-
});
|
|
815
|
-
} catch {}
|
|
816
|
-
} else {
|
|
817
|
-
return [
|
|
818
|
-
`Error: Roundtable #${sessionID} is still active (phase: ${originalState.phase}).`,
|
|
819
|
-
"Cannot extend until the roundtable concludes."
|
|
820
|
-
].join(`
|
|
821
|
-
`);
|
|
822
|
-
}
|
|
823
|
-
} else {
|
|
845
|
+
if (originalState.phase !== "done") {
|
|
824
846
|
return [
|
|
825
847
|
`Error: Roundtable #${sessionID} is in state "${originalState.phase}" and cannot be extended.`
|
|
826
848
|
].join(`
|
|
@@ -862,14 +884,12 @@ async function extendRoundtable(ctx, args, _toolCtx) {
|
|
|
862
884
|
errors: [...originalState.errors],
|
|
863
885
|
createdAt: Date.now(),
|
|
864
886
|
currentGeneration: 0,
|
|
865
|
-
userInterjections: [...originalState.userInterjections ?? []]
|
|
887
|
+
userInterjections: [...originalState.userInterjections ?? []],
|
|
888
|
+
observerPrompt: args.observerPrompt ?? originalState.observerPrompt
|
|
866
889
|
};
|
|
867
890
|
states.set(sessionID, newState);
|
|
868
891
|
try {
|
|
869
|
-
await
|
|
870
|
-
path: { id: sessionID },
|
|
871
|
-
body: { noReply: true, parts: [{ type: "text", text: serializeState(newState) }] }
|
|
872
|
-
});
|
|
892
|
+
await saveStateFile(newState);
|
|
873
893
|
const agentList = originalState.agents.join(" vs ");
|
|
874
894
|
await ctx.client.session.update({
|
|
875
895
|
path: { id: sessionID },
|
|
@@ -881,7 +901,7 @@ async function extendRoundtable(ctx, args, _toolCtx) {
|
|
|
881
901
|
noReply: true,
|
|
882
902
|
parts: [{
|
|
883
903
|
type: "text",
|
|
884
|
-
text:
|
|
904
|
+
text: `⚙ Roundtable extended — #${sessionID} • +${args.rounds} round(s)`
|
|
885
905
|
}]
|
|
886
906
|
}
|
|
887
907
|
});
|
|
@@ -954,16 +974,19 @@ var RoundtablePlugin = async (ctx) => {
|
|
|
954
974
|
|
|
955
975
|
` + "Note: this tool injects system-level prompts into each agent's context during " + `the debate (role-setting, topic, turn routing, and lifecycle signals).
|
|
956
976
|
|
|
977
|
+
` + "IMPORTANT: This tool requires 2+ agents. For single-agent tasks, use a regular " + `session prompt instead — do NOT use roundtable.
|
|
978
|
+
|
|
957
979
|
` + "Choosing agents: select agents based on their expertise (e.g., pm for product decisions, " + "dev for technical trade-offs, rv for code review). You can also specify in the prompt " + `what each agent should focus on (e.g., 'pm: focus on cost; dev: focus on maintainability').
|
|
958
980
|
|
|
959
981
|
` + "Multiple rounds: for complex topics, use 2+ rounds and INCLUDE per-round focus " + "instructions IN the prompt itself (e.g., prompt: 'Round 1: list pros. Round 2: " + "list cons. Round 3: propose an implementation plan'). This way all agents see " + "the full agenda. The default is 1 round.",
|
|
960
982
|
args: {
|
|
961
|
-
agents: tool.schema.array(tool.schema.string()).min(2).describe("Agent names in speaking order (minimum 2). Choose agents based on
|
|
983
|
+
agents: tool.schema.array(tool.schema.string()).min(2).describe("Agent names in speaking order (minimum 2). For single-agent tasks, use a regular " + "session — do NOT use roundtable. Choose agents based on their expertise — " + 'each brings a different perspective. Example: ["pm", "dev", "rv"]'),
|
|
962
984
|
prompt: tool.schema.string().describe("Topic or challenge for the agents to debate. For multi-round debates, " + "include per-round instructions here (e.g., 'Round 1: pros. Round 2: cons. " + "Round 3: plan.'). All agents will see this and follow the round structure."),
|
|
963
985
|
rounds: tool.schema.number().min(1).max(50).describe("Number of complete rounds (each round = all agents speak once). " + "Default: 1. Max: 50. For complex topics with 2+ rounds, include per-round focus " + "instructions in the prompt parameter so all agents see the agenda."),
|
|
964
986
|
observer: tool.schema.string().optional().describe("Agent name for final consolidation. The observer does not debate — it " + "summarizes after all rounds. Omit to use the built-in observer."),
|
|
965
987
|
sessionID: tool.schema.string().optional().describe("Session ID (format: ses_xxxx) from a previous roundtable call to " + "continue a concluded debate. Omit this parameter and pass agents + " + "prompt to start a fresh debate."),
|
|
966
|
-
title: tool.schema.string().optional().describe("Custom title for the session (max 200 chars). If omitted, auto-generated " + 'as "(Roundtable) - {first 80 chars of prompt, truncated at word boundary}".')
|
|
988
|
+
title: tool.schema.string().optional().describe("Custom title for the session (max 200 chars). If omitted, auto-generated " + 'as "(Roundtable) - {first 80 chars of prompt, truncated at word boundary}".'),
|
|
989
|
+
observerPrompt: tool.schema.string().optional().describe("Override the default observer consolidation prompt. Use this to control " + "the format and focus of the final summary — e.g., ask the observer to " + "save a detailed report to file, focus on technical decisions only, " + "output as JSON, extract action items, etc. " + "If omitted, the default observer prompt is used (executive summary).")
|
|
967
990
|
},
|
|
968
991
|
async execute(args, toolCtx) {
|
|
969
992
|
try {
|
|
@@ -1020,6 +1043,22 @@ var RoundtablePlugin = async (ctx) => {
|
|
|
1020
1043
|
return "Error: Could not fetch agent list. The server might not be ready.";
|
|
1021
1044
|
}
|
|
1022
1045
|
}
|
|
1046
|
+
}),
|
|
1047
|
+
active_roundtables: tool({
|
|
1048
|
+
description: "Lists all active roundtables with their status. " + "Each entry shows session ID, agents, current round, and phase. " + "Returns clickable session IDs that can be used to navigate.",
|
|
1049
|
+
args: {},
|
|
1050
|
+
async execute() {
|
|
1051
|
+
const active = [];
|
|
1052
|
+
for (const [sid, s] of states) {
|
|
1053
|
+
const status = s.phase === "active" ? "debating" : s.phase === "observing" ? "consolidating" : s.phase === "done" ? "concluded" : s.phase;
|
|
1054
|
+
active.push(`- #${sid} · ${s.agents.join("→")} (R${s.currentRound + 1}/${s.totalRounds}) · ${status}`);
|
|
1055
|
+
}
|
|
1056
|
+
if (active.length === 0)
|
|
1057
|
+
return "No active roundtables.";
|
|
1058
|
+
return `Active roundtables:
|
|
1059
|
+
${active.join(`
|
|
1060
|
+
`)}`;
|
|
1061
|
+
}
|
|
1023
1062
|
})
|
|
1024
1063
|
}
|
|
1025
1064
|
};
|
|
@@ -1028,5 +1067,5 @@ export {
|
|
|
1028
1067
|
RoundtablePlugin
|
|
1029
1068
|
};
|
|
1030
1069
|
|
|
1031
|
-
//# debugId=
|
|
1070
|
+
//# debugId=C8BED17810CFAA4D64756E2164756E21
|
|
1032
1071
|
//# sourceMappingURL=index.js.map
|