tagteam 0.3.0 → 0.4.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 +13 -3
- package/dist/index.js +452 -86
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import {
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { execSync as execSync3 } from "child_process";
|
|
6
7
|
import chalk from "chalk";
|
|
7
8
|
|
|
8
9
|
// src/config.ts
|
|
@@ -22,7 +23,7 @@ var DEFAULT_CONFIG = {
|
|
|
22
23
|
model: "gemini-2.5-pro"
|
|
23
24
|
},
|
|
24
25
|
discussion: {
|
|
25
|
-
max_rounds:
|
|
26
|
+
max_rounds: 5
|
|
26
27
|
}
|
|
27
28
|
};
|
|
28
29
|
function getConfigDir() {
|
|
@@ -111,8 +112,8 @@ function setConfigValue(key, value) {
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
// src/ui.tsx
|
|
114
|
-
import { useState as useState2, useEffect, useCallback, useRef } from "react";
|
|
115
|
-
import { render as render2, Box as Box2, Text as Text2, useApp as useApp2, useInput as useInput2 } from "ink";
|
|
115
|
+
import React2, { useState as useState2, useEffect, useCallback, useRef, useMemo } from "react";
|
|
116
|
+
import { render as render2, Box as Box2, Text as Text2, useApp as useApp2, useInput as useInput2, Static } from "ink";
|
|
116
117
|
import TextInput2 from "ink-text-input";
|
|
117
118
|
import Spinner from "ink-spinner";
|
|
118
119
|
import { marked } from "marked";
|
|
@@ -458,6 +459,16 @@ var AGENTS = {
|
|
|
458
459
|
cliBinary: "claude",
|
|
459
460
|
installUrl: "https://docs.anthropic.com/en/docs/claude-code",
|
|
460
461
|
org: "Anthropic",
|
|
462
|
+
profile: {
|
|
463
|
+
strength: "architecture-implementation",
|
|
464
|
+
role: "The Builder",
|
|
465
|
+
focus: [
|
|
466
|
+
"multi-file coherence and refactoring",
|
|
467
|
+
"production-quality implementation",
|
|
468
|
+
"design patterns and maintainability",
|
|
469
|
+
"comprehensive working solutions"
|
|
470
|
+
]
|
|
471
|
+
},
|
|
461
472
|
run: runClaude
|
|
462
473
|
},
|
|
463
474
|
codex: {
|
|
@@ -467,6 +478,16 @@ var AGENTS = {
|
|
|
467
478
|
cliBinary: "codex",
|
|
468
479
|
installUrl: "https://github.com/openai/codex",
|
|
469
480
|
org: "OpenAI",
|
|
481
|
+
profile: {
|
|
482
|
+
strength: "correctness-verification",
|
|
483
|
+
role: "The Verifier",
|
|
484
|
+
focus: [
|
|
485
|
+
"algorithmic correctness and edge cases",
|
|
486
|
+
"test coverage and failure modes",
|
|
487
|
+
"standards compliance and best practices",
|
|
488
|
+
"performance characteristics and benchmarks"
|
|
489
|
+
]
|
|
490
|
+
},
|
|
470
491
|
run: runCodex
|
|
471
492
|
},
|
|
472
493
|
gemini: {
|
|
@@ -476,12 +497,49 @@ var AGENTS = {
|
|
|
476
497
|
cliBinary: "gemini",
|
|
477
498
|
installUrl: "https://github.com/google-gemini/gemini-cli",
|
|
478
499
|
org: "Google",
|
|
500
|
+
profile: {
|
|
501
|
+
strength: "context-strategy",
|
|
502
|
+
role: "The Strategist",
|
|
503
|
+
focus: [
|
|
504
|
+
"broad codebase context and upstream/downstream effects",
|
|
505
|
+
"current ecosystem conventions and documentation",
|
|
506
|
+
"architectural fit and scope assessment",
|
|
507
|
+
"planning, decomposition, and tradeoff analysis"
|
|
508
|
+
]
|
|
509
|
+
},
|
|
479
510
|
run: runGemini
|
|
480
511
|
}
|
|
481
512
|
};
|
|
513
|
+
var PEER_ROLES = {
|
|
514
|
+
"claude,codex": {
|
|
515
|
+
claude: "Correctness & Standards \u2014 they verify edge cases, test coverage, and standards compliance",
|
|
516
|
+
codex: "Architecture & Implementation \u2014 they propose complete solutions and assess structural coherence",
|
|
517
|
+
gemini: ""
|
|
518
|
+
// not in this pair
|
|
519
|
+
},
|
|
520
|
+
"claude,gemini": {
|
|
521
|
+
claude: "Strategic Context \u2014 they assess broad codebase fit, ecosystem conventions, and architectural tradeoffs",
|
|
522
|
+
gemini: "Architecture & Implementation \u2014 they propose complete solutions and assess structural coherence",
|
|
523
|
+
codex: ""
|
|
524
|
+
// not in this pair
|
|
525
|
+
},
|
|
526
|
+
"codex,gemini": {
|
|
527
|
+
codex: "Strategic Context \u2014 they assess broad codebase fit, ecosystem conventions, and architectural tradeoffs",
|
|
528
|
+
gemini: "Correctness & Standards \u2014 they verify edge cases, test coverage, and standards compliance",
|
|
529
|
+
claude: ""
|
|
530
|
+
// not in this pair
|
|
531
|
+
}
|
|
532
|
+
};
|
|
482
533
|
function getAgent(name) {
|
|
483
534
|
return AGENTS[name];
|
|
484
535
|
}
|
|
536
|
+
function getAgentProfile(name) {
|
|
537
|
+
return AGENTS[name].profile;
|
|
538
|
+
}
|
|
539
|
+
function getPeerRoleDescription(agent, pair) {
|
|
540
|
+
const key = [...pair].sort().join(",");
|
|
541
|
+
return PEER_ROLES[key]?.[agent] ?? "";
|
|
542
|
+
}
|
|
485
543
|
function getAllAgentNames() {
|
|
486
544
|
return Object.keys(AGENTS);
|
|
487
545
|
}
|
|
@@ -569,6 +627,34 @@ function copyToClipboard(text) {
|
|
|
569
627
|
}
|
|
570
628
|
}
|
|
571
629
|
|
|
630
|
+
// src/gist.ts
|
|
631
|
+
import { execSync as execSync2 } from "child_process";
|
|
632
|
+
function createGist(content, filename) {
|
|
633
|
+
try {
|
|
634
|
+
execSync2("gh auth status", { stdio: "ignore" });
|
|
635
|
+
} catch {
|
|
636
|
+
try {
|
|
637
|
+
execSync2("which gh", { stdio: "ignore" });
|
|
638
|
+
} catch {
|
|
639
|
+
throw new Error(
|
|
640
|
+
"gh CLI not found. Install it from https://cli.github.com"
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
throw new Error(
|
|
644
|
+
"gh CLI is not authenticated. Run: gh auth login"
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
const result = execSync2(
|
|
649
|
+
`gh gist create --private --filename "${filename}" -`,
|
|
650
|
+
{ input: content, stdio: ["pipe", "pipe", "ignore"] }
|
|
651
|
+
);
|
|
652
|
+
return result.toString().trim();
|
|
653
|
+
} catch {
|
|
654
|
+
throw new Error("Failed to create gist.");
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
572
658
|
// src/db/index.ts
|
|
573
659
|
import Database from "better-sqlite3";
|
|
574
660
|
import { join as join2 } from "path";
|
|
@@ -694,66 +780,279 @@ function deleteMessagesFromRound(sessionId, fromRound) {
|
|
|
694
780
|
}
|
|
695
781
|
|
|
696
782
|
// src/prompts.ts
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
783
|
+
var CONSENSUS_MARKER = "[CONSENSUS]";
|
|
784
|
+
function formatConversationHistory(messages) {
|
|
785
|
+
return messages.map((m) => {
|
|
786
|
+
let label;
|
|
787
|
+
if (m.role === "user") {
|
|
788
|
+
label = "User";
|
|
789
|
+
} else if (isValidAgentName(m.role)) {
|
|
790
|
+
label = getAgent(m.role).displayName;
|
|
791
|
+
} else if (m.agent && isValidAgentName(m.agent)) {
|
|
792
|
+
label = getAgent(m.agent).displayName;
|
|
793
|
+
} else {
|
|
794
|
+
label = m.role;
|
|
795
|
+
}
|
|
796
|
+
return `[${label}]: ${m.content}`;
|
|
797
|
+
}).join("\n\n");
|
|
701
798
|
}
|
|
702
|
-
function
|
|
703
|
-
return `You are
|
|
799
|
+
function basePrompt() {
|
|
800
|
+
return `You are one of two expert coding agents in a structured technical discussion.
|
|
801
|
+
You will independently analyze the problem, then engage in focused rounds of
|
|
802
|
+
critique and refinement with your peer.
|
|
803
|
+
|
|
804
|
+
Ground rules:
|
|
805
|
+
- You are evaluated on the ACCURACY and QUALITY of your final position, not
|
|
806
|
+
on agreement with your peer.
|
|
807
|
+
- When you change your position, you MUST name the specific argument that
|
|
808
|
+
changed your mind and explain why your previous reasoning was flawed.
|
|
809
|
+
Changing position without this justification is not acceptable.
|
|
810
|
+
- Each response must either: (a) introduce new evidence or a new argument,
|
|
811
|
+
(b) identify a specific logical flaw or unsupported claim in your peer's
|
|
812
|
+
reasoning, or (c) concede a point with explicit justification. Restating
|
|
813
|
+
or paraphrasing existing points is not acceptable.
|
|
814
|
+
- Your peer is a different AI model with different training. Their perspective
|
|
815
|
+
may reveal genuine blind spots in yours \u2014 and vice versa.`;
|
|
816
|
+
}
|
|
817
|
+
var ROLE_TEMPLATES = {
|
|
818
|
+
claude: `Your role: Architecture & Implementation Reviewer.
|
|
819
|
+
|
|
820
|
+
Focus your analysis on:
|
|
821
|
+
- Code structure, design patterns, and maintainability
|
|
822
|
+
- Multi-file coherence \u2014 how changes ripple across the codebase
|
|
823
|
+
- Production readiness \u2014 error handling, logging, edge cases in real usage
|
|
824
|
+
- Proposing complete, working implementations (not just pseudocode)
|
|
825
|
+
|
|
826
|
+
When you propose a solution, provide the actual implementation. When you
|
|
827
|
+
critique, point to specific structural issues and show what the fix looks
|
|
828
|
+
like. Your peer's role is {peerRole} \u2014 they will stress-test your proposals
|
|
829
|
+
from a different angle.`,
|
|
830
|
+
codex: `Your role: Correctness & Standards Reviewer.
|
|
831
|
+
|
|
832
|
+
Focus your analysis on:
|
|
833
|
+
- Algorithmic correctness \u2014 does the logic actually work for all inputs?
|
|
834
|
+
- Edge cases and failure modes \u2014 what breaks, what's untested?
|
|
835
|
+
- Standards compliance \u2014 does this follow language/framework conventions?
|
|
836
|
+
- Performance characteristics \u2014 time/space complexity, benchmarks
|
|
837
|
+
|
|
838
|
+
When you critique, provide specific test cases or inputs that demonstrate
|
|
839
|
+
the issue. When you propose alternatives, explain the correctness guarantees.
|
|
840
|
+
Your peer's role is {peerRole} \u2014 they will focus on different aspects of the
|
|
841
|
+
same problem.`,
|
|
842
|
+
gemini: `Your role: Strategic Context Analyst.
|
|
843
|
+
|
|
844
|
+
Focus your analysis on:
|
|
845
|
+
- Broad codebase context \u2014 how does this change fit the larger system?
|
|
846
|
+
- Current ecosystem conventions \u2014 what do the docs, community, and recent
|
|
847
|
+
releases recommend?
|
|
848
|
+
- Upstream and downstream effects \u2014 what will this break or enable elsewhere?
|
|
849
|
+
- Scope and planning \u2014 is this the right approach at the right level of
|
|
850
|
+
abstraction?
|
|
851
|
+
|
|
852
|
+
When you critique, ground your position in the broader context your peer may
|
|
853
|
+
be missing. When you propose alternatives, explain the architectural tradeoffs.
|
|
854
|
+
Your peer's role is {peerRole} \u2014 they will focus on different aspects of the
|
|
855
|
+
same problem.`
|
|
856
|
+
};
|
|
857
|
+
function rolePrompt(agent, pair) {
|
|
858
|
+
const template = ROLE_TEMPLATES[agent];
|
|
859
|
+
const peerRole = getPeerRoleDescription(agent, pair);
|
|
860
|
+
return template.replace("{peerRole}", peerRole);
|
|
704
861
|
}
|
|
705
|
-
function
|
|
706
|
-
return `${
|
|
862
|
+
function collaborationSystemPrompt(agent, pair) {
|
|
863
|
+
return `${basePrompt()}
|
|
707
864
|
|
|
708
|
-
|
|
865
|
+
${rolePrompt(agent, pair)}`;
|
|
866
|
+
}
|
|
867
|
+
function discussionRoundPrompt(agent, conversationContext, pair) {
|
|
868
|
+
return `${basePrompt()}
|
|
709
869
|
|
|
710
|
-
${
|
|
870
|
+
${rolePrompt(agent, pair)}
|
|
871
|
+
|
|
872
|
+
Here is the discussion so far:
|
|
873
|
+
|
|
874
|
+
${conversationContext}
|
|
875
|
+
|
|
876
|
+
For this round:
|
|
877
|
+
1. What is the strongest point in your peer's response?
|
|
878
|
+
2. What is the weakest point, or what claim lacks supporting evidence?
|
|
879
|
+
3. Has your position changed? State one of: HELD / PARTIALLY_CHANGED / CHANGED
|
|
880
|
+
\u2014 with explicit reasoning for why.
|
|
881
|
+
4. If proposing code, show the specific implementation and explain tradeoffs
|
|
882
|
+
versus your peer's approach.
|
|
883
|
+
5. Confidence in your current position: LOW | MEDIUM | HIGH
|
|
711
884
|
|
|
712
|
-
|
|
885
|
+
CONFIDENCE: HIGH | MEDIUM | LOW
|
|
886
|
+
|
|
887
|
+
Keep it concise. Do not restate points already established.`;
|
|
713
888
|
}
|
|
714
|
-
function
|
|
715
|
-
|
|
716
|
-
|
|
889
|
+
function debateSystemPrompt(agent, pair) {
|
|
890
|
+
return `${basePrompt()}
|
|
891
|
+
|
|
892
|
+
${rolePrompt(agent, pair)}
|
|
893
|
+
|
|
894
|
+
This is a structured discussion aimed at reaching a well-reasoned position
|
|
895
|
+
through genuine deliberation.
|
|
896
|
+
|
|
897
|
+
Additional rules for discussion mode:
|
|
898
|
+
- Structure your arguments: STATE your claim, provide EVIDENCE (code examples,
|
|
899
|
+
documentation, benchmarks), explain your REASONING connecting evidence to
|
|
900
|
+
claim, and note CAVEATS (when your claim doesn't hold).
|
|
901
|
+
- Express confidence: end your response with CONFIDENCE: HIGH | MEDIUM | LOW
|
|
902
|
+
and a one-line explanation of what would change your mind.
|
|
903
|
+
- Consensus signaling: when you believe you and your peer agree on all key
|
|
904
|
+
points AND your confidence is HIGH, end your response with ${CONSENSUS_MARKER} on
|
|
905
|
+
its own line. Only signal consensus when:
|
|
906
|
+
(a) You can state the shared position in one sentence
|
|
907
|
+
(b) You have HIGH confidence
|
|
908
|
+
(c) You are not just deferring \u2014 you genuinely agree with the reasoning`;
|
|
909
|
+
}
|
|
910
|
+
function debateRoundPrompt(agent, conversationContext, pair) {
|
|
911
|
+
return `${debateSystemPrompt(agent, pair)}
|
|
717
912
|
|
|
718
|
-
|
|
719
|
-
- Address specific points of agreement and disagreement
|
|
720
|
-
- Refine your position based on valid arguments from ${other}
|
|
721
|
-
- Be concise \u2014 don't repeat points already established
|
|
913
|
+
${conversationContext}
|
|
722
914
|
|
|
723
|
-
|
|
915
|
+
For this round:
|
|
916
|
+
1. Address your peer's strongest argument directly \u2014 do you accept it? Why or
|
|
917
|
+
why not?
|
|
918
|
+
2. If your peer identified a flaw in your reasoning, acknowledge it explicitly
|
|
919
|
+
or defend with new evidence.
|
|
920
|
+
3. State your current position with EVIDENCE and REASONING.
|
|
921
|
+
4. CONFIDENCE: HIGH | MEDIUM | LOW \u2014 what specific evidence would change
|
|
922
|
+
your remaining position?
|
|
923
|
+
5. If consensus: state the shared position in one sentence, then ${CONSENSUS_MARKER}.
|
|
924
|
+
|
|
925
|
+
POSITION: HELD | PARTIALLY_CHANGED | CHANGED`;
|
|
724
926
|
}
|
|
725
|
-
function
|
|
726
|
-
|
|
727
|
-
|
|
927
|
+
function steelmanPrompt() {
|
|
928
|
+
return `You and your peer appear to largely agree after Round 1. Before confirming
|
|
929
|
+
consensus, steelman the opposing view:
|
|
728
930
|
|
|
729
|
-
|
|
931
|
+
- What is the strongest argument AGAINST your shared position?
|
|
932
|
+
- What context or edge case might make a different approach better?
|
|
933
|
+
- Is there a tradeoff you're both overlooking?
|
|
730
934
|
|
|
731
|
-
|
|
935
|
+
If after considering the counterarguments you still hold your position, explain
|
|
936
|
+
why the counterarguments don't apply here. Then proceed with your normal round
|
|
937
|
+
response.
|
|
732
938
|
|
|
733
|
-
|
|
939
|
+
`;
|
|
734
940
|
}
|
|
735
|
-
function directPrompt(conversationHistory) {
|
|
736
|
-
|
|
941
|
+
function directPrompt(agent, conversationHistory) {
|
|
942
|
+
const profile = getAgentProfile(agent);
|
|
943
|
+
const focusAreas = profile.focus.map((f) => `- ${f}`).join("\n");
|
|
944
|
+
return `You are being addressed directly in a multi-agent session. The user wants YOUR
|
|
945
|
+
specific perspective.
|
|
737
946
|
|
|
947
|
+
Here is the conversation so far:
|
|
738
948
|
${conversationHistory}
|
|
739
949
|
|
|
740
|
-
Respond to the user's latest message.
|
|
950
|
+
Respond to the user's latest message. Focus on your area of expertise:
|
|
951
|
+
${focusAreas}
|
|
952
|
+
|
|
953
|
+
Be concise and direct.`;
|
|
741
954
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
955
|
+
|
|
956
|
+
// src/discussion.ts
|
|
957
|
+
var CONFIDENCE_RE = /CONFIDENCE:\s*(HIGH|MEDIUM|LOW)/i;
|
|
958
|
+
var POSITION_RE = /POSITION:\s*(HELD|PARTIALLY_CHANGED|CHANGED)/i;
|
|
959
|
+
var CONSENSUS_RE = /\[CONSENSUS\]/;
|
|
960
|
+
function parseRoundAnalysis(agent, responseText) {
|
|
961
|
+
const confidenceMatch = responseText.match(CONFIDENCE_RE);
|
|
962
|
+
const positionMatch = responseText.match(POSITION_RE);
|
|
963
|
+
const signaledConsensus = CONSENSUS_RE.test(responseText);
|
|
964
|
+
const stripped = responseText.replace(CONFIDENCE_RE, "").replace(POSITION_RE, "").replace(CONSENSUS_RE, "").trim();
|
|
965
|
+
const hasNovelContent = stripped.length > 100;
|
|
966
|
+
return {
|
|
967
|
+
agent,
|
|
968
|
+
confidence: confidenceMatch?.[1]?.toUpperCase() ?? "MEDIUM",
|
|
969
|
+
positionChange: positionMatch?.[1]?.toUpperCase() ?? "HELD",
|
|
970
|
+
signaledConsensus,
|
|
971
|
+
hasNovelContent
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
function checkTermination(state, maxRounds) {
|
|
975
|
+
const { round, analyses } = state;
|
|
976
|
+
if (analyses.length > 0) {
|
|
977
|
+
const latest = analyses[analyses.length - 1];
|
|
978
|
+
if (latest && latest.length >= 2) {
|
|
979
|
+
const allConsensus = latest.every((a) => a.signaledConsensus);
|
|
980
|
+
const allHigh = latest.every((a) => a.confidence === "HIGH");
|
|
981
|
+
if (allConsensus && allHigh) {
|
|
982
|
+
return { terminated: true, reason: "mutual-consensus" };
|
|
983
|
+
}
|
|
754
984
|
}
|
|
755
|
-
|
|
756
|
-
|
|
985
|
+
}
|
|
986
|
+
if (analyses.length >= 2) {
|
|
987
|
+
const prev = analyses[analyses.length - 2];
|
|
988
|
+
const curr = analyses[analyses.length - 1];
|
|
989
|
+
if (prev && curr && prev.length >= 2 && curr.length >= 2) {
|
|
990
|
+
const prevStale = prev.every((a) => a.positionChange === "HELD" && !a.hasNovelContent);
|
|
991
|
+
const currStale = curr.every((a) => a.positionChange === "HELD" && !a.hasNovelContent);
|
|
992
|
+
if (prevStale && currStale) {
|
|
993
|
+
return { terminated: true, reason: "stale-no-progress" };
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
if (analyses.length >= 2) {
|
|
998
|
+
const prev = analyses[analyses.length - 2];
|
|
999
|
+
const curr = analyses[analyses.length - 1];
|
|
1000
|
+
if (prev && curr && prev.length >= 2 && curr.length >= 2) {
|
|
1001
|
+
const prevSwap = prev.every((a) => a.positionChange === "CHANGED");
|
|
1002
|
+
const currSwap = curr.every((a) => a.positionChange === "CHANGED");
|
|
1003
|
+
if (prevSwap && currSwap) {
|
|
1004
|
+
return { terminated: true, reason: "cyclic-swap" };
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (round >= maxRounds) {
|
|
1009
|
+
return { terminated: true, reason: "max-rounds" };
|
|
1010
|
+
}
|
|
1011
|
+
return { terminated: false };
|
|
1012
|
+
}
|
|
1013
|
+
function terminationMessage(reason) {
|
|
1014
|
+
switch (reason) {
|
|
1015
|
+
case "mutual-consensus":
|
|
1016
|
+
return "Consensus reached.";
|
|
1017
|
+
case "stale-no-progress":
|
|
1018
|
+
return "Discussion stalled \u2014 no new arguments. Showing final positions.";
|
|
1019
|
+
case "cyclic-swap":
|
|
1020
|
+
return "Agents are trading positions. Showing both perspectives.";
|
|
1021
|
+
case "max-rounds":
|
|
1022
|
+
return "Maximum rounds reached. Showing final positions.";
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
function shouldInjectSteelman(state) {
|
|
1026
|
+
return state.round === 1 && state.analyses.length === 1;
|
|
1027
|
+
}
|
|
1028
|
+
function buildConversationContext(allMessages, analyses, currentRound, _pair) {
|
|
1029
|
+
if (currentRound <= 2) {
|
|
1030
|
+
return formatConversationHistory(allMessages);
|
|
1031
|
+
}
|
|
1032
|
+
const summaryParts = [];
|
|
1033
|
+
for (let i = 0; i < analyses.length - 1; i++) {
|
|
1034
|
+
const roundAnalyses = analyses[i];
|
|
1035
|
+
if (!roundAnalyses) continue;
|
|
1036
|
+
const roundSummary = roundAnalyses.map((a) => {
|
|
1037
|
+
return `${a.agent}: confidence=${a.confidence}, position=${a.positionChange}${a.signaledConsensus ? ", signaled consensus" : ""}`;
|
|
1038
|
+
}).join("; ");
|
|
1039
|
+
summaryParts.push(`Round ${i + 1}: ${roundSummary}`);
|
|
1040
|
+
}
|
|
1041
|
+
const latestMessages = allMessages.slice(-3);
|
|
1042
|
+
const summary = summaryParts.length > 0 ? `Previous rounds summary:
|
|
1043
|
+
${summaryParts.join("\n")}
|
|
1044
|
+
|
|
1045
|
+
Latest exchange:
|
|
1046
|
+
${formatConversationHistory(latestMessages)}` : formatConversationHistory(allMessages);
|
|
1047
|
+
return summary;
|
|
1048
|
+
}
|
|
1049
|
+
function analysisToMetadata(analysis) {
|
|
1050
|
+
return {
|
|
1051
|
+
confidence: analysis.confidence,
|
|
1052
|
+
positionChange: analysis.positionChange,
|
|
1053
|
+
signaledConsensus: analysis.signaledConsensus,
|
|
1054
|
+
hasNovelContent: analysis.hasNovelContent
|
|
1055
|
+
};
|
|
757
1056
|
}
|
|
758
1057
|
|
|
759
1058
|
// src/config-editor.tsx
|
|
@@ -940,10 +1239,10 @@ function parseInput(input) {
|
|
|
940
1239
|
return { target: "both", prompt: input, discuss: false };
|
|
941
1240
|
}
|
|
942
1241
|
function RenderedMarkdown({ text }) {
|
|
943
|
-
const rendered = marked.parse(text).trimEnd();
|
|
1242
|
+
const rendered = useMemo(() => marked.parse(text).trimEnd(), [text]);
|
|
944
1243
|
return /* @__PURE__ */ jsx2(Text2, { children: rendered });
|
|
945
1244
|
}
|
|
946
|
-
|
|
1245
|
+
var AgentResponseBlock = React2.memo(function AgentResponseBlock2({
|
|
947
1246
|
agent,
|
|
948
1247
|
content,
|
|
949
1248
|
error
|
|
@@ -965,7 +1264,7 @@ function AgentResponseBlock({
|
|
|
965
1264
|
] }),
|
|
966
1265
|
/* @__PURE__ */ jsx2(Box2, { marginLeft: 1, children: /* @__PURE__ */ jsx2(RenderedMarkdown, { text: content }) })
|
|
967
1266
|
] });
|
|
968
|
-
}
|
|
1267
|
+
});
|
|
969
1268
|
function Header({ sessionId }) {
|
|
970
1269
|
return /* @__PURE__ */ jsxs2(Box2, { marginBottom: 1, children: [
|
|
971
1270
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2500\u2500 " }),
|
|
@@ -992,7 +1291,7 @@ function QuickHelp() {
|
|
|
992
1291
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "/help for more" })
|
|
993
1292
|
] });
|
|
994
1293
|
}
|
|
995
|
-
|
|
1294
|
+
var UserMessage = React2.memo(function UserMessage2({ content }) {
|
|
996
1295
|
return /* @__PURE__ */ jsxs2(Box2, { marginLeft: 1, marginBottom: 1, children: [
|
|
997
1296
|
/* @__PURE__ */ jsxs2(Text2, { bold: true, color: "white", children: [
|
|
998
1297
|
"You:",
|
|
@@ -1000,7 +1299,7 @@ function UserMessage({ content }) {
|
|
|
1000
1299
|
] }),
|
|
1001
1300
|
/* @__PURE__ */ jsx2(Text2, { children: content })
|
|
1002
1301
|
] });
|
|
1003
|
-
}
|
|
1302
|
+
});
|
|
1004
1303
|
function ThinkingIndicator({ agent }) {
|
|
1005
1304
|
const descriptor = getAgent(agent);
|
|
1006
1305
|
return /* @__PURE__ */ jsxs2(Box2, { marginLeft: 1, children: [
|
|
@@ -1077,6 +1376,7 @@ function App({
|
|
|
1077
1376
|
}
|
|
1078
1377
|
return 0;
|
|
1079
1378
|
});
|
|
1379
|
+
const [committedCount, setCommittedCount] = useState2(showTranscript2?.length ?? 0);
|
|
1080
1380
|
const sessionCreatedRef = useRef(!!existingSessionId);
|
|
1081
1381
|
const ensureSession = (id) => {
|
|
1082
1382
|
if (!sessionCreatedRef.current) {
|
|
@@ -1107,7 +1407,9 @@ function App({
|
|
|
1107
1407
|
if (runningRoundRef.current !== null) {
|
|
1108
1408
|
deleteMessagesFromRound(sessionId, runningRoundRef.current);
|
|
1109
1409
|
const fromRound = runningRoundRef.current;
|
|
1110
|
-
|
|
1410
|
+
const remaining = messages.filter((m) => m.round < fromRound);
|
|
1411
|
+
setMessages(remaining);
|
|
1412
|
+
setCommittedCount(remaining.length);
|
|
1111
1413
|
runningRoundRef.current = null;
|
|
1112
1414
|
}
|
|
1113
1415
|
setThinkingAgents([]);
|
|
@@ -1116,7 +1418,7 @@ function App({
|
|
|
1116
1418
|
setState("input");
|
|
1117
1419
|
}
|
|
1118
1420
|
});
|
|
1119
|
-
const runAgents = async (currentMessages, round, target, promptOverride, isDebate = false) => {
|
|
1421
|
+
const runAgents = async (currentMessages, round, target, promptOverride, isDebate = false, systemPromptPerAgent) => {
|
|
1120
1422
|
const activeAgents = Array.isArray(target) ? target : target === "both" ? [...pair] : [target];
|
|
1121
1423
|
setThinkingAgents(activeAgents);
|
|
1122
1424
|
const history = formatConversationHistory(
|
|
@@ -1138,15 +1440,16 @@ function App({
|
|
|
1138
1440
|
return "Provide your response for this round.";
|
|
1139
1441
|
};
|
|
1140
1442
|
const agentSystemPrompt = (agent) => {
|
|
1443
|
+
if (systemPromptPerAgent?.[agent]) return systemPromptPerAgent[agent];
|
|
1141
1444
|
if (isSingleAgent) {
|
|
1142
|
-
return history ? directPrompt(history) : void 0;
|
|
1445
|
+
return history ? directPrompt(agent, history) : void 0;
|
|
1143
1446
|
}
|
|
1144
1447
|
if (isDebate) {
|
|
1145
|
-
if (isFirstRound) return
|
|
1448
|
+
if (isFirstRound) return debateSystemPrompt(agent, promptPair);
|
|
1146
1449
|
return debateRoundPrompt(agent, history, promptPair);
|
|
1147
1450
|
}
|
|
1148
|
-
if (isFirstRound) return
|
|
1149
|
-
return
|
|
1451
|
+
if (isFirstRound) return collaborationSystemPrompt(agent, promptPair);
|
|
1452
|
+
return discussionRoundPrompt(agent, history, promptPair);
|
|
1150
1453
|
};
|
|
1151
1454
|
const ac = new AbortController();
|
|
1152
1455
|
abortRef.current = ac;
|
|
@@ -1180,22 +1483,26 @@ function App({
|
|
|
1180
1483
|
if (result.status === "fulfilled") {
|
|
1181
1484
|
const resp = result.value;
|
|
1182
1485
|
const msg = {
|
|
1486
|
+
id: `${round}-${agent}`,
|
|
1183
1487
|
role: agent,
|
|
1184
1488
|
content: resp.error || resp.text,
|
|
1185
1489
|
round,
|
|
1186
1490
|
error: !!resp.error
|
|
1187
1491
|
};
|
|
1188
1492
|
newMessages.push(msg);
|
|
1493
|
+
const metadata = isDebate && !resp.error ? analysisToMetadata(parseRoundAnalysis(agent, resp.text)) : void 0;
|
|
1189
1494
|
insertMessage({
|
|
1190
1495
|
sessionId,
|
|
1191
1496
|
role: agent,
|
|
1192
1497
|
content: resp.error || resp.text,
|
|
1193
1498
|
round,
|
|
1194
|
-
durationMs: resp.durationMs
|
|
1499
|
+
durationMs: resp.durationMs,
|
|
1500
|
+
metadata
|
|
1195
1501
|
});
|
|
1196
1502
|
} else {
|
|
1197
1503
|
const errorMsg = result.reason?.message || "Failed to run";
|
|
1198
1504
|
const msg = {
|
|
1505
|
+
id: `${round}-${agent}`,
|
|
1199
1506
|
role: agent,
|
|
1200
1507
|
content: errorMsg,
|
|
1201
1508
|
round,
|
|
@@ -1225,6 +1532,7 @@ function App({
|
|
|
1225
1532
|
updateSessionTitle(sessionId, title);
|
|
1226
1533
|
}
|
|
1227
1534
|
const userMsg = {
|
|
1535
|
+
id: `${currentRound}-user`,
|
|
1228
1536
|
role: "user",
|
|
1229
1537
|
content: rawInput,
|
|
1230
1538
|
round: currentRound
|
|
@@ -1240,6 +1548,7 @@ function App({
|
|
|
1240
1548
|
const newMessages = await runAgents(allMessages, currentRound, target);
|
|
1241
1549
|
if (newMessages.length === 0) return;
|
|
1242
1550
|
setMessages((prev) => [...prev, ...newMessages]);
|
|
1551
|
+
setCommittedCount((prev) => prev + 1 + newMessages.length);
|
|
1243
1552
|
setRoundNum(currentRound + 1);
|
|
1244
1553
|
runningRoundRef.current = null;
|
|
1245
1554
|
touchSession(sessionId);
|
|
@@ -1247,6 +1556,7 @@ function App({
|
|
|
1247
1556
|
};
|
|
1248
1557
|
const runDiscussion = async (prompt, adHocPair) => {
|
|
1249
1558
|
const discussionTarget = adHocPair ?? "both";
|
|
1559
|
+
const activePair = adHocPair ?? pair;
|
|
1250
1560
|
setConsensusReached(false);
|
|
1251
1561
|
let currentRound = roundNum;
|
|
1252
1562
|
runningRoundRef.current = currentRound;
|
|
@@ -1256,6 +1566,7 @@ function App({
|
|
|
1256
1566
|
updateSessionTitle(sessionId, title);
|
|
1257
1567
|
}
|
|
1258
1568
|
const userMsg = {
|
|
1569
|
+
id: `${currentRound}-user`,
|
|
1259
1570
|
role: "user",
|
|
1260
1571
|
content: `discuss ${prompt}`,
|
|
1261
1572
|
round: currentRound
|
|
@@ -1268,26 +1579,63 @@ function App({
|
|
|
1268
1579
|
round: currentRound
|
|
1269
1580
|
});
|
|
1270
1581
|
let allMessages = [...messages, userMsg];
|
|
1582
|
+
const discState = {
|
|
1583
|
+
round: 0,
|
|
1584
|
+
analyses: [],
|
|
1585
|
+
terminated: false
|
|
1586
|
+
};
|
|
1271
1587
|
for (let disc = 1; disc <= config.discussion.max_rounds; disc++) {
|
|
1272
1588
|
setDiscussionRound(disc);
|
|
1589
|
+
discState.round = disc;
|
|
1590
|
+
let perAgentPrompts;
|
|
1591
|
+
if (disc >= 2) {
|
|
1592
|
+
const context = buildConversationContext(
|
|
1593
|
+
allMessages.map((m) => ({
|
|
1594
|
+
role: m.role,
|
|
1595
|
+
agent: isValidAgentName(m.role) ? m.role : void 0,
|
|
1596
|
+
content: m.content
|
|
1597
|
+
})),
|
|
1598
|
+
discState.analyses,
|
|
1599
|
+
disc,
|
|
1600
|
+
activePair
|
|
1601
|
+
);
|
|
1602
|
+
perAgentPrompts = {};
|
|
1603
|
+
for (const agent of activePair) {
|
|
1604
|
+
let prompt_text = "";
|
|
1605
|
+
if (shouldInjectSteelman(discState)) {
|
|
1606
|
+
prompt_text += steelmanPrompt();
|
|
1607
|
+
}
|
|
1608
|
+
prompt_text += debateRoundPrompt(agent, context, activePair);
|
|
1609
|
+
perAgentPrompts[agent] = prompt_text;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1273
1612
|
const newMessages = await runAgents(
|
|
1274
1613
|
allMessages,
|
|
1275
1614
|
currentRound,
|
|
1276
1615
|
discussionTarget,
|
|
1277
1616
|
disc === 1 ? prompt : "Provide your response for this round.",
|
|
1278
|
-
true
|
|
1617
|
+
true,
|
|
1618
|
+
perAgentPrompts
|
|
1279
1619
|
);
|
|
1280
1620
|
if (newMessages.length === 0) break;
|
|
1281
1621
|
setMessages((prev) => [...prev, ...newMessages]);
|
|
1622
|
+
setCommittedCount((prev) => prev + (disc === 1 ? 1 : 0) + newMessages.length);
|
|
1282
1623
|
allMessages = [...allMessages, ...newMessages];
|
|
1283
1624
|
currentRound++;
|
|
1284
|
-
const
|
|
1285
|
-
const consensusFlags = activePair.map((agent) => {
|
|
1625
|
+
const roundAnalyses = activePair.map((agent) => {
|
|
1286
1626
|
const msg = newMessages.find((m) => m.role === agent && !m.error);
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1627
|
+
if (!msg) return null;
|
|
1628
|
+
return parseRoundAnalysis(agent, msg.content);
|
|
1629
|
+
}).filter((a) => a !== null);
|
|
1630
|
+
discState.analyses.push(roundAnalyses);
|
|
1631
|
+
const termResult = checkTermination(discState, config.discussion.max_rounds);
|
|
1632
|
+
if (termResult.terminated && termResult.reason) {
|
|
1633
|
+
discState.terminated = true;
|
|
1634
|
+
discState.terminationReason = termResult.reason;
|
|
1635
|
+
setStatusMessage(terminationMessage(termResult.reason));
|
|
1636
|
+
if (termResult.reason === "mutual-consensus") {
|
|
1637
|
+
setConsensusReached(true);
|
|
1638
|
+
}
|
|
1291
1639
|
break;
|
|
1292
1640
|
}
|
|
1293
1641
|
}
|
|
@@ -1311,6 +1659,7 @@ function App({
|
|
|
1311
1659
|
"/config Edit configuration",
|
|
1312
1660
|
"/new Start a new session",
|
|
1313
1661
|
"/copy Copy conversation to clipboard",
|
|
1662
|
+
"/gist Create a private GitHub gist",
|
|
1314
1663
|
"/exit Exit the app",
|
|
1315
1664
|
"",
|
|
1316
1665
|
"Esc Interrupt running agents"
|
|
@@ -1327,6 +1676,7 @@ function App({
|
|
|
1327
1676
|
sessionCreatedRef.current = false;
|
|
1328
1677
|
setSessionId(newId);
|
|
1329
1678
|
setMessages([]);
|
|
1679
|
+
setCommittedCount(0);
|
|
1330
1680
|
setRoundNum(0);
|
|
1331
1681
|
setConsensusReached(false);
|
|
1332
1682
|
setDiscussionRound(0);
|
|
@@ -1343,28 +1693,40 @@ function App({
|
|
|
1343
1693
|
}
|
|
1344
1694
|
return;
|
|
1345
1695
|
}
|
|
1696
|
+
if (value === "/gist") {
|
|
1697
|
+
if (messages.length === 0) {
|
|
1698
|
+
setStatusMessage("Nothing to gist.");
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
try {
|
|
1702
|
+
const md = formatAsMarkdown(messages);
|
|
1703
|
+
const filename = `tagteam-${sessionId.slice(0, 7)}.md`;
|
|
1704
|
+
const url = createGist(md, filename);
|
|
1705
|
+
setStatusMessage(`Gist created: ${url}`);
|
|
1706
|
+
} catch (e) {
|
|
1707
|
+
setStatusMessage(e.message || "Failed to create gist.");
|
|
1708
|
+
}
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1346
1711
|
setConsensusReached(false);
|
|
1347
1712
|
setState("running");
|
|
1348
1713
|
runRound(value);
|
|
1349
1714
|
};
|
|
1715
|
+
const staticItems = useMemo(() => [
|
|
1716
|
+
{ id: `header-${sessionId}`, role: "__header", content: "", round: -1 },
|
|
1717
|
+
...messages.slice(0, committedCount)
|
|
1718
|
+
], [sessionId, messages, committedCount]);
|
|
1350
1719
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1351
|
-
/* @__PURE__ */ jsx2(
|
|
1720
|
+
/* @__PURE__ */ jsx2(Static, { items: staticItems, children: (item) => {
|
|
1721
|
+
if (item.role === "__header") return /* @__PURE__ */ jsx2(Header, { sessionId }, item.id);
|
|
1722
|
+
if (item.role === "user") return /* @__PURE__ */ jsx2(UserMessage, { content: item.content }, item.id);
|
|
1723
|
+
if (isValidAgentName(item.role)) return /* @__PURE__ */ jsx2(AgentResponseBlock, { agent: item.role, content: item.content, error: item.error }, item.id);
|
|
1724
|
+
return /* @__PURE__ */ jsx2(Box2, {}, item.id);
|
|
1725
|
+
} }),
|
|
1352
1726
|
messages.length === 0 && state === "input" && /* @__PURE__ */ jsx2(QuickHelp, {}),
|
|
1353
|
-
messages.map((msg
|
|
1354
|
-
if (msg.role === "user") {
|
|
1355
|
-
|
|
1356
|
-
}
|
|
1357
|
-
if (isValidAgentName(msg.role)) {
|
|
1358
|
-
return /* @__PURE__ */ jsx2(
|
|
1359
|
-
AgentResponseBlock,
|
|
1360
|
-
{
|
|
1361
|
-
agent: msg.role,
|
|
1362
|
-
content: msg.content,
|
|
1363
|
-
error: msg.error
|
|
1364
|
-
},
|
|
1365
|
-
i
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1727
|
+
messages.slice(committedCount).map((msg) => {
|
|
1728
|
+
if (msg.role === "user") return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content }, msg.id);
|
|
1729
|
+
if (isValidAgentName(msg.role)) return /* @__PURE__ */ jsx2(AgentResponseBlock, { agent: msg.role, content: msg.content, error: msg.error }, msg.id);
|
|
1368
1730
|
return null;
|
|
1369
1731
|
}),
|
|
1370
1732
|
discussionRound > 0 && thinkingAgents.length > 0 && /* @__PURE__ */ jsx2(DiscussionStatus, { round: discussionRound, maxRounds: config.discussion.max_rounds }),
|
|
@@ -1410,6 +1772,7 @@ function showTranscriptMarkdown(sessionId) {
|
|
|
1410
1772
|
function showTranscript(sessionId) {
|
|
1411
1773
|
const dbMessages = getMessages(sessionId);
|
|
1412
1774
|
const messages = dbMessages.map((m) => ({
|
|
1775
|
+
id: `${m.round}-${m.role}`,
|
|
1413
1776
|
role: m.role,
|
|
1414
1777
|
content: m.content,
|
|
1415
1778
|
round: m.round
|
|
@@ -1417,12 +1780,12 @@ function showTranscript(sessionId) {
|
|
|
1417
1780
|
const { unmount } = render2(
|
|
1418
1781
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1419
1782
|
/* @__PURE__ */ jsx2(Header, { sessionId }),
|
|
1420
|
-
messages.map((msg
|
|
1783
|
+
messages.map((msg) => {
|
|
1421
1784
|
if (msg.role === "user") {
|
|
1422
|
-
return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content },
|
|
1785
|
+
return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content }, msg.id);
|
|
1423
1786
|
}
|
|
1424
1787
|
if (isValidAgentName(msg.role)) {
|
|
1425
|
-
return /* @__PURE__ */ jsx2(AgentResponseBlock, { agent: msg.role, content: msg.content },
|
|
1788
|
+
return /* @__PURE__ */ jsx2(AgentResponseBlock, { agent: msg.role, content: msg.content }, msg.id);
|
|
1426
1789
|
}
|
|
1427
1790
|
return null;
|
|
1428
1791
|
}),
|
|
@@ -1457,7 +1820,7 @@ process.on("SIGINT", () => {
|
|
|
1457
1820
|
});
|
|
1458
1821
|
function checkCli(name, installUrl) {
|
|
1459
1822
|
try {
|
|
1460
|
-
|
|
1823
|
+
execSync3(`${name} --version`, { stdio: "ignore" });
|
|
1461
1824
|
return true;
|
|
1462
1825
|
} catch {
|
|
1463
1826
|
console.error(
|
|
@@ -1493,7 +1856,7 @@ function resolveAgentModels(pair, opts, config) {
|
|
|
1493
1856
|
};
|
|
1494
1857
|
}
|
|
1495
1858
|
var program = new Command();
|
|
1496
|
-
program.name("tagteam").description("Tag Team - Orchestrate AI agents collaboratively").version(
|
|
1859
|
+
program.name("tagteam").description("Tag Team - Orchestrate AI agents collaboratively").version(createRequire(import.meta.url)("../package.json").version).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) => {
|
|
1497
1860
|
const config = loadConfig();
|
|
1498
1861
|
const pair = resolveAgentPair(opts, config);
|
|
1499
1862
|
preflight(pair);
|
|
@@ -1533,6 +1896,7 @@ program.command("continue").description("Resume the most recent session").action
|
|
|
1533
1896
|
}
|
|
1534
1897
|
const dbMessages = getMessages(session.id);
|
|
1535
1898
|
const transcript = dbMessages.map((m) => ({
|
|
1899
|
+
id: `${m.round}-${m.role}`,
|
|
1536
1900
|
role: m.role,
|
|
1537
1901
|
content: m.content,
|
|
1538
1902
|
round: m.round
|
|
@@ -1583,6 +1947,7 @@ program.command("resume [id]").description("Resume a session by ID, or pick inte
|
|
|
1583
1947
|
const session2 = sessions[num - 1];
|
|
1584
1948
|
const dbMessages2 = getMessages(session2.id);
|
|
1585
1949
|
const transcript2 = dbMessages2.map((m) => ({
|
|
1950
|
+
id: `${m.round}-${m.role}`,
|
|
1586
1951
|
role: m.role,
|
|
1587
1952
|
content: m.content,
|
|
1588
1953
|
round: m.round
|
|
@@ -1606,6 +1971,7 @@ program.command("resume [id]").description("Resume a session by ID, or pick inte
|
|
|
1606
1971
|
}
|
|
1607
1972
|
const dbMessages = getMessages(session.id);
|
|
1608
1973
|
const transcript = dbMessages.map((m) => ({
|
|
1974
|
+
id: `${m.round}-${m.role}`,
|
|
1609
1975
|
role: m.role,
|
|
1610
1976
|
content: m.content,
|
|
1611
1977
|
round: m.round
|