tagteam 0.1.0 → 0.2.0
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 +48 -7
- package/dist/index.js +382 -97
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import { execSync as execSync2 } from "child_process";
|
|
5
6
|
import chalk from "chalk";
|
|
6
7
|
|
|
7
8
|
// src/config.ts
|
|
@@ -10,17 +11,27 @@ import { join } from "path";
|
|
|
10
11
|
import { homedir } from "os";
|
|
11
12
|
import { parse, stringify } from "smol-toml";
|
|
12
13
|
var DEFAULT_CONFIG = {
|
|
14
|
+
agents: ["claude", "codex"],
|
|
13
15
|
claude: {
|
|
14
16
|
model: "sonnet"
|
|
15
17
|
},
|
|
16
18
|
codex: {
|
|
17
19
|
model: "gpt-5.3-codex"
|
|
18
20
|
},
|
|
21
|
+
gemini: {
|
|
22
|
+
model: "gemini-2.5-pro"
|
|
23
|
+
},
|
|
19
24
|
discussion: {
|
|
20
25
|
max_rounds: 10
|
|
21
26
|
}
|
|
22
27
|
};
|
|
23
28
|
function getConfigDir() {
|
|
29
|
+
if (process.platform === "win32") {
|
|
30
|
+
return join(
|
|
31
|
+
process.env.APPDATA || join(homedir(), "AppData", "Roaming"),
|
|
32
|
+
"tagteam"
|
|
33
|
+
);
|
|
34
|
+
}
|
|
24
35
|
return join(homedir(), ".tagteam");
|
|
25
36
|
}
|
|
26
37
|
function getConfigPath() {
|
|
@@ -41,8 +52,10 @@ function loadConfig() {
|
|
|
41
52
|
const raw = readFileSync(configPath, "utf-8");
|
|
42
53
|
const parsed = parse(raw);
|
|
43
54
|
return {
|
|
55
|
+
agents: Array.isArray(parsed.agents) && parsed.agents.length === 2 ? parsed.agents : [...DEFAULT_CONFIG.agents],
|
|
44
56
|
claude: { ...DEFAULT_CONFIG.claude, ...parsed.claude },
|
|
45
57
|
codex: { ...DEFAULT_CONFIG.codex, ...parsed.codex },
|
|
58
|
+
gemini: { ...DEFAULT_CONFIG.gemini, ...parsed.gemini },
|
|
46
59
|
discussion: { ...DEFAULT_CONFIG.discussion, ...parsed.discussion }
|
|
47
60
|
};
|
|
48
61
|
} catch {
|
|
@@ -59,12 +72,18 @@ function setConfigValue(key, value) {
|
|
|
59
72
|
const parts = key.split(".");
|
|
60
73
|
if (parts.length === 1) {
|
|
61
74
|
switch (parts[0]) {
|
|
75
|
+
case "agents":
|
|
76
|
+
config.agents = value.split(",").map((s) => s.trim());
|
|
77
|
+
break;
|
|
62
78
|
case "claude_model":
|
|
63
79
|
config.claude.model = value;
|
|
64
80
|
break;
|
|
65
81
|
case "codex_model":
|
|
66
82
|
config.codex.model = value;
|
|
67
83
|
break;
|
|
84
|
+
case "gemini_model":
|
|
85
|
+
config.gemini.model = value;
|
|
86
|
+
break;
|
|
68
87
|
case "discussion_max_rounds":
|
|
69
88
|
config.discussion.max_rounds = Number(value);
|
|
70
89
|
break;
|
|
@@ -77,6 +96,8 @@ function setConfigValue(key, value) {
|
|
|
77
96
|
config.claude.model = value;
|
|
78
97
|
} else if (section === "codex" && field === "model") {
|
|
79
98
|
config.codex.model = value;
|
|
99
|
+
} else if (section === "gemini" && field === "model") {
|
|
100
|
+
config.gemini.model = value;
|
|
80
101
|
} else if (section === "discussion" && field === "max_rounds") {
|
|
81
102
|
config.discussion.max_rounds = Number(value);
|
|
82
103
|
} else {
|
|
@@ -338,6 +359,150 @@ async function runCodex(options) {
|
|
|
338
359
|
};
|
|
339
360
|
}
|
|
340
361
|
|
|
362
|
+
// src/agents/gemini.ts
|
|
363
|
+
import { spawn as spawn3 } from "child_process";
|
|
364
|
+
async function* streamGemini(options) {
|
|
365
|
+
const args = ["-p"];
|
|
366
|
+
let fullPrompt = options.prompt;
|
|
367
|
+
if (options.systemPrompt) {
|
|
368
|
+
fullPrompt = `${options.systemPrompt}
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
${options.prompt}`;
|
|
373
|
+
}
|
|
374
|
+
args.push(fullPrompt, "--output-format", "json", "--yolo");
|
|
375
|
+
if (options.model) {
|
|
376
|
+
args.push("-m", options.model);
|
|
377
|
+
}
|
|
378
|
+
const proc = spawn3("gemini", args, {
|
|
379
|
+
cwd: options.cwd,
|
|
380
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
381
|
+
env: process.env
|
|
382
|
+
});
|
|
383
|
+
let spawnErrorMsg = "";
|
|
384
|
+
proc.on("error", (err) => {
|
|
385
|
+
spawnErrorMsg = err.message;
|
|
386
|
+
});
|
|
387
|
+
if (options.signal) {
|
|
388
|
+
if (options.signal.aborted) {
|
|
389
|
+
proc.kill();
|
|
390
|
+
} else {
|
|
391
|
+
options.signal.addEventListener("abort", () => proc.kill(), { once: true });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const chunks = [];
|
|
395
|
+
proc.stdout.on("data", (chunk) => {
|
|
396
|
+
chunks.push(chunk);
|
|
397
|
+
});
|
|
398
|
+
await new Promise((resolve) => {
|
|
399
|
+
proc.on("close", resolve);
|
|
400
|
+
});
|
|
401
|
+
if (spawnErrorMsg) {
|
|
402
|
+
yield {
|
|
403
|
+
type: "error",
|
|
404
|
+
agent: "gemini",
|
|
405
|
+
content: `Failed to spawn gemini: ${spawnErrorMsg}`
|
|
406
|
+
};
|
|
407
|
+
yield { type: "done", agent: "gemini" };
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const stdout = Buffer.concat(chunks).toString("utf-8").trim();
|
|
411
|
+
if (!stdout) {
|
|
412
|
+
yield { type: "error", agent: "gemini", content: "No output from gemini" };
|
|
413
|
+
yield { type: "done", agent: "gemini" };
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const data = JSON.parse(stdout);
|
|
418
|
+
if (data.error) {
|
|
419
|
+
yield { type: "error", agent: "gemini", content: data.error };
|
|
420
|
+
} else if (data.response) {
|
|
421
|
+
yield { type: "text", agent: "gemini", content: data.response };
|
|
422
|
+
} else {
|
|
423
|
+
yield { type: "error", agent: "gemini", content: "Unexpected response format" };
|
|
424
|
+
}
|
|
425
|
+
} catch {
|
|
426
|
+
yield { type: "text", agent: "gemini", content: stdout };
|
|
427
|
+
}
|
|
428
|
+
yield { type: "done", agent: "gemini" };
|
|
429
|
+
}
|
|
430
|
+
async function runGemini(options) {
|
|
431
|
+
const start = Date.now();
|
|
432
|
+
const events = [];
|
|
433
|
+
const textParts = [];
|
|
434
|
+
const errors = [];
|
|
435
|
+
for await (const event of streamGemini(options)) {
|
|
436
|
+
events.push(event);
|
|
437
|
+
if (event.type === "text" && event.content) {
|
|
438
|
+
textParts.push(event.content);
|
|
439
|
+
} else if (event.type === "error" && event.content) {
|
|
440
|
+
errors.push(event.content);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
agent: "gemini",
|
|
445
|
+
text: textParts.join(""),
|
|
446
|
+
events,
|
|
447
|
+
durationMs: Date.now() - start,
|
|
448
|
+
error: errors.length > 0 ? errors.join("\n") : void 0
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/agents/registry.ts
|
|
453
|
+
var AGENTS = {
|
|
454
|
+
claude: {
|
|
455
|
+
name: "claude",
|
|
456
|
+
displayName: "Claude",
|
|
457
|
+
color: "magenta",
|
|
458
|
+
cliBinary: "claude",
|
|
459
|
+
installUrl: "https://docs.anthropic.com/en/docs/claude-code",
|
|
460
|
+
org: "Anthropic",
|
|
461
|
+
run: runClaude
|
|
462
|
+
},
|
|
463
|
+
codex: {
|
|
464
|
+
name: "codex",
|
|
465
|
+
displayName: "Codex",
|
|
466
|
+
color: "green",
|
|
467
|
+
cliBinary: "codex",
|
|
468
|
+
installUrl: "https://github.com/openai/codex",
|
|
469
|
+
org: "OpenAI",
|
|
470
|
+
run: runCodex
|
|
471
|
+
},
|
|
472
|
+
gemini: {
|
|
473
|
+
name: "gemini",
|
|
474
|
+
displayName: "Gemini",
|
|
475
|
+
color: "blue",
|
|
476
|
+
cliBinary: "gemini",
|
|
477
|
+
installUrl: "https://github.com/google-gemini/gemini-cli",
|
|
478
|
+
org: "Google",
|
|
479
|
+
run: runGemini
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
function getAgent(name) {
|
|
483
|
+
return AGENTS[name];
|
|
484
|
+
}
|
|
485
|
+
function getAllAgentNames() {
|
|
486
|
+
return Object.keys(AGENTS);
|
|
487
|
+
}
|
|
488
|
+
function isValidAgentName(name) {
|
|
489
|
+
return name in AGENTS;
|
|
490
|
+
}
|
|
491
|
+
function validateAgentPair(pair) {
|
|
492
|
+
if (pair.length !== 2) {
|
|
493
|
+
throw new Error("Agent pair must contain exactly 2 agents");
|
|
494
|
+
}
|
|
495
|
+
if (pair[0] === pair[1]) {
|
|
496
|
+
throw new Error("Agent pair must contain 2 distinct agents");
|
|
497
|
+
}
|
|
498
|
+
for (const name of pair) {
|
|
499
|
+
if (!isValidAgentName(name)) {
|
|
500
|
+
throw new Error(`Unknown agent: "${name}". Valid agents: ${getAllAgentNames().join(", ")}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return pair;
|
|
504
|
+
}
|
|
505
|
+
|
|
341
506
|
// src/format.ts
|
|
342
507
|
function formatAsMarkdown(messages) {
|
|
343
508
|
const parts = [];
|
|
@@ -345,8 +510,8 @@ function formatAsMarkdown(messages) {
|
|
|
345
510
|
if (msg.role === "system") continue;
|
|
346
511
|
if (msg.role === "user") {
|
|
347
512
|
parts.push(`**You:** ${msg.content}`);
|
|
348
|
-
} else {
|
|
349
|
-
const name = msg.role
|
|
513
|
+
} else if (isValidAgentName(msg.role)) {
|
|
514
|
+
const name = getAgent(msg.role).displayName;
|
|
350
515
|
if (msg.error) {
|
|
351
516
|
parts.push(`**${name}:** *(error)*
|
|
352
517
|
|
|
@@ -371,14 +536,37 @@ function copyToClipboard(text) {
|
|
|
371
536
|
} else if (platform === "win32") {
|
|
372
537
|
cmd = "clip";
|
|
373
538
|
} else {
|
|
539
|
+
let hasXclip = false;
|
|
540
|
+
let hasXsel = false;
|
|
374
541
|
try {
|
|
375
542
|
execSync("which xclip", { stdio: "ignore" });
|
|
376
|
-
|
|
543
|
+
hasXclip = true;
|
|
377
544
|
} catch {
|
|
545
|
+
}
|
|
546
|
+
if (!hasXclip) {
|
|
547
|
+
try {
|
|
548
|
+
execSync("which xsel", { stdio: "ignore" });
|
|
549
|
+
hasXsel = true;
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (hasXclip) {
|
|
554
|
+
cmd = "xclip -selection clipboard";
|
|
555
|
+
} else if (hasXsel) {
|
|
378
556
|
cmd = "xsel --clipboard --input";
|
|
557
|
+
} else {
|
|
558
|
+
throw new Error(
|
|
559
|
+
"Clipboard requires xclip or xsel. Install one with: sudo apt install xclip"
|
|
560
|
+
);
|
|
379
561
|
}
|
|
380
562
|
}
|
|
381
|
-
|
|
563
|
+
try {
|
|
564
|
+
execSync(cmd, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
565
|
+
} catch {
|
|
566
|
+
throw new Error(
|
|
567
|
+
`Failed to copy to clipboard using "${cmd.split(" ")[0]}". Check that it is installed and working.`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
382
570
|
}
|
|
383
571
|
|
|
384
572
|
// src/db/index.ts
|
|
@@ -496,15 +684,16 @@ function deleteMessagesFromRound(sessionId, fromRound) {
|
|
|
496
684
|
}
|
|
497
685
|
|
|
498
686
|
// src/prompts.ts
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
|
|
687
|
+
function otherAgent(agent, pair) {
|
|
688
|
+
const other = pair[0] === agent ? pair[1] : pair[0];
|
|
689
|
+
const desc = getAgent(other);
|
|
690
|
+
return `${desc.displayName} (${desc.org})`;
|
|
691
|
+
}
|
|
692
|
+
function collaborationPrompt(agent, pair) {
|
|
693
|
+
return `You are in a collaborative session with ${otherAgent(agent, pair)}. You'll both respond to the user's prompt independently, then see each other's responses. In discussion rounds: highlight where you agree, constructively address disagreements, and build on each other's ideas. Be concise - avoid repeating what was already said.`;
|
|
505
694
|
}
|
|
506
|
-
function discussionPrompt(agent, conversationHistory) {
|
|
507
|
-
return `${collaborationPrompt(agent)}
|
|
695
|
+
function discussionPrompt(agent, conversationHistory, pair) {
|
|
696
|
+
return `${collaborationPrompt(agent, pair)}
|
|
508
697
|
|
|
509
698
|
Here is the conversation so far:
|
|
510
699
|
|
|
@@ -512,29 +701,40 @@ ${conversationHistory}
|
|
|
512
701
|
|
|
513
702
|
Now provide your response for this discussion round. Build on what was said, highlight agreements, and address any disagreements constructively. Be concise.`;
|
|
514
703
|
}
|
|
515
|
-
function debatePrompt(agent) {
|
|
516
|
-
|
|
704
|
+
function debatePrompt(agent, pair) {
|
|
705
|
+
const other = otherAgent(agent, pair);
|
|
706
|
+
return `You are in a structured debate with ${other}. You'll both respond to the user's prompt, then see each other's responses and discuss.
|
|
517
707
|
|
|
518
708
|
Your goal is to reach consensus through constructive discussion. In each round:
|
|
519
709
|
- Address specific points of agreement and disagreement
|
|
520
|
-
- Refine your position based on valid arguments from ${
|
|
710
|
+
- Refine your position based on valid arguments from ${other}
|
|
521
711
|
- Be concise \u2014 don't repeat points already established
|
|
522
712
|
|
|
523
|
-
When you believe you and ${
|
|
713
|
+
When you believe you and ${other} have reached substantial agreement on the key points, end your response with [CONSENSUS] on its own line. Only do this when you genuinely agree \u2014 don't force premature consensus.`;
|
|
524
714
|
}
|
|
525
|
-
function debateRoundPrompt(agent, conversationHistory) {
|
|
526
|
-
|
|
715
|
+
function debateRoundPrompt(agent, conversationHistory, pair) {
|
|
716
|
+
const other = otherAgent(agent, pair);
|
|
717
|
+
return `${debatePrompt(agent, pair)}
|
|
527
718
|
|
|
528
719
|
Here is the conversation so far:
|
|
529
720
|
|
|
530
721
|
${conversationHistory}
|
|
531
722
|
|
|
532
|
-
Respond to the latest round. If you agree with ${
|
|
723
|
+
Respond to the latest round. If you agree with ${other}'s position on all key points, end with [CONSENSUS]. Otherwise, continue the discussion.`;
|
|
533
724
|
}
|
|
534
725
|
var CONSENSUS_MARKER = "[CONSENSUS]";
|
|
535
726
|
function formatConversationHistory(messages) {
|
|
536
727
|
return messages.map((m) => {
|
|
537
|
-
|
|
728
|
+
let label;
|
|
729
|
+
if (m.role === "user") {
|
|
730
|
+
label = "User";
|
|
731
|
+
} else if (isValidAgentName(m.role)) {
|
|
732
|
+
label = getAgent(m.role).displayName;
|
|
733
|
+
} else if (m.agent && isValidAgentName(m.agent)) {
|
|
734
|
+
label = getAgent(m.agent).displayName;
|
|
735
|
+
} else {
|
|
736
|
+
label = m.role;
|
|
737
|
+
}
|
|
538
738
|
return `[${label}]: ${m.content}`;
|
|
539
739
|
}).join("\n\n");
|
|
540
740
|
}
|
|
@@ -545,6 +745,21 @@ import { render, Box, Text, useApp, useInput } from "ink";
|
|
|
545
745
|
import TextInput from "ink-text-input";
|
|
546
746
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
547
747
|
var CONFIG_FIELDS = [
|
|
748
|
+
{
|
|
749
|
+
key: "agents",
|
|
750
|
+
label: "Agent pair",
|
|
751
|
+
get: (c) => c.agents.join(", "),
|
|
752
|
+
type: "string",
|
|
753
|
+
validate: (value) => {
|
|
754
|
+
try {
|
|
755
|
+
const names = value.split(",").map((s) => s.trim());
|
|
756
|
+
validateAgentPair(names);
|
|
757
|
+
return null;
|
|
758
|
+
} catch (e) {
|
|
759
|
+
return e.message;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
},
|
|
548
763
|
{
|
|
549
764
|
key: "claude.model",
|
|
550
765
|
label: "Claude model",
|
|
@@ -557,6 +772,12 @@ var CONFIG_FIELDS = [
|
|
|
557
772
|
get: (c) => c.codex.model,
|
|
558
773
|
type: "string"
|
|
559
774
|
},
|
|
775
|
+
{
|
|
776
|
+
key: "gemini.model",
|
|
777
|
+
label: "Gemini model",
|
|
778
|
+
get: (c) => c.gemini.model,
|
|
779
|
+
type: "string"
|
|
780
|
+
},
|
|
560
781
|
{
|
|
561
782
|
key: "discussion.max_rounds",
|
|
562
783
|
label: "Discussion max rounds",
|
|
@@ -608,6 +829,14 @@ function InlineConfigEditor({ isActive, onClose }) {
|
|
|
608
829
|
return;
|
|
609
830
|
}
|
|
610
831
|
}
|
|
832
|
+
if (field.validate) {
|
|
833
|
+
const error = field.validate(value);
|
|
834
|
+
if (error) {
|
|
835
|
+
setSavedMessage(`Error: ${error}`);
|
|
836
|
+
setMode("select");
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
611
840
|
try {
|
|
612
841
|
const updated = setConfigValue(field.key, value);
|
|
613
842
|
setConfig(updated);
|
|
@@ -658,16 +887,15 @@ function startConfigEditor() {
|
|
|
658
887
|
// src/ui.tsx
|
|
659
888
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
660
889
|
marked.use(markedTerminal());
|
|
661
|
-
function parseInput(input) {
|
|
890
|
+
function parseInput(input, pair) {
|
|
662
891
|
const lower = input.toLowerCase();
|
|
663
892
|
if (lower.startsWith("discuss ")) {
|
|
664
893
|
return { target: "both", prompt: input.slice(8).trim(), discuss: true };
|
|
665
894
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return { target: "codex", prompt: input.slice(input.indexOf(" ") + 1).trim(), discuss: false };
|
|
895
|
+
for (const agent of pair) {
|
|
896
|
+
if (lower.startsWith(`${agent} `) || lower.startsWith(`${agent}, `)) {
|
|
897
|
+
return { target: agent, prompt: input.slice(input.indexOf(" ") + 1).trim(), discuss: false };
|
|
898
|
+
}
|
|
671
899
|
}
|
|
672
900
|
return { target: "both", prompt: input, discuss: false };
|
|
673
901
|
}
|
|
@@ -680,19 +908,19 @@ function AgentResponseBlock({
|
|
|
680
908
|
content,
|
|
681
909
|
error
|
|
682
910
|
}) {
|
|
683
|
-
const
|
|
911
|
+
const descriptor = getAgent(agent);
|
|
684
912
|
if (error) {
|
|
685
913
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginLeft: 1, marginBottom: 1, children: [
|
|
686
914
|
/* @__PURE__ */ jsxs2(Text2, { color: "red", bold: true, children: [
|
|
687
|
-
|
|
915
|
+
descriptor.displayName,
|
|
688
916
|
" error:"
|
|
689
917
|
] }),
|
|
690
918
|
/* @__PURE__ */ jsx2(Box2, { marginLeft: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "red", children: content }) })
|
|
691
919
|
] });
|
|
692
920
|
}
|
|
693
921
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginLeft: 1, marginBottom: 1, children: [
|
|
694
|
-
/* @__PURE__ */ jsxs2(Text2, { color, bold: true, children: [
|
|
695
|
-
|
|
922
|
+
/* @__PURE__ */ jsxs2(Text2, { color: descriptor.color, bold: true, children: [
|
|
923
|
+
descriptor.displayName,
|
|
696
924
|
":"
|
|
697
925
|
] }),
|
|
698
926
|
/* @__PURE__ */ jsx2(Box2, { marginLeft: 1, children: /* @__PURE__ */ jsx2(RenderedMarkdown, { text: content }) })
|
|
@@ -717,13 +945,12 @@ function UserMessage({ content }) {
|
|
|
717
945
|
] });
|
|
718
946
|
}
|
|
719
947
|
function ThinkingIndicator({ agent }) {
|
|
720
|
-
const
|
|
721
|
-
const label = agent === "claude" ? "Claude" : "Codex";
|
|
948
|
+
const descriptor = getAgent(agent);
|
|
722
949
|
return /* @__PURE__ */ jsxs2(Box2, { marginLeft: 1, children: [
|
|
723
|
-
/* @__PURE__ */ jsx2(Text2, { color, children: /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) }),
|
|
724
|
-
/* @__PURE__ */ jsxs2(Text2, { color, children: [
|
|
950
|
+
/* @__PURE__ */ jsx2(Text2, { color: descriptor.color, children: /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) }),
|
|
951
|
+
/* @__PURE__ */ jsxs2(Text2, { color: descriptor.color, children: [
|
|
725
952
|
" ",
|
|
726
|
-
|
|
953
|
+
descriptor.displayName,
|
|
727
954
|
" is thinking..."
|
|
728
955
|
] })
|
|
729
956
|
] });
|
|
@@ -768,9 +995,9 @@ function PromptInput({
|
|
|
768
995
|
function App({
|
|
769
996
|
initialPrompt,
|
|
770
997
|
sessionId: existingSessionId,
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
config,
|
|
998
|
+
agents: initialPair,
|
|
999
|
+
agentModels: initialAgentModels,
|
|
1000
|
+
config: initialConfig,
|
|
774
1001
|
showTranscript: showTranscript2,
|
|
775
1002
|
discuss: initialDiscuss
|
|
776
1003
|
}) {
|
|
@@ -780,6 +1007,9 @@ function App({
|
|
|
780
1007
|
const [state, setState] = useState2(
|
|
781
1008
|
initialPrompt ? "running" : "input"
|
|
782
1009
|
);
|
|
1010
|
+
const [pair, setPair] = useState2(initialPair);
|
|
1011
|
+
const [agentModels, setAgentModels] = useState2(initialAgentModels);
|
|
1012
|
+
const [config, setConfig] = useState2(initialConfig);
|
|
783
1013
|
const [thinkingAgents, setThinkingAgents] = useState2([]);
|
|
784
1014
|
const [discussionRound, setDiscussionRound] = useState2(0);
|
|
785
1015
|
const [consensusReached, setConsensusReached] = useState2(false);
|
|
@@ -828,22 +1058,18 @@ function App({
|
|
|
828
1058
|
}
|
|
829
1059
|
});
|
|
830
1060
|
const runAgents = async (currentMessages, round, target, promptOverride, isDebate = false) => {
|
|
831
|
-
const
|
|
832
|
-
const runCx = target === "both" || target === "codex";
|
|
833
|
-
const activeAgents = [];
|
|
834
|
-
if (runCl) activeAgents.push("claude");
|
|
835
|
-
if (runCx) activeAgents.push("codex");
|
|
1061
|
+
const activeAgents = target === "both" ? [...pair] : [target];
|
|
836
1062
|
setThinkingAgents(activeAgents);
|
|
837
1063
|
const history = formatConversationHistory(
|
|
838
1064
|
currentMessages.map((m) => ({
|
|
839
1065
|
role: m.role,
|
|
840
|
-
agent: m.role
|
|
1066
|
+
agent: isValidAgentName(m.role) ? m.role : void 0,
|
|
841
1067
|
content: m.content
|
|
842
1068
|
}))
|
|
843
1069
|
);
|
|
844
1070
|
const cwd = process.cwd();
|
|
845
1071
|
const isFirstRound = round === 0 && !existingSessionId;
|
|
846
|
-
const agentPrompt = (
|
|
1072
|
+
const agentPrompt = (_agent) => {
|
|
847
1073
|
if (promptOverride) return promptOverride;
|
|
848
1074
|
if (isFirstRound && target === "both") {
|
|
849
1075
|
return currentMessages[currentMessages.length - 1]?.content || "";
|
|
@@ -852,48 +1078,31 @@ function App({
|
|
|
852
1078
|
};
|
|
853
1079
|
const agentSystemPrompt = (agent) => {
|
|
854
1080
|
if (isDebate) {
|
|
855
|
-
if (isFirstRound) return debatePrompt(agent);
|
|
856
|
-
return debateRoundPrompt(agent, history);
|
|
1081
|
+
if (isFirstRound) return debatePrompt(agent, pair);
|
|
1082
|
+
return debateRoundPrompt(agent, history, pair);
|
|
857
1083
|
}
|
|
858
|
-
if (isFirstRound && target === "both") return collaborationPrompt(agent);
|
|
859
|
-
return discussionPrompt(agent, history);
|
|
1084
|
+
if (isFirstRound && target === "both") return collaborationPrompt(agent, pair);
|
|
1085
|
+
return discussionPrompt(agent, history, pair);
|
|
860
1086
|
};
|
|
861
1087
|
const ac = new AbortController();
|
|
862
1088
|
abortRef.current = ac;
|
|
863
1089
|
const results = [];
|
|
864
1090
|
const promises = [];
|
|
865
|
-
|
|
1091
|
+
for (const agent of activeAgents) {
|
|
1092
|
+
const descriptor = getAgent(agent);
|
|
866
1093
|
promises.push(
|
|
867
|
-
|
|
868
|
-
prompt: agentPrompt(
|
|
869
|
-
systemPrompt: agentSystemPrompt(
|
|
870
|
-
model:
|
|
1094
|
+
descriptor.run({
|
|
1095
|
+
prompt: agentPrompt(agent),
|
|
1096
|
+
systemPrompt: agentSystemPrompt(agent),
|
|
1097
|
+
model: agentModels[agent],
|
|
871
1098
|
cwd,
|
|
872
1099
|
signal: ac.signal
|
|
873
1100
|
}).then(
|
|
874
1101
|
(value) => {
|
|
875
|
-
results.push({ agent
|
|
1102
|
+
results.push({ agent, result: { status: "fulfilled", value } });
|
|
876
1103
|
},
|
|
877
1104
|
(reason) => {
|
|
878
|
-
results.push({ agent
|
|
879
|
-
}
|
|
880
|
-
)
|
|
881
|
-
);
|
|
882
|
-
}
|
|
883
|
-
if (runCx) {
|
|
884
|
-
promises.push(
|
|
885
|
-
runCodex({
|
|
886
|
-
prompt: agentPrompt("codex"),
|
|
887
|
-
systemPrompt: agentSystemPrompt("codex"),
|
|
888
|
-
model: codexModel,
|
|
889
|
-
cwd,
|
|
890
|
-
signal: ac.signal
|
|
891
|
-
}).then(
|
|
892
|
-
(value) => {
|
|
893
|
-
results.push({ agent: "codex", result: { status: "fulfilled", value } });
|
|
894
|
-
},
|
|
895
|
-
(reason) => {
|
|
896
|
-
results.push({ agent: "codex", result: { status: "rejected", reason } });
|
|
1105
|
+
results.push({ agent, result: { status: "rejected", reason } });
|
|
897
1106
|
}
|
|
898
1107
|
)
|
|
899
1108
|
);
|
|
@@ -940,7 +1149,7 @@ function App({
|
|
|
940
1149
|
return newMessages;
|
|
941
1150
|
};
|
|
942
1151
|
const runRound = async (rawInput) => {
|
|
943
|
-
const { target, prompt, discuss } = parseInput(rawInput);
|
|
1152
|
+
const { target, prompt, discuss } = parseInput(rawInput, pair);
|
|
944
1153
|
if (discuss) {
|
|
945
1154
|
return runDiscussion(prompt);
|
|
946
1155
|
}
|
|
@@ -1005,11 +1214,11 @@ function App({
|
|
|
1005
1214
|
setMessages((prev) => [...prev, ...newMessages]);
|
|
1006
1215
|
allMessages = [...allMessages, ...newMessages];
|
|
1007
1216
|
currentRound++;
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
if (
|
|
1217
|
+
const consensusFlags = pair.map((agent) => {
|
|
1218
|
+
const msg = newMessages.find((m) => m.role === agent && !m.error);
|
|
1219
|
+
return msg?.content.includes(CONSENSUS_MARKER) ?? false;
|
|
1220
|
+
});
|
|
1221
|
+
if (consensusFlags.every(Boolean)) {
|
|
1013
1222
|
setConsensusReached(true);
|
|
1014
1223
|
break;
|
|
1015
1224
|
}
|
|
@@ -1076,7 +1285,7 @@ function App({
|
|
|
1076
1285
|
if (msg.role === "user") {
|
|
1077
1286
|
return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content }, i);
|
|
1078
1287
|
}
|
|
1079
|
-
if (msg.role
|
|
1288
|
+
if (isValidAgentName(msg.role)) {
|
|
1080
1289
|
return /* @__PURE__ */ jsx2(
|
|
1081
1290
|
AgentResponseBlock,
|
|
1082
1291
|
{
|
|
@@ -1097,7 +1306,21 @@ function App({
|
|
|
1097
1306
|
InlineConfigEditor,
|
|
1098
1307
|
{
|
|
1099
1308
|
isActive: state === "config",
|
|
1100
|
-
onClose: () =>
|
|
1309
|
+
onClose: () => {
|
|
1310
|
+
const updated = loadConfig();
|
|
1311
|
+
setConfig(updated);
|
|
1312
|
+
try {
|
|
1313
|
+
setPair(validateAgentPair(updated.agents));
|
|
1314
|
+
} catch {
|
|
1315
|
+
}
|
|
1316
|
+
setAgentModels((prev) => ({
|
|
1317
|
+
...prev,
|
|
1318
|
+
claude: updated.claude.model,
|
|
1319
|
+
codex: updated.codex.model,
|
|
1320
|
+
gemini: updated.gemini.model
|
|
1321
|
+
}));
|
|
1322
|
+
setState("input");
|
|
1323
|
+
}
|
|
1101
1324
|
}
|
|
1102
1325
|
),
|
|
1103
1326
|
state === "input" && /* @__PURE__ */ jsx2(PromptInput, { onSubmit: handleSubmit })
|
|
@@ -1129,7 +1352,7 @@ function showTranscript(sessionId) {
|
|
|
1129
1352
|
if (msg.role === "user") {
|
|
1130
1353
|
return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content }, i);
|
|
1131
1354
|
}
|
|
1132
|
-
if (msg.role
|
|
1355
|
+
if (isValidAgentName(msg.role)) {
|
|
1133
1356
|
return /* @__PURE__ */ jsx2(AgentResponseBlock, { agent: msg.role, content: msg.content }, i);
|
|
1134
1357
|
}
|
|
1135
1358
|
return null;
|
|
@@ -1155,25 +1378,75 @@ function showSessionList(sessions) {
|
|
|
1155
1378
|
}
|
|
1156
1379
|
|
|
1157
1380
|
// src/index.ts
|
|
1381
|
+
process.on("SIGTERM", () => {
|
|
1382
|
+
closeDb();
|
|
1383
|
+
process.exit(0);
|
|
1384
|
+
});
|
|
1385
|
+
process.on("SIGINT", () => {
|
|
1386
|
+
closeDb();
|
|
1387
|
+
process.exit(0);
|
|
1388
|
+
});
|
|
1389
|
+
function checkCli(name, installUrl) {
|
|
1390
|
+
try {
|
|
1391
|
+
execSync2(`${name} --version`, { stdio: "ignore" });
|
|
1392
|
+
return true;
|
|
1393
|
+
} catch {
|
|
1394
|
+
console.error(
|
|
1395
|
+
chalk.red(`"${name}" not found. Install it from: ${installUrl}`)
|
|
1396
|
+
);
|
|
1397
|
+
return false;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
function preflight(pair) {
|
|
1401
|
+
let allFound = true;
|
|
1402
|
+
for (const agent of pair) {
|
|
1403
|
+
const descriptor = getAgent(agent);
|
|
1404
|
+
if (!checkCli(descriptor.cliBinary, descriptor.installUrl)) {
|
|
1405
|
+
allFound = false;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
if (!allFound) {
|
|
1409
|
+
process.exit(1);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function resolveAgentPair(opts, config) {
|
|
1413
|
+
if (opts.agents) {
|
|
1414
|
+
const names = opts.agents.split(",").map((s) => s.trim());
|
|
1415
|
+
return validateAgentPair(names);
|
|
1416
|
+
}
|
|
1417
|
+
return validateAgentPair(config.agents);
|
|
1418
|
+
}
|
|
1419
|
+
function resolveAgentModels(pair, opts, config) {
|
|
1420
|
+
return {
|
|
1421
|
+
claude: opts.claudeModel ?? config.claude.model,
|
|
1422
|
+
codex: opts.codexModel ?? config.codex.model,
|
|
1423
|
+
gemini: opts.geminiModel ?? config.gemini.model
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1158
1426
|
var program = new Command();
|
|
1159
|
-
program.name("tagteam").description("Tag Team - Orchestrate
|
|
1427
|
+
program.name("tagteam").description("Tag Team - Orchestrate AI agents collaboratively").version("0.2.0").option("--agents <pair>", "Agent pair to use (comma-separated, e.g. claude,gemini)").option("--claude-model <model>", "Claude model to use").option("--codex-model <model>", "Codex model to use").option("--gemini-model <model>", "Gemini model to use").argument("[prompt...]", "Prompt to send to both agents").action(async (promptParts, opts) => {
|
|
1160
1428
|
const config = loadConfig();
|
|
1429
|
+
const pair = resolveAgentPair(opts, config);
|
|
1430
|
+
preflight(pair);
|
|
1161
1431
|
const prompt = promptParts.join(" ") || void 0;
|
|
1162
1432
|
const instance = startApp({
|
|
1163
1433
|
initialPrompt: prompt,
|
|
1164
|
-
|
|
1165
|
-
|
|
1434
|
+
agents: pair,
|
|
1435
|
+
agentModels: resolveAgentModels(pair, opts, config),
|
|
1166
1436
|
config
|
|
1167
1437
|
});
|
|
1168
1438
|
await instance.waitUntilExit();
|
|
1169
1439
|
});
|
|
1170
|
-
program.command("discuss").description("Have
|
|
1440
|
+
program.command("discuss").description("Have agents discuss a topic until they reach consensus").argument("<prompt...>", "Topic to discuss").action(async (promptParts) => {
|
|
1171
1441
|
const config = loadConfig();
|
|
1442
|
+
const parentOpts = program.opts();
|
|
1443
|
+
const pair = resolveAgentPair(parentOpts, config);
|
|
1444
|
+
preflight(pair);
|
|
1172
1445
|
const prompt = promptParts.join(" ");
|
|
1173
1446
|
const instance = startApp({
|
|
1174
1447
|
initialPrompt: prompt,
|
|
1175
|
-
|
|
1176
|
-
|
|
1448
|
+
agents: pair,
|
|
1449
|
+
agentModels: resolveAgentModels(pair, parentOpts, config),
|
|
1177
1450
|
config,
|
|
1178
1451
|
discuss: true
|
|
1179
1452
|
});
|
|
@@ -1181,6 +1454,9 @@ program.command("discuss").description("Have Claude and Codex discuss a topic un
|
|
|
1181
1454
|
});
|
|
1182
1455
|
program.command("continue").description("Resume the most recent session").action(async () => {
|
|
1183
1456
|
const config = loadConfig();
|
|
1457
|
+
const parentOpts = program.opts();
|
|
1458
|
+
const pair = resolveAgentPair(parentOpts, config);
|
|
1459
|
+
preflight(pair);
|
|
1184
1460
|
const session = getMostRecentSession();
|
|
1185
1461
|
if (!session) {
|
|
1186
1462
|
console.log(chalk.red(" No active sessions found."));
|
|
@@ -1194,8 +1470,8 @@ program.command("continue").description("Resume the most recent session").action
|
|
|
1194
1470
|
}));
|
|
1195
1471
|
const instance = startApp({
|
|
1196
1472
|
sessionId: session.id,
|
|
1197
|
-
|
|
1198
|
-
|
|
1473
|
+
agents: pair,
|
|
1474
|
+
agentModels: resolveAgentModels(pair, parentOpts, config),
|
|
1199
1475
|
config,
|
|
1200
1476
|
showTranscript: transcript
|
|
1201
1477
|
});
|
|
@@ -1203,6 +1479,9 @@ program.command("continue").description("Resume the most recent session").action
|
|
|
1203
1479
|
});
|
|
1204
1480
|
program.command("resume [id]").description("Resume a session by ID, or pick interactively").action(async (id) => {
|
|
1205
1481
|
const config = loadConfig();
|
|
1482
|
+
const parentOpts = program.opts();
|
|
1483
|
+
const pair = resolveAgentPair(parentOpts, config);
|
|
1484
|
+
preflight(pair);
|
|
1206
1485
|
if (!id) {
|
|
1207
1486
|
const sessions = listSessions(20);
|
|
1208
1487
|
if (sessions.length === 0) {
|
|
@@ -1241,8 +1520,8 @@ program.command("resume [id]").description("Resume a session by ID, or pick inte
|
|
|
1241
1520
|
}));
|
|
1242
1521
|
const instance2 = startApp({
|
|
1243
1522
|
sessionId: session2.id,
|
|
1244
|
-
|
|
1245
|
-
|
|
1523
|
+
agents: pair,
|
|
1524
|
+
agentModels: resolveAgentModels(pair, parentOpts, config),
|
|
1246
1525
|
config,
|
|
1247
1526
|
showTranscript: transcript2
|
|
1248
1527
|
});
|
|
@@ -1264,8 +1543,8 @@ program.command("resume [id]").description("Resume a session by ID, or pick inte
|
|
|
1264
1543
|
}));
|
|
1265
1544
|
const instance = startApp({
|
|
1266
1545
|
sessionId: session.id,
|
|
1267
|
-
|
|
1268
|
-
|
|
1546
|
+
agents: pair,
|
|
1547
|
+
agentModels: resolveAgentModels(pair, parentOpts, config),
|
|
1269
1548
|
config,
|
|
1270
1549
|
showTranscript: transcript
|
|
1271
1550
|
});
|
|
@@ -1303,10 +1582,16 @@ configCmd.command("show").description("Show current configuration").action(() =>
|
|
|
1303
1582
|
const config = loadConfig();
|
|
1304
1583
|
console.log(chalk.bold("\n Configuration:\n"));
|
|
1305
1584
|
console.log(
|
|
1306
|
-
chalk.dim("
|
|
1585
|
+
chalk.dim(" agents = ") + chalk.white(config.agents.join(", "))
|
|
1586
|
+
);
|
|
1587
|
+
console.log(
|
|
1588
|
+
chalk.dim(" claude.model = ") + chalk.white(config.claude.model)
|
|
1589
|
+
);
|
|
1590
|
+
console.log(
|
|
1591
|
+
chalk.dim(" codex.model = ") + chalk.white(config.codex.model)
|
|
1307
1592
|
);
|
|
1308
1593
|
console.log(
|
|
1309
|
-
chalk.dim("
|
|
1594
|
+
chalk.dim(" gemini.model = ") + chalk.white(config.gemini.model)
|
|
1310
1595
|
);
|
|
1311
1596
|
console.log(
|
|
1312
1597
|
chalk.dim(" discussion.max_rounds = ") + chalk.white(String(config.discussion.max_rounds))
|