tagteam 0.2.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -3
- package/dist/index.js +533 -104
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import { createRequire } from "module";
|
|
5
6
|
import { execSync as execSync2 } from "child_process";
|
|
6
7
|
import chalk from "chalk";
|
|
7
8
|
|
|
@@ -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
|
}
|
|
@@ -655,6 +713,16 @@ function touchSession(id) {
|
|
|
655
713
|
id
|
|
656
714
|
);
|
|
657
715
|
}
|
|
716
|
+
function deleteSession(id) {
|
|
717
|
+
const db2 = getDb();
|
|
718
|
+
db2.prepare(`DELETE FROM messages WHERE session_id = ?`).run(id);
|
|
719
|
+
db2.prepare(`DELETE FROM sessions WHERE id = ?`).run(id);
|
|
720
|
+
}
|
|
721
|
+
function deleteAllSessions() {
|
|
722
|
+
const db2 = getDb();
|
|
723
|
+
db2.prepare(`DELETE FROM messages`).run();
|
|
724
|
+
db2.prepare(`DELETE FROM sessions`).run();
|
|
725
|
+
}
|
|
658
726
|
|
|
659
727
|
// src/db/messages.ts
|
|
660
728
|
function insertMessage(params) {
|
|
@@ -684,44 +752,6 @@ function deleteMessagesFromRound(sessionId, fromRound) {
|
|
|
684
752
|
}
|
|
685
753
|
|
|
686
754
|
// src/prompts.ts
|
|
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.`;
|
|
694
|
-
}
|
|
695
|
-
function discussionPrompt(agent, conversationHistory, pair) {
|
|
696
|
-
return `${collaborationPrompt(agent, pair)}
|
|
697
|
-
|
|
698
|
-
Here is the conversation so far:
|
|
699
|
-
|
|
700
|
-
${conversationHistory}
|
|
701
|
-
|
|
702
|
-
Now provide your response for this discussion round. Build on what was said, highlight agreements, and address any disagreements constructively. Be concise.`;
|
|
703
|
-
}
|
|
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.
|
|
707
|
-
|
|
708
|
-
Your goal is to reach consensus through constructive discussion. In each round:
|
|
709
|
-
- Address specific points of agreement and disagreement
|
|
710
|
-
- Refine your position based on valid arguments from ${other}
|
|
711
|
-
- Be concise \u2014 don't repeat points already established
|
|
712
|
-
|
|
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.`;
|
|
714
|
-
}
|
|
715
|
-
function debateRoundPrompt(agent, conversationHistory, pair) {
|
|
716
|
-
const other = otherAgent(agent, pair);
|
|
717
|
-
return `${debatePrompt(agent, pair)}
|
|
718
|
-
|
|
719
|
-
Here is the conversation so far:
|
|
720
|
-
|
|
721
|
-
${conversationHistory}
|
|
722
|
-
|
|
723
|
-
Respond to the latest round. If you agree with ${other}'s position on all key points, end with [CONSENSUS]. Otherwise, continue the discussion.`;
|
|
724
|
-
}
|
|
725
755
|
var CONSENSUS_MARKER = "[CONSENSUS]";
|
|
726
756
|
function formatConversationHistory(messages) {
|
|
727
757
|
return messages.map((m) => {
|
|
@@ -738,6 +768,264 @@ function formatConversationHistory(messages) {
|
|
|
738
768
|
return `[${label}]: ${m.content}`;
|
|
739
769
|
}).join("\n\n");
|
|
740
770
|
}
|
|
771
|
+
function basePrompt() {
|
|
772
|
+
return `You are one of two expert coding agents in a structured technical discussion.
|
|
773
|
+
You will independently analyze the problem, then engage in focused rounds of
|
|
774
|
+
critique and refinement with your peer.
|
|
775
|
+
|
|
776
|
+
Ground rules:
|
|
777
|
+
- You are evaluated on the ACCURACY and QUALITY of your final position, not
|
|
778
|
+
on agreement with your peer.
|
|
779
|
+
- When you change your position, you MUST name the specific argument that
|
|
780
|
+
changed your mind and explain why your previous reasoning was flawed.
|
|
781
|
+
Changing position without this justification is not acceptable.
|
|
782
|
+
- Each response must either: (a) introduce new evidence or a new argument,
|
|
783
|
+
(b) identify a specific logical flaw or unsupported claim in your peer's
|
|
784
|
+
reasoning, or (c) concede a point with explicit justification. Restating
|
|
785
|
+
or paraphrasing existing points is not acceptable.
|
|
786
|
+
- Your peer is a different AI model with different training. Their perspective
|
|
787
|
+
may reveal genuine blind spots in yours \u2014 and vice versa.`;
|
|
788
|
+
}
|
|
789
|
+
var ROLE_TEMPLATES = {
|
|
790
|
+
claude: `Your role: Architecture & Implementation Reviewer.
|
|
791
|
+
|
|
792
|
+
Focus your analysis on:
|
|
793
|
+
- Code structure, design patterns, and maintainability
|
|
794
|
+
- Multi-file coherence \u2014 how changes ripple across the codebase
|
|
795
|
+
- Production readiness \u2014 error handling, logging, edge cases in real usage
|
|
796
|
+
- Proposing complete, working implementations (not just pseudocode)
|
|
797
|
+
|
|
798
|
+
When you propose a solution, provide the actual implementation. When you
|
|
799
|
+
critique, point to specific structural issues and show what the fix looks
|
|
800
|
+
like. Your peer's role is {peerRole} \u2014 they will stress-test your proposals
|
|
801
|
+
from a different angle.`,
|
|
802
|
+
codex: `Your role: Correctness & Standards Reviewer.
|
|
803
|
+
|
|
804
|
+
Focus your analysis on:
|
|
805
|
+
- Algorithmic correctness \u2014 does the logic actually work for all inputs?
|
|
806
|
+
- Edge cases and failure modes \u2014 what breaks, what's untested?
|
|
807
|
+
- Standards compliance \u2014 does this follow language/framework conventions?
|
|
808
|
+
- Performance characteristics \u2014 time/space complexity, benchmarks
|
|
809
|
+
|
|
810
|
+
When you critique, provide specific test cases or inputs that demonstrate
|
|
811
|
+
the issue. When you propose alternatives, explain the correctness guarantees.
|
|
812
|
+
Your peer's role is {peerRole} \u2014 they will focus on different aspects of the
|
|
813
|
+
same problem.`,
|
|
814
|
+
gemini: `Your role: Strategic Context Analyst.
|
|
815
|
+
|
|
816
|
+
Focus your analysis on:
|
|
817
|
+
- Broad codebase context \u2014 how does this change fit the larger system?
|
|
818
|
+
- Current ecosystem conventions \u2014 what do the docs, community, and recent
|
|
819
|
+
releases recommend?
|
|
820
|
+
- Upstream and downstream effects \u2014 what will this break or enable elsewhere?
|
|
821
|
+
- Scope and planning \u2014 is this the right approach at the right level of
|
|
822
|
+
abstraction?
|
|
823
|
+
|
|
824
|
+
When you critique, ground your position in the broader context your peer may
|
|
825
|
+
be missing. When you propose alternatives, explain the architectural tradeoffs.
|
|
826
|
+
Your peer's role is {peerRole} \u2014 they will focus on different aspects of the
|
|
827
|
+
same problem.`
|
|
828
|
+
};
|
|
829
|
+
function rolePrompt(agent, pair) {
|
|
830
|
+
const template = ROLE_TEMPLATES[agent];
|
|
831
|
+
const peerRole = getPeerRoleDescription(agent, pair);
|
|
832
|
+
return template.replace("{peerRole}", peerRole);
|
|
833
|
+
}
|
|
834
|
+
function collaborationSystemPrompt(agent, pair) {
|
|
835
|
+
return `${basePrompt()}
|
|
836
|
+
|
|
837
|
+
${rolePrompt(agent, pair)}`;
|
|
838
|
+
}
|
|
839
|
+
function discussionRoundPrompt(agent, conversationContext, pair) {
|
|
840
|
+
return `${basePrompt()}
|
|
841
|
+
|
|
842
|
+
${rolePrompt(agent, pair)}
|
|
843
|
+
|
|
844
|
+
Here is the discussion so far:
|
|
845
|
+
|
|
846
|
+
${conversationContext}
|
|
847
|
+
|
|
848
|
+
For this round:
|
|
849
|
+
1. What is the strongest point in your peer's response?
|
|
850
|
+
2. What is the weakest point, or what claim lacks supporting evidence?
|
|
851
|
+
3. Has your position changed? State one of: HELD / PARTIALLY_CHANGED / CHANGED
|
|
852
|
+
\u2014 with explicit reasoning for why.
|
|
853
|
+
4. If proposing code, show the specific implementation and explain tradeoffs
|
|
854
|
+
versus your peer's approach.
|
|
855
|
+
5. Confidence in your current position: LOW | MEDIUM | HIGH
|
|
856
|
+
|
|
857
|
+
CONFIDENCE: HIGH | MEDIUM | LOW
|
|
858
|
+
|
|
859
|
+
Keep it concise. Do not restate points already established.`;
|
|
860
|
+
}
|
|
861
|
+
function debateSystemPrompt(agent, pair) {
|
|
862
|
+
return `${basePrompt()}
|
|
863
|
+
|
|
864
|
+
${rolePrompt(agent, pair)}
|
|
865
|
+
|
|
866
|
+
This is a structured discussion aimed at reaching a well-reasoned position
|
|
867
|
+
through genuine deliberation.
|
|
868
|
+
|
|
869
|
+
Additional rules for discussion mode:
|
|
870
|
+
- Structure your arguments: STATE your claim, provide EVIDENCE (code examples,
|
|
871
|
+
documentation, benchmarks), explain your REASONING connecting evidence to
|
|
872
|
+
claim, and note CAVEATS (when your claim doesn't hold).
|
|
873
|
+
- Express confidence: end your response with CONFIDENCE: HIGH | MEDIUM | LOW
|
|
874
|
+
and a one-line explanation of what would change your mind.
|
|
875
|
+
- Consensus signaling: when you believe you and your peer agree on all key
|
|
876
|
+
points AND your confidence is HIGH, end your response with ${CONSENSUS_MARKER} on
|
|
877
|
+
its own line. Only signal consensus when:
|
|
878
|
+
(a) You can state the shared position in one sentence
|
|
879
|
+
(b) You have HIGH confidence
|
|
880
|
+
(c) You are not just deferring \u2014 you genuinely agree with the reasoning`;
|
|
881
|
+
}
|
|
882
|
+
function debateRoundPrompt(agent, conversationContext, pair) {
|
|
883
|
+
return `${debateSystemPrompt(agent, pair)}
|
|
884
|
+
|
|
885
|
+
${conversationContext}
|
|
886
|
+
|
|
887
|
+
For this round:
|
|
888
|
+
1. Address your peer's strongest argument directly \u2014 do you accept it? Why or
|
|
889
|
+
why not?
|
|
890
|
+
2. If your peer identified a flaw in your reasoning, acknowledge it explicitly
|
|
891
|
+
or defend with new evidence.
|
|
892
|
+
3. State your current position with EVIDENCE and REASONING.
|
|
893
|
+
4. CONFIDENCE: HIGH | MEDIUM | LOW \u2014 what specific evidence would change
|
|
894
|
+
your remaining position?
|
|
895
|
+
5. If consensus: state the shared position in one sentence, then ${CONSENSUS_MARKER}.
|
|
896
|
+
|
|
897
|
+
POSITION: HELD | PARTIALLY_CHANGED | CHANGED`;
|
|
898
|
+
}
|
|
899
|
+
function steelmanPrompt() {
|
|
900
|
+
return `You and your peer appear to largely agree after Round 1. Before confirming
|
|
901
|
+
consensus, steelman the opposing view:
|
|
902
|
+
|
|
903
|
+
- What is the strongest argument AGAINST your shared position?
|
|
904
|
+
- What context or edge case might make a different approach better?
|
|
905
|
+
- Is there a tradeoff you're both overlooking?
|
|
906
|
+
|
|
907
|
+
If after considering the counterarguments you still hold your position, explain
|
|
908
|
+
why the counterarguments don't apply here. Then proceed with your normal round
|
|
909
|
+
response.
|
|
910
|
+
|
|
911
|
+
`;
|
|
912
|
+
}
|
|
913
|
+
function directPrompt(agent, conversationHistory) {
|
|
914
|
+
const profile = getAgentProfile(agent);
|
|
915
|
+
const focusAreas = profile.focus.map((f) => `- ${f}`).join("\n");
|
|
916
|
+
return `You are being addressed directly in a multi-agent session. The user wants YOUR
|
|
917
|
+
specific perspective.
|
|
918
|
+
|
|
919
|
+
Here is the conversation so far:
|
|
920
|
+
${conversationHistory}
|
|
921
|
+
|
|
922
|
+
Respond to the user's latest message. Focus on your area of expertise:
|
|
923
|
+
${focusAreas}
|
|
924
|
+
|
|
925
|
+
Be concise and direct.`;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// src/discussion.ts
|
|
929
|
+
var CONFIDENCE_RE = /CONFIDENCE:\s*(HIGH|MEDIUM|LOW)/i;
|
|
930
|
+
var POSITION_RE = /POSITION:\s*(HELD|PARTIALLY_CHANGED|CHANGED)/i;
|
|
931
|
+
var CONSENSUS_RE = /\[CONSENSUS\]/;
|
|
932
|
+
function parseRoundAnalysis(agent, responseText) {
|
|
933
|
+
const confidenceMatch = responseText.match(CONFIDENCE_RE);
|
|
934
|
+
const positionMatch = responseText.match(POSITION_RE);
|
|
935
|
+
const signaledConsensus = CONSENSUS_RE.test(responseText);
|
|
936
|
+
const stripped = responseText.replace(CONFIDENCE_RE, "").replace(POSITION_RE, "").replace(CONSENSUS_RE, "").trim();
|
|
937
|
+
const hasNovelContent = stripped.length > 100;
|
|
938
|
+
return {
|
|
939
|
+
agent,
|
|
940
|
+
confidence: confidenceMatch?.[1]?.toUpperCase() ?? "MEDIUM",
|
|
941
|
+
positionChange: positionMatch?.[1]?.toUpperCase() ?? "HELD",
|
|
942
|
+
signaledConsensus,
|
|
943
|
+
hasNovelContent
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
function checkTermination(state, maxRounds) {
|
|
947
|
+
const { round, analyses } = state;
|
|
948
|
+
if (analyses.length > 0) {
|
|
949
|
+
const latest = analyses[analyses.length - 1];
|
|
950
|
+
if (latest && latest.length >= 2) {
|
|
951
|
+
const allConsensus = latest.every((a) => a.signaledConsensus);
|
|
952
|
+
const allHigh = latest.every((a) => a.confidence === "HIGH");
|
|
953
|
+
if (allConsensus && allHigh) {
|
|
954
|
+
return { terminated: true, reason: "mutual-consensus" };
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (analyses.length >= 2) {
|
|
959
|
+
const prev = analyses[analyses.length - 2];
|
|
960
|
+
const curr = analyses[analyses.length - 1];
|
|
961
|
+
if (prev && curr && prev.length >= 2 && curr.length >= 2) {
|
|
962
|
+
const prevStale = prev.every((a) => a.positionChange === "HELD" && !a.hasNovelContent);
|
|
963
|
+
const currStale = curr.every((a) => a.positionChange === "HELD" && !a.hasNovelContent);
|
|
964
|
+
if (prevStale && currStale) {
|
|
965
|
+
return { terminated: true, reason: "stale-no-progress" };
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (analyses.length >= 2) {
|
|
970
|
+
const prev = analyses[analyses.length - 2];
|
|
971
|
+
const curr = analyses[analyses.length - 1];
|
|
972
|
+
if (prev && curr && prev.length >= 2 && curr.length >= 2) {
|
|
973
|
+
const prevSwap = prev.every((a) => a.positionChange === "CHANGED");
|
|
974
|
+
const currSwap = curr.every((a) => a.positionChange === "CHANGED");
|
|
975
|
+
if (prevSwap && currSwap) {
|
|
976
|
+
return { terminated: true, reason: "cyclic-swap" };
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (round >= maxRounds) {
|
|
981
|
+
return { terminated: true, reason: "max-rounds" };
|
|
982
|
+
}
|
|
983
|
+
return { terminated: false };
|
|
984
|
+
}
|
|
985
|
+
function terminationMessage(reason) {
|
|
986
|
+
switch (reason) {
|
|
987
|
+
case "mutual-consensus":
|
|
988
|
+
return "Consensus reached.";
|
|
989
|
+
case "stale-no-progress":
|
|
990
|
+
return "Discussion stalled \u2014 no new arguments. Showing final positions.";
|
|
991
|
+
case "cyclic-swap":
|
|
992
|
+
return "Agents are trading positions. Showing both perspectives.";
|
|
993
|
+
case "max-rounds":
|
|
994
|
+
return "Maximum rounds reached. Showing final positions.";
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function shouldInjectSteelman(state) {
|
|
998
|
+
return state.round === 1 && state.analyses.length === 1;
|
|
999
|
+
}
|
|
1000
|
+
function buildConversationContext(allMessages, analyses, currentRound, _pair) {
|
|
1001
|
+
if (currentRound <= 2) {
|
|
1002
|
+
return formatConversationHistory(allMessages);
|
|
1003
|
+
}
|
|
1004
|
+
const summaryParts = [];
|
|
1005
|
+
for (let i = 0; i < analyses.length - 1; i++) {
|
|
1006
|
+
const roundAnalyses = analyses[i];
|
|
1007
|
+
if (!roundAnalyses) continue;
|
|
1008
|
+
const roundSummary = roundAnalyses.map((a) => {
|
|
1009
|
+
return `${a.agent}: confidence=${a.confidence}, position=${a.positionChange}${a.signaledConsensus ? ", signaled consensus" : ""}`;
|
|
1010
|
+
}).join("; ");
|
|
1011
|
+
summaryParts.push(`Round ${i + 1}: ${roundSummary}`);
|
|
1012
|
+
}
|
|
1013
|
+
const latestMessages = allMessages.slice(-3);
|
|
1014
|
+
const summary = summaryParts.length > 0 ? `Previous rounds summary:
|
|
1015
|
+
${summaryParts.join("\n")}
|
|
1016
|
+
|
|
1017
|
+
Latest exchange:
|
|
1018
|
+
${formatConversationHistory(latestMessages)}` : formatConversationHistory(allMessages);
|
|
1019
|
+
return summary;
|
|
1020
|
+
}
|
|
1021
|
+
function analysisToMetadata(analysis) {
|
|
1022
|
+
return {
|
|
1023
|
+
confidence: analysis.confidence,
|
|
1024
|
+
positionChange: analysis.positionChange,
|
|
1025
|
+
signaledConsensus: analysis.signaledConsensus,
|
|
1026
|
+
hasNovelContent: analysis.hasNovelContent
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
741
1029
|
|
|
742
1030
|
// src/config-editor.tsx
|
|
743
1031
|
import { useState } from "react";
|
|
@@ -887,12 +1175,35 @@ function startConfigEditor() {
|
|
|
887
1175
|
// src/ui.tsx
|
|
888
1176
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
889
1177
|
marked.use(markedTerminal());
|
|
890
|
-
|
|
1178
|
+
var PAIR_SEPARATORS = /^(\w+)\s*(?:and|&|\/|,)\s*(\w+)[\s,]\s*/i;
|
|
1179
|
+
function tryParsePair(text) {
|
|
1180
|
+
const match = text.toLowerCase().match(PAIR_SEPARATORS);
|
|
1181
|
+
if (!match) return null;
|
|
1182
|
+
const [fullMatch, first, second] = match;
|
|
1183
|
+
if (isValidAgentName(first) && isValidAgentName(second) && first !== second) {
|
|
1184
|
+
return { pair: [first, second], rest: text.slice(fullMatch.length).trim() };
|
|
1185
|
+
}
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
function parseInput(input) {
|
|
891
1189
|
const lower = input.toLowerCase();
|
|
892
1190
|
if (lower.startsWith("discuss ")) {
|
|
893
|
-
|
|
1191
|
+
const rest = input.slice(8).trim();
|
|
1192
|
+
const adHoc2 = tryParsePair(rest);
|
|
1193
|
+
if (adHoc2) {
|
|
1194
|
+
return { target: adHoc2.pair, prompt: adHoc2.rest, discuss: true };
|
|
1195
|
+
}
|
|
1196
|
+
return { target: "both", prompt: rest, discuss: true };
|
|
894
1197
|
}
|
|
895
|
-
|
|
1198
|
+
const adHoc = tryParsePair(input);
|
|
1199
|
+
if (adHoc) {
|
|
1200
|
+
const adHocLower = adHoc.rest.toLowerCase();
|
|
1201
|
+
if (adHocLower.startsWith("discuss ")) {
|
|
1202
|
+
return { target: adHoc.pair, prompt: adHoc.rest.slice(8).trim(), discuss: true };
|
|
1203
|
+
}
|
|
1204
|
+
return { target: adHoc.pair, prompt: adHoc.rest, discuss: false };
|
|
1205
|
+
}
|
|
1206
|
+
for (const agent of getAllAgentNames()) {
|
|
896
1207
|
if (lower.startsWith(`${agent} `) || lower.startsWith(`${agent}, `)) {
|
|
897
1208
|
return { target: agent, prompt: input.slice(input.indexOf(" ") + 1).trim(), discuss: false };
|
|
898
1209
|
}
|
|
@@ -900,10 +1211,10 @@ function parseInput(input, pair) {
|
|
|
900
1211
|
return { target: "both", prompt: input, discuss: false };
|
|
901
1212
|
}
|
|
902
1213
|
function RenderedMarkdown({ text }) {
|
|
903
|
-
const rendered = marked.parse(text).trimEnd();
|
|
1214
|
+
const rendered = useMemo(() => marked.parse(text).trimEnd(), [text]);
|
|
904
1215
|
return /* @__PURE__ */ jsx2(Text2, { children: rendered });
|
|
905
1216
|
}
|
|
906
|
-
|
|
1217
|
+
var AgentResponseBlock = React2.memo(function AgentResponseBlock2({
|
|
907
1218
|
agent,
|
|
908
1219
|
content,
|
|
909
1220
|
error
|
|
@@ -925,7 +1236,7 @@ function AgentResponseBlock({
|
|
|
925
1236
|
] }),
|
|
926
1237
|
/* @__PURE__ */ jsx2(Box2, { marginLeft: 1, children: /* @__PURE__ */ jsx2(RenderedMarkdown, { text: content }) })
|
|
927
1238
|
] });
|
|
928
|
-
}
|
|
1239
|
+
});
|
|
929
1240
|
function Header({ sessionId }) {
|
|
930
1241
|
return /* @__PURE__ */ jsxs2(Box2, { marginBottom: 1, children: [
|
|
931
1242
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2500\u2500 " }),
|
|
@@ -935,7 +1246,24 @@ function Header({ sessionId }) {
|
|
|
935
1246
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " " + "\u2500".repeat(35) })
|
|
936
1247
|
] });
|
|
937
1248
|
}
|
|
938
|
-
function
|
|
1249
|
+
function QuickHelp() {
|
|
1250
|
+
const col = 38;
|
|
1251
|
+
const examples = [
|
|
1252
|
+
["ask anything", "sends to both agents"],
|
|
1253
|
+
["gemini explain this", "sends to one agent"],
|
|
1254
|
+
["discuss best approach", "multi-round debate"],
|
|
1255
|
+
["gemini and codex discuss review app", "pick agents + debate"]
|
|
1256
|
+
];
|
|
1257
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
1258
|
+
examples.map(([cmd, desc]) => /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
1259
|
+
cmd.padEnd(col),
|
|
1260
|
+
"\u2192 ",
|
|
1261
|
+
desc
|
|
1262
|
+
] }, cmd)),
|
|
1263
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "/help for more" })
|
|
1264
|
+
] });
|
|
1265
|
+
}
|
|
1266
|
+
var UserMessage = React2.memo(function UserMessage2({ content }) {
|
|
939
1267
|
return /* @__PURE__ */ jsxs2(Box2, { marginLeft: 1, marginBottom: 1, children: [
|
|
940
1268
|
/* @__PURE__ */ jsxs2(Text2, { bold: true, color: "white", children: [
|
|
941
1269
|
"You:",
|
|
@@ -943,7 +1271,7 @@ function UserMessage({ content }) {
|
|
|
943
1271
|
] }),
|
|
944
1272
|
/* @__PURE__ */ jsx2(Text2, { children: content })
|
|
945
1273
|
] });
|
|
946
|
-
}
|
|
1274
|
+
});
|
|
947
1275
|
function ThinkingIndicator({ agent }) {
|
|
948
1276
|
const descriptor = getAgent(agent);
|
|
949
1277
|
return /* @__PURE__ */ jsxs2(Box2, { marginLeft: 1, children: [
|
|
@@ -1020,11 +1348,14 @@ function App({
|
|
|
1020
1348
|
}
|
|
1021
1349
|
return 0;
|
|
1022
1350
|
});
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1351
|
+
const [committedCount, setCommittedCount] = useState2(showTranscript2?.length ?? 0);
|
|
1352
|
+
const sessionCreatedRef = useRef(!!existingSessionId);
|
|
1353
|
+
const ensureSession = (id) => {
|
|
1354
|
+
if (!sessionCreatedRef.current) {
|
|
1355
|
+
createSession(id, process.cwd());
|
|
1356
|
+
sessionCreatedRef.current = true;
|
|
1026
1357
|
}
|
|
1027
|
-
}
|
|
1358
|
+
};
|
|
1028
1359
|
useEffect(() => {
|
|
1029
1360
|
if (initialPrompt && state === "running") {
|
|
1030
1361
|
if (initialDiscuss) {
|
|
@@ -1048,7 +1379,9 @@ function App({
|
|
|
1048
1379
|
if (runningRoundRef.current !== null) {
|
|
1049
1380
|
deleteMessagesFromRound(sessionId, runningRoundRef.current);
|
|
1050
1381
|
const fromRound = runningRoundRef.current;
|
|
1051
|
-
|
|
1382
|
+
const remaining = messages.filter((m) => m.round < fromRound);
|
|
1383
|
+
setMessages(remaining);
|
|
1384
|
+
setCommittedCount(remaining.length);
|
|
1052
1385
|
runningRoundRef.current = null;
|
|
1053
1386
|
}
|
|
1054
1387
|
setThinkingAgents([]);
|
|
@@ -1057,8 +1390,8 @@ function App({
|
|
|
1057
1390
|
setState("input");
|
|
1058
1391
|
}
|
|
1059
1392
|
});
|
|
1060
|
-
const runAgents = async (currentMessages, round, target, promptOverride, isDebate = false) => {
|
|
1061
|
-
const activeAgents = target === "both" ? [...pair] : [target];
|
|
1393
|
+
const runAgents = async (currentMessages, round, target, promptOverride, isDebate = false, systemPromptPerAgent) => {
|
|
1394
|
+
const activeAgents = Array.isArray(target) ? target : target === "both" ? [...pair] : [target];
|
|
1062
1395
|
setThinkingAgents(activeAgents);
|
|
1063
1396
|
const history = formatConversationHistory(
|
|
1064
1397
|
currentMessages.map((m) => ({
|
|
@@ -1069,20 +1402,26 @@ function App({
|
|
|
1069
1402
|
);
|
|
1070
1403
|
const cwd = process.cwd();
|
|
1071
1404
|
const isFirstRound = round === 0 && !existingSessionId;
|
|
1405
|
+
const userPrompt = currentMessages[currentMessages.length - 1]?.content || "";
|
|
1406
|
+
const isSingleAgent = !Array.isArray(target) && target !== "both";
|
|
1407
|
+
const promptPair = Array.isArray(target) ? target : pair;
|
|
1072
1408
|
const agentPrompt = (_agent) => {
|
|
1073
1409
|
if (promptOverride) return promptOverride;
|
|
1074
|
-
if (
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1410
|
+
if (isSingleAgent) return userPrompt;
|
|
1411
|
+
if (isFirstRound) return userPrompt;
|
|
1077
1412
|
return "Provide your response for this round.";
|
|
1078
1413
|
};
|
|
1079
1414
|
const agentSystemPrompt = (agent) => {
|
|
1415
|
+
if (systemPromptPerAgent?.[agent]) return systemPromptPerAgent[agent];
|
|
1416
|
+
if (isSingleAgent) {
|
|
1417
|
+
return history ? directPrompt(agent, history) : void 0;
|
|
1418
|
+
}
|
|
1080
1419
|
if (isDebate) {
|
|
1081
|
-
if (isFirstRound) return
|
|
1082
|
-
return debateRoundPrompt(agent, history,
|
|
1420
|
+
if (isFirstRound) return debateSystemPrompt(agent, promptPair);
|
|
1421
|
+
return debateRoundPrompt(agent, history, promptPair);
|
|
1083
1422
|
}
|
|
1084
|
-
if (isFirstRound
|
|
1085
|
-
return
|
|
1423
|
+
if (isFirstRound) return collaborationSystemPrompt(agent, promptPair);
|
|
1424
|
+
return discussionRoundPrompt(agent, history, promptPair);
|
|
1086
1425
|
};
|
|
1087
1426
|
const ac = new AbortController();
|
|
1088
1427
|
abortRef.current = ac;
|
|
@@ -1116,22 +1455,26 @@ function App({
|
|
|
1116
1455
|
if (result.status === "fulfilled") {
|
|
1117
1456
|
const resp = result.value;
|
|
1118
1457
|
const msg = {
|
|
1458
|
+
id: `${round}-${agent}`,
|
|
1119
1459
|
role: agent,
|
|
1120
1460
|
content: resp.error || resp.text,
|
|
1121
1461
|
round,
|
|
1122
1462
|
error: !!resp.error
|
|
1123
1463
|
};
|
|
1124
1464
|
newMessages.push(msg);
|
|
1465
|
+
const metadata = isDebate && !resp.error ? analysisToMetadata(parseRoundAnalysis(agent, resp.text)) : void 0;
|
|
1125
1466
|
insertMessage({
|
|
1126
1467
|
sessionId,
|
|
1127
1468
|
role: agent,
|
|
1128
1469
|
content: resp.error || resp.text,
|
|
1129
1470
|
round,
|
|
1130
|
-
durationMs: resp.durationMs
|
|
1471
|
+
durationMs: resp.durationMs,
|
|
1472
|
+
metadata
|
|
1131
1473
|
});
|
|
1132
1474
|
} else {
|
|
1133
1475
|
const errorMsg = result.reason?.message || "Failed to run";
|
|
1134
1476
|
const msg = {
|
|
1477
|
+
id: `${round}-${agent}`,
|
|
1135
1478
|
role: agent,
|
|
1136
1479
|
content: errorMsg,
|
|
1137
1480
|
round,
|
|
@@ -1149,13 +1492,19 @@ function App({
|
|
|
1149
1492
|
return newMessages;
|
|
1150
1493
|
};
|
|
1151
1494
|
const runRound = async (rawInput) => {
|
|
1152
|
-
const { target, prompt, discuss } = parseInput(rawInput
|
|
1495
|
+
const { target, prompt, discuss } = parseInput(rawInput);
|
|
1153
1496
|
if (discuss) {
|
|
1154
|
-
return runDiscussion(prompt);
|
|
1497
|
+
return runDiscussion(prompt, Array.isArray(target) ? target : void 0);
|
|
1155
1498
|
}
|
|
1156
1499
|
const currentRound = roundNum;
|
|
1157
1500
|
runningRoundRef.current = currentRound;
|
|
1501
|
+
ensureSession(sessionId);
|
|
1502
|
+
if (currentRound === 0) {
|
|
1503
|
+
const title = prompt.length > 60 ? prompt.slice(0, 57) + "..." : prompt;
|
|
1504
|
+
updateSessionTitle(sessionId, title);
|
|
1505
|
+
}
|
|
1158
1506
|
const userMsg = {
|
|
1507
|
+
id: `${currentRound}-user`,
|
|
1159
1508
|
role: "user",
|
|
1160
1509
|
content: rawInput,
|
|
1161
1510
|
round: currentRound
|
|
@@ -1171,20 +1520,25 @@ function App({
|
|
|
1171
1520
|
const newMessages = await runAgents(allMessages, currentRound, target);
|
|
1172
1521
|
if (newMessages.length === 0) return;
|
|
1173
1522
|
setMessages((prev) => [...prev, ...newMessages]);
|
|
1523
|
+
setCommittedCount((prev) => prev + 1 + newMessages.length);
|
|
1174
1524
|
setRoundNum(currentRound + 1);
|
|
1175
1525
|
runningRoundRef.current = null;
|
|
1176
|
-
if (currentRound === 0 && !existingSessionId) {
|
|
1177
|
-
const title = prompt.length > 60 ? prompt.slice(0, 57) + "..." : prompt;
|
|
1178
|
-
updateSessionTitle(sessionId, title);
|
|
1179
|
-
}
|
|
1180
1526
|
touchSession(sessionId);
|
|
1181
1527
|
setState("input");
|
|
1182
1528
|
};
|
|
1183
|
-
const runDiscussion = async (prompt) => {
|
|
1529
|
+
const runDiscussion = async (prompt, adHocPair) => {
|
|
1530
|
+
const discussionTarget = adHocPair ?? "both";
|
|
1531
|
+
const activePair = adHocPair ?? pair;
|
|
1184
1532
|
setConsensusReached(false);
|
|
1185
1533
|
let currentRound = roundNum;
|
|
1186
1534
|
runningRoundRef.current = currentRound;
|
|
1535
|
+
ensureSession(sessionId);
|
|
1536
|
+
if (currentRound === 0) {
|
|
1537
|
+
const title = prompt.length > 60 ? prompt.slice(0, 57) + "..." : prompt;
|
|
1538
|
+
updateSessionTitle(sessionId, title);
|
|
1539
|
+
}
|
|
1187
1540
|
const userMsg = {
|
|
1541
|
+
id: `${currentRound}-user`,
|
|
1188
1542
|
role: "user",
|
|
1189
1543
|
content: `discuss ${prompt}`,
|
|
1190
1544
|
round: currentRound
|
|
@@ -1197,29 +1551,63 @@ function App({
|
|
|
1197
1551
|
round: currentRound
|
|
1198
1552
|
});
|
|
1199
1553
|
let allMessages = [...messages, userMsg];
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1554
|
+
const discState = {
|
|
1555
|
+
round: 0,
|
|
1556
|
+
analyses: [],
|
|
1557
|
+
terminated: false
|
|
1558
|
+
};
|
|
1204
1559
|
for (let disc = 1; disc <= config.discussion.max_rounds; disc++) {
|
|
1205
1560
|
setDiscussionRound(disc);
|
|
1561
|
+
discState.round = disc;
|
|
1562
|
+
let perAgentPrompts;
|
|
1563
|
+
if (disc >= 2) {
|
|
1564
|
+
const context = buildConversationContext(
|
|
1565
|
+
allMessages.map((m) => ({
|
|
1566
|
+
role: m.role,
|
|
1567
|
+
agent: isValidAgentName(m.role) ? m.role : void 0,
|
|
1568
|
+
content: m.content
|
|
1569
|
+
})),
|
|
1570
|
+
discState.analyses,
|
|
1571
|
+
disc,
|
|
1572
|
+
activePair
|
|
1573
|
+
);
|
|
1574
|
+
perAgentPrompts = {};
|
|
1575
|
+
for (const agent of activePair) {
|
|
1576
|
+
let prompt_text = "";
|
|
1577
|
+
if (shouldInjectSteelman(discState)) {
|
|
1578
|
+
prompt_text += steelmanPrompt();
|
|
1579
|
+
}
|
|
1580
|
+
prompt_text += debateRoundPrompt(agent, context, activePair);
|
|
1581
|
+
perAgentPrompts[agent] = prompt_text;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1206
1584
|
const newMessages = await runAgents(
|
|
1207
1585
|
allMessages,
|
|
1208
1586
|
currentRound,
|
|
1209
|
-
|
|
1587
|
+
discussionTarget,
|
|
1210
1588
|
disc === 1 ? prompt : "Provide your response for this round.",
|
|
1211
|
-
true
|
|
1589
|
+
true,
|
|
1590
|
+
perAgentPrompts
|
|
1212
1591
|
);
|
|
1213
1592
|
if (newMessages.length === 0) break;
|
|
1214
1593
|
setMessages((prev) => [...prev, ...newMessages]);
|
|
1594
|
+
setCommittedCount((prev) => prev + (disc === 1 ? 1 : 0) + newMessages.length);
|
|
1215
1595
|
allMessages = [...allMessages, ...newMessages];
|
|
1216
1596
|
currentRound++;
|
|
1217
|
-
const
|
|
1597
|
+
const roundAnalyses = activePair.map((agent) => {
|
|
1218
1598
|
const msg = newMessages.find((m) => m.role === agent && !m.error);
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1599
|
+
if (!msg) return null;
|
|
1600
|
+
return parseRoundAnalysis(agent, msg.content);
|
|
1601
|
+
}).filter((a) => a !== null);
|
|
1602
|
+
discState.analyses.push(roundAnalyses);
|
|
1603
|
+
const termResult = checkTermination(discState, config.discussion.max_rounds);
|
|
1604
|
+
if (termResult.terminated && termResult.reason) {
|
|
1605
|
+
discState.terminated = true;
|
|
1606
|
+
discState.terminationReason = termResult.reason;
|
|
1607
|
+
setStatusMessage(terminationMessage(termResult.reason));
|
|
1608
|
+
if (termResult.reason === "mutual-consensus") {
|
|
1609
|
+
setConsensusReached(true);
|
|
1610
|
+
}
|
|
1223
1611
|
break;
|
|
1224
1612
|
}
|
|
1225
1613
|
}
|
|
@@ -1256,9 +1644,10 @@ function App({
|
|
|
1256
1644
|
}
|
|
1257
1645
|
if (value === "/new") {
|
|
1258
1646
|
const newId = nanoid(12);
|
|
1259
|
-
|
|
1647
|
+
sessionCreatedRef.current = false;
|
|
1260
1648
|
setSessionId(newId);
|
|
1261
1649
|
setMessages([]);
|
|
1650
|
+
setCommittedCount(0);
|
|
1262
1651
|
setRoundNum(0);
|
|
1263
1652
|
setConsensusReached(false);
|
|
1264
1653
|
setDiscussionRound(0);
|
|
@@ -1279,23 +1668,21 @@ function App({
|
|
|
1279
1668
|
setState("running");
|
|
1280
1669
|
runRound(value);
|
|
1281
1670
|
};
|
|
1671
|
+
const staticItems = useMemo(() => [
|
|
1672
|
+
{ id: `header-${sessionId}`, role: "__header", content: "", round: -1 },
|
|
1673
|
+
...messages.slice(0, committedCount)
|
|
1674
|
+
], [sessionId, messages, committedCount]);
|
|
1282
1675
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1283
|
-
/* @__PURE__ */ jsx2(
|
|
1284
|
-
|
|
1285
|
-
if (
|
|
1286
|
-
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
content: msg.content,
|
|
1294
|
-
error: msg.error
|
|
1295
|
-
},
|
|
1296
|
-
i
|
|
1297
|
-
);
|
|
1298
|
-
}
|
|
1676
|
+
/* @__PURE__ */ jsx2(Static, { items: staticItems, children: (item) => {
|
|
1677
|
+
if (item.role === "__header") return /* @__PURE__ */ jsx2(Header, { sessionId }, item.id);
|
|
1678
|
+
if (item.role === "user") return /* @__PURE__ */ jsx2(UserMessage, { content: item.content }, item.id);
|
|
1679
|
+
if (isValidAgentName(item.role)) return /* @__PURE__ */ jsx2(AgentResponseBlock, { agent: item.role, content: item.content, error: item.error }, item.id);
|
|
1680
|
+
return /* @__PURE__ */ jsx2(Box2, {}, item.id);
|
|
1681
|
+
} }),
|
|
1682
|
+
messages.length === 0 && state === "input" && /* @__PURE__ */ jsx2(QuickHelp, {}),
|
|
1683
|
+
messages.slice(committedCount).map((msg) => {
|
|
1684
|
+
if (msg.role === "user") return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content }, msg.id);
|
|
1685
|
+
if (isValidAgentName(msg.role)) return /* @__PURE__ */ jsx2(AgentResponseBlock, { agent: msg.role, content: msg.content, error: msg.error }, msg.id);
|
|
1299
1686
|
return null;
|
|
1300
1687
|
}),
|
|
1301
1688
|
discussionRound > 0 && thinkingAgents.length > 0 && /* @__PURE__ */ jsx2(DiscussionStatus, { round: discussionRound, maxRounds: config.discussion.max_rounds }),
|
|
@@ -1341,6 +1728,7 @@ function showTranscriptMarkdown(sessionId) {
|
|
|
1341
1728
|
function showTranscript(sessionId) {
|
|
1342
1729
|
const dbMessages = getMessages(sessionId);
|
|
1343
1730
|
const messages = dbMessages.map((m) => ({
|
|
1731
|
+
id: `${m.round}-${m.role}`,
|
|
1344
1732
|
role: m.role,
|
|
1345
1733
|
content: m.content,
|
|
1346
1734
|
round: m.round
|
|
@@ -1348,12 +1736,12 @@ function showTranscript(sessionId) {
|
|
|
1348
1736
|
const { unmount } = render2(
|
|
1349
1737
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1350
1738
|
/* @__PURE__ */ jsx2(Header, { sessionId }),
|
|
1351
|
-
messages.map((msg
|
|
1739
|
+
messages.map((msg) => {
|
|
1352
1740
|
if (msg.role === "user") {
|
|
1353
|
-
return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content },
|
|
1741
|
+
return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content }, msg.id);
|
|
1354
1742
|
}
|
|
1355
1743
|
if (isValidAgentName(msg.role)) {
|
|
1356
|
-
return /* @__PURE__ */ jsx2(AgentResponseBlock, { agent: msg.role, content: msg.content },
|
|
1744
|
+
return /* @__PURE__ */ jsx2(AgentResponseBlock, { agent: msg.role, content: msg.content }, msg.id);
|
|
1357
1745
|
}
|
|
1358
1746
|
return null;
|
|
1359
1747
|
}),
|
|
@@ -1424,7 +1812,7 @@ function resolveAgentModels(pair, opts, config) {
|
|
|
1424
1812
|
};
|
|
1425
1813
|
}
|
|
1426
1814
|
var program = new Command();
|
|
1427
|
-
program.name("tagteam").description("Tag Team - Orchestrate AI agents collaboratively").version(
|
|
1815
|
+
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) => {
|
|
1428
1816
|
const config = loadConfig();
|
|
1429
1817
|
const pair = resolveAgentPair(opts, config);
|
|
1430
1818
|
preflight(pair);
|
|
@@ -1464,6 +1852,7 @@ program.command("continue").description("Resume the most recent session").action
|
|
|
1464
1852
|
}
|
|
1465
1853
|
const dbMessages = getMessages(session.id);
|
|
1466
1854
|
const transcript = dbMessages.map((m) => ({
|
|
1855
|
+
id: `${m.round}-${m.role}`,
|
|
1467
1856
|
role: m.role,
|
|
1468
1857
|
content: m.content,
|
|
1469
1858
|
round: m.round
|
|
@@ -1514,6 +1903,7 @@ program.command("resume [id]").description("Resume a session by ID, or pick inte
|
|
|
1514
1903
|
const session2 = sessions[num - 1];
|
|
1515
1904
|
const dbMessages2 = getMessages(session2.id);
|
|
1516
1905
|
const transcript2 = dbMessages2.map((m) => ({
|
|
1906
|
+
id: `${m.round}-${m.role}`,
|
|
1517
1907
|
role: m.role,
|
|
1518
1908
|
content: m.content,
|
|
1519
1909
|
round: m.round
|
|
@@ -1537,6 +1927,7 @@ program.command("resume [id]").description("Resume a session by ID, or pick inte
|
|
|
1537
1927
|
}
|
|
1538
1928
|
const dbMessages = getMessages(session.id);
|
|
1539
1929
|
const transcript = dbMessages.map((m) => ({
|
|
1930
|
+
id: `${m.round}-${m.role}`,
|
|
1540
1931
|
role: m.role,
|
|
1541
1932
|
content: m.content,
|
|
1542
1933
|
round: m.round
|
|
@@ -1550,13 +1941,51 @@ program.command("resume [id]").description("Resume a session by ID, or pick inte
|
|
|
1550
1941
|
});
|
|
1551
1942
|
await instance.waitUntilExit();
|
|
1552
1943
|
});
|
|
1553
|
-
program.command("history").description("List
|
|
1944
|
+
var historyCmd = program.command("history").description("List, remove, or clear sessions").option("-n, --limit <n>", "Number of sessions to show", parseInt, 20).action((opts) => {
|
|
1554
1945
|
const sessions = listSessions(opts.limit);
|
|
1555
1946
|
console.log(chalk.bold("\n Recent sessions:\n"));
|
|
1556
1947
|
showSessionList(sessions);
|
|
1557
1948
|
console.log();
|
|
1558
1949
|
closeDb();
|
|
1559
1950
|
});
|
|
1951
|
+
historyCmd.command("rm <id>").description("Delete a session by ID or prefix").action((id) => {
|
|
1952
|
+
const session = getSession(id) || getSessionByPrefix(id);
|
|
1953
|
+
if (!session) {
|
|
1954
|
+
console.log(chalk.red(` Session not found: ${id}`));
|
|
1955
|
+
process.exit(1);
|
|
1956
|
+
}
|
|
1957
|
+
deleteSession(session.id);
|
|
1958
|
+
const sid = session.id.slice(0, 7);
|
|
1959
|
+
const title = session.title || "(untitled)";
|
|
1960
|
+
console.log(chalk.green(` Deleted session ${sid} \u2014 ${title}`));
|
|
1961
|
+
closeDb();
|
|
1962
|
+
});
|
|
1963
|
+
historyCmd.command("clear").description("Delete all sessions").action(async () => {
|
|
1964
|
+
const sessions = listSessions();
|
|
1965
|
+
if (sessions.length === 0) {
|
|
1966
|
+
console.log(chalk.yellow(" No sessions to delete."));
|
|
1967
|
+
closeDb();
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
const readline = await import("readline");
|
|
1971
|
+
const rl = readline.createInterface({
|
|
1972
|
+
input: process.stdin,
|
|
1973
|
+
output: process.stdout
|
|
1974
|
+
});
|
|
1975
|
+
rl.question(
|
|
1976
|
+
chalk.yellow(` Delete all ${sessions.length} session(s)? [y/N] `),
|
|
1977
|
+
(answer) => {
|
|
1978
|
+
rl.close();
|
|
1979
|
+
if (answer.trim().toLowerCase() === "y") {
|
|
1980
|
+
deleteAllSessions();
|
|
1981
|
+
console.log(chalk.green(` Deleted ${sessions.length} session(s).`));
|
|
1982
|
+
} else {
|
|
1983
|
+
console.log(" Cancelled.");
|
|
1984
|
+
}
|
|
1985
|
+
closeDb();
|
|
1986
|
+
}
|
|
1987
|
+
);
|
|
1988
|
+
});
|
|
1560
1989
|
program.command("show <id>").description("Print full transcript for a session").option("-m, --markdown", "Output as GitHub-compatible markdown").action((id, opts) => {
|
|
1561
1990
|
const session = getSession(id) || getSessionByPrefix(id);
|
|
1562
1991
|
if (!session) {
|