aemeathcli 1.0.10 → 1.0.12
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/LICENSE +21 -0
- package/README.md +66 -54
- package/dist/App-JQ622M66.js +4431 -0
- package/dist/App-JQ622M66.js.map +1 -0
- package/dist/agent-store/architect.md +32 -0
- package/dist/agent-store/debugger.md +32 -0
- package/dist/agent-store/developer.md +29 -0
- package/dist/agent-store/documenter.md +30 -0
- package/dist/agent-store/researcher.md +31 -0
- package/dist/agent-store/reviewer.md +28 -0
- package/dist/agent-store/supervisor.md +37 -0
- package/dist/agent-store/tester.md +30 -0
- package/dist/api-key-fallback-RJLPM3KH.js +11 -0
- package/dist/{api-key-fallback-YQQBOQIL.js.map → api-key-fallback-RJLPM3KH.js.map} +1 -1
- package/dist/auth-status-JQJOKUPF.js +13 -0
- package/dist/auth-status-JQJOKUPF.js.map +1 -0
- package/dist/{chunk-RWCNNAL7.js → chunk-2KMA5RBC.js} +25 -48
- package/dist/chunk-2KMA5RBC.js.map +1 -0
- package/dist/{chunk-CYQNBB25.js → chunk-2Y7TR6BS.js} +28 -5
- package/dist/chunk-2Y7TR6BS.js.map +1 -0
- package/dist/{chunk-DAHGLHNR.js → chunk-2ZYK5IJG.js} +6 -141
- package/dist/chunk-2ZYK5IJG.js.map +1 -0
- package/dist/chunk-36RXCZOV.js +88 -0
- package/dist/chunk-36RXCZOV.js.map +1 -0
- package/dist/{chunk-DMBPX3RG.js → chunk-7EBLXPL4.js} +9 -9
- package/dist/{chunk-DMBPX3RG.js.map → chunk-7EBLXPL4.js.map} +1 -1
- package/dist/chunk-BIMQL4AG.js +186 -0
- package/dist/chunk-BIMQL4AG.js.map +1 -0
- package/dist/{chunk-NBR3GHMT.js → chunk-D275MCIH.js} +39 -7
- package/dist/chunk-D275MCIH.js.map +1 -0
- package/dist/{chunk-Y5XVD2CD.js → chunk-FFS4T7BZ.js} +109 -82
- package/dist/chunk-FFS4T7BZ.js.map +1 -0
- package/dist/{chunk-CARHU3DO.js → chunk-GXAJGP2T.js} +64 -16
- package/dist/chunk-GXAJGP2T.js.map +1 -0
- package/dist/{chunk-I5PZ4JTS.js → chunk-HESQLCLU.js} +4 -4
- package/dist/{chunk-I5PZ4JTS.js.map → chunk-HESQLCLU.js.map} +1 -1
- package/dist/{chunk-JAXXTYID.js → chunk-IR5HLBMH.js} +2 -2
- package/dist/{chunk-JAXXTYID.js.map → chunk-IR5HLBMH.js.map} +1 -1
- package/dist/{chunk-MFBHNWGV.js → chunk-K2FCMRXH.js} +11 -19
- package/dist/chunk-K2FCMRXH.js.map +1 -0
- package/dist/{chunk-H66O5Z2V.js → chunk-KIC7UI5U.js} +41 -6
- package/dist/chunk-KIC7UI5U.js.map +1 -0
- package/dist/{chunk-MXZSI3AY.js → chunk-KMOAJRDE.js} +42 -10
- package/dist/chunk-KMOAJRDE.js.map +1 -0
- package/dist/chunk-LQBALETG.js +71 -0
- package/dist/chunk-LQBALETG.js.map +1 -0
- package/dist/chunk-M3FPQSRU.js +12 -0
- package/dist/chunk-M3FPQSRU.js.map +1 -0
- package/dist/chunk-NQEUK763.js +26 -0
- package/dist/chunk-NQEUK763.js.map +1 -0
- package/dist/chunk-OPWAFS6Y.js +38 -0
- package/dist/chunk-OPWAFS6Y.js.map +1 -0
- package/dist/{chunk-6PDJ45T4.js → chunk-PS4WEFW6.js} +50 -25
- package/dist/chunk-PS4WEFW6.js.map +1 -0
- package/dist/{chunk-HMJRPNPZ.js → chunk-QK7TKNHV.js} +93 -21
- package/dist/chunk-QK7TKNHV.js.map +1 -0
- package/dist/{chunk-LSOYPSAT.js → chunk-RADJSEG5.js} +4 -4
- package/dist/chunk-RADJSEG5.js.map +1 -0
- package/dist/{chunk-4IJD72YB.js → chunk-SNWPI6XJ.js} +7 -7
- package/dist/chunk-SNWPI6XJ.js.map +1 -0
- package/dist/{chunk-TEVZS4FA.js → chunk-UM7MSLOV.js} +16 -9
- package/dist/chunk-UM7MSLOV.js.map +1 -0
- package/dist/chunk-VNZ3YTQD.js +232 -0
- package/dist/chunk-VNZ3YTQD.js.map +1 -0
- package/dist/{chunk-IYW62KKR.js → chunk-WXIN65UG.js} +66 -23
- package/dist/chunk-WXIN65UG.js.map +1 -0
- package/dist/chunk-XEXWX7C7.js +241 -0
- package/dist/chunk-XEXWX7C7.js.map +1 -0
- package/dist/{chunk-CGEV3ARR.js → chunk-YCCYXDW7.js} +3 -3
- package/dist/chunk-YCCYXDW7.js.map +1 -0
- package/dist/chunk-YPQ2MLAV.js +140 -0
- package/dist/chunk-YPQ2MLAV.js.map +1 -0
- package/dist/chunk-ZCOVMVK4.js +26 -0
- package/dist/chunk-ZCOVMVK4.js.map +1 -0
- package/dist/{claude-login-5WELXPKT.js → claude-login-AIFIWTYF.js} +9 -9
- package/dist/{claude-login-5WELXPKT.js.map → claude-login-AIFIWTYF.js.map} +1 -1
- package/dist/cli.js +370 -171
- package/dist/cli.js.map +1 -1
- package/dist/{codex-login-GZIFXUWD.js → codex-login-LW5X7GAM.js} +10 -10
- package/dist/codex-login-LW5X7GAM.js.map +1 -0
- package/dist/config-store-NF56VHFU.js +7 -0
- package/dist/{config-store-W6FBCQAQ.js.map → config-store-NF56VHFU.js.map} +1 -1
- package/dist/conversation-store-7GRDQZD2.js +4 -0
- package/dist/conversation-store-7GRDQZD2.js.map +1 -0
- package/dist/detect-providers-QICJ5U3R.js +4 -0
- package/dist/detect-providers-QICJ5U3R.js.map +1 -0
- package/dist/executor-FTABX2AW.js +4 -0
- package/dist/{executor-6RIKIGXK.js.map → executor-FTABX2AW.js.map} +1 -1
- package/dist/first-run-ADROZVYF.js +230 -0
- package/dist/first-run-ADROZVYF.js.map +1 -0
- package/dist/{gemini-login-AZGL3CE7.js → gemini-login-TST454MX.js} +9 -9
- package/dist/{gemini-login-AZGL3CE7.js.map → gemini-login-TST454MX.js.map} +1 -1
- package/dist/index.d.ts +46 -70
- package/dist/index.js +79 -468
- package/dist/index.js.map +1 -1
- package/dist/input-history-BEICE7PT.js +57 -0
- package/dist/input-history-BEICE7PT.js.map +1 -0
- package/dist/kimi-adapter-7FYOAKOI.js +6 -0
- package/dist/{kimi-adapter-JN4HFFHU.js.map → kimi-adapter-7FYOAKOI.js.map} +1 -1
- package/dist/{kimi-login-6LUWB7P6.js → kimi-login-3IGVOBJI.js} +9 -9
- package/dist/{kimi-login-6LUWB7P6.js.map → kimi-login-3IGVOBJI.js.map} +1 -1
- package/dist/logger-KGHUQ4VE.js +3 -0
- package/dist/logger-KGHUQ4VE.js.map +1 -0
- package/dist/model-discovery-AAJDHRFO.js +6 -0
- package/dist/model-discovery-AAJDHRFO.js.map +1 -0
- package/dist/native-cli-adapters-CLONTZOA.js +8 -0
- package/dist/{native-cli-adapters-OLW3XX57.js.map → native-cli-adapters-CLONTZOA.js.map} +1 -1
- package/dist/ollama-adapter-2N5OQIEV.js +5 -0
- package/dist/{ollama-adapter-OJQ3FKWK.js.map → ollama-adapter-2N5OQIEV.js.map} +1 -1
- package/dist/pathResolver-UVAB2FCW.js +3 -0
- package/dist/pathResolver-UVAB2FCW.js.map +1 -0
- package/dist/profile-loader-EMLV4J7S.js +162 -0
- package/dist/profile-loader-EMLV4J7S.js.map +1 -0
- package/dist/registry-LRURZVUL.js +5 -0
- package/dist/{registry-AZ2LOHHJ.js.map → registry-LRURZVUL.js.map} +1 -1
- package/dist/registry-MVNSXCEF.js +6 -0
- package/dist/{registry-H7B3AHPQ.js.map → registry-MVNSXCEF.js.map} +1 -1
- package/dist/server-manager-THGZBBZB.js +5 -0
- package/dist/{server-manager-PTGBHCLS.js.map → server-manager-THGZBBZB.js.map} +1 -1
- package/dist/session-manager-X3DXT53M.js +12 -0
- package/dist/{session-manager-XOMDMC77.js.map → session-manager-X3DXT53M.js.map} +1 -1
- package/dist/skills/built-in/code-review/SKILL.md +85 -0
- package/dist/skills/built-in/commit/SKILL.md +83 -0
- package/dist/skills/built-in/debug/SKILL.md +119 -0
- package/dist/skills/built-in/plan/SKILL.md +123 -0
- package/dist/skills/built-in/refactor/SKILL.md +132 -0
- package/dist/skills/built-in/test/SKILL.md +128 -0
- package/dist/sqlite-store-7OECRTXM.js +5 -0
- package/dist/sqlite-store-7OECRTXM.js.map +1 -0
- package/dist/team-manager-2VSMALAA.js +11 -0
- package/dist/{team-manager-HC4XGCFY.js.map → team-manager-2VSMALAA.js.map} +1 -1
- package/dist/team-state-HZNVMQHT.js +3 -0
- package/dist/team-state-HZNVMQHT.js.map +1 -0
- package/dist/tmux-manager-57QCUVHU.js +6 -0
- package/dist/{tmux-manager-GPYZ3WQH.js.map → tmux-manager-57QCUVHU.js.map} +1 -1
- package/dist/tools-KWFSYT56.js +6 -0
- package/dist/{tools-TSMXMHIF.js.map → tools-KWFSYT56.js.map} +1 -1
- package/package.json +11 -11
- package/dist/App-FKRSMFMB.js +0 -2789
- package/dist/App-FKRSMFMB.js.map +0 -1
- package/dist/api-key-fallback-YQQBOQIL.js +0 -11
- package/dist/chunk-4IJD72YB.js.map +0 -1
- package/dist/chunk-6PDJ45T4.js.map +0 -1
- package/dist/chunk-CARHU3DO.js.map +0 -1
- package/dist/chunk-CGEV3ARR.js.map +0 -1
- package/dist/chunk-CS5X3BWX.js +0 -27
- package/dist/chunk-CS5X3BWX.js.map +0 -1
- package/dist/chunk-CYQNBB25.js.map +0 -1
- package/dist/chunk-DAHGLHNR.js.map +0 -1
- package/dist/chunk-H66O5Z2V.js.map +0 -1
- package/dist/chunk-HMJRPNPZ.js.map +0 -1
- package/dist/chunk-IYW62KKR.js.map +0 -1
- package/dist/chunk-LSOYPSAT.js.map +0 -1
- package/dist/chunk-MFBHNWGV.js.map +0 -1
- package/dist/chunk-MXZSI3AY.js.map +0 -1
- package/dist/chunk-NBR3GHMT.js.map +0 -1
- package/dist/chunk-RWCNNAL7.js.map +0 -1
- package/dist/chunk-TEVZS4FA.js.map +0 -1
- package/dist/chunk-UY2SYSEZ.js +0 -211
- package/dist/chunk-UY2SYSEZ.js.map +0 -1
- package/dist/chunk-WAHVZH7V.js +0 -260
- package/dist/chunk-WAHVZH7V.js.map +0 -1
- package/dist/chunk-WPP3PEDE.js +0 -234
- package/dist/chunk-WPP3PEDE.js.map +0 -1
- package/dist/chunk-Y5XVD2CD.js.map +0 -1
- package/dist/claude-adapter-QMLFMSP3.js +0 -6
- package/dist/claude-adapter-QMLFMSP3.js.map +0 -1
- package/dist/codex-login-GZIFXUWD.js.map +0 -1
- package/dist/config-store-W6FBCQAQ.js +0 -6
- package/dist/executor-6RIKIGXK.js +0 -4
- package/dist/gemini-adapter-6JIHZ7WI.js +0 -6
- package/dist/gemini-adapter-6JIHZ7WI.js.map +0 -1
- package/dist/kimi-adapter-JN4HFFHU.js +0 -6
- package/dist/native-cli-adapters-OLW3XX57.js +0 -6
- package/dist/ollama-adapter-OJQ3FKWK.js +0 -6
- package/dist/openai-adapter-XU46EN7B.js +0 -6
- package/dist/openai-adapter-XU46EN7B.js.map +0 -1
- package/dist/registry-AZ2LOHHJ.js +0 -6
- package/dist/registry-H7B3AHPQ.js +0 -5
- package/dist/server-manager-PTGBHCLS.js +0 -5
- package/dist/session-manager-XOMDMC77.js +0 -12
- package/dist/team-manager-HC4XGCFY.js +0 -11
- package/dist/tmux-manager-GPYZ3WQH.js +0 -6
- package/dist/tools-TSMXMHIF.js +0 -6
|
@@ -0,0 +1,4431 @@
|
|
|
1
|
+
import { getCliProviderEntry, getCliProviderForModelProvider } from './chunk-LQBALETG.js';
|
|
2
|
+
import { getActiveTeamName, getActiveTeamManager, getActiveTmuxCleanup, setActiveTmuxCleanup, setActiveTeamManager, setActiveTeamName } from './chunk-ZCOVMVK4.js';
|
|
3
|
+
import { CostTracker } from './chunk-2ZYK5IJG.js';
|
|
4
|
+
import { getEventBus } from './chunk-YL5XFHR3.js';
|
|
5
|
+
import { createModelRouter } from './chunk-YPQ2MLAV.js';
|
|
6
|
+
import { formatCost, formatTokenCount } from './chunk-YCCYXDW7.js';
|
|
7
|
+
import './chunk-OPWAFS6Y.js';
|
|
8
|
+
import './chunk-ZGOHARPV.js';
|
|
9
|
+
import { getThinkingConfigForModel, SUPPORTED_MODELS, PROVIDER_MODEL_ORDER } from './chunk-HCIHOHLX.js';
|
|
10
|
+
import { DEFAULT_CONFIG, PACKAGE_VERSION } from './chunk-2Y7TR6BS.js';
|
|
11
|
+
import './chunk-IR5HLBMH.js';
|
|
12
|
+
import './chunk-D275MCIH.js';
|
|
13
|
+
import React11, { useRef, useState, useCallback, useEffect, useMemo, startTransition } from 'react';
|
|
14
|
+
import { Box, Text, render, useInput } from 'ink';
|
|
15
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
16
|
+
import { randomUUID } from 'crypto';
|
|
17
|
+
|
|
18
|
+
// src/ui/theme.ts
|
|
19
|
+
var BRAND_COLOR = "#F0C5DA";
|
|
20
|
+
var colors = {
|
|
21
|
+
text: {
|
|
22
|
+
primary: "#F9F5F5",
|
|
23
|
+
secondary: "#d3acb3",
|
|
24
|
+
muted: "#9e8085",
|
|
25
|
+
accent: "#F0C5DA",
|
|
26
|
+
response: "#F9F5F5"
|
|
27
|
+
},
|
|
28
|
+
border: {
|
|
29
|
+
dim: "#6b5459",
|
|
30
|
+
active: "#d3acb3"},
|
|
31
|
+
syntax: {
|
|
32
|
+
keyword: "#F0C5DA",
|
|
33
|
+
string: "#EDD6DC"},
|
|
34
|
+
status: {
|
|
35
|
+
success: "#F0C5DA",
|
|
36
|
+
error: "#f87171",
|
|
37
|
+
warning: "#EDD6DC",
|
|
38
|
+
info: "#F0C5DA",
|
|
39
|
+
active: "#F0C5DA"
|
|
40
|
+
},
|
|
41
|
+
role: {
|
|
42
|
+
user: "#F0C5DA",
|
|
43
|
+
assistant: "#F9F5F5",
|
|
44
|
+
system: "#EDD6DC",
|
|
45
|
+
tool: "#d3acb3"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function parseBlocks(raw) {
|
|
49
|
+
const blocks = [];
|
|
50
|
+
const lines = raw.split("\n");
|
|
51
|
+
let i = 0;
|
|
52
|
+
while (i < lines.length) {
|
|
53
|
+
const line = lines[i];
|
|
54
|
+
if (line === void 0) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
if (line.startsWith("```")) {
|
|
58
|
+
const lang = line.slice(3).trim();
|
|
59
|
+
const codeLines = [];
|
|
60
|
+
i++;
|
|
61
|
+
while (i < lines.length) {
|
|
62
|
+
const currentLine = lines[i];
|
|
63
|
+
if (currentLine === void 0 || currentLine.startsWith("```")) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
codeLines.push(currentLine);
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
blocks.push({ type: "code", content: codeLines.join("\n"), lang });
|
|
70
|
+
i++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const headerMatch = line.match(/^(#{1,4})\s+(.+)$/);
|
|
74
|
+
if (headerMatch) {
|
|
75
|
+
const hashes = headerMatch[1];
|
|
76
|
+
const headerContent = headerMatch[2];
|
|
77
|
+
if (hashes === void 0 || headerContent === void 0) {
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
blocks.push({
|
|
82
|
+
type: "header",
|
|
83
|
+
content: headerContent,
|
|
84
|
+
level: hashes.length
|
|
85
|
+
});
|
|
86
|
+
i++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (/^[-*_]{3,}\s*$/.test(line)) {
|
|
90
|
+
blocks.push({ type: "hr", content: "" });
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (line.startsWith("> ")) {
|
|
95
|
+
const quoteLines = [];
|
|
96
|
+
while (i < lines.length) {
|
|
97
|
+
const quoteLine = lines[i];
|
|
98
|
+
if (quoteLine === void 0 || !quoteLine.startsWith("> ")) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
quoteLines.push(quoteLine.slice(2));
|
|
102
|
+
i++;
|
|
103
|
+
}
|
|
104
|
+
blocks.push({ type: "blockquote", content: quoteLines.join("\n") });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (/^\s*[-*+]\s/.test(line) || /^\s*\d+\.\s/.test(line)) {
|
|
108
|
+
const listLines = [];
|
|
109
|
+
while (i < lines.length) {
|
|
110
|
+
const l = lines[i];
|
|
111
|
+
if (l === void 0) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
if (/^\s*[-*+]\s/.test(l) || /^\s*\d+\.\s/.test(l) || l.startsWith(" ") && listLines.length > 0) {
|
|
115
|
+
listLines.push(l);
|
|
116
|
+
i++;
|
|
117
|
+
} else {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
blocks.push({ type: "list", content: listLines.join("\n") });
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const textLines = [];
|
|
125
|
+
while (i < lines.length) {
|
|
126
|
+
const l = lines[i];
|
|
127
|
+
if (l === void 0) {
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
if (l.startsWith("```") || l.startsWith("#") || l.startsWith("> ") || /^[-*_]{3,}\s*$/.test(l) || /^\s*[-*+]\s/.test(l) || /^\s*\d+\.\s/.test(l)) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
textLines.push(l);
|
|
134
|
+
i++;
|
|
135
|
+
}
|
|
136
|
+
if (textLines.length > 0) {
|
|
137
|
+
blocks.push({ type: "text", content: textLines.join("\n") });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return blocks.length > 0 ? blocks : [{ type: "text", content: raw }];
|
|
141
|
+
}
|
|
142
|
+
function InlineMarkdown({
|
|
143
|
+
text
|
|
144
|
+
}) {
|
|
145
|
+
const segments = [];
|
|
146
|
+
let remaining = text;
|
|
147
|
+
let key = 0;
|
|
148
|
+
while (remaining.length > 0) {
|
|
149
|
+
const boldMatch = remaining.match(/^\*\*(.+?)\*\*/);
|
|
150
|
+
if (boldMatch) {
|
|
151
|
+
segments.push(
|
|
152
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: boldMatch[1] }, key++)
|
|
153
|
+
);
|
|
154
|
+
remaining = remaining.slice(boldMatch[0].length);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const italicMatch = remaining.match(/^\*(.+?)\*/);
|
|
158
|
+
if (italicMatch) {
|
|
159
|
+
segments.push(
|
|
160
|
+
/* @__PURE__ */ jsx(Text, { italic: true, children: italicMatch[1] }, key++)
|
|
161
|
+
);
|
|
162
|
+
remaining = remaining.slice(italicMatch[0].length);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const codeMatch = remaining.match(/^`([^`]+)`/);
|
|
166
|
+
if (codeMatch) {
|
|
167
|
+
segments.push(
|
|
168
|
+
/* @__PURE__ */ jsx(Text, { color: colors.syntax.string, bold: true, children: codeMatch[1] }, key++)
|
|
169
|
+
);
|
|
170
|
+
remaining = remaining.slice(codeMatch[0].length);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const strikeMatch = remaining.match(/^~~(.+?)~~/);
|
|
174
|
+
if (strikeMatch) {
|
|
175
|
+
segments.push(
|
|
176
|
+
/* @__PURE__ */ jsx(Text, { strikethrough: true, dimColor: true, children: strikeMatch[1] }, key++)
|
|
177
|
+
);
|
|
178
|
+
remaining = remaining.slice(strikeMatch[0].length);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)]+)\)/);
|
|
182
|
+
if (linkMatch) {
|
|
183
|
+
segments.push(
|
|
184
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.info, underline: true, children: linkMatch[1] }, key++)
|
|
185
|
+
);
|
|
186
|
+
remaining = remaining.slice(linkMatch[0].length);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const nextSpecial = remaining.search(/[[*`~]/);
|
|
190
|
+
if (nextSpecial === -1) {
|
|
191
|
+
segments.push(/* @__PURE__ */ jsx(Text, { children: remaining }, key++));
|
|
192
|
+
break;
|
|
193
|
+
} else if (nextSpecial === 0) {
|
|
194
|
+
segments.push(/* @__PURE__ */ jsx(Text, { children: remaining[0] }, key++));
|
|
195
|
+
remaining = remaining.slice(1);
|
|
196
|
+
} else {
|
|
197
|
+
segments.push(
|
|
198
|
+
/* @__PURE__ */ jsx(Text, { children: remaining.slice(0, nextSpecial) }, key++)
|
|
199
|
+
);
|
|
200
|
+
remaining = remaining.slice(nextSpecial);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: segments });
|
|
204
|
+
}
|
|
205
|
+
function HeaderBlock({
|
|
206
|
+
content,
|
|
207
|
+
level
|
|
208
|
+
}) {
|
|
209
|
+
const headerColors = [
|
|
210
|
+
colors.text.accent,
|
|
211
|
+
colors.status.active,
|
|
212
|
+
colors.syntax.keyword,
|
|
213
|
+
colors.text.secondary
|
|
214
|
+
];
|
|
215
|
+
const color = headerColors[level - 1] ?? colors.text.primary;
|
|
216
|
+
const prefix = level === 1 ? "\u25C6 " : level === 2 ? "\u25B8 " : " ";
|
|
217
|
+
return /* @__PURE__ */ jsx(Box, { marginY: level <= 2 ? 1 : 0, children: /* @__PURE__ */ jsxs(Text, { color, bold: level <= 2, children: [
|
|
218
|
+
prefix,
|
|
219
|
+
content
|
|
220
|
+
] }) });
|
|
221
|
+
}
|
|
222
|
+
function ListBlock({
|
|
223
|
+
content
|
|
224
|
+
}) {
|
|
225
|
+
const items = content.split("\n");
|
|
226
|
+
const bullets = ["\u25B8", "\u25E6", "\xB7", "\u2023"];
|
|
227
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((item, i) => {
|
|
228
|
+
const indent = item.match(/^(\s*)/)?.[1]?.length ?? 0;
|
|
229
|
+
const bulletLevel = Math.floor(indent / 2);
|
|
230
|
+
const clean = item.replace(/^\s*[-*+]\s/, "").replace(/^\s*\d+\.\s/, "");
|
|
231
|
+
const bullet = bullets[bulletLevel % bullets.length];
|
|
232
|
+
return /* @__PURE__ */ jsxs(Box, { marginLeft: bulletLevel * 2, children: [
|
|
233
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.status.active, children: [
|
|
234
|
+
bullet,
|
|
235
|
+
" "
|
|
236
|
+
] }),
|
|
237
|
+
/* @__PURE__ */ jsx(InlineMarkdown, { text: clean })
|
|
238
|
+
] }, i);
|
|
239
|
+
}) });
|
|
240
|
+
}
|
|
241
|
+
function CodeBlockRender({
|
|
242
|
+
content,
|
|
243
|
+
lang
|
|
244
|
+
}) {
|
|
245
|
+
const lines = content.split("\n");
|
|
246
|
+
if (lines.length > 0 && lines[lines.length - 1]?.trim() === "") {
|
|
247
|
+
lines.pop();
|
|
248
|
+
}
|
|
249
|
+
return /* @__PURE__ */ jsxs(
|
|
250
|
+
Box,
|
|
251
|
+
{
|
|
252
|
+
flexDirection: "column",
|
|
253
|
+
borderStyle: "round",
|
|
254
|
+
borderColor: colors.border.dim,
|
|
255
|
+
marginY: 1,
|
|
256
|
+
children: [
|
|
257
|
+
lang ? /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.text.muted, dimColor: true, children: lang }) }) : null,
|
|
258
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: lines.map((line, i) => /* @__PURE__ */ jsxs(Box, { children: [
|
|
259
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.text.muted, dimColor: true, children: [
|
|
260
|
+
String(i + 1).padStart(3, " "),
|
|
261
|
+
" ",
|
|
262
|
+
"\u2502",
|
|
263
|
+
" "
|
|
264
|
+
] }),
|
|
265
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.response, children: line })
|
|
266
|
+
] }, i)) })
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
function MarkdownContent({
|
|
272
|
+
content
|
|
273
|
+
}) {
|
|
274
|
+
const blocks = parseBlocks(content);
|
|
275
|
+
if (blocks.length === 1 && blocks[0]?.type === "text") {
|
|
276
|
+
const text = blocks[0].content;
|
|
277
|
+
if (!/[[*`~]/.test(text)) {
|
|
278
|
+
return /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: text });
|
|
279
|
+
}
|
|
280
|
+
return /* @__PURE__ */ jsx(InlineMarkdown, { text });
|
|
281
|
+
}
|
|
282
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: blocks.map((block, i) => {
|
|
283
|
+
switch (block.type) {
|
|
284
|
+
case "code":
|
|
285
|
+
return /* @__PURE__ */ jsx(
|
|
286
|
+
CodeBlockRender,
|
|
287
|
+
{
|
|
288
|
+
content: block.content,
|
|
289
|
+
lang: block.lang
|
|
290
|
+
},
|
|
291
|
+
i
|
|
292
|
+
);
|
|
293
|
+
case "header":
|
|
294
|
+
return /* @__PURE__ */ jsx(
|
|
295
|
+
HeaderBlock,
|
|
296
|
+
{
|
|
297
|
+
content: block.content,
|
|
298
|
+
level: block.level ?? 1
|
|
299
|
+
},
|
|
300
|
+
i
|
|
301
|
+
);
|
|
302
|
+
case "list":
|
|
303
|
+
return /* @__PURE__ */ jsx(ListBlock, { content: block.content }, i);
|
|
304
|
+
case "hr":
|
|
305
|
+
return /* @__PURE__ */ jsx(Box, { marginY: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.border.dim, children: "\u2500".repeat(40) }) }, i);
|
|
306
|
+
case "blockquote":
|
|
307
|
+
return /* @__PURE__ */ jsx(
|
|
308
|
+
Box,
|
|
309
|
+
{
|
|
310
|
+
marginLeft: 1,
|
|
311
|
+
borderStyle: "single",
|
|
312
|
+
borderLeft: true,
|
|
313
|
+
borderRight: false,
|
|
314
|
+
borderTop: false,
|
|
315
|
+
borderBottom: false,
|
|
316
|
+
borderColor: colors.border.active,
|
|
317
|
+
paddingLeft: 1,
|
|
318
|
+
children: /* @__PURE__ */ jsx(Text, { color: colors.text.secondary, italic: true, wrap: "wrap", children: block.content })
|
|
319
|
+
},
|
|
320
|
+
i
|
|
321
|
+
);
|
|
322
|
+
case "text":
|
|
323
|
+
default:
|
|
324
|
+
return /* @__PURE__ */ jsx(InlineMarkdown, { text: block.content }, i);
|
|
325
|
+
}
|
|
326
|
+
}) });
|
|
327
|
+
}
|
|
328
|
+
var animationBuckets = /* @__PURE__ */ new Map();
|
|
329
|
+
function getOrCreateBucket(intervalMs) {
|
|
330
|
+
const existing = animationBuckets.get(intervalMs);
|
|
331
|
+
if (existing !== void 0) {
|
|
332
|
+
return existing;
|
|
333
|
+
}
|
|
334
|
+
const bucket = {
|
|
335
|
+
tick: 0,
|
|
336
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
337
|
+
timer: setInterval(() => {
|
|
338
|
+
bucket.tick += 1;
|
|
339
|
+
for (const listener of bucket.listeners) {
|
|
340
|
+
listener(bucket.tick);
|
|
341
|
+
}
|
|
342
|
+
}, intervalMs)
|
|
343
|
+
};
|
|
344
|
+
animationBuckets.set(intervalMs, bucket);
|
|
345
|
+
return bucket;
|
|
346
|
+
}
|
|
347
|
+
function useAnimationTick(intervalMs, enabled = true) {
|
|
348
|
+
const [tick, setTick] = useState(0);
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
if (!enabled) {
|
|
351
|
+
setTick(0);
|
|
352
|
+
return void 0;
|
|
353
|
+
}
|
|
354
|
+
const bucket = getOrCreateBucket(intervalMs);
|
|
355
|
+
setTick(bucket.tick);
|
|
356
|
+
const listener = (nextTick) => {
|
|
357
|
+
setTick(nextTick);
|
|
358
|
+
};
|
|
359
|
+
bucket.listeners.add(listener);
|
|
360
|
+
return () => {
|
|
361
|
+
bucket.listeners.delete(listener);
|
|
362
|
+
if (bucket.listeners.size === 0) {
|
|
363
|
+
clearInterval(bucket.timer);
|
|
364
|
+
animationBuckets.delete(intervalMs);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}, [enabled, intervalMs]);
|
|
368
|
+
return tick;
|
|
369
|
+
}
|
|
370
|
+
var SPINNER_VARIANTS = {
|
|
371
|
+
dots: {
|
|
372
|
+
frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
|
|
373
|
+
interval: 120
|
|
374
|
+
},
|
|
375
|
+
braille: {
|
|
376
|
+
frames: ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"],
|
|
377
|
+
interval: 120
|
|
378
|
+
},
|
|
379
|
+
arc: {
|
|
380
|
+
frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"],
|
|
381
|
+
interval: 100
|
|
382
|
+
},
|
|
383
|
+
pulse: {
|
|
384
|
+
frames: ["\u25C9", "\u25CE", "\u25CB", "\u25CE", "\u25C9", "\u25CF"],
|
|
385
|
+
interval: 120
|
|
386
|
+
},
|
|
387
|
+
bounce: {
|
|
388
|
+
frames: ["\u2801", "\u2802", "\u2804", "\u2840", "\u2880", "\u2820", "\u2810", "\u2808"],
|
|
389
|
+
interval: 120
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
function GradientSpinner({
|
|
393
|
+
label,
|
|
394
|
+
labelColor = "#888888",
|
|
395
|
+
variant = "dots",
|
|
396
|
+
speed
|
|
397
|
+
}) {
|
|
398
|
+
const spinnerDef = SPINNER_VARIANTS[variant] ?? SPINNER_VARIANTS["dots"];
|
|
399
|
+
if (!spinnerDef) {
|
|
400
|
+
throw new Error("Missing default spinner configuration");
|
|
401
|
+
}
|
|
402
|
+
const interval = speed ?? spinnerDef.interval;
|
|
403
|
+
const tick = useAnimationTick(interval);
|
|
404
|
+
const frame = tick % spinnerDef.frames.length;
|
|
405
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
406
|
+
/* @__PURE__ */ jsx(Text, { color: BRAND_COLOR, children: spinnerDef.frames[frame] }),
|
|
407
|
+
label ? /* @__PURE__ */ jsxs(Text, { color: labelColor, children: [
|
|
408
|
+
" ",
|
|
409
|
+
label
|
|
410
|
+
] }) : null
|
|
411
|
+
] });
|
|
412
|
+
}
|
|
413
|
+
function StatusIcon({
|
|
414
|
+
status
|
|
415
|
+
}) {
|
|
416
|
+
switch (status) {
|
|
417
|
+
case "pending":
|
|
418
|
+
return /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "\u25CB" });
|
|
419
|
+
case "executing":
|
|
420
|
+
return /* @__PURE__ */ jsx(GradientSpinner, { variant: "dots" });
|
|
421
|
+
case "success":
|
|
422
|
+
return /* @__PURE__ */ jsx(Text, { color: colors.status.success, children: "\u2713" });
|
|
423
|
+
case "error":
|
|
424
|
+
return /* @__PURE__ */ jsx(Text, { color: colors.status.error, children: "\u2717" });
|
|
425
|
+
case "cancelled":
|
|
426
|
+
return /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "\u2298" });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function formatDuration(ms) {
|
|
430
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
431
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
432
|
+
}
|
|
433
|
+
function getToolIcon(name) {
|
|
434
|
+
switch (name) {
|
|
435
|
+
case "read":
|
|
436
|
+
return "\u{1F4C4}";
|
|
437
|
+
case "write":
|
|
438
|
+
return "\u270F\uFE0F";
|
|
439
|
+
case "edit":
|
|
440
|
+
return "\u{1F4DD}";
|
|
441
|
+
case "glob":
|
|
442
|
+
return "\u{1F50D}";
|
|
443
|
+
case "grep":
|
|
444
|
+
return "\u{1F50E}";
|
|
445
|
+
case "bash":
|
|
446
|
+
return "\u26A1";
|
|
447
|
+
case "web_search":
|
|
448
|
+
case "webSearch":
|
|
449
|
+
return "\u{1F310}";
|
|
450
|
+
case "web_fetch":
|
|
451
|
+
case "webFetch":
|
|
452
|
+
return "\u{1F4E1}";
|
|
453
|
+
default:
|
|
454
|
+
return "\u2699";
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function ToolCallDisplay({
|
|
458
|
+
toolName,
|
|
459
|
+
status,
|
|
460
|
+
description,
|
|
461
|
+
output,
|
|
462
|
+
isError,
|
|
463
|
+
duration,
|
|
464
|
+
isCollapsed = true
|
|
465
|
+
}) {
|
|
466
|
+
const borderColor = status === "error" ? colors.status.error : status === "executing" ? colors.status.active : colors.border.dim;
|
|
467
|
+
const icon = getToolIcon(toolName);
|
|
468
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 0, children: [
|
|
469
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
470
|
+
/* @__PURE__ */ jsx(StatusIcon, { status }),
|
|
471
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
472
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.role.tool, bold: true, children: [
|
|
473
|
+
icon,
|
|
474
|
+
" ",
|
|
475
|
+
toolName
|
|
476
|
+
] }),
|
|
477
|
+
description ? /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, children: [
|
|
478
|
+
" ",
|
|
479
|
+
description
|
|
480
|
+
] }) : null,
|
|
481
|
+
duration !== void 0 && status !== "executing" ? /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, dimColor: true, children: [
|
|
482
|
+
" ",
|
|
483
|
+
"(",
|
|
484
|
+
formatDuration(duration),
|
|
485
|
+
")"
|
|
486
|
+
] }) : null
|
|
487
|
+
] }),
|
|
488
|
+
!isCollapsed && output ? /* @__PURE__ */ jsx(
|
|
489
|
+
Box,
|
|
490
|
+
{
|
|
491
|
+
flexDirection: "column",
|
|
492
|
+
marginLeft: 2,
|
|
493
|
+
borderStyle: "single",
|
|
494
|
+
borderLeft: true,
|
|
495
|
+
borderRight: false,
|
|
496
|
+
borderTop: false,
|
|
497
|
+
borderBottom: false,
|
|
498
|
+
borderColor,
|
|
499
|
+
paddingLeft: 1,
|
|
500
|
+
children: /* @__PURE__ */ jsx(
|
|
501
|
+
Text,
|
|
502
|
+
{
|
|
503
|
+
wrap: "wrap",
|
|
504
|
+
color: isError ? colors.status.error : colors.text.secondary,
|
|
505
|
+
children: output.length > 2e3 ? output.slice(0, 2e3) + "\n\u2026 (truncated)" : output
|
|
506
|
+
}
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
) : null
|
|
510
|
+
] });
|
|
511
|
+
}
|
|
512
|
+
function getRoleColor(role) {
|
|
513
|
+
switch (role) {
|
|
514
|
+
case "user":
|
|
515
|
+
return colors.role.user;
|
|
516
|
+
case "assistant":
|
|
517
|
+
return colors.role.assistant;
|
|
518
|
+
case "system":
|
|
519
|
+
return colors.role.system;
|
|
520
|
+
case "tool":
|
|
521
|
+
return colors.role.tool;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function getRoleIcon(role) {
|
|
525
|
+
switch (role) {
|
|
526
|
+
case "user":
|
|
527
|
+
return "\u276F";
|
|
528
|
+
case "assistant":
|
|
529
|
+
return "\u2726";
|
|
530
|
+
case "system":
|
|
531
|
+
return "\u2022";
|
|
532
|
+
case "tool":
|
|
533
|
+
return "\u2699";
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function getRoleLabel(role, model) {
|
|
537
|
+
switch (role) {
|
|
538
|
+
case "user":
|
|
539
|
+
return "You";
|
|
540
|
+
case "assistant":
|
|
541
|
+
return model ?? "Assistant";
|
|
542
|
+
case "system":
|
|
543
|
+
return "System";
|
|
544
|
+
case "tool":
|
|
545
|
+
return "Tool";
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function shortModelName(model) {
|
|
549
|
+
if (model.includes("opus")) return "Opus 4.6";
|
|
550
|
+
if (model.includes("sonnet")) return "Sonnet 4.6";
|
|
551
|
+
if (model.includes("haiku")) return "Haiku 4.5";
|
|
552
|
+
if (model.includes("gpt-5.2-mini")) return "GPT-5.2 mini";
|
|
553
|
+
if (model.includes("gpt-5.2")) return "GPT-5.2";
|
|
554
|
+
if (model.includes("o3")) return "o3";
|
|
555
|
+
if (model.includes("gemini") && model.includes("pro")) return "Gemini Pro";
|
|
556
|
+
if (model.includes("gemini") && model.includes("flash")) return "Gemini Flash";
|
|
557
|
+
if (model.includes("kimi") || model.includes("k2")) return "Kimi K2.5";
|
|
558
|
+
return model;
|
|
559
|
+
}
|
|
560
|
+
function formatToolArgs(name, args) {
|
|
561
|
+
switch (name) {
|
|
562
|
+
case "read":
|
|
563
|
+
case "write":
|
|
564
|
+
case "edit":
|
|
565
|
+
return typeof args["file_path"] === "string" ? args["file_path"] : "";
|
|
566
|
+
case "glob":
|
|
567
|
+
return typeof args["pattern"] === "string" ? args["pattern"] : "";
|
|
568
|
+
case "grep": {
|
|
569
|
+
const pat = typeof args["pattern"] === "string" ? args["pattern"] : "";
|
|
570
|
+
const dir = typeof args["path"] === "string" ? ` in ${args["path"]}` : "";
|
|
571
|
+
return `"${pat}"${dir}`;
|
|
572
|
+
}
|
|
573
|
+
case "bash": {
|
|
574
|
+
const cmd = typeof args["command"] === "string" ? args["command"] : "";
|
|
575
|
+
return cmd.length > 60 ? cmd.slice(0, 60) + "\u2026" : cmd;
|
|
576
|
+
}
|
|
577
|
+
default:
|
|
578
|
+
return JSON.stringify(args).slice(0, 80);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function MessageItem({ message }) {
|
|
582
|
+
const color = getRoleColor(message.role);
|
|
583
|
+
const icon = getRoleIcon(message.role);
|
|
584
|
+
const label = getRoleLabel(
|
|
585
|
+
message.role,
|
|
586
|
+
message.model ? shortModelName(message.model) : void 0
|
|
587
|
+
);
|
|
588
|
+
if (message.role === "system") {
|
|
589
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs(Box, { children: [
|
|
590
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.role.system, dimColor: true, children: [
|
|
591
|
+
icon,
|
|
592
|
+
" "
|
|
593
|
+
] }),
|
|
594
|
+
/* @__PURE__ */ jsx(Text, { color: colors.role.system, dimColor: true, wrap: "wrap", children: message.content })
|
|
595
|
+
] }) });
|
|
596
|
+
}
|
|
597
|
+
if (message.role === "tool" && message.toolCalls && message.toolCalls.length > 0) {
|
|
598
|
+
const firstCall = message.toolCalls[0];
|
|
599
|
+
if (firstCall) {
|
|
600
|
+
return /* @__PURE__ */ jsx(Box, { marginY: 0, marginLeft: 2, children: /* @__PURE__ */ jsx(
|
|
601
|
+
ToolCallDisplay,
|
|
602
|
+
{
|
|
603
|
+
toolName: firstCall.name,
|
|
604
|
+
status: message.content.startsWith("Error:") ? "error" : "success",
|
|
605
|
+
description: formatToolArgs(firstCall.name, firstCall.arguments),
|
|
606
|
+
output: message.content,
|
|
607
|
+
isError: message.content.startsWith("Error:"),
|
|
608
|
+
isCollapsed: message.content.length > 500
|
|
609
|
+
}
|
|
610
|
+
) });
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
614
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
615
|
+
/* @__PURE__ */ jsxs(Text, { color, bold: true, children: [
|
|
616
|
+
icon,
|
|
617
|
+
" ",
|
|
618
|
+
label
|
|
619
|
+
] }),
|
|
620
|
+
message.tokenUsage ? /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, dimColor: true, children: [
|
|
621
|
+
" ",
|
|
622
|
+
"(",
|
|
623
|
+
message.tokenUsage.totalTokens,
|
|
624
|
+
" tokens)"
|
|
625
|
+
] }) : null
|
|
626
|
+
] }),
|
|
627
|
+
/* @__PURE__ */ jsx(Box, { marginLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx(MarkdownContent, { content: message.content }) }),
|
|
628
|
+
message.toolCalls && message.toolCalls.length > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: 2, flexDirection: "column", marginTop: 0, children: message.toolCalls.map((call) => {
|
|
629
|
+
const argSummary = formatToolArgs(call.name, call.arguments);
|
|
630
|
+
return /* @__PURE__ */ jsx(
|
|
631
|
+
ToolCallDisplay,
|
|
632
|
+
{
|
|
633
|
+
toolName: call.name,
|
|
634
|
+
status: "success",
|
|
635
|
+
description: argSummary
|
|
636
|
+
},
|
|
637
|
+
call.id
|
|
638
|
+
);
|
|
639
|
+
}) }) : null
|
|
640
|
+
] });
|
|
641
|
+
}
|
|
642
|
+
var MAX_VISIBLE_MESSAGES = 50;
|
|
643
|
+
function MessageView({
|
|
644
|
+
messages
|
|
645
|
+
}) {
|
|
646
|
+
const visibleMessages = messages.length > MAX_VISIBLE_MESSAGES ? messages.slice(-MAX_VISIBLE_MESSAGES) : messages;
|
|
647
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
|
|
648
|
+
messages.length > MAX_VISIBLE_MESSAGES ? /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, dimColor: true, children: [
|
|
649
|
+
" ",
|
|
650
|
+
"(",
|
|
651
|
+
messages.length - MAX_VISIBLE_MESSAGES,
|
|
652
|
+
" earlier messages hidden)"
|
|
653
|
+
] }) : null,
|
|
654
|
+
visibleMessages.map((msg) => /* @__PURE__ */ jsx(MessageItem, { message: msg }, msg.id))
|
|
655
|
+
] });
|
|
656
|
+
}
|
|
657
|
+
var MAX_VISIBLE_ITEMS = 8;
|
|
658
|
+
var SCROLL_UP_LABEL = "\u25B2";
|
|
659
|
+
var SCROLL_DOWN_LABEL = "\u25BC";
|
|
660
|
+
var SELECTED_LABEL = "\u25B8 ";
|
|
661
|
+
function AutocompletePopup({
|
|
662
|
+
items,
|
|
663
|
+
selectedIndex
|
|
664
|
+
}) {
|
|
665
|
+
if (items.length === 0) return null;
|
|
666
|
+
const totalItems = items.length;
|
|
667
|
+
const windowSize = Math.min(MAX_VISIBLE_ITEMS, totalItems);
|
|
668
|
+
let scrollOffset = 0;
|
|
669
|
+
if (selectedIndex >= windowSize) {
|
|
670
|
+
scrollOffset = selectedIndex - windowSize + 1;
|
|
671
|
+
}
|
|
672
|
+
scrollOffset = Math.max(
|
|
673
|
+
0,
|
|
674
|
+
Math.min(scrollOffset, totalItems - windowSize)
|
|
675
|
+
);
|
|
676
|
+
const visibleItems = items.slice(scrollOffset, scrollOffset + windowSize);
|
|
677
|
+
const hasMore = scrollOffset + windowSize < totalItems;
|
|
678
|
+
const hasLess = scrollOffset > 0;
|
|
679
|
+
return /* @__PURE__ */ jsxs(
|
|
680
|
+
Box,
|
|
681
|
+
{
|
|
682
|
+
flexDirection: "column",
|
|
683
|
+
borderStyle: "round",
|
|
684
|
+
borderColor: colors.status.active,
|
|
685
|
+
paddingX: 1,
|
|
686
|
+
marginBottom: 0,
|
|
687
|
+
children: [
|
|
688
|
+
hasLess ? /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, dimColor: true, children: [
|
|
689
|
+
" ",
|
|
690
|
+
SCROLL_UP_LABEL,
|
|
691
|
+
" ",
|
|
692
|
+
scrollOffset,
|
|
693
|
+
" above"
|
|
694
|
+
] }) : null,
|
|
695
|
+
visibleItems.map((item, visibleIndex) => {
|
|
696
|
+
const actualIndex = scrollOffset + visibleIndex;
|
|
697
|
+
const isSelected = actualIndex === selectedIndex;
|
|
698
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
699
|
+
/* @__PURE__ */ jsxs(
|
|
700
|
+
Text,
|
|
701
|
+
{
|
|
702
|
+
color: isSelected ? colors.status.active : colors.text.primary,
|
|
703
|
+
bold: isSelected,
|
|
704
|
+
children: [
|
|
705
|
+
isSelected ? SELECTED_LABEL : " ",
|
|
706
|
+
item.label
|
|
707
|
+
]
|
|
708
|
+
}
|
|
709
|
+
),
|
|
710
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.text.muted, dimColor: true, children: [
|
|
711
|
+
" ",
|
|
712
|
+
item.description
|
|
713
|
+
] })
|
|
714
|
+
] }, `${item.label}-${actualIndex}`);
|
|
715
|
+
}),
|
|
716
|
+
hasMore ? /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, dimColor: true, children: [
|
|
717
|
+
" ",
|
|
718
|
+
SCROLL_DOWN_LABEL,
|
|
719
|
+
" ",
|
|
720
|
+
totalItems - scrollOffset - windowSize,
|
|
721
|
+
" more"
|
|
722
|
+
] }) : null
|
|
723
|
+
]
|
|
724
|
+
}
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/ui/autocomplete-data.ts
|
|
729
|
+
var SLASH_COMMANDS = [
|
|
730
|
+
{ command: "/login", description: "Log in to a provider (interactive)" },
|
|
731
|
+
{ command: "/help", description: "Show available commands" },
|
|
732
|
+
{ command: "/model", description: "Select a model with provider-specific thinking options" },
|
|
733
|
+
{ command: "/role", description: "Switch role (planning, coding, review, testing, bugfix)" },
|
|
734
|
+
{ command: "/cost", description: "Show session cost breakdown" },
|
|
735
|
+
{ command: "/clear", description: "Clear conversation" },
|
|
736
|
+
{ command: "/compact", description: "Compress context" },
|
|
737
|
+
{ command: "/team list", description: "List active teams" },
|
|
738
|
+
{ command: "/team stop", description: "Deactivate team and return to single-pane" },
|
|
739
|
+
{ command: "/mcp list", description: "List connected MCP servers" },
|
|
740
|
+
{ command: "/mcp add", description: "Add an MCP server" },
|
|
741
|
+
{ command: "/skill list", description: "List available skills" },
|
|
742
|
+
{ command: "/panel", description: "Show swarm layout information" },
|
|
743
|
+
{ command: "/login status", description: "Show login status for all providers" },
|
|
744
|
+
{ command: "/login logout", description: "Log out of a provider" },
|
|
745
|
+
{ command: "/config get", description: "Get a configuration value" },
|
|
746
|
+
{ command: "/config set", description: "Set a configuration value" },
|
|
747
|
+
{ command: "/history", description: "List past conversations" },
|
|
748
|
+
{ command: "/resume", description: "Resume a past conversation by number or ID" },
|
|
749
|
+
{ command: "/quit", description: "Exit" },
|
|
750
|
+
{ command: "/exit", description: "Exit" }
|
|
751
|
+
];
|
|
752
|
+
var BUILTIN_CONTEXT_REFS = [
|
|
753
|
+
{ label: "@codebase", description: "Reference the entire codebase" },
|
|
754
|
+
{ label: "@git", description: "Reference git state" },
|
|
755
|
+
{ label: "@docs", description: "Reference project docs" },
|
|
756
|
+
{ label: "@web", description: "Reference web context" }
|
|
757
|
+
];
|
|
758
|
+
var dynamicFileRefs = [];
|
|
759
|
+
var CODE_REFS = [
|
|
760
|
+
{ label: "`src/", description: "Source directory" },
|
|
761
|
+
{ label: "`src/ui/", description: "UI components" },
|
|
762
|
+
{ label: "`src/auth/", description: "Authentication modules" },
|
|
763
|
+
{ label: "`src/providers/", description: "LLM provider adapters" },
|
|
764
|
+
{ label: "`src/teams/", description: "Team management" },
|
|
765
|
+
{ label: "`src/core/", description: "Core engine" },
|
|
766
|
+
{ label: "`src/tools/", description: "Tool implementations" },
|
|
767
|
+
{ label: "`src/types/", description: "Type definitions" },
|
|
768
|
+
{ label: "`src/storage/", description: "Storage layer" },
|
|
769
|
+
{ label: "`src/utils/", description: "Utility functions" }
|
|
770
|
+
];
|
|
771
|
+
var BUILTIN_SKILL_REFS = [
|
|
772
|
+
{ label: "$review", description: "Comprehensive code review" },
|
|
773
|
+
{ label: "$commit", description: "Smart git commit with message generation" },
|
|
774
|
+
{ label: "$plan", description: "Create implementation plan from requirements" },
|
|
775
|
+
{ label: "$debug", description: "Systematic debugging workflow" },
|
|
776
|
+
{ label: "$test", description: "Generate tests for code" },
|
|
777
|
+
{ label: "$refactor", description: "Refactoring with safety checks" }
|
|
778
|
+
];
|
|
779
|
+
var dynamicSkillRefs = BUILTIN_SKILL_REFS;
|
|
780
|
+
function registerDynamicSkills(skills) {
|
|
781
|
+
dynamicSkillRefs = skills.length > 0 ? skills : BUILTIN_SKILL_REFS;
|
|
782
|
+
}
|
|
783
|
+
function registerDynamicFileRefs(files) {
|
|
784
|
+
dynamicFileRefs = files;
|
|
785
|
+
}
|
|
786
|
+
function getAutocompleteItems(trigger, query) {
|
|
787
|
+
const normalizedQuery = query.toLowerCase();
|
|
788
|
+
switch (trigger) {
|
|
789
|
+
case "/": {
|
|
790
|
+
const filtered = SLASH_COMMANDS.filter((cmd) => cmd.command.toLowerCase().includes(normalizedQuery));
|
|
791
|
+
return filtered.map((cmd) => ({ label: cmd.command, description: cmd.description }));
|
|
792
|
+
}
|
|
793
|
+
case "@": {
|
|
794
|
+
const fileMatches = dynamicFileRefs.filter((ref) => ref.label.toLowerCase().includes(normalizedQuery));
|
|
795
|
+
const builtinMatches = BUILTIN_CONTEXT_REFS.filter((ref) => ref.label.toLowerCase().includes(normalizedQuery));
|
|
796
|
+
return [...fileMatches, ...builtinMatches];
|
|
797
|
+
}
|
|
798
|
+
case "`": {
|
|
799
|
+
return CODE_REFS.filter((ref) => ref.label.toLowerCase().includes(normalizedQuery));
|
|
800
|
+
}
|
|
801
|
+
case "$": {
|
|
802
|
+
return dynamicSkillRefs.filter((ref) => ref.label.toLowerCase().includes(normalizedQuery));
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// src/ui/input-utils.ts
|
|
808
|
+
function toCodePoints(value) {
|
|
809
|
+
return Array.from(value);
|
|
810
|
+
}
|
|
811
|
+
function codePointLength(value) {
|
|
812
|
+
return toCodePoints(value).length;
|
|
813
|
+
}
|
|
814
|
+
function sliceCodePoints(value, start, end) {
|
|
815
|
+
return toCodePoints(value).slice(start, end).join("");
|
|
816
|
+
}
|
|
817
|
+
function clampCursorOffset(value, offset) {
|
|
818
|
+
return Math.max(0, Math.min(offset, codePointLength(value)));
|
|
819
|
+
}
|
|
820
|
+
function insertTextAtCursor(value, cursorOffset, insertedText) {
|
|
821
|
+
const points = toCodePoints(value);
|
|
822
|
+
const insertion = toCodePoints(insertedText);
|
|
823
|
+
const safeOffset = clampCursorOffset(value, cursorOffset);
|
|
824
|
+
points.splice(safeOffset, 0, ...insertion);
|
|
825
|
+
return {
|
|
826
|
+
text: points.join(""),
|
|
827
|
+
cursorOffset: safeOffset + insertion.length
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function backspaceAtCursor(value, cursorOffset) {
|
|
831
|
+
const safeOffset = clampCursorOffset(value, cursorOffset);
|
|
832
|
+
if (safeOffset === 0) {
|
|
833
|
+
return { text: value, cursorOffset: safeOffset };
|
|
834
|
+
}
|
|
835
|
+
const points = toCodePoints(value);
|
|
836
|
+
points.splice(safeOffset - 1, 1);
|
|
837
|
+
return {
|
|
838
|
+
text: points.join(""),
|
|
839
|
+
cursorOffset: safeOffset - 1
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function deleteAtCursor(value, cursorOffset) {
|
|
843
|
+
const safeOffset = clampCursorOffset(value, cursorOffset);
|
|
844
|
+
const points = toCodePoints(value);
|
|
845
|
+
if (safeOffset >= points.length) {
|
|
846
|
+
return { text: value, cursorOffset: safeOffset };
|
|
847
|
+
}
|
|
848
|
+
points.splice(safeOffset, 1);
|
|
849
|
+
return {
|
|
850
|
+
text: points.join(""),
|
|
851
|
+
cursorOffset: safeOffset
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/ui/history-navigation.ts
|
|
856
|
+
function navigateHistoryState(state, nextIndex, defaultCursor) {
|
|
857
|
+
const cache = { ...state.cache };
|
|
858
|
+
const previousIndex = state.historyIndex;
|
|
859
|
+
cache[previousIndex] = {
|
|
860
|
+
text: state.currentInput,
|
|
861
|
+
cursorOffset: clampCursorOffset(state.currentInput, state.currentCursorOffset)
|
|
862
|
+
};
|
|
863
|
+
const saved = cache[nextIndex];
|
|
864
|
+
const isReturningToPrevious = nextIndex === -1 || nextIndex === state.previousHistoryIndex;
|
|
865
|
+
if (isReturningToPrevious && saved && saved.cursorOffset > 0 && saved.cursorOffset < codePointLength(saved.text)) {
|
|
866
|
+
return {
|
|
867
|
+
historyIndex: nextIndex,
|
|
868
|
+
previousHistoryIndex: previousIndex,
|
|
869
|
+
cache,
|
|
870
|
+
input: saved.text,
|
|
871
|
+
cursorOffset: saved.cursorOffset
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
if (nextIndex === -1) {
|
|
875
|
+
const text = saved?.text ?? "";
|
|
876
|
+
return {
|
|
877
|
+
historyIndex: nextIndex,
|
|
878
|
+
previousHistoryIndex: previousIndex,
|
|
879
|
+
cache,
|
|
880
|
+
input: text,
|
|
881
|
+
cursorOffset: defaultCursor === "start" ? 0 : codePointLength(text)
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
if (saved) {
|
|
885
|
+
return {
|
|
886
|
+
historyIndex: nextIndex,
|
|
887
|
+
previousHistoryIndex: previousIndex,
|
|
888
|
+
cache,
|
|
889
|
+
input: saved.text,
|
|
890
|
+
cursorOffset: defaultCursor === "start" ? 0 : codePointLength(saved.text)
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
const input = state.history[state.history.length - 1 - nextIndex] ?? "";
|
|
894
|
+
return {
|
|
895
|
+
historyIndex: nextIndex,
|
|
896
|
+
previousHistoryIndex: previousIndex,
|
|
897
|
+
cache,
|
|
898
|
+
input,
|
|
899
|
+
cursorOffset: defaultCursor === "start" ? 0 : codePointLength(input)
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
var MODE_ORDER = ["agent-swarm", "accept-edits", "chat"];
|
|
903
|
+
var MODE_DISPLAY = {
|
|
904
|
+
"agent-swarm": {
|
|
905
|
+
label: "swarm orchestrator",
|
|
906
|
+
icon: "\u25B6\u25B6",
|
|
907
|
+
color: BRAND_COLOR
|
|
908
|
+
},
|
|
909
|
+
"accept-edits": {
|
|
910
|
+
label: "guided edits",
|
|
911
|
+
icon: "\u2551\u2551",
|
|
912
|
+
color: "#EDD6DC"
|
|
913
|
+
},
|
|
914
|
+
chat: {
|
|
915
|
+
label: "direct chat",
|
|
916
|
+
icon: "\u25CB",
|
|
917
|
+
color: "#8D7176"
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
var TRIGGER_CHARS = /* @__PURE__ */ new Set(["/", "@", "`", "$"]);
|
|
921
|
+
function detectTrigger(input, cursorOffset) {
|
|
922
|
+
const safeOffset = clampCursorOffset(input, cursorOffset);
|
|
923
|
+
const beforeCursor = sliceCodePoints(input, 0, safeOffset);
|
|
924
|
+
if (beforeCursor.length === 0) return null;
|
|
925
|
+
if (beforeCursor[0] === "/") return { trigger: "/", query: beforeCursor, rangeStart: 0 };
|
|
926
|
+
if (beforeCursor[0] === "$") return { trigger: "$", query: beforeCursor, rangeStart: 0 };
|
|
927
|
+
const points = Array.from(beforeCursor);
|
|
928
|
+
for (let i = points.length - 1; i >= 0; i--) {
|
|
929
|
+
const ch = points[i];
|
|
930
|
+
if (ch === void 0) continue;
|
|
931
|
+
if (TRIGGER_CHARS.has(ch) && ch !== "/" && ch !== "$") {
|
|
932
|
+
if (i === 0 || points[i - 1] === " ") {
|
|
933
|
+
return {
|
|
934
|
+
trigger: ch,
|
|
935
|
+
query: points.slice(i).join(""),
|
|
936
|
+
rangeStart: i
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (ch === " ") break;
|
|
941
|
+
}
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
function InputBar({
|
|
945
|
+
onSubmit,
|
|
946
|
+
isProcessing,
|
|
947
|
+
placeholder,
|
|
948
|
+
onCancel,
|
|
949
|
+
initialHistory,
|
|
950
|
+
mode = "agent-swarm",
|
|
951
|
+
onModeChange
|
|
952
|
+
}) {
|
|
953
|
+
const [input, setInput] = useState("");
|
|
954
|
+
const [cursorOffset, setCursorOffset] = useState(0);
|
|
955
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
956
|
+
const [history, setHistory] = useState(
|
|
957
|
+
initialHistory ? [...initialHistory] : []
|
|
958
|
+
);
|
|
959
|
+
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
960
|
+
const inputRef = useRef(input);
|
|
961
|
+
const cursorOffsetRef = useRef(cursorOffset);
|
|
962
|
+
const historyRef = useRef(history);
|
|
963
|
+
const historyIndexRef = useRef(historyIndex);
|
|
964
|
+
const triggerStateRef = useRef(null);
|
|
965
|
+
const autocompleteItemsRef = useRef([]);
|
|
966
|
+
const isAutocompleteActiveRef = useRef(false);
|
|
967
|
+
const previousHistoryIndexRef = useRef(void 0);
|
|
968
|
+
const historyCacheRef = useRef({});
|
|
969
|
+
const setInputWithCursor = useCallback(
|
|
970
|
+
(nextInput, cursorPosition = "end") => {
|
|
971
|
+
const nextCursor = cursorPosition === "start" ? 0 : cursorPosition === "end" ? codePointLength(nextInput) : clampCursorOffset(nextInput, cursorPosition);
|
|
972
|
+
inputRef.current = nextInput;
|
|
973
|
+
cursorOffsetRef.current = nextCursor;
|
|
974
|
+
setInput(nextInput);
|
|
975
|
+
setCursorOffset(nextCursor);
|
|
976
|
+
setSelectedIndex(0);
|
|
977
|
+
},
|
|
978
|
+
[]
|
|
979
|
+
);
|
|
980
|
+
const setCursorPosition = useCallback(
|
|
981
|
+
(nextCursor) => {
|
|
982
|
+
const clamped = clampCursorOffset(inputRef.current, nextCursor);
|
|
983
|
+
cursorOffsetRef.current = clamped;
|
|
984
|
+
setCursorOffset(clamped);
|
|
985
|
+
},
|
|
986
|
+
[]
|
|
987
|
+
);
|
|
988
|
+
const resetHistoryNavigation = useCallback(() => {
|
|
989
|
+
historyIndexRef.current = -1;
|
|
990
|
+
setHistoryIndex(-1);
|
|
991
|
+
previousHistoryIndexRef.current = void 0;
|
|
992
|
+
historyCacheRef.current = {};
|
|
993
|
+
}, []);
|
|
994
|
+
const navigateHistory = useCallback(
|
|
995
|
+
(nextIndex, defaultCursor) => {
|
|
996
|
+
const result = navigateHistoryState(
|
|
997
|
+
{
|
|
998
|
+
history: historyRef.current,
|
|
999
|
+
historyIndex: historyIndexRef.current,
|
|
1000
|
+
previousHistoryIndex: previousHistoryIndexRef.current,
|
|
1001
|
+
cache: historyCacheRef.current,
|
|
1002
|
+
currentInput: inputRef.current,
|
|
1003
|
+
currentCursorOffset: cursorOffsetRef.current
|
|
1004
|
+
},
|
|
1005
|
+
nextIndex,
|
|
1006
|
+
defaultCursor
|
|
1007
|
+
);
|
|
1008
|
+
historyCacheRef.current = result.cache;
|
|
1009
|
+
historyIndexRef.current = result.historyIndex;
|
|
1010
|
+
previousHistoryIndexRef.current = result.previousHistoryIndex;
|
|
1011
|
+
setHistoryIndex(result.historyIndex);
|
|
1012
|
+
setInputWithCursor(result.input, result.cursorOffset);
|
|
1013
|
+
},
|
|
1014
|
+
[setInputWithCursor]
|
|
1015
|
+
);
|
|
1016
|
+
const applyAutocompleteSelection = useCallback(
|
|
1017
|
+
(selected, triggerMatch) => {
|
|
1018
|
+
const currentInput = inputRef.current;
|
|
1019
|
+
const currentCursorOffset = cursorOffsetRef.current;
|
|
1020
|
+
const before = sliceCodePoints(currentInput, 0, triggerMatch.rangeStart);
|
|
1021
|
+
const after = sliceCodePoints(currentInput, currentCursorOffset);
|
|
1022
|
+
const nextInput = `${before}${selected.label} ${after}`;
|
|
1023
|
+
setInputWithCursor(nextInput, codePointLength(before) + codePointLength(selected.label) + 1);
|
|
1024
|
+
},
|
|
1025
|
+
[setInputWithCursor]
|
|
1026
|
+
);
|
|
1027
|
+
useEffect(() => {
|
|
1028
|
+
if (initialHistory && initialHistory.length > 0) {
|
|
1029
|
+
setHistory((prev) => {
|
|
1030
|
+
if (prev.length === 0) return [...initialHistory];
|
|
1031
|
+
return prev;
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
}, [initialHistory]);
|
|
1035
|
+
useEffect(() => {
|
|
1036
|
+
inputRef.current = input;
|
|
1037
|
+
cursorOffsetRef.current = cursorOffset;
|
|
1038
|
+
historyRef.current = history;
|
|
1039
|
+
historyIndexRef.current = historyIndex;
|
|
1040
|
+
}, [cursorOffset, history, historyIndex, input]);
|
|
1041
|
+
const triggerState = useMemo(() => detectTrigger(input, cursorOffset), [cursorOffset, input]);
|
|
1042
|
+
const autocompleteItems = useMemo(() => {
|
|
1043
|
+
if (triggerState === null) return [];
|
|
1044
|
+
return getAutocompleteItems(triggerState.trigger, triggerState.query);
|
|
1045
|
+
}, [triggerState]);
|
|
1046
|
+
const isAutocompleteActive = autocompleteItems.length > 0;
|
|
1047
|
+
useEffect(() => {
|
|
1048
|
+
triggerStateRef.current = triggerState;
|
|
1049
|
+
autocompleteItemsRef.current = autocompleteItems;
|
|
1050
|
+
isAutocompleteActiveRef.current = isAutocompleteActive;
|
|
1051
|
+
}, [autocompleteItems, isAutocompleteActive, triggerState]);
|
|
1052
|
+
const appendToHistory = useCallback((entry) => {
|
|
1053
|
+
setHistory((prev) => {
|
|
1054
|
+
const next = [...prev.slice(-99), entry];
|
|
1055
|
+
historyRef.current = next;
|
|
1056
|
+
return next;
|
|
1057
|
+
});
|
|
1058
|
+
}, []);
|
|
1059
|
+
useInput((inputChar, key) => {
|
|
1060
|
+
const currentInput = inputRef.current;
|
|
1061
|
+
const currentCursorOffset = cursorOffsetRef.current;
|
|
1062
|
+
const currentHistoryIndex = historyIndexRef.current;
|
|
1063
|
+
const currentHistory = historyRef.current;
|
|
1064
|
+
const currentTriggerState = triggerStateRef.current;
|
|
1065
|
+
const currentAutocompleteItems = autocompleteItemsRef.current;
|
|
1066
|
+
const currentAutocompleteActive = isAutocompleteActiveRef.current;
|
|
1067
|
+
if (isProcessing) {
|
|
1068
|
+
if (key.escape && onCancel) {
|
|
1069
|
+
onCancel();
|
|
1070
|
+
}
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
if (key.leftArrow) {
|
|
1074
|
+
setCursorPosition(currentCursorOffset - 1);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
if (key.rightArrow) {
|
|
1078
|
+
setCursorPosition(currentCursorOffset + 1);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (key.tab && key.shift) {
|
|
1082
|
+
if (onModeChange) {
|
|
1083
|
+
const idx = MODE_ORDER.indexOf(mode);
|
|
1084
|
+
const next = MODE_ORDER[(idx + 1) % MODE_ORDER.length];
|
|
1085
|
+
if (next) {
|
|
1086
|
+
onModeChange(next);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
if (key.upArrow) {
|
|
1092
|
+
if (currentHistoryIndex !== -1) {
|
|
1093
|
+
if (currentHistoryIndex < currentHistory.length - 1) {
|
|
1094
|
+
navigateHistory(currentHistoryIndex + 1, "end");
|
|
1095
|
+
}
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (currentAutocompleteActive) {
|
|
1099
|
+
setSelectedIndex(
|
|
1100
|
+
(prev) => prev > 0 ? prev - 1 : currentAutocompleteItems.length - 1
|
|
1101
|
+
);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
if (currentHistory.length > 0) {
|
|
1105
|
+
navigateHistory(0, "end");
|
|
1106
|
+
}
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
if (key.downArrow) {
|
|
1110
|
+
if (currentHistoryIndex !== -1) {
|
|
1111
|
+
navigateHistory(currentHistoryIndex - 1, "end");
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
if (currentAutocompleteActive) {
|
|
1115
|
+
setSelectedIndex(
|
|
1116
|
+
(prev) => prev < currentAutocompleteItems.length - 1 ? prev + 1 : 0
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
if (currentAutocompleteActive) {
|
|
1122
|
+
if (key.tab) {
|
|
1123
|
+
const selected = currentAutocompleteItems[selectedIndex];
|
|
1124
|
+
if (selected && currentTriggerState) {
|
|
1125
|
+
applyAutocompleteSelection(selected, currentTriggerState);
|
|
1126
|
+
}
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (key.escape) {
|
|
1130
|
+
const result = insertTextAtCursor(currentInput, currentCursorOffset, " ");
|
|
1131
|
+
setInputWithCursor(result.text, result.cursorOffset);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (key.return) {
|
|
1136
|
+
if (currentHistoryIndex !== -1 && currentInput.trim().length > 0) {
|
|
1137
|
+
appendToHistory(currentInput.trim());
|
|
1138
|
+
onSubmit(currentInput.trim());
|
|
1139
|
+
setInputWithCursor("");
|
|
1140
|
+
resetHistoryNavigation();
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (currentAutocompleteActive) {
|
|
1144
|
+
const selected = currentAutocompleteItems[selectedIndex];
|
|
1145
|
+
if (selected && currentTriggerState) {
|
|
1146
|
+
if (currentTriggerState.trigger === "/" && currentHistoryIndex === -1) {
|
|
1147
|
+
appendToHistory(selected.label.trim());
|
|
1148
|
+
onSubmit(selected.label.trim());
|
|
1149
|
+
setInputWithCursor("");
|
|
1150
|
+
} else {
|
|
1151
|
+
applyAutocompleteSelection(selected, currentTriggerState);
|
|
1152
|
+
}
|
|
1153
|
+
resetHistoryNavigation();
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (currentInput.trim().length > 0) {
|
|
1158
|
+
appendToHistory(currentInput.trim());
|
|
1159
|
+
onSubmit(currentInput.trim());
|
|
1160
|
+
setInputWithCursor("");
|
|
1161
|
+
resetHistoryNavigation();
|
|
1162
|
+
}
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
if (key.backspace || key.delete || key.ctrl && inputChar === "h") {
|
|
1166
|
+
const result = backspaceAtCursor(currentInput, currentCursorOffset);
|
|
1167
|
+
setInputWithCursor(result.text, result.cursorOffset);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if (key.ctrl && inputChar === "d" && currentInput.length > 0) {
|
|
1171
|
+
const result = deleteAtCursor(currentInput, currentCursorOffset);
|
|
1172
|
+
setInputWithCursor(result.text, result.cursorOffset);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
if (key.ctrl && inputChar === "c") {
|
|
1176
|
+
process.exit(0);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
if (key.ctrl && inputChar === "l") return;
|
|
1180
|
+
if (!key.ctrl && !key.meta && inputChar) {
|
|
1181
|
+
const result = insertTextAtCursor(currentInput, currentCursorOffset, inputChar);
|
|
1182
|
+
setInputWithCursor(result.text, result.cursorOffset);
|
|
1183
|
+
}
|
|
1184
|
+
});
|
|
1185
|
+
const modeInfo = MODE_DISPLAY[mode];
|
|
1186
|
+
const borderColor = isProcessing ? colors.border.dim : modeInfo.color;
|
|
1187
|
+
const cursorChar = sliceCodePoints(input, cursorOffset, cursorOffset + 1);
|
|
1188
|
+
const beforeCursor = sliceCodePoints(input, 0, cursorOffset);
|
|
1189
|
+
const afterCursor = cursorChar.length > 0 ? sliceCodePoints(input, cursorOffset + 1) : "";
|
|
1190
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1191
|
+
isAutocompleteActive ? /* @__PURE__ */ jsx(AutocompletePopup, { items: autocompleteItems, selectedIndex }) : null,
|
|
1192
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
1193
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.border.dim, children: [
|
|
1194
|
+
"\u2500".repeat(4),
|
|
1195
|
+
" "
|
|
1196
|
+
] }),
|
|
1197
|
+
/* @__PURE__ */ jsxs(Text, { color: modeInfo.color, bold: true, children: [
|
|
1198
|
+
modeInfo.icon,
|
|
1199
|
+
" "
|
|
1200
|
+
] }),
|
|
1201
|
+
/* @__PURE__ */ jsx(Text, { color: modeInfo.color, bold: true, children: modeInfo.label }),
|
|
1202
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: " (shift+tab to cycle)" })
|
|
1203
|
+
] }),
|
|
1204
|
+
/* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor, paddingX: 1, children: [
|
|
1205
|
+
/* @__PURE__ */ jsxs(Text, { color: modeInfo.color, bold: true, children: [
|
|
1206
|
+
"\u276F",
|
|
1207
|
+
" "
|
|
1208
|
+
] }),
|
|
1209
|
+
isProcessing ? /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: onCancel ? "esc to cancel" : "Processing\u2026" }) : input.length > 0 ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
1210
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.primary, children: beforeCursor }),
|
|
1211
|
+
cursorChar.length > 0 ? /* @__PURE__ */ jsx(Text, { color: colors.text.primary, inverse: true, children: cursorChar }) : /* @__PURE__ */ jsx(Text, { color: modeInfo.color, children: "\u2588" }),
|
|
1212
|
+
afterCursor.length > 0 ? /* @__PURE__ */ jsx(Text, { color: colors.text.primary, children: afterCursor }) : null
|
|
1213
|
+
] }) : /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, children: [
|
|
1214
|
+
placeholder ?? "Type a message\u2026",
|
|
1215
|
+
/* @__PURE__ */ jsx(Text, { color: modeInfo.color, children: "\u2588" })
|
|
1216
|
+
] })
|
|
1217
|
+
] })
|
|
1218
|
+
] });
|
|
1219
|
+
}
|
|
1220
|
+
function shortModelLabel(model) {
|
|
1221
|
+
if (model.includes("opus")) return "Opus 4.6";
|
|
1222
|
+
if (model.includes("sonnet")) return "Sonnet 4.6";
|
|
1223
|
+
if (model.includes("haiku")) return "Haiku 4.5";
|
|
1224
|
+
if (model.includes("gpt-5.2-mini")) return "GPT-5.2m";
|
|
1225
|
+
if (model.includes("gpt-5.2")) return "GPT-5.2";
|
|
1226
|
+
if (model.includes("o3")) return "o3";
|
|
1227
|
+
if (model.includes("gemini") && model.includes("pro")) return "Gem Pro";
|
|
1228
|
+
if (model.includes("gemini") && model.includes("flash")) return "Gem Flash";
|
|
1229
|
+
if (model.includes("kimi") || model.includes("k2")) return "Kimi";
|
|
1230
|
+
return model;
|
|
1231
|
+
}
|
|
1232
|
+
var SEP = " \u2502 ";
|
|
1233
|
+
function StatusBar({
|
|
1234
|
+
model,
|
|
1235
|
+
role,
|
|
1236
|
+
tokenCount,
|
|
1237
|
+
cost,
|
|
1238
|
+
gitBranch,
|
|
1239
|
+
gitChanges
|
|
1240
|
+
}) {
|
|
1241
|
+
return /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: colors.border.dim, paddingX: 1, children: [
|
|
1242
|
+
/* @__PURE__ */ jsxs(Text, { color: BRAND_COLOR, bold: true, children: [
|
|
1243
|
+
"\u25C6",
|
|
1244
|
+
" "
|
|
1245
|
+
] }),
|
|
1246
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.active, bold: true, children: "Aemeath Agent Swarm" }),
|
|
1247
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: SEP }),
|
|
1248
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.warning, bold: true, children: shortModelLabel(model) }),
|
|
1249
|
+
role ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1250
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: SEP }),
|
|
1251
|
+
/* @__PURE__ */ jsx(Text, { color: colors.role.tool, children: role })
|
|
1252
|
+
] }) : null,
|
|
1253
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: SEP }),
|
|
1254
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.text.secondary, children: [
|
|
1255
|
+
tokenCount,
|
|
1256
|
+
" tok"
|
|
1257
|
+
] }),
|
|
1258
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: SEP }),
|
|
1259
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.success, children: cost }),
|
|
1260
|
+
gitBranch ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1261
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: SEP }),
|
|
1262
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.status.info, children: [
|
|
1263
|
+
"\u2387",
|
|
1264
|
+
" ",
|
|
1265
|
+
gitBranch,
|
|
1266
|
+
gitChanges !== void 0 && gitChanges > 0 ? ` \xB1${gitChanges}` : ""
|
|
1267
|
+
] })
|
|
1268
|
+
] }) : null
|
|
1269
|
+
] });
|
|
1270
|
+
}
|
|
1271
|
+
var THINKING_PHRASES = [
|
|
1272
|
+
"Thinking",
|
|
1273
|
+
"Analyzing",
|
|
1274
|
+
"Reasoning",
|
|
1275
|
+
"Processing",
|
|
1276
|
+
"Understanding",
|
|
1277
|
+
"Considering",
|
|
1278
|
+
"Evaluating"
|
|
1279
|
+
];
|
|
1280
|
+
var PHRASE_CYCLE_MS = 2500;
|
|
1281
|
+
function ThinkingIndicator({
|
|
1282
|
+
activity,
|
|
1283
|
+
isStreaming,
|
|
1284
|
+
modelName,
|
|
1285
|
+
startTime
|
|
1286
|
+
}) {
|
|
1287
|
+
const tick = useAnimationTick(1e3);
|
|
1288
|
+
const elapsed = startTime === void 0 ? 0 : Math.max(0, Date.now() - startTime);
|
|
1289
|
+
const phraseIndex = Math.floor(elapsed / PHRASE_CYCLE_MS) % THINKING_PHRASES.length;
|
|
1290
|
+
const elapsedStr = useMemo(() => {
|
|
1291
|
+
const secs = Math.floor(elapsed / 1e3);
|
|
1292
|
+
if (secs < 1) return "";
|
|
1293
|
+
if (secs < 60) return `${secs}s`;
|
|
1294
|
+
const mins = Math.floor(secs / 60);
|
|
1295
|
+
return `${mins}m${secs % 60}s`;
|
|
1296
|
+
}, [elapsed]);
|
|
1297
|
+
const dotCount = tick % 4;
|
|
1298
|
+
const dots = ".".repeat(dotCount);
|
|
1299
|
+
const phrase = THINKING_PHRASES[phraseIndex] ?? "Thinking";
|
|
1300
|
+
const displayText = activity ? activity : isStreaming ? "Streaming response" : `${phrase}${dots}`;
|
|
1301
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1302
|
+
modelName ? /* @__PURE__ */ jsxs(Box, { children: [
|
|
1303
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.role.assistant, bold: true, children: [
|
|
1304
|
+
"\u2726",
|
|
1305
|
+
" "
|
|
1306
|
+
] }),
|
|
1307
|
+
/* @__PURE__ */ jsx(Text, { color: colors.role.assistant, bold: true, children: modelName })
|
|
1308
|
+
] }) : null,
|
|
1309
|
+
/* @__PURE__ */ jsxs(Box, { marginLeft: 2, children: [
|
|
1310
|
+
/* @__PURE__ */ jsx(
|
|
1311
|
+
GradientSpinner,
|
|
1312
|
+
{
|
|
1313
|
+
variant: activity ? "braille" : "dots",
|
|
1314
|
+
label: displayText,
|
|
1315
|
+
labelColor: activity ? colors.text.secondary : colors.text.muted
|
|
1316
|
+
}
|
|
1317
|
+
),
|
|
1318
|
+
elapsedStr ? /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, children: [
|
|
1319
|
+
" (",
|
|
1320
|
+
elapsedStr,
|
|
1321
|
+
")"
|
|
1322
|
+
] }) : null
|
|
1323
|
+
] }),
|
|
1324
|
+
/* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, dimColor: true, children: [
|
|
1325
|
+
" ",
|
|
1326
|
+
"esc to cancel"
|
|
1327
|
+
] }) })
|
|
1328
|
+
] });
|
|
1329
|
+
}
|
|
1330
|
+
var MASCOT_LINES = [
|
|
1331
|
+
" :- ",
|
|
1332
|
+
" :::::::: ",
|
|
1333
|
+
" .:.:::.:::: ",
|
|
1334
|
+
" ::--:::-:: :: ",
|
|
1335
|
+
" -:##**%%:- ",
|
|
1336
|
+
" :=+==++: : ",
|
|
1337
|
+
" ::==++=-:. ",
|
|
1338
|
+
"-:-=*:----:=-::*==:-",
|
|
1339
|
+
" = -::::- - "
|
|
1340
|
+
];
|
|
1341
|
+
var LOGO_LINES = [
|
|
1342
|
+
" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
1343
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2554\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
1344
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551",
|
|
1345
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u2554\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551",
|
|
1346
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2554\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
1347
|
+
"\u2554\u2550\u255D \u2554\u2550\u255D\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u2554\u2550\u255D \u2554\u2550\u255D\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u2554\u2550\u255D \u2554\u2550\u255D \u2554\u2550\u255D \u2554\u2550\u255D \u2554\u2550\u255D"
|
|
1348
|
+
];
|
|
1349
|
+
var TIPS = [
|
|
1350
|
+
{ key: "/help", desc: "Show all commands" },
|
|
1351
|
+
{ key: "/model", desc: "Switch AI model" },
|
|
1352
|
+
{ key: "Shift+Tab", desc: "Cycle swarm/edit/chat modes" },
|
|
1353
|
+
{ key: "Tab", desc: "Autocomplete or focus next agent" },
|
|
1354
|
+
{ key: "@", desc: "Reference project files" },
|
|
1355
|
+
{ key: "$skill", desc: "Invoke skills" }
|
|
1356
|
+
];
|
|
1357
|
+
var TIP_ROWS = [TIPS.slice(0, 2), TIPS.slice(2, 4), TIPS.slice(4)];
|
|
1358
|
+
function WelcomeScreen({
|
|
1359
|
+
version
|
|
1360
|
+
}) {
|
|
1361
|
+
return /* @__PURE__ */ jsxs(
|
|
1362
|
+
Box,
|
|
1363
|
+
{
|
|
1364
|
+
flexDirection: "column",
|
|
1365
|
+
flexGrow: 1,
|
|
1366
|
+
paddingX: 2,
|
|
1367
|
+
justifyContent: "center",
|
|
1368
|
+
alignItems: "center",
|
|
1369
|
+
marginBottom: 1,
|
|
1370
|
+
children: [
|
|
1371
|
+
/* @__PURE__ */ jsxs(Box, { alignItems: "center", children: [
|
|
1372
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", marginRight: 2, children: MASCOT_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: BRAND_COLOR, children: line }, `m${i}`)) }),
|
|
1373
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", children: LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: BRAND_COLOR, children: line }, i)) })
|
|
1374
|
+
] }),
|
|
1375
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 0, children: [
|
|
1376
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.border.active, children: [
|
|
1377
|
+
"\u2500".repeat(16),
|
|
1378
|
+
" "
|
|
1379
|
+
] }),
|
|
1380
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.accent, bold: true, children: "Aemeath Agent Swarm" }),
|
|
1381
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.border.active, children: [
|
|
1382
|
+
" ",
|
|
1383
|
+
"\u2500".repeat(16)
|
|
1384
|
+
] })
|
|
1385
|
+
] }),
|
|
1386
|
+
version ? /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, children: [
|
|
1387
|
+
"v",
|
|
1388
|
+
version
|
|
1389
|
+
] }) }) : null,
|
|
1390
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1391
|
+
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.text.secondary, bold: true, children: "Quick Start" }) }),
|
|
1392
|
+
TIP_ROWS.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { children: row.map((tip, tipIndex) => /* @__PURE__ */ jsxs(
|
|
1393
|
+
Box,
|
|
1394
|
+
{
|
|
1395
|
+
flexDirection: "column",
|
|
1396
|
+
width: 40,
|
|
1397
|
+
marginRight: tipIndex < row.length - 1 ? 3 : 0,
|
|
1398
|
+
children: [
|
|
1399
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.active, bold: true, children: tip.key }),
|
|
1400
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: tip.desc })
|
|
1401
|
+
]
|
|
1402
|
+
},
|
|
1403
|
+
tip.key
|
|
1404
|
+
)) }, `tip-row-${rowIndex}`))
|
|
1405
|
+
] })
|
|
1406
|
+
]
|
|
1407
|
+
}
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
function shortModelName2(model) {
|
|
1411
|
+
if (model.includes("opus")) return "Opus 4.6";
|
|
1412
|
+
if (model.includes("sonnet")) return "Sonnet 4.6";
|
|
1413
|
+
if (model.includes("haiku")) return "Haiku 4.5";
|
|
1414
|
+
if (model.includes("gpt-5.2-mini")) return "GPT-5.2m";
|
|
1415
|
+
if (model.includes("gpt-5.2")) return "GPT-5.2";
|
|
1416
|
+
if (model.includes("o3")) return "o3";
|
|
1417
|
+
if (model.includes("gemini") && model.includes("pro")) return "Gem Pro";
|
|
1418
|
+
if (model.includes("gemini") && model.includes("flash")) return "Gem Flash";
|
|
1419
|
+
if (model.includes("kimi") || model.includes("k2")) return "Kimi";
|
|
1420
|
+
return model;
|
|
1421
|
+
}
|
|
1422
|
+
function tailLines(text, maxLines) {
|
|
1423
|
+
const lines = text.split("\n");
|
|
1424
|
+
if (lines.length <= maxLines) return text;
|
|
1425
|
+
return lines.slice(-maxLines).join("\n");
|
|
1426
|
+
}
|
|
1427
|
+
function SinglePane({
|
|
1428
|
+
messages,
|
|
1429
|
+
isProcessing,
|
|
1430
|
+
onSubmit,
|
|
1431
|
+
onCancel,
|
|
1432
|
+
model,
|
|
1433
|
+
role,
|
|
1434
|
+
tokenCount,
|
|
1435
|
+
cost,
|
|
1436
|
+
gitBranch,
|
|
1437
|
+
gitChanges,
|
|
1438
|
+
streamingContent,
|
|
1439
|
+
activity,
|
|
1440
|
+
initialHistory,
|
|
1441
|
+
mode,
|
|
1442
|
+
onModeChange
|
|
1443
|
+
}) {
|
|
1444
|
+
const processingStartRef = useRef(void 0);
|
|
1445
|
+
if (isProcessing && processingStartRef.current === void 0) {
|
|
1446
|
+
processingStartRef.current = Date.now();
|
|
1447
|
+
} else if (!isProcessing) {
|
|
1448
|
+
processingStartRef.current = void 0;
|
|
1449
|
+
}
|
|
1450
|
+
const hasContent = streamingContent !== void 0 && streamingContent.length > 0;
|
|
1451
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: "100%", children: [
|
|
1452
|
+
/* @__PURE__ */ jsx(
|
|
1453
|
+
StatusBar,
|
|
1454
|
+
{
|
|
1455
|
+
model,
|
|
1456
|
+
role,
|
|
1457
|
+
tokenCount,
|
|
1458
|
+
cost,
|
|
1459
|
+
gitBranch,
|
|
1460
|
+
gitChanges
|
|
1461
|
+
}
|
|
1462
|
+
),
|
|
1463
|
+
messages.length === 0 && !isProcessing ? (
|
|
1464
|
+
/* ── Welcome screen ────────────────────────────────── */
|
|
1465
|
+
/* @__PURE__ */ jsx(
|
|
1466
|
+
WelcomeScreen,
|
|
1467
|
+
{
|
|
1468
|
+
version: PACKAGE_VERSION
|
|
1469
|
+
}
|
|
1470
|
+
)
|
|
1471
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1472
|
+
/* @__PURE__ */ jsx(MessageView, { messages }),
|
|
1473
|
+
isProcessing ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, marginBottom: 1, children: [
|
|
1474
|
+
hasContent ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
1475
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: colors.role.assistant, bold: true, children: [
|
|
1476
|
+
"\u2726",
|
|
1477
|
+
" ",
|
|
1478
|
+
shortModelName2(model)
|
|
1479
|
+
] }) }),
|
|
1480
|
+
/* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(MarkdownContent, { content: tailLines(streamingContent, 12) }) })
|
|
1481
|
+
] }) : null,
|
|
1482
|
+
/* @__PURE__ */ jsx(
|
|
1483
|
+
ThinkingIndicator,
|
|
1484
|
+
{
|
|
1485
|
+
activity,
|
|
1486
|
+
isStreaming: hasContent,
|
|
1487
|
+
modelName: hasContent ? void 0 : shortModelName2(model),
|
|
1488
|
+
startTime: processingStartRef.current
|
|
1489
|
+
}
|
|
1490
|
+
)
|
|
1491
|
+
] }) : null
|
|
1492
|
+
] }),
|
|
1493
|
+
/* @__PURE__ */ jsx(
|
|
1494
|
+
InputBar,
|
|
1495
|
+
{
|
|
1496
|
+
onSubmit,
|
|
1497
|
+
isProcessing,
|
|
1498
|
+
onCancel,
|
|
1499
|
+
initialHistory,
|
|
1500
|
+
mode,
|
|
1501
|
+
onModeChange
|
|
1502
|
+
}
|
|
1503
|
+
)
|
|
1504
|
+
] });
|
|
1505
|
+
}
|
|
1506
|
+
function getStatusColor(status) {
|
|
1507
|
+
switch (status) {
|
|
1508
|
+
case "active":
|
|
1509
|
+
return colors.status.success;
|
|
1510
|
+
case "idle":
|
|
1511
|
+
return colors.status.warning;
|
|
1512
|
+
case "error":
|
|
1513
|
+
return colors.status.error;
|
|
1514
|
+
case "shutdown":
|
|
1515
|
+
return colors.text.muted;
|
|
1516
|
+
default:
|
|
1517
|
+
return colors.text.primary;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
function shortModelLabel2(model) {
|
|
1521
|
+
if (model.includes("opus")) return "Opus";
|
|
1522
|
+
if (model.includes("sonnet")) return "Sonnet";
|
|
1523
|
+
if (model.includes("haiku")) return "Haiku";
|
|
1524
|
+
if (model.includes("gpt-5")) return "GPT-5";
|
|
1525
|
+
if (model.includes("gemini") && model.includes("pro")) return "Gem-Pro";
|
|
1526
|
+
if (model.includes("gemini") && model.includes("flash")) return "Gem-Flash";
|
|
1527
|
+
if (model.includes("kimi") || model.includes("k2")) return "Kimi";
|
|
1528
|
+
const parts = model.split("-");
|
|
1529
|
+
return parts[parts.length - 1] ?? model.slice(0, 8);
|
|
1530
|
+
}
|
|
1531
|
+
function statusIndicator(status) {
|
|
1532
|
+
switch (status) {
|
|
1533
|
+
case "active":
|
|
1534
|
+
return "\u25CF";
|
|
1535
|
+
case "idle":
|
|
1536
|
+
return "\u25CB";
|
|
1537
|
+
case "error":
|
|
1538
|
+
return "\u2716";
|
|
1539
|
+
default:
|
|
1540
|
+
return "\u2500";
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
function classifyLine(line) {
|
|
1544
|
+
if (line.length === 0) return "empty";
|
|
1545
|
+
if (line.startsWith("\u2699") || line.startsWith("\u2699\uFE0F")) return "tool";
|
|
1546
|
+
if (line.startsWith(" \u2192")) return "result";
|
|
1547
|
+
if (line.startsWith("Error:") || line.startsWith("Stream error:")) return "error";
|
|
1548
|
+
return "text";
|
|
1549
|
+
}
|
|
1550
|
+
function renderLine(line, key) {
|
|
1551
|
+
const type = classifyLine(line);
|
|
1552
|
+
switch (type) {
|
|
1553
|
+
case "tool":
|
|
1554
|
+
return /* @__PURE__ */ jsx(Text, { color: colors.role.tool, children: line }, key);
|
|
1555
|
+
case "result":
|
|
1556
|
+
return /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: line }, key);
|
|
1557
|
+
case "error":
|
|
1558
|
+
return /* @__PURE__ */ jsx(Text, { color: colors.status.error, bold: true, children: line }, key);
|
|
1559
|
+
case "empty":
|
|
1560
|
+
return /* @__PURE__ */ jsx(Text, { children: " " }, key);
|
|
1561
|
+
default:
|
|
1562
|
+
return /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: line }, key);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
function getVisibleLines(output, maxLines) {
|
|
1566
|
+
const lines = output.split("\n");
|
|
1567
|
+
const startIndex = Math.max(0, lines.length - maxLines);
|
|
1568
|
+
return lines.slice(startIndex).map((content, index) => ({
|
|
1569
|
+
absoluteIndex: startIndex + index,
|
|
1570
|
+
content
|
|
1571
|
+
}));
|
|
1572
|
+
}
|
|
1573
|
+
function getLastActivity(output) {
|
|
1574
|
+
const lines = output.split("\n");
|
|
1575
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
1576
|
+
const line = lines[index];
|
|
1577
|
+
if (!line) continue;
|
|
1578
|
+
const trimmed = line.trim();
|
|
1579
|
+
if (trimmed.length === 0) continue;
|
|
1580
|
+
const type = classifyLine(trimmed);
|
|
1581
|
+
if (type === "tool" || type === "result" || type === "error") {
|
|
1582
|
+
return trimmed;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
return void 0;
|
|
1586
|
+
}
|
|
1587
|
+
function AgentFrameComponent({
|
|
1588
|
+
agent,
|
|
1589
|
+
output,
|
|
1590
|
+
titlePrefix,
|
|
1591
|
+
isFocused,
|
|
1592
|
+
maxLines
|
|
1593
|
+
}) {
|
|
1594
|
+
const previewLines = React11.useMemo(
|
|
1595
|
+
() => getVisibleLines(output, maxLines),
|
|
1596
|
+
[maxLines, output]
|
|
1597
|
+
);
|
|
1598
|
+
const lastActivity = React11.useMemo(
|
|
1599
|
+
() => getLastActivity(output),
|
|
1600
|
+
[output]
|
|
1601
|
+
);
|
|
1602
|
+
return /* @__PURE__ */ jsxs(
|
|
1603
|
+
Box,
|
|
1604
|
+
{
|
|
1605
|
+
flexDirection: "column",
|
|
1606
|
+
borderStyle: isFocused ? "round" : "single",
|
|
1607
|
+
borderColor: isFocused ? colors.status.active : colors.border.dim,
|
|
1608
|
+
paddingX: 1,
|
|
1609
|
+
paddingY: 0,
|
|
1610
|
+
marginBottom: 1,
|
|
1611
|
+
children: [
|
|
1612
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
1613
|
+
/* @__PURE__ */ jsxs(Text, { color: getStatusColor(agent.status), children: [
|
|
1614
|
+
statusIndicator(agent.status),
|
|
1615
|
+
" "
|
|
1616
|
+
] }),
|
|
1617
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.status.active, bold: true, children: [
|
|
1618
|
+
titlePrefix,
|
|
1619
|
+
" ",
|
|
1620
|
+
agent.config.name
|
|
1621
|
+
] }),
|
|
1622
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.text.muted, children: [
|
|
1623
|
+
" ",
|
|
1624
|
+
"\u2014",
|
|
1625
|
+
" ",
|
|
1626
|
+
agent.config.role,
|
|
1627
|
+
" ",
|
|
1628
|
+
"\xB7",
|
|
1629
|
+
" ",
|
|
1630
|
+
shortModelLabel2(agent.config.model)
|
|
1631
|
+
] })
|
|
1632
|
+
] }),
|
|
1633
|
+
lastActivity ? /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: colors.text.muted, dimColor: true, children: lastActivity.length > 72 ? `${lastActivity.slice(0, 72)}\u2026` : lastActivity }) }) : null,
|
|
1634
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: previewLines.length > 0 ? previewLines.map((line) => renderLine(
|
|
1635
|
+
line.content,
|
|
1636
|
+
`${agent.config.agentId}-${line.absoluteIndex}`
|
|
1637
|
+
)) : /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: agent.status === "active" ? "Initializing\u2026" : "Waiting for work\u2026" }) }),
|
|
1638
|
+
agent.status === "active" ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: titlePrefix === "Master" ? "Coordinating\u2026" : "Working\u2026" }) }) : null
|
|
1639
|
+
]
|
|
1640
|
+
}
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
var AgentFrame = React11.memo(
|
|
1644
|
+
AgentFrameComponent,
|
|
1645
|
+
(previousProps, nextProps) => previousProps.agent === nextProps.agent && previousProps.output === nextProps.output && previousProps.titlePrefix === nextProps.titlePrefix && previousProps.isFocused === nextProps.isFocused && previousProps.maxLines === nextProps.maxLines
|
|
1646
|
+
);
|
|
1647
|
+
function SplitPanel({
|
|
1648
|
+
agents,
|
|
1649
|
+
activeAgentIndex,
|
|
1650
|
+
onSelectAgent,
|
|
1651
|
+
agentOutputs
|
|
1652
|
+
}) {
|
|
1653
|
+
useInput((input, key) => {
|
|
1654
|
+
if (key.tab) {
|
|
1655
|
+
onSelectAgent((activeAgentIndex + 1) % agents.length);
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
const numericKey = Number.parseInt(input, 10);
|
|
1659
|
+
if (!Number.isNaN(numericKey) && numericKey >= 1 && numericKey <= agents.length && key.ctrl) {
|
|
1660
|
+
onSelectAgent(numericKey - 1);
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
const [masterAgent, ...workerAgents] = agents;
|
|
1664
|
+
const focusedAgent = agents[activeAgentIndex];
|
|
1665
|
+
if (!masterAgent) {
|
|
1666
|
+
return /* @__PURE__ */ jsx(Box, { flexGrow: 1, borderStyle: "round", borderColor: colors.border.dim, paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "No agents active" }) });
|
|
1667
|
+
}
|
|
1668
|
+
const masterOutput = agentOutputs.get(masterAgent.config.agentId) ?? "";
|
|
1669
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
1670
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
1671
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "Focus: " }),
|
|
1672
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.active, bold: true, children: focusedAgent?.config.name ?? masterAgent.config.name }),
|
|
1673
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: " " }),
|
|
1674
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.active, bold: true, children: "Tab" }),
|
|
1675
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: " cycle " }),
|
|
1676
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.active, bold: true, children: "Ctrl+1" }),
|
|
1677
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "-" }),
|
|
1678
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.active, bold: true, children: agents.length }),
|
|
1679
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: " jump " }),
|
|
1680
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.warning, bold: true, children: "/team stop" }),
|
|
1681
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: " exit swarm" })
|
|
1682
|
+
] }),
|
|
1683
|
+
/* @__PURE__ */ jsxs(Box, { flexGrow: 1, children: [
|
|
1684
|
+
/* @__PURE__ */ jsx(Box, { flexBasis: "50%", flexDirection: "column", paddingRight: 1, children: /* @__PURE__ */ jsx(
|
|
1685
|
+
AgentFrame,
|
|
1686
|
+
{
|
|
1687
|
+
agent: masterAgent,
|
|
1688
|
+
output: masterOutput,
|
|
1689
|
+
titlePrefix: "Master",
|
|
1690
|
+
isFocused: activeAgentIndex === 0,
|
|
1691
|
+
maxLines: 28
|
|
1692
|
+
}
|
|
1693
|
+
) }),
|
|
1694
|
+
/* @__PURE__ */ jsx(Box, { flexBasis: "50%", flexDirection: "column", paddingLeft: 1, children: workerAgents.length > 0 ? workerAgents.map((agent, index) => /* @__PURE__ */ jsx(
|
|
1695
|
+
AgentFrame,
|
|
1696
|
+
{
|
|
1697
|
+
agent,
|
|
1698
|
+
output: agentOutputs.get(agent.config.agentId) ?? "",
|
|
1699
|
+
titlePrefix: "Worker",
|
|
1700
|
+
isFocused: activeAgentIndex === index + 1,
|
|
1701
|
+
maxLines: 8
|
|
1702
|
+
},
|
|
1703
|
+
agent.config.agentId
|
|
1704
|
+
)) : /* @__PURE__ */ jsx(Box, { borderStyle: "single", borderColor: colors.border.dim, paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "No worker agents in this swarm." }) }) })
|
|
1705
|
+
] }),
|
|
1706
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 0, children: /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "Input is sent to the focused agent. The master agent remains pinned to the left pane." }) })
|
|
1707
|
+
] });
|
|
1708
|
+
}
|
|
1709
|
+
function SplitPane({
|
|
1710
|
+
agents,
|
|
1711
|
+
activeAgentIndex,
|
|
1712
|
+
onSelectAgent,
|
|
1713
|
+
agentOutputs,
|
|
1714
|
+
isProcessing,
|
|
1715
|
+
onSubmit,
|
|
1716
|
+
onCancel,
|
|
1717
|
+
model,
|
|
1718
|
+
role,
|
|
1719
|
+
tokenCount,
|
|
1720
|
+
cost,
|
|
1721
|
+
gitBranch
|
|
1722
|
+
}) {
|
|
1723
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: "100%", children: [
|
|
1724
|
+
/* @__PURE__ */ jsx(StatusBar, { model, role, tokenCount, cost, gitBranch }),
|
|
1725
|
+
/* @__PURE__ */ jsx(
|
|
1726
|
+
SplitPanel,
|
|
1727
|
+
{
|
|
1728
|
+
agents,
|
|
1729
|
+
activeAgentIndex,
|
|
1730
|
+
onSelectAgent,
|
|
1731
|
+
agentOutputs
|
|
1732
|
+
}
|
|
1733
|
+
),
|
|
1734
|
+
/* @__PURE__ */ jsx(InputBar, { onSubmit, isProcessing, onCancel })
|
|
1735
|
+
] });
|
|
1736
|
+
}
|
|
1737
|
+
function useModel(config, initialModel, initialRole) {
|
|
1738
|
+
const router = useMemo(() => createModelRouter(config), [config]);
|
|
1739
|
+
const [userOverride, setUserOverride] = useState(initialModel);
|
|
1740
|
+
const [currentRole, setCurrentRole] = useState(initialRole);
|
|
1741
|
+
const resolution = useMemo(
|
|
1742
|
+
() => {
|
|
1743
|
+
router.setUserOverride(userOverride);
|
|
1744
|
+
return router.resolve(currentRole);
|
|
1745
|
+
},
|
|
1746
|
+
[router, currentRole, userOverride]
|
|
1747
|
+
);
|
|
1748
|
+
const switchModel = useCallback((modelId) => {
|
|
1749
|
+
setUserOverride(modelId);
|
|
1750
|
+
}, []);
|
|
1751
|
+
const switchRole = useCallback(
|
|
1752
|
+
(role) => {
|
|
1753
|
+
router.setUserOverride(void 0);
|
|
1754
|
+
setCurrentRole(role);
|
|
1755
|
+
setUserOverride(void 0);
|
|
1756
|
+
},
|
|
1757
|
+
[router]
|
|
1758
|
+
);
|
|
1759
|
+
return {
|
|
1760
|
+
resolution,
|
|
1761
|
+
modelId: resolution.modelId,
|
|
1762
|
+
switchModel,
|
|
1763
|
+
switchRole,
|
|
1764
|
+
router
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
function formatToolActivity(toolCall) {
|
|
1768
|
+
const args = toolCall.arguments;
|
|
1769
|
+
switch (toolCall.name) {
|
|
1770
|
+
case "read": {
|
|
1771
|
+
const fp = typeof args["file_path"] === "string" ? args["file_path"] : "";
|
|
1772
|
+
const short = fp.split("/").slice(-2).join("/");
|
|
1773
|
+
return `Reading ${short || "file"}`;
|
|
1774
|
+
}
|
|
1775
|
+
case "write": {
|
|
1776
|
+
const fp = typeof args["file_path"] === "string" ? args["file_path"] : "";
|
|
1777
|
+
const short = fp.split("/").slice(-2).join("/");
|
|
1778
|
+
return `Writing ${short || "file"}`;
|
|
1779
|
+
}
|
|
1780
|
+
case "edit": {
|
|
1781
|
+
const fp = typeof args["file_path"] === "string" ? args["file_path"] : "";
|
|
1782
|
+
const short = fp.split("/").slice(-2).join("/");
|
|
1783
|
+
return `Editing ${short || "file"}`;
|
|
1784
|
+
}
|
|
1785
|
+
case "glob": {
|
|
1786
|
+
const pat = typeof args["pattern"] === "string" ? args["pattern"] : "";
|
|
1787
|
+
return `Searching files ${pat}`;
|
|
1788
|
+
}
|
|
1789
|
+
case "grep": {
|
|
1790
|
+
const pat = typeof args["pattern"] === "string" ? args["pattern"] : "";
|
|
1791
|
+
return `Searching for "${pat.length > 30 ? pat.slice(0, 30) + "\u2026" : pat}"`;
|
|
1792
|
+
}
|
|
1793
|
+
case "bash": {
|
|
1794
|
+
const cmd = typeof args["command"] === "string" ? args["command"] : "";
|
|
1795
|
+
const short = cmd.length > 40 ? cmd.slice(0, 40) + "\u2026" : cmd;
|
|
1796
|
+
return `Running ${short}`;
|
|
1797
|
+
}
|
|
1798
|
+
case "web_search":
|
|
1799
|
+
case "webSearch":
|
|
1800
|
+
return "Searching the web";
|
|
1801
|
+
case "web_fetch":
|
|
1802
|
+
case "webFetch":
|
|
1803
|
+
return "Fetching URL";
|
|
1804
|
+
default:
|
|
1805
|
+
return `Calling ${toolCall.name}`;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
function useStream() {
|
|
1809
|
+
const [state, setState] = useState({
|
|
1810
|
+
isStreaming: false,
|
|
1811
|
+
content: "",
|
|
1812
|
+
usage: void 0,
|
|
1813
|
+
error: void 0,
|
|
1814
|
+
activity: void 0,
|
|
1815
|
+
toolCalls: [],
|
|
1816
|
+
startTime: void 0
|
|
1817
|
+
});
|
|
1818
|
+
const cancelRef = useRef(false);
|
|
1819
|
+
const isCancelled = () => cancelRef.current;
|
|
1820
|
+
const startStream = useCallback(
|
|
1821
|
+
async (stream) => {
|
|
1822
|
+
cancelRef.current = false;
|
|
1823
|
+
setState({
|
|
1824
|
+
isStreaming: true,
|
|
1825
|
+
content: "",
|
|
1826
|
+
usage: void 0,
|
|
1827
|
+
error: void 0,
|
|
1828
|
+
activity: void 0,
|
|
1829
|
+
toolCalls: [],
|
|
1830
|
+
startTime: Date.now()
|
|
1831
|
+
});
|
|
1832
|
+
try {
|
|
1833
|
+
for await (const chunk of stream) {
|
|
1834
|
+
if (isCancelled()) break;
|
|
1835
|
+
switch (chunk.type) {
|
|
1836
|
+
case "text":
|
|
1837
|
+
if (chunk.content !== void 0) {
|
|
1838
|
+
const text = chunk.content;
|
|
1839
|
+
setState((prev) => ({
|
|
1840
|
+
...prev,
|
|
1841
|
+
content: prev.content + text,
|
|
1842
|
+
activity: void 0
|
|
1843
|
+
}));
|
|
1844
|
+
}
|
|
1845
|
+
break;
|
|
1846
|
+
case "tool_call":
|
|
1847
|
+
if (chunk.toolCall !== void 0) {
|
|
1848
|
+
const toolCall = chunk.toolCall;
|
|
1849
|
+
const desc = formatToolActivity(toolCall);
|
|
1850
|
+
const callState = {
|
|
1851
|
+
id: toolCall.name + "-" + Date.now().toString(36),
|
|
1852
|
+
name: toolCall.name,
|
|
1853
|
+
description: desc,
|
|
1854
|
+
status: "executing",
|
|
1855
|
+
startTime: Date.now()
|
|
1856
|
+
};
|
|
1857
|
+
setState((prev) => ({
|
|
1858
|
+
...prev,
|
|
1859
|
+
activity: desc,
|
|
1860
|
+
toolCalls: [
|
|
1861
|
+
// Mark previous executing calls as success
|
|
1862
|
+
...prev.toolCalls.map(
|
|
1863
|
+
(tc) => tc.status === "executing" ? {
|
|
1864
|
+
...tc,
|
|
1865
|
+
status: "success",
|
|
1866
|
+
endTime: Date.now()
|
|
1867
|
+
} : tc
|
|
1868
|
+
),
|
|
1869
|
+
callState
|
|
1870
|
+
]
|
|
1871
|
+
}));
|
|
1872
|
+
}
|
|
1873
|
+
break;
|
|
1874
|
+
case "usage":
|
|
1875
|
+
if (chunk.usage) {
|
|
1876
|
+
setState((prev) => ({
|
|
1877
|
+
...prev,
|
|
1878
|
+
usage: chunk.usage
|
|
1879
|
+
}));
|
|
1880
|
+
}
|
|
1881
|
+
break;
|
|
1882
|
+
case "error":
|
|
1883
|
+
setState((prev) => ({
|
|
1884
|
+
...prev,
|
|
1885
|
+
error: chunk.error,
|
|
1886
|
+
isStreaming: false,
|
|
1887
|
+
activity: void 0,
|
|
1888
|
+
toolCalls: prev.toolCalls.map(
|
|
1889
|
+
(tc) => tc.status === "executing" ? {
|
|
1890
|
+
...tc,
|
|
1891
|
+
status: "error",
|
|
1892
|
+
endTime: Date.now()
|
|
1893
|
+
} : tc
|
|
1894
|
+
)
|
|
1895
|
+
}));
|
|
1896
|
+
return;
|
|
1897
|
+
case "done":
|
|
1898
|
+
setState((prev) => ({
|
|
1899
|
+
...prev,
|
|
1900
|
+
isStreaming: false,
|
|
1901
|
+
usage: chunk.usage ?? prev.usage,
|
|
1902
|
+
activity: void 0,
|
|
1903
|
+
toolCalls: prev.toolCalls.map(
|
|
1904
|
+
(tc) => tc.status === "executing" ? {
|
|
1905
|
+
...tc,
|
|
1906
|
+
status: "success",
|
|
1907
|
+
endTime: Date.now()
|
|
1908
|
+
} : tc
|
|
1909
|
+
)
|
|
1910
|
+
}));
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
setState((prev) => ({
|
|
1915
|
+
...prev,
|
|
1916
|
+
isStreaming: false,
|
|
1917
|
+
activity: void 0,
|
|
1918
|
+
toolCalls: prev.toolCalls.map(
|
|
1919
|
+
(tc) => tc.status === "executing" ? {
|
|
1920
|
+
...tc,
|
|
1921
|
+
status: "success",
|
|
1922
|
+
endTime: Date.now()
|
|
1923
|
+
} : tc
|
|
1924
|
+
)
|
|
1925
|
+
}));
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
setState((prev) => ({
|
|
1928
|
+
...prev,
|
|
1929
|
+
isStreaming: false,
|
|
1930
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1931
|
+
activity: void 0
|
|
1932
|
+
}));
|
|
1933
|
+
}
|
|
1934
|
+
},
|
|
1935
|
+
[]
|
|
1936
|
+
);
|
|
1937
|
+
const cancelStream = useCallback(() => {
|
|
1938
|
+
cancelRef.current = true;
|
|
1939
|
+
setState((prev) => ({
|
|
1940
|
+
...prev,
|
|
1941
|
+
isStreaming: false,
|
|
1942
|
+
toolCalls: prev.toolCalls.map(
|
|
1943
|
+
(tc) => tc.status === "executing" ? {
|
|
1944
|
+
...tc,
|
|
1945
|
+
status: "cancelled",
|
|
1946
|
+
endTime: Date.now()
|
|
1947
|
+
} : tc
|
|
1948
|
+
)
|
|
1949
|
+
}));
|
|
1950
|
+
}, []);
|
|
1951
|
+
const reset = useCallback(() => {
|
|
1952
|
+
cancelRef.current = true;
|
|
1953
|
+
setState({
|
|
1954
|
+
isStreaming: false,
|
|
1955
|
+
content: "",
|
|
1956
|
+
usage: void 0,
|
|
1957
|
+
error: void 0,
|
|
1958
|
+
activity: void 0,
|
|
1959
|
+
toolCalls: [],
|
|
1960
|
+
startTime: void 0
|
|
1961
|
+
});
|
|
1962
|
+
}, []);
|
|
1963
|
+
return { state, startStream, cancelStream, reset };
|
|
1964
|
+
}
|
|
1965
|
+
function useCost(config) {
|
|
1966
|
+
const trackerRef = useRef(new CostTracker(config));
|
|
1967
|
+
const [totalCost, setTotalCost] = useState("$0.00");
|
|
1968
|
+
const [totalTokens, setTotalTokens] = useState("0");
|
|
1969
|
+
const [isBudgetExceeded, setIsBudgetExceeded] = useState(false);
|
|
1970
|
+
useEffect(() => {
|
|
1971
|
+
const eventBus = getEventBus();
|
|
1972
|
+
const unsubCost = eventBus.on("cost:updated", ({ total }) => {
|
|
1973
|
+
setTotalCost(formatCost(total));
|
|
1974
|
+
const tokens = trackerRef.current.getSessionTokens();
|
|
1975
|
+
setTotalTokens(formatTokenCount(tokens.total));
|
|
1976
|
+
});
|
|
1977
|
+
const unsubExceeded = eventBus.on("cost:exceeded", () => {
|
|
1978
|
+
setIsBudgetExceeded(true);
|
|
1979
|
+
});
|
|
1980
|
+
return () => {
|
|
1981
|
+
unsubCost();
|
|
1982
|
+
unsubExceeded();
|
|
1983
|
+
};
|
|
1984
|
+
}, []);
|
|
1985
|
+
const record = useCallback(
|
|
1986
|
+
(provider, model, inputTokens, outputTokens, role) => {
|
|
1987
|
+
trackerRef.current.record(provider, model, inputTokens, outputTokens, role);
|
|
1988
|
+
},
|
|
1989
|
+
[]
|
|
1990
|
+
);
|
|
1991
|
+
return {
|
|
1992
|
+
totalCost,
|
|
1993
|
+
totalTokens,
|
|
1994
|
+
isBudgetExceeded,
|
|
1995
|
+
record,
|
|
1996
|
+
tracker: trackerRef.current
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
function usePanel() {
|
|
2000
|
+
const flushIntervalMs = 100;
|
|
2001
|
+
const [agents, setAgentsState] = useState([]);
|
|
2002
|
+
const [activeAgentIndex, setActiveAgentIndex] = useState(0);
|
|
2003
|
+
const [agentOutputs, setAgentOutputs] = useState(/* @__PURE__ */ new Map());
|
|
2004
|
+
const [isSplitPanelActive, setIsSplitPanelActive] = useState(false);
|
|
2005
|
+
const pendingOutputRef = useRef(/* @__PURE__ */ new Map());
|
|
2006
|
+
const flushTimerRef = useRef(void 0);
|
|
2007
|
+
const flushPendingOutput = useCallback(() => {
|
|
2008
|
+
flushTimerRef.current = void 0;
|
|
2009
|
+
if (pendingOutputRef.current.size === 0) {
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
const pending = pendingOutputRef.current;
|
|
2013
|
+
pendingOutputRef.current = /* @__PURE__ */ new Map();
|
|
2014
|
+
startTransition(() => {
|
|
2015
|
+
setAgentOutputs((prev) => {
|
|
2016
|
+
const next = new Map(prev);
|
|
2017
|
+
for (const [agentId, content] of pending.entries()) {
|
|
2018
|
+
const existing = next.get(agentId) ?? "";
|
|
2019
|
+
next.set(agentId, existing + content);
|
|
2020
|
+
}
|
|
2021
|
+
return next;
|
|
2022
|
+
});
|
|
2023
|
+
});
|
|
2024
|
+
}, []);
|
|
2025
|
+
useEffect(() => {
|
|
2026
|
+
return () => {
|
|
2027
|
+
if (flushTimerRef.current !== void 0) {
|
|
2028
|
+
clearTimeout(flushTimerRef.current);
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
}, []);
|
|
2032
|
+
const selectAgent = useCallback((index) => {
|
|
2033
|
+
setActiveAgentIndex(index);
|
|
2034
|
+
}, []);
|
|
2035
|
+
const appendOutput = useCallback((agentId, content, options) => {
|
|
2036
|
+
const pending = pendingOutputRef.current;
|
|
2037
|
+
pending.set(agentId, (pending.get(agentId) ?? "") + content);
|
|
2038
|
+
if (options?.immediate === true) {
|
|
2039
|
+
if (flushTimerRef.current !== void 0) {
|
|
2040
|
+
clearTimeout(flushTimerRef.current);
|
|
2041
|
+
}
|
|
2042
|
+
flushPendingOutput();
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
if (flushTimerRef.current === void 0) {
|
|
2046
|
+
flushTimerRef.current = setTimeout(flushPendingOutput, flushIntervalMs);
|
|
2047
|
+
}
|
|
2048
|
+
}, [flushIntervalMs, flushPendingOutput]);
|
|
2049
|
+
const updateAgentStatus = useCallback((agentId, status) => {
|
|
2050
|
+
setAgentsState(
|
|
2051
|
+
(prev) => prev.map(
|
|
2052
|
+
(agent) => agent.config.agentId === agentId ? { ...agent, status } : agent
|
|
2053
|
+
)
|
|
2054
|
+
);
|
|
2055
|
+
}, []);
|
|
2056
|
+
const setAgents = useCallback((newAgents) => {
|
|
2057
|
+
setAgentsState(newAgents);
|
|
2058
|
+
setActiveAgentIndex(0);
|
|
2059
|
+
}, []);
|
|
2060
|
+
const activate = useCallback(() => {
|
|
2061
|
+
setIsSplitPanelActive(true);
|
|
2062
|
+
}, []);
|
|
2063
|
+
const deactivate = useCallback(() => {
|
|
2064
|
+
if (flushTimerRef.current !== void 0) {
|
|
2065
|
+
clearTimeout(flushTimerRef.current);
|
|
2066
|
+
flushTimerRef.current = void 0;
|
|
2067
|
+
}
|
|
2068
|
+
pendingOutputRef.current = /* @__PURE__ */ new Map();
|
|
2069
|
+
setIsSplitPanelActive(false);
|
|
2070
|
+
setAgentsState([]);
|
|
2071
|
+
setAgentOutputs(/* @__PURE__ */ new Map());
|
|
2072
|
+
setActiveAgentIndex(0);
|
|
2073
|
+
}, []);
|
|
2074
|
+
return useMemo(() => ({
|
|
2075
|
+
agents,
|
|
2076
|
+
activeAgentIndex,
|
|
2077
|
+
agentOutputs,
|
|
2078
|
+
isSplitPanelActive,
|
|
2079
|
+
selectAgent,
|
|
2080
|
+
appendOutput,
|
|
2081
|
+
updateAgentStatus,
|
|
2082
|
+
setAgents,
|
|
2083
|
+
activate,
|
|
2084
|
+
deactivate
|
|
2085
|
+
}), [agents, activeAgentIndex, agentOutputs, isSplitPanelActive, selectAgent, appendOutput, updateAgentStatus, setAgents, activate, deactivate]);
|
|
2086
|
+
}
|
|
2087
|
+
function v4Id() {
|
|
2088
|
+
return randomUUID();
|
|
2089
|
+
}
|
|
2090
|
+
var PROVIDER_LABELS = {
|
|
2091
|
+
anthropic: "\u2726 Claude (Anthropic)",
|
|
2092
|
+
openai: "\u2B22 Codex (OpenAI)",
|
|
2093
|
+
google: "\u25C6 Gemini (Google)",
|
|
2094
|
+
kimi: "\u25CE Kimi (Moonshot)"
|
|
2095
|
+
};
|
|
2096
|
+
function ModelSelector({
|
|
2097
|
+
currentModelId,
|
|
2098
|
+
onSelect,
|
|
2099
|
+
onCancel,
|
|
2100
|
+
modelOrder
|
|
2101
|
+
}) {
|
|
2102
|
+
const rows = useMemo(() => {
|
|
2103
|
+
const order = modelOrder ?? {};
|
|
2104
|
+
const result = [];
|
|
2105
|
+
for (const [providerKey, entries] of Object.entries(order)) {
|
|
2106
|
+
result.push({
|
|
2107
|
+
type: "header",
|
|
2108
|
+
label: PROVIDER_LABELS[providerKey] ?? providerKey
|
|
2109
|
+
});
|
|
2110
|
+
for (const entry of entries) {
|
|
2111
|
+
result.push({
|
|
2112
|
+
type: "model",
|
|
2113
|
+
label: entry.label,
|
|
2114
|
+
description: entry.description,
|
|
2115
|
+
modelId: entry.id,
|
|
2116
|
+
isCurrent: entry.id === currentModelId
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
return result;
|
|
2121
|
+
}, [currentModelId]);
|
|
2122
|
+
const selectableIndices = useMemo(() => {
|
|
2123
|
+
const indices = [];
|
|
2124
|
+
for (let i = 0; i < rows.length; i++) {
|
|
2125
|
+
if (rows[i]?.type === "model") indices.push(i);
|
|
2126
|
+
}
|
|
2127
|
+
return indices;
|
|
2128
|
+
}, [rows]);
|
|
2129
|
+
const initialIndex = useMemo(() => {
|
|
2130
|
+
const rowIdx = rows.findIndex(
|
|
2131
|
+
(r) => r.type === "model" && r.modelId === currentModelId
|
|
2132
|
+
);
|
|
2133
|
+
const selectIdx = selectableIndices.indexOf(rowIdx);
|
|
2134
|
+
return selectIdx >= 0 ? selectIdx : 0;
|
|
2135
|
+
}, [rows, selectableIndices, currentModelId]);
|
|
2136
|
+
const [cursor, setCursor] = useState(initialIndex);
|
|
2137
|
+
useInput((_input, key) => {
|
|
2138
|
+
if (key.upArrow) {
|
|
2139
|
+
setCursor(
|
|
2140
|
+
(prev) => prev > 0 ? prev - 1 : selectableIndices.length - 1
|
|
2141
|
+
);
|
|
2142
|
+
} else if (key.downArrow) {
|
|
2143
|
+
setCursor(
|
|
2144
|
+
(prev) => prev < selectableIndices.length - 1 ? prev + 1 : 0
|
|
2145
|
+
);
|
|
2146
|
+
} else if (key.return) {
|
|
2147
|
+
const rowIdx = selectableIndices[cursor];
|
|
2148
|
+
const row = rowIdx !== void 0 ? rows[rowIdx] : void 0;
|
|
2149
|
+
if (row?.modelId) onSelect(row.modelId);
|
|
2150
|
+
} else if (key.escape) {
|
|
2151
|
+
onCancel();
|
|
2152
|
+
}
|
|
2153
|
+
});
|
|
2154
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
2155
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
2156
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: colors.status.active, children: "Select Model" }),
|
|
2157
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.text.muted, children: [
|
|
2158
|
+
" ",
|
|
2159
|
+
"\\u2191\\u2193 navigate \\u00B7 Enter select \\u00B7 Esc cancel"
|
|
2160
|
+
] })
|
|
2161
|
+
] }),
|
|
2162
|
+
rows.map((row, idx) => {
|
|
2163
|
+
if (row.type === "header") {
|
|
2164
|
+
return /* @__PURE__ */ jsx(Box, { marginTop: idx > 0 ? 1 : 0, children: /* @__PURE__ */ jsxs(Text, { bold: true, color: colors.status.warning, children: [
|
|
2165
|
+
" ",
|
|
2166
|
+
row.label
|
|
2167
|
+
] }) }, `header-${idx}`);
|
|
2168
|
+
}
|
|
2169
|
+
const isHighlighted = selectableIndices[cursor] === idx;
|
|
2170
|
+
const currentTag = row.isCurrent ? " (current)" : "";
|
|
2171
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
2172
|
+
/* @__PURE__ */ jsxs(
|
|
2173
|
+
Text,
|
|
2174
|
+
{
|
|
2175
|
+
color: isHighlighted ? colors.status.success : row.isCurrent ? colors.text.accent : colors.text.primary,
|
|
2176
|
+
bold: isHighlighted,
|
|
2177
|
+
children: [
|
|
2178
|
+
isHighlighted ? "\u25B8 " : " ",
|
|
2179
|
+
row.label.padEnd(30)
|
|
2180
|
+
]
|
|
2181
|
+
}
|
|
2182
|
+
),
|
|
2183
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.text.muted, children: [
|
|
2184
|
+
" ",
|
|
2185
|
+
row.description,
|
|
2186
|
+
currentTag
|
|
2187
|
+
] })
|
|
2188
|
+
] }, row.modelId ?? `row-${idx}`);
|
|
2189
|
+
}),
|
|
2190
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: colors.text.muted, children: [
|
|
2191
|
+
" ",
|
|
2192
|
+
"Current: ",
|
|
2193
|
+
currentModelId
|
|
2194
|
+
] }) })
|
|
2195
|
+
] });
|
|
2196
|
+
}
|
|
2197
|
+
function formatMethod(method) {
|
|
2198
|
+
switch (method) {
|
|
2199
|
+
case "extended_thinking":
|
|
2200
|
+
return "Extended Thinking";
|
|
2201
|
+
case "reasoning_effort":
|
|
2202
|
+
return "Reasoning Effort";
|
|
2203
|
+
case "thinking_budget":
|
|
2204
|
+
return "Thinking Budget";
|
|
2205
|
+
case "thinking_level":
|
|
2206
|
+
return "Thinking Level";
|
|
2207
|
+
case "thinking_mode":
|
|
2208
|
+
return "Thinking Mode";
|
|
2209
|
+
default:
|
|
2210
|
+
return "Thinking";
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
function ThinkingSelector({
|
|
2214
|
+
modelId,
|
|
2215
|
+
modelName,
|
|
2216
|
+
currentValue,
|
|
2217
|
+
onSelect,
|
|
2218
|
+
onBack
|
|
2219
|
+
}) {
|
|
2220
|
+
const config = useMemo(() => getThinkingConfigForModel(modelId), [modelId]);
|
|
2221
|
+
const initialIdx = useMemo(() => {
|
|
2222
|
+
if (!config) return 0;
|
|
2223
|
+
const idx = config.options.findIndex((o) => o.value === currentValue);
|
|
2224
|
+
if (idx >= 0) return idx;
|
|
2225
|
+
const defaultIdx = config.options.findIndex((o) => o.value === config.defaultValue);
|
|
2226
|
+
return Math.max(0, defaultIdx);
|
|
2227
|
+
}, [config, currentValue]);
|
|
2228
|
+
const [cursor, setCursor] = useState(initialIdx);
|
|
2229
|
+
useInput((_input, key) => {
|
|
2230
|
+
if (!config) {
|
|
2231
|
+
if (key.return || key.escape) onBack();
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
if (key.upArrow) {
|
|
2235
|
+
setCursor((prev) => prev > 0 ? prev - 1 : config.options.length - 1);
|
|
2236
|
+
} else if (key.downArrow) {
|
|
2237
|
+
setCursor((prev) => prev < config.options.length - 1 ? prev + 1 : 0);
|
|
2238
|
+
} else if (key.return) {
|
|
2239
|
+
const option = config.options[cursor];
|
|
2240
|
+
if (option) onSelect(option.value);
|
|
2241
|
+
} else if (key.escape) {
|
|
2242
|
+
onBack();
|
|
2243
|
+
}
|
|
2244
|
+
});
|
|
2245
|
+
if (!config) {
|
|
2246
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
2247
|
+
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
2248
|
+
"No thinking options available for ",
|
|
2249
|
+
modelName,
|
|
2250
|
+
"."
|
|
2251
|
+
] }),
|
|
2252
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "Press Enter or Esc to continue." })
|
|
2253
|
+
] });
|
|
2254
|
+
}
|
|
2255
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
2256
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [
|
|
2257
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: formatMethod(config.method) }),
|
|
2258
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2259
|
+
" Model: ",
|
|
2260
|
+
modelName,
|
|
2261
|
+
" (up/down navigate, Enter select, Esc back)"
|
|
2262
|
+
] })
|
|
2263
|
+
] }),
|
|
2264
|
+
config.options.map((option, idx) => {
|
|
2265
|
+
const isHighlighted = cursor === idx;
|
|
2266
|
+
const isCurrent = option.value === currentValue;
|
|
2267
|
+
const currentTag = isCurrent ? " (current)" : "";
|
|
2268
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
2269
|
+
/* @__PURE__ */ jsxs(Text, { ...isHighlighted ? { color: "green" } : {}, bold: isHighlighted, children: [
|
|
2270
|
+
isHighlighted ? "> " : " ",
|
|
2271
|
+
option.label.padEnd(22)
|
|
2272
|
+
] }),
|
|
2273
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2274
|
+
" ",
|
|
2275
|
+
option.description,
|
|
2276
|
+
currentTag
|
|
2277
|
+
] })
|
|
2278
|
+
] }, option.value);
|
|
2279
|
+
})
|
|
2280
|
+
] });
|
|
2281
|
+
}
|
|
2282
|
+
var PROVIDERS = [
|
|
2283
|
+
{ label: "Claude", value: "claude", description: "Anthropic \u2014 Claude models" },
|
|
2284
|
+
{ label: "Codex", value: "codex", description: "OpenAI \u2014 GPT / Codex models" },
|
|
2285
|
+
{ label: "Gemini", value: "gemini", description: "Google \u2014 Gemini models" },
|
|
2286
|
+
{ label: "Kimi", value: "kimi", description: "Moonshot \u2014 Kimi models" }
|
|
2287
|
+
];
|
|
2288
|
+
function LoginSelector({ onSelect, onCancel }) {
|
|
2289
|
+
const [cursor, setCursor] = useState(0);
|
|
2290
|
+
useInput((_input, key) => {
|
|
2291
|
+
if (key.upArrow) {
|
|
2292
|
+
setCursor((prev) => prev > 0 ? prev - 1 : PROVIDERS.length - 1);
|
|
2293
|
+
} else if (key.downArrow) {
|
|
2294
|
+
setCursor((prev) => prev < PROVIDERS.length - 1 ? prev + 1 : 0);
|
|
2295
|
+
} else if (key.return) {
|
|
2296
|
+
const selected = PROVIDERS[cursor];
|
|
2297
|
+
if (selected) onSelect(selected.value);
|
|
2298
|
+
} else if (key.escape) {
|
|
2299
|
+
onCancel();
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
2303
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
2304
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Select a provider to log in to" }),
|
|
2305
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: " (up/down navigate, Enter select, Esc cancel)" })
|
|
2306
|
+
] }),
|
|
2307
|
+
PROVIDERS.map((provider, idx) => {
|
|
2308
|
+
const isHighlighted = cursor === idx;
|
|
2309
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
2310
|
+
/* @__PURE__ */ jsxs(Text, { ...isHighlighted ? { color: "green" } : {}, bold: isHighlighted, children: [
|
|
2311
|
+
isHighlighted ? "> " : " ",
|
|
2312
|
+
provider.label.padEnd(12)
|
|
2313
|
+
] }),
|
|
2314
|
+
/* @__PURE__ */ jsxs(Text, { color: "gray", children: [
|
|
2315
|
+
" ",
|
|
2316
|
+
provider.description
|
|
2317
|
+
] })
|
|
2318
|
+
] }, provider.value);
|
|
2319
|
+
}),
|
|
2320
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: " Login will open your browser for authentication." }) })
|
|
2321
|
+
] });
|
|
2322
|
+
}
|
|
2323
|
+
function SwarmOnboarding({
|
|
2324
|
+
detectedProviders,
|
|
2325
|
+
currentPrimaryProvider,
|
|
2326
|
+
onSelect,
|
|
2327
|
+
onSkip
|
|
2328
|
+
}) {
|
|
2329
|
+
const entries = useMemo(
|
|
2330
|
+
() => detectedProviders.map((provider) => getCliProviderEntry(provider)),
|
|
2331
|
+
[detectedProviders]
|
|
2332
|
+
);
|
|
2333
|
+
const initialCursor = useMemo(() => {
|
|
2334
|
+
if (!currentPrimaryProvider) {
|
|
2335
|
+
return 0;
|
|
2336
|
+
}
|
|
2337
|
+
const currentIndex = detectedProviders.indexOf(currentPrimaryProvider);
|
|
2338
|
+
return currentIndex >= 0 ? currentIndex : 0;
|
|
2339
|
+
}, [currentPrimaryProvider, detectedProviders]);
|
|
2340
|
+
const [cursor, setCursor] = useState(initialCursor);
|
|
2341
|
+
useInput((_input, key) => {
|
|
2342
|
+
if (entries.length === 0) {
|
|
2343
|
+
if (key.return || key.escape) {
|
|
2344
|
+
onSkip();
|
|
2345
|
+
}
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
if (key.upArrow) {
|
|
2349
|
+
setCursor((prev) => prev > 0 ? prev - 1 : entries.length - 1);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
if (key.downArrow) {
|
|
2353
|
+
setCursor((prev) => prev < entries.length - 1 ? prev + 1 : 0);
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
if (key.return) {
|
|
2357
|
+
const selected = detectedProviders[cursor];
|
|
2358
|
+
if (selected) {
|
|
2359
|
+
onSelect(selected);
|
|
2360
|
+
}
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
if (key.escape && entries.length === 0) {
|
|
2364
|
+
onSkip();
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
2368
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [
|
|
2369
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.active, bold: true, children: "Swarm Setup" }),
|
|
2370
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "Select the master agent provider for swarm orchestration." })
|
|
2371
|
+
] }),
|
|
2372
|
+
entries.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2373
|
+
entries.map((entry, index) => {
|
|
2374
|
+
const isSelected = index === cursor;
|
|
2375
|
+
const isCurrent = currentPrimaryProvider === entry.type;
|
|
2376
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
2377
|
+
/* @__PURE__ */ jsxs(
|
|
2378
|
+
Text,
|
|
2379
|
+
{
|
|
2380
|
+
color: isSelected ? colors.status.success : colors.text.primary,
|
|
2381
|
+
bold: isSelected,
|
|
2382
|
+
children: [
|
|
2383
|
+
isSelected ? "\u25B8 " : " ",
|
|
2384
|
+
entry.label.padEnd(16)
|
|
2385
|
+
]
|
|
2386
|
+
}
|
|
2387
|
+
),
|
|
2388
|
+
/* @__PURE__ */ jsxs(Text, { color: colors.text.muted, children: [
|
|
2389
|
+
entry.description,
|
|
2390
|
+
isCurrent ? " (current)" : ""
|
|
2391
|
+
] })
|
|
2392
|
+
] }, entry.type);
|
|
2393
|
+
}),
|
|
2394
|
+
/* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
|
|
2395
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "Enter sets the master agent. Remaining detected providers become fallbacks." }),
|
|
2396
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "\\u2191\\u2193 navigate \xB7 Enter confirm" })
|
|
2397
|
+
] })
|
|
2398
|
+
] }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2399
|
+
/* @__PURE__ */ jsx(Text, { color: colors.status.warning, children: "No supported native agent CLI was detected." }),
|
|
2400
|
+
/* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "Install Claude Code, Codex, Gemini CLI, Kimi CLI, or Ollama, then restart." }),
|
|
2401
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.text.muted, children: "Enter or Esc continues without swarm setup." }) })
|
|
2402
|
+
] })
|
|
2403
|
+
] });
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// src/ui/commands/types.ts
|
|
2407
|
+
function addSystemMessage(ctx, content) {
|
|
2408
|
+
ctx.setMessages((prev) => [
|
|
2409
|
+
...prev,
|
|
2410
|
+
{
|
|
2411
|
+
id: v4Id(),
|
|
2412
|
+
role: "system",
|
|
2413
|
+
content,
|
|
2414
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2415
|
+
}
|
|
2416
|
+
]);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
// src/ui/commands/model-helpers.ts
|
|
2420
|
+
function resolveModelSelection(input) {
|
|
2421
|
+
if (SUPPORTED_MODELS[input]) {
|
|
2422
|
+
return input;
|
|
2423
|
+
}
|
|
2424
|
+
if (/^\d+$/.test(input)) {
|
|
2425
|
+
const index = Number(input);
|
|
2426
|
+
let globalIndex = 1;
|
|
2427
|
+
for (const entries of Object.values(PROVIDER_MODEL_ORDER)) {
|
|
2428
|
+
for (const entry of entries) {
|
|
2429
|
+
if (globalIndex === index) {
|
|
2430
|
+
return entry.id;
|
|
2431
|
+
}
|
|
2432
|
+
globalIndex++;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
return void 0;
|
|
2437
|
+
}
|
|
2438
|
+
function formatThinkingMethod(method) {
|
|
2439
|
+
switch (method) {
|
|
2440
|
+
case "extended_thinking":
|
|
2441
|
+
return "Extended Thinking";
|
|
2442
|
+
case "reasoning_effort":
|
|
2443
|
+
return "Reasoning Effort";
|
|
2444
|
+
case "thinking_budget":
|
|
2445
|
+
return "Thinking Budget";
|
|
2446
|
+
case "thinking_level":
|
|
2447
|
+
return "Thinking Level";
|
|
2448
|
+
case "thinking_mode":
|
|
2449
|
+
return "Thinking Mode";
|
|
2450
|
+
default:
|
|
2451
|
+
return "Thinking";
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
// src/ui/commands/team-commands.ts
|
|
2456
|
+
async function handleTeamCommand(args, ctx) {
|
|
2457
|
+
const subcommand = args[0];
|
|
2458
|
+
if (subcommand === "stop") {
|
|
2459
|
+
const cleanup = getActiveTmuxCleanup();
|
|
2460
|
+
if (cleanup) {
|
|
2461
|
+
try {
|
|
2462
|
+
await cleanup();
|
|
2463
|
+
} catch (error) {
|
|
2464
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
2465
|
+
addSystemMessage(ctx, `Warning: tmux cleanup failed (${errMsg}). Session may need manual cleanup.`);
|
|
2466
|
+
}
|
|
2467
|
+
setActiveTmuxCleanup(void 0);
|
|
2468
|
+
}
|
|
2469
|
+
const manager = getActiveTeamManager();
|
|
2470
|
+
const teamName = getActiveTeamName();
|
|
2471
|
+
if (manager && teamName) {
|
|
2472
|
+
try {
|
|
2473
|
+
await manager.deleteTeam(teamName);
|
|
2474
|
+
} catch (error) {
|
|
2475
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
2476
|
+
addSystemMessage(ctx, `Warning: team cleanup failed (${errMsg}). Processes may need manual cleanup.`);
|
|
2477
|
+
}
|
|
2478
|
+
setActiveTeamManager(void 0);
|
|
2479
|
+
}
|
|
2480
|
+
setActiveTeamName(void 0);
|
|
2481
|
+
ctx.panel.deactivate();
|
|
2482
|
+
addSystemMessage(ctx, "Team shut down. All agents stopped. Returned to single-pane mode.");
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
if (subcommand === "list") {
|
|
2486
|
+
try {
|
|
2487
|
+
const { TeamManager } = await import('./team-manager-2VSMALAA.js');
|
|
2488
|
+
const manager = new TeamManager();
|
|
2489
|
+
const teams = manager.listTeams();
|
|
2490
|
+
if (teams.length === 0) {
|
|
2491
|
+
addSystemMessage(ctx, "No active teams.");
|
|
2492
|
+
} else {
|
|
2493
|
+
const lines = teams.map((t) => ` ${t.teamName} \u2014 ${t.members.length} agents (${t.status})`);
|
|
2494
|
+
addSystemMessage(ctx, lines.join("\n"));
|
|
2495
|
+
}
|
|
2496
|
+
} catch (error) {
|
|
2497
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2498
|
+
addSystemMessage(ctx, `Failed to list teams: ${msg}`);
|
|
2499
|
+
}
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
addSystemMessage(
|
|
2503
|
+
ctx,
|
|
2504
|
+
'Usage: /team list | /team stop\nTeams are created automatically from natural language.\nExamples: "Create a team to refactor the auth module"\n "I need agents to review this PR from different angles"'
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
// src/ui/commands/mcp-commands.ts
|
|
2509
|
+
async function handleMcpCommand(args, ctx) {
|
|
2510
|
+
const subcommand = args[0];
|
|
2511
|
+
if (subcommand === "list") {
|
|
2512
|
+
try {
|
|
2513
|
+
const { MCPServerManager } = await import('./server-manager-THGZBBZB.js');
|
|
2514
|
+
const manager = new MCPServerManager();
|
|
2515
|
+
const connected = manager.getConnectedServers();
|
|
2516
|
+
if (connected.length === 0) {
|
|
2517
|
+
addSystemMessage(ctx, "No MCP servers connected.\nConfigure servers in ~/.aemeathcli/mcp.json");
|
|
2518
|
+
} else {
|
|
2519
|
+
const lines = connected.map((name) => {
|
|
2520
|
+
const status = manager.getServerStatus(name) ?? "unknown";
|
|
2521
|
+
return ` ${name} \u2014 ${status}`;
|
|
2522
|
+
});
|
|
2523
|
+
addSystemMessage(ctx, `MCP Servers:
|
|
2524
|
+
${lines.join("\n")}`);
|
|
2525
|
+
}
|
|
2526
|
+
} catch (error) {
|
|
2527
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2528
|
+
addSystemMessage(ctx, `Failed to list MCP servers: ${msg}`);
|
|
2529
|
+
}
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
if (subcommand === "add") {
|
|
2533
|
+
const name = args[1];
|
|
2534
|
+
if (!name) {
|
|
2535
|
+
addSystemMessage(ctx, "Usage: /mcp add <server-name>\n\nExample:\n /mcp add my-server");
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
addSystemMessage(
|
|
2539
|
+
ctx,
|
|
2540
|
+
`To add MCP server "${name}", add this to ~/.aemeathcli/mcp.json:
|
|
2541
|
+
|
|
2542
|
+
{
|
|
2543
|
+
"mcpServers": {
|
|
2544
|
+
"${name}": {
|
|
2545
|
+
"command": "npx",
|
|
2546
|
+
"args": ["-y", "@your-org/${name}-server"],
|
|
2547
|
+
"env": {}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
Then restart AemeathCLI. Use /mcp list to verify the connection.`
|
|
2553
|
+
);
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
addSystemMessage(ctx, "Usage: /mcp list | /mcp add <name>");
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// src/ui/commands/skill-commands.ts
|
|
2560
|
+
async function handleSkillCommand(args, ctx) {
|
|
2561
|
+
const subcommand = args[0];
|
|
2562
|
+
if (subcommand === "list") {
|
|
2563
|
+
try {
|
|
2564
|
+
const { SkillRegistry } = await import('./registry-LRURZVUL.js');
|
|
2565
|
+
const { findProjectRoot } = await import('./pathResolver-UVAB2FCW.js');
|
|
2566
|
+
const registry = new SkillRegistry();
|
|
2567
|
+
await registry.initialize(findProjectRoot());
|
|
2568
|
+
const skills = registry.listAll();
|
|
2569
|
+
if (skills.length === 0) {
|
|
2570
|
+
addSystemMessage(ctx, "No skills found.\nAdd skills in ~/.agents/skills/, ~/.aemeathcli/skills/, .agents/skills/, or .aemeathcli/skills/");
|
|
2571
|
+
} else {
|
|
2572
|
+
const lines = skills.map((s) => ` $${s.name.padEnd(16)} ${s.description} [${s.source}]`);
|
|
2573
|
+
addSystemMessage(ctx, `Available Skills:
|
|
2574
|
+
${lines.join("\n")}`);
|
|
2575
|
+
}
|
|
2576
|
+
} catch (error) {
|
|
2577
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2578
|
+
addSystemMessage(ctx, `Failed to list skills: ${msg}`);
|
|
2579
|
+
}
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
addSystemMessage(ctx, "Usage: /skill list\nInvoke a skill with $skill-name (e.g., $review, $commit, $plan)");
|
|
2583
|
+
}
|
|
2584
|
+
async function handleSkillInvocation(input, setMessages) {
|
|
2585
|
+
const parts = input.trim().split(/\s+/);
|
|
2586
|
+
const trigger = parts[0] ?? "";
|
|
2587
|
+
const skillName = trigger.replace(/^\$/, "");
|
|
2588
|
+
if (!skillName) {
|
|
2589
|
+
setMessages((prev) => [
|
|
2590
|
+
...prev,
|
|
2591
|
+
{ id: v4Id(), role: "system", content: "Usage: $skill-name [args]\nType /skill list to see available skills.", createdAt: /* @__PURE__ */ new Date() }
|
|
2592
|
+
]);
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
setMessages((prev) => [
|
|
2596
|
+
...prev,
|
|
2597
|
+
{ id: v4Id(), role: "user", content: input, createdAt: /* @__PURE__ */ new Date() }
|
|
2598
|
+
]);
|
|
2599
|
+
try {
|
|
2600
|
+
const { SkillRegistry } = await import('./registry-LRURZVUL.js');
|
|
2601
|
+
const { SkillExecutor } = await import('./executor-FTABX2AW.js');
|
|
2602
|
+
const { findProjectRoot } = await import('./pathResolver-UVAB2FCW.js');
|
|
2603
|
+
const registry = new SkillRegistry();
|
|
2604
|
+
await registry.initialize(findProjectRoot());
|
|
2605
|
+
const executor = new SkillExecutor(registry);
|
|
2606
|
+
const result = await executor.activateByTrigger(trigger);
|
|
2607
|
+
if (!result.success) {
|
|
2608
|
+
setMessages((prev) => [
|
|
2609
|
+
...prev,
|
|
2610
|
+
{ id: v4Id(), role: "system", content: result.errorMessage ?? `Skill not found: "${skillName}"
|
|
2611
|
+
Type /skill list to see available skills.`, createdAt: /* @__PURE__ */ new Date() }
|
|
2612
|
+
]);
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
const content = executor.getActiveSkillContent();
|
|
2616
|
+
const warningText = result.warnings && result.warnings.length > 0 ? `
|
|
2617
|
+
Warnings: ${result.warnings.join(", ")}` : "";
|
|
2618
|
+
setMessages((prev) => [
|
|
2619
|
+
...prev,
|
|
2620
|
+
{ id: v4Id(), role: "system", content: `Skill "$${skillName}" activated.${warningText}
|
|
2621
|
+
${content ? content.slice(0, 500) : ""}`, createdAt: /* @__PURE__ */ new Date() }
|
|
2622
|
+
]);
|
|
2623
|
+
} catch (error) {
|
|
2624
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2625
|
+
setMessages((prev) => [
|
|
2626
|
+
...prev,
|
|
2627
|
+
{ id: v4Id(), role: "system", content: `Skill error: ${msg}`, createdAt: /* @__PURE__ */ new Date() }
|
|
2628
|
+
]);
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
// src/ui/commands/login-commands.ts
|
|
2633
|
+
async function handleLoginSlashCommand(args, ctx) {
|
|
2634
|
+
const subcommand = args[0];
|
|
2635
|
+
if (subcommand === "status") {
|
|
2636
|
+
try {
|
|
2637
|
+
const { getAuthStatusRecords, formatCompactAuthStatusLine } = await import('./auth-status-JQJOKUPF.js');
|
|
2638
|
+
const records = await getAuthStatusRecords();
|
|
2639
|
+
const lines = records.map((record) => formatCompactAuthStatusLine(record));
|
|
2640
|
+
addSystemMessage(ctx, lines.join("\n"));
|
|
2641
|
+
} catch (error) {
|
|
2642
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2643
|
+
addSystemMessage(ctx, `Failed to get auth status: ${msg}`);
|
|
2644
|
+
}
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
if (subcommand === "logout") {
|
|
2648
|
+
const provider = args[1];
|
|
2649
|
+
if (!provider) {
|
|
2650
|
+
addSystemMessage(ctx, "Usage: /login logout <provider>\nProviders: claude, codex, gemini, kimi");
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
try {
|
|
2654
|
+
const loginMod = await loadLoginModuleForSlash(provider);
|
|
2655
|
+
await loginMod.logout();
|
|
2656
|
+
addSystemMessage(ctx, `Logged out of ${provider}`);
|
|
2657
|
+
} catch (error) {
|
|
2658
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2659
|
+
addSystemMessage(ctx, `Logout failed: ${msg}`);
|
|
2660
|
+
}
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
ctx.setSelectionMode({ type: "login" });
|
|
2664
|
+
}
|
|
2665
|
+
async function loadLoginModuleForSlash(provider) {
|
|
2666
|
+
switch (provider) {
|
|
2667
|
+
case "claude": {
|
|
2668
|
+
const mod = await import('./claude-login-AIFIWTYF.js');
|
|
2669
|
+
return new mod.ClaudeLogin();
|
|
2670
|
+
}
|
|
2671
|
+
case "codex": {
|
|
2672
|
+
const mod = await import('./codex-login-LW5X7GAM.js');
|
|
2673
|
+
return new mod.CodexLogin();
|
|
2674
|
+
}
|
|
2675
|
+
case "gemini": {
|
|
2676
|
+
const mod = await import('./gemini-login-TST454MX.js');
|
|
2677
|
+
return new mod.GeminiLogin();
|
|
2678
|
+
}
|
|
2679
|
+
case "kimi": {
|
|
2680
|
+
const mod = await import('./kimi-login-3IGVOBJI.js');
|
|
2681
|
+
return new mod.KimiLogin();
|
|
2682
|
+
}
|
|
2683
|
+
default:
|
|
2684
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
// src/ui/commands/config-commands.ts
|
|
2689
|
+
async function handleConfigSlashCommand(args, ctx) {
|
|
2690
|
+
const subcommand = args[0];
|
|
2691
|
+
if (subcommand === "get") {
|
|
2692
|
+
const key = args[1];
|
|
2693
|
+
try {
|
|
2694
|
+
const { ConfigStore } = await import('./config-store-NF56VHFU.js');
|
|
2695
|
+
const store = new ConfigStore();
|
|
2696
|
+
const cfg = store.loadGlobal();
|
|
2697
|
+
if (!key) {
|
|
2698
|
+
addSystemMessage(ctx, JSON.stringify(cfg, null, 2));
|
|
2699
|
+
} else {
|
|
2700
|
+
const value = getNestedConfigValue(cfg, key);
|
|
2701
|
+
if (value === void 0) {
|
|
2702
|
+
addSystemMessage(ctx, `Key not found: ${key}`);
|
|
2703
|
+
} else {
|
|
2704
|
+
addSystemMessage(ctx, `${key} = ${JSON.stringify(value, null, 2)}`);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
} catch (error) {
|
|
2708
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2709
|
+
addSystemMessage(ctx, `Failed to read config: ${msg}`);
|
|
2710
|
+
}
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
if (subcommand === "set") {
|
|
2714
|
+
const key = args[1];
|
|
2715
|
+
const value = args.slice(2).join(" ");
|
|
2716
|
+
if (!key || !value) {
|
|
2717
|
+
addSystemMessage(ctx, "Usage: /config set <key> <value>");
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
try {
|
|
2721
|
+
const { ConfigStore } = await import('./config-store-NF56VHFU.js');
|
|
2722
|
+
const store = new ConfigStore();
|
|
2723
|
+
const cfg = store.loadGlobal();
|
|
2724
|
+
let parsedValue;
|
|
2725
|
+
try {
|
|
2726
|
+
parsedValue = JSON.parse(value);
|
|
2727
|
+
} catch {
|
|
2728
|
+
parsedValue = value;
|
|
2729
|
+
}
|
|
2730
|
+
setNestedConfigValue(cfg, key, parsedValue);
|
|
2731
|
+
store.saveGlobal(cfg);
|
|
2732
|
+
addSystemMessage(ctx, `Set ${key} = ${JSON.stringify(parsedValue)}`);
|
|
2733
|
+
} catch (error) {
|
|
2734
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2735
|
+
addSystemMessage(ctx, `Failed to set config: ${msg}`);
|
|
2736
|
+
}
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
addSystemMessage(ctx, "Usage: /config get [key] | /config set <key> <value>");
|
|
2740
|
+
}
|
|
2741
|
+
function getNestedConfigValue(obj, path) {
|
|
2742
|
+
const keys = path.split(".");
|
|
2743
|
+
let current = obj;
|
|
2744
|
+
for (const key of keys) {
|
|
2745
|
+
if (current === null || current === void 0 || typeof current !== "object") return void 0;
|
|
2746
|
+
current = current[key];
|
|
2747
|
+
}
|
|
2748
|
+
return current;
|
|
2749
|
+
}
|
|
2750
|
+
function setNestedConfigValue(obj, path, value) {
|
|
2751
|
+
const keys = path.split(".");
|
|
2752
|
+
let current = obj;
|
|
2753
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
2754
|
+
const key = keys[i];
|
|
2755
|
+
if (!key) continue;
|
|
2756
|
+
if (typeof current[key] !== "object" || current[key] === null) current[key] = {};
|
|
2757
|
+
current = current[key];
|
|
2758
|
+
}
|
|
2759
|
+
const lastKey = keys[keys.length - 1];
|
|
2760
|
+
if (lastKey) current[lastKey] = value;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// src/ui/commands/history-commands.ts
|
|
2764
|
+
async function handleHistoryCommand(ctx) {
|
|
2765
|
+
try {
|
|
2766
|
+
const { SqliteStore } = await import('./sqlite-store-7OECRTXM.js');
|
|
2767
|
+
const { ConversationStore } = await import('./conversation-store-7GRDQZD2.js');
|
|
2768
|
+
const db = new SqliteStore();
|
|
2769
|
+
db.open();
|
|
2770
|
+
const store = new ConversationStore(db);
|
|
2771
|
+
const conversations = store.listConversations(ctx.projectRoot);
|
|
2772
|
+
db.close();
|
|
2773
|
+
if (conversations.length === 0) {
|
|
2774
|
+
addSystemMessage(ctx, "No conversation history for this project.");
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
const lines = conversations.slice(0, 20).map((c, i) => {
|
|
2778
|
+
const date = new Date(c.createdAt).toLocaleDateString();
|
|
2779
|
+
const model = c.defaultModel ?? "unknown";
|
|
2780
|
+
return ` ${String(i + 1).padStart(2)}. ${date} ${model.padEnd(20)} ${c.id.slice(0, 8)}\u2026`;
|
|
2781
|
+
});
|
|
2782
|
+
addSystemMessage(
|
|
2783
|
+
ctx,
|
|
2784
|
+
`Conversations in this project (${conversations.length} total):
|
|
2785
|
+
${lines.join("\n")}
|
|
2786
|
+
|
|
2787
|
+
Use /resume <number> or /resume <id> to load.`
|
|
2788
|
+
);
|
|
2789
|
+
} catch (error) {
|
|
2790
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2791
|
+
addSystemMessage(ctx, `Failed to load history: ${msg}`);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
async function handleResumeCommand(arg, ctx) {
|
|
2795
|
+
if (!arg) {
|
|
2796
|
+
addSystemMessage(ctx, "Usage: /resume <number> or /resume <conversation-id>\nUse /history to see past conversations.");
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
try {
|
|
2800
|
+
const { SqliteStore } = await import('./sqlite-store-7OECRTXM.js');
|
|
2801
|
+
const { ConversationStore } = await import('./conversation-store-7GRDQZD2.js');
|
|
2802
|
+
const db = new SqliteStore();
|
|
2803
|
+
db.open();
|
|
2804
|
+
const store = new ConversationStore(db);
|
|
2805
|
+
let conversationId;
|
|
2806
|
+
const num = parseInt(arg, 10);
|
|
2807
|
+
if (!isNaN(num) && num > 0) {
|
|
2808
|
+
const conversations = store.listConversations(ctx.projectRoot);
|
|
2809
|
+
const target = conversations[num - 1];
|
|
2810
|
+
if (!target) {
|
|
2811
|
+
db.close();
|
|
2812
|
+
addSystemMessage(ctx, `Conversation #${num} not found. Use /history to see available.`);
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
conversationId = target.id;
|
|
2816
|
+
} else {
|
|
2817
|
+
const conversations = store.listConversations(ctx.projectRoot);
|
|
2818
|
+
const match = conversations.find((c) => c.id.startsWith(arg));
|
|
2819
|
+
if (!match) {
|
|
2820
|
+
db.close();
|
|
2821
|
+
addSystemMessage(ctx, `No conversation matching "${arg}". Use /history to see available.`);
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2824
|
+
conversationId = match.id;
|
|
2825
|
+
}
|
|
2826
|
+
const storedMessages = store.getMessages(conversationId);
|
|
2827
|
+
db.close();
|
|
2828
|
+
if (storedMessages.length === 0) {
|
|
2829
|
+
addSystemMessage(ctx, "Conversation found but contains no messages.");
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
const restored = storedMessages.map((m) => ({
|
|
2833
|
+
id: v4Id(),
|
|
2834
|
+
role: m.role,
|
|
2835
|
+
content: m.content,
|
|
2836
|
+
model: m.model ?? void 0,
|
|
2837
|
+
provider: m.provider ?? void 0,
|
|
2838
|
+
createdAt: new Date(m.createdAt)
|
|
2839
|
+
}));
|
|
2840
|
+
ctx.setMessages(restored);
|
|
2841
|
+
addSystemMessage(ctx, `Resumed conversation (${restored.length} messages loaded).`);
|
|
2842
|
+
} catch (error) {
|
|
2843
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2844
|
+
addSystemMessage(ctx, `Failed to resume: ${msg}`);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
// src/ui/commands/slash-router.ts
|
|
2849
|
+
async function handleInternalCommand(input, switchModel, switchRole, ctx) {
|
|
2850
|
+
const parts = input.trim().split(/\s+/);
|
|
2851
|
+
const command = parts[0];
|
|
2852
|
+
const args = parts.slice(1);
|
|
2853
|
+
const arg = args[0];
|
|
2854
|
+
switch (command) {
|
|
2855
|
+
case "/help": {
|
|
2856
|
+
const helpLines = SLASH_COMMANDS.map((cmd) => ` ${cmd.command.padEnd(17)}${cmd.description}`).join("\n");
|
|
2857
|
+
addSystemMessage(ctx, helpLines);
|
|
2858
|
+
break;
|
|
2859
|
+
}
|
|
2860
|
+
case "/model": {
|
|
2861
|
+
if (arg) {
|
|
2862
|
+
const resolvedId = resolveModelSelection(arg);
|
|
2863
|
+
if (!resolvedId) {
|
|
2864
|
+
addSystemMessage(ctx, `Unknown model: ${arg}`);
|
|
2865
|
+
break;
|
|
2866
|
+
}
|
|
2867
|
+
const info = SUPPORTED_MODELS[resolvedId];
|
|
2868
|
+
if (!info) {
|
|
2869
|
+
addSystemMessage(ctx, `Unknown model: ${arg}`);
|
|
2870
|
+
break;
|
|
2871
|
+
}
|
|
2872
|
+
switchModel(resolvedId);
|
|
2873
|
+
const thinkingCfg = getThinkingConfigForModel(resolvedId);
|
|
2874
|
+
if (thinkingCfg) {
|
|
2875
|
+
const isValid = thinkingCfg.options.some((o) => o.value === ctx.thinkingValue);
|
|
2876
|
+
if (!isValid) ctx.setThinkingValue(thinkingCfg.defaultValue);
|
|
2877
|
+
}
|
|
2878
|
+
addSystemMessage(ctx, `Switched to model: ${info.name}`);
|
|
2879
|
+
} else {
|
|
2880
|
+
ctx.setSelectionMode({ type: "model" });
|
|
2881
|
+
}
|
|
2882
|
+
break;
|
|
2883
|
+
}
|
|
2884
|
+
case "/role": {
|
|
2885
|
+
if (arg) {
|
|
2886
|
+
const validRoles = ["planning", "coding", "review", "testing", "bugfix", "documentation"];
|
|
2887
|
+
if (validRoles.includes(arg)) {
|
|
2888
|
+
switchRole(arg);
|
|
2889
|
+
addSystemMessage(ctx, `Switched to role: ${arg}`);
|
|
2890
|
+
} else {
|
|
2891
|
+
addSystemMessage(ctx, `Unknown role: ${arg}
|
|
2892
|
+
Valid roles: ${validRoles.join(", ")}`);
|
|
2893
|
+
}
|
|
2894
|
+
} else {
|
|
2895
|
+
addSystemMessage(ctx, `Current role: ${ctx.resolution.role ?? "default"}`);
|
|
2896
|
+
}
|
|
2897
|
+
break;
|
|
2898
|
+
}
|
|
2899
|
+
case "/cost":
|
|
2900
|
+
addSystemMessage(ctx, `Session cost: ${ctx.totalCost} | Tokens: ${ctx.totalTokens}`);
|
|
2901
|
+
break;
|
|
2902
|
+
case "/clear":
|
|
2903
|
+
ctx.setMessages([]);
|
|
2904
|
+
break;
|
|
2905
|
+
case "/compact":
|
|
2906
|
+
ctx.setMessages((prev) => {
|
|
2907
|
+
if (prev.length <= 4) return prev;
|
|
2908
|
+
const systemMsgs = prev.filter((m) => m.role === "system");
|
|
2909
|
+
const recent = prev.filter((m) => m.role !== "system").slice(-4);
|
|
2910
|
+
return [...systemMsgs, ...recent];
|
|
2911
|
+
});
|
|
2912
|
+
addSystemMessage(ctx, `Context compacted \u2014 kept last 4 messages. Use /clear to remove all.`);
|
|
2913
|
+
break;
|
|
2914
|
+
case "/team":
|
|
2915
|
+
await handleTeamCommand(args, ctx);
|
|
2916
|
+
break;
|
|
2917
|
+
case "/mcp":
|
|
2918
|
+
await handleMcpCommand(args, ctx);
|
|
2919
|
+
break;
|
|
2920
|
+
case "/skill":
|
|
2921
|
+
await handleSkillCommand(args, ctx);
|
|
2922
|
+
break;
|
|
2923
|
+
case "/panel": {
|
|
2924
|
+
const layout = arg;
|
|
2925
|
+
if (!layout) {
|
|
2926
|
+
addSystemMessage(
|
|
2927
|
+
ctx,
|
|
2928
|
+
"Swarm layout is fixed to hub-and-spoke:\n- master agent on the left half\n- worker agents stacked on the right\nUse Shift+Tab to enter swarm mode, then Tab/Ctrl+N to focus agents."
|
|
2929
|
+
);
|
|
2930
|
+
} else {
|
|
2931
|
+
addSystemMessage(ctx, `Panel layout overrides are disabled. Active swarm layout remains hub-spoke (master left, workers right). Ignored: ${layout}`);
|
|
2932
|
+
}
|
|
2933
|
+
break;
|
|
2934
|
+
}
|
|
2935
|
+
case "/login":
|
|
2936
|
+
await handleLoginSlashCommand(args, ctx);
|
|
2937
|
+
break;
|
|
2938
|
+
case "/config":
|
|
2939
|
+
await handleConfigSlashCommand(args, ctx);
|
|
2940
|
+
break;
|
|
2941
|
+
case "/launch":
|
|
2942
|
+
addSystemMessage(
|
|
2943
|
+
ctx,
|
|
2944
|
+
"Swarm orchestration now lives inside the default TUI.\n\nUse Shift+Tab to switch into swarm mode, then describe the work you want the team to handle.\nThe configured master agent will sponsor the team and own the left pane."
|
|
2945
|
+
);
|
|
2946
|
+
break;
|
|
2947
|
+
case "/history":
|
|
2948
|
+
await handleHistoryCommand(ctx);
|
|
2949
|
+
break;
|
|
2950
|
+
case "/resume":
|
|
2951
|
+
await handleResumeCommand(arg, ctx);
|
|
2952
|
+
break;
|
|
2953
|
+
case "/quit":
|
|
2954
|
+
case "/exit": {
|
|
2955
|
+
const { getActiveTeamManager: getActiveTeamManager2, getActiveTeamName: getActiveTeamName2, getActiveTmuxCleanup: getActiveTmuxCleanup2 } = await import('./team-state-HZNVMQHT.js');
|
|
2956
|
+
const teamCleanup = getActiveTmuxCleanup2();
|
|
2957
|
+
if (teamCleanup) {
|
|
2958
|
+
try {
|
|
2959
|
+
await teamCleanup();
|
|
2960
|
+
} catch {
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
const mgr = getActiveTeamManager2();
|
|
2964
|
+
const name = getActiveTeamName2();
|
|
2965
|
+
if (mgr && name) {
|
|
2966
|
+
try {
|
|
2967
|
+
await mgr.deleteTeam(name);
|
|
2968
|
+
} catch {
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
process.exit(0);
|
|
2972
|
+
break;
|
|
2973
|
+
}
|
|
2974
|
+
default:
|
|
2975
|
+
addSystemMessage(ctx, `Unknown command: ${command}. Type /help for available commands.`);
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
// src/ui/conversation-persistence.ts
|
|
2980
|
+
var convDbRef;
|
|
2981
|
+
async function persistMessages(projectRoot, userMsg, assistantMsg, model, provider) {
|
|
2982
|
+
try {
|
|
2983
|
+
const { SqliteStore } = await import('./sqlite-store-7OECRTXM.js');
|
|
2984
|
+
const { ConversationStore } = await import('./conversation-store-7GRDQZD2.js');
|
|
2985
|
+
if (!convDbRef) {
|
|
2986
|
+
const db = new SqliteStore();
|
|
2987
|
+
db.open();
|
|
2988
|
+
const store2 = new ConversationStore(db);
|
|
2989
|
+
const conv = store2.createConversation(projectRoot, model);
|
|
2990
|
+
convDbRef = { store: store2, convId: conv.id };
|
|
2991
|
+
}
|
|
2992
|
+
const { store, convId } = convDbRef;
|
|
2993
|
+
store.addMessage({
|
|
2994
|
+
conversationId: convId,
|
|
2995
|
+
role: "user",
|
|
2996
|
+
content: userMsg.content
|
|
2997
|
+
});
|
|
2998
|
+
store.addMessage({
|
|
2999
|
+
conversationId: convId,
|
|
3000
|
+
role: "assistant",
|
|
3001
|
+
model,
|
|
3002
|
+
provider,
|
|
3003
|
+
content: assistantMsg.content
|
|
3004
|
+
});
|
|
3005
|
+
} catch (error) {
|
|
3006
|
+
const { logger } = await import('./logger-KGHUQ4VE.js');
|
|
3007
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3008
|
+
logger.warn({ error: msg }, "Conversation persistence failed");
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
// src/ui/team-design.ts
|
|
3013
|
+
var TEAM_DESIGN_SYSTEM_PROMPT = `You are an expert team architect for an AI-powered CLI coding tool. Your job is to analyze a user's request and design an optimal agent team to accomplish it.
|
|
3014
|
+
|
|
3015
|
+
You MUST respond with ONLY a JSON array (no markdown, no explanation, no code fences). Each element represents one agent with these exact fields:
|
|
3016
|
+
- "name": string \u2014 PascalCase name describing the agent's function (e.g. "ProjectManager", "TypeScriptDeveloper", "SecurityAuditor")
|
|
3017
|
+
- "agentType": string \u2014 short role type (e.g. "lead", "developer", "reviewer", "researcher", "designer", "architect", "auditor", "tester")
|
|
3018
|
+
- "model": string \u2014 one of the available models (provided in the user message)
|
|
3019
|
+
- "role": string \u2014 one of: "planning", "coding", "review", "testing", "bugfix", "documentation"
|
|
3020
|
+
- "taskPrompt": string \u2014 detailed, specific instructions for this agent. Include: what to focus on, what files/areas to examine, what deliverables are expected, and what tools to use. This must be tailored to the user's actual request, NOT generic.
|
|
3021
|
+
|
|
3022
|
+
Guidelines for team design:
|
|
3023
|
+
- Use the MINIMUM number of agents needed to accomplish the task (typically 2-5, max 8)
|
|
3024
|
+
- Assign the most capable models (opus, gpt-5.2, gemini pro) to critical roles like planning and review
|
|
3025
|
+
- Assign efficient models (sonnet, haiku, flash) to implementation, testing, and documentation
|
|
3026
|
+
- Every team MUST have at least one agent with role "coding" to do actual implementation
|
|
3027
|
+
- For complex tasks, include a planner/lead agent and a reviewer agent
|
|
3028
|
+
- If only one provider's models are available, use those models for all agents
|
|
3029
|
+
- Each agent's taskPrompt must be highly specific to the user's request \u2014 never use generic instructions
|
|
3030
|
+
- Agent names should reflect their specific responsibility in this task
|
|
3031
|
+
`;
|
|
3032
|
+
var VALID_ROLES = /* @__PURE__ */ new Set([
|
|
3033
|
+
"planning",
|
|
3034
|
+
"coding",
|
|
3035
|
+
"review",
|
|
3036
|
+
"testing",
|
|
3037
|
+
"bugfix",
|
|
3038
|
+
"documentation"
|
|
3039
|
+
]);
|
|
3040
|
+
function parseLLMTeamDesign(response, availableModels, fallbackModel) {
|
|
3041
|
+
let jsonStr = response.trim();
|
|
3042
|
+
const fenceMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?```/.exec(jsonStr);
|
|
3043
|
+
if (fenceMatch?.[1] !== void 0) {
|
|
3044
|
+
jsonStr = fenceMatch[1].trim();
|
|
3045
|
+
}
|
|
3046
|
+
const arrayStart = jsonStr.indexOf("[");
|
|
3047
|
+
const arrayEnd = jsonStr.lastIndexOf("]");
|
|
3048
|
+
if (arrayStart === -1 || arrayEnd === -1 || arrayEnd <= arrayStart) {
|
|
3049
|
+
throw new Error("LLM response does not contain a valid JSON array for team design");
|
|
3050
|
+
}
|
|
3051
|
+
jsonStr = jsonStr.slice(arrayStart, arrayEnd + 1);
|
|
3052
|
+
let parsed;
|
|
3053
|
+
try {
|
|
3054
|
+
parsed = JSON.parse(jsonStr);
|
|
3055
|
+
} catch {
|
|
3056
|
+
throw new Error("Failed to parse LLM team design as JSON");
|
|
3057
|
+
}
|
|
3058
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
3059
|
+
throw new Error("LLM team design must be a non-empty array");
|
|
3060
|
+
}
|
|
3061
|
+
const availableSet = new Set(availableModels);
|
|
3062
|
+
const specs = [];
|
|
3063
|
+
for (const item of parsed) {
|
|
3064
|
+
if (typeof item !== "object" || item === null) continue;
|
|
3065
|
+
const record = item;
|
|
3066
|
+
const name = typeof record["name"] === "string" && record["name"].length > 0 ? record["name"] : `Agent${specs.length + 1}`;
|
|
3067
|
+
const agentType = typeof record["agentType"] === "string" && record["agentType"].length > 0 ? record["agentType"] : "developer";
|
|
3068
|
+
const rawModel = typeof record["model"] === "string" ? record["model"] : "";
|
|
3069
|
+
const rawRole = typeof record["role"] === "string" ? record["role"] : "coding";
|
|
3070
|
+
const taskPrompt = typeof record["taskPrompt"] === "string" ? record["taskPrompt"] : "";
|
|
3071
|
+
const model = availableSet.has(rawModel) ? rawModel : fallbackModel;
|
|
3072
|
+
const role = VALID_ROLES.has(rawRole) ? rawRole : "coding";
|
|
3073
|
+
specs.push({ name, agentType, model, role, taskPrompt });
|
|
3074
|
+
}
|
|
3075
|
+
if (specs.length === 0) {
|
|
3076
|
+
throw new Error("LLM team design produced no valid agent specifications");
|
|
3077
|
+
}
|
|
3078
|
+
return specs.slice(0, 8);
|
|
3079
|
+
}
|
|
3080
|
+
function buildTeamDesignUserPrompt(userRequest, availableModels) {
|
|
3081
|
+
const modelList = availableModels.map((id) => {
|
|
3082
|
+
const info = SUPPORTED_MODELS[id];
|
|
3083
|
+
if (!info) return `- ${id}`;
|
|
3084
|
+
return `- ${id} (${info.provider}, ${info.name})`;
|
|
3085
|
+
}).join("\n");
|
|
3086
|
+
return `Available models:
|
|
3087
|
+
${modelList}
|
|
3088
|
+
|
|
3089
|
+
User request:
|
|
3090
|
+
${userRequest}
|
|
3091
|
+
|
|
3092
|
+
Design the agent team. Respond with ONLY a JSON array.`;
|
|
3093
|
+
}
|
|
3094
|
+
function resolveMasterProviderPriority(swarmConfig, installedProviders) {
|
|
3095
|
+
const configuredProviders = [
|
|
3096
|
+
...swarmConfig.primaryMasterProvider ? [swarmConfig.primaryMasterProvider] : [],
|
|
3097
|
+
...swarmConfig.fallbackMasterProviders
|
|
3098
|
+
];
|
|
3099
|
+
const prioritized = configuredProviders.filter((provider) => installedProviders.includes(provider));
|
|
3100
|
+
const remaining = installedProviders.filter((provider) => !prioritized.includes(provider));
|
|
3101
|
+
return [...prioritized, ...remaining];
|
|
3102
|
+
}
|
|
3103
|
+
function getAvailableModelsForProviders(providers) {
|
|
3104
|
+
const allowedProviders = new Set(
|
|
3105
|
+
providers.map((provider) => getCliProviderEntry(provider).provider)
|
|
3106
|
+
);
|
|
3107
|
+
return Object.keys(SUPPORTED_MODELS).filter((modelId) => {
|
|
3108
|
+
const info = SUPPORTED_MODELS[modelId];
|
|
3109
|
+
return info !== void 0 && allowedProviders.has(info.provider);
|
|
3110
|
+
});
|
|
3111
|
+
}
|
|
3112
|
+
function pickLeadModel(config, prioritizedProviders, availableModels) {
|
|
3113
|
+
for (const provider of prioritizedProviders) {
|
|
3114
|
+
const providerName = getCliProviderEntry(provider).provider;
|
|
3115
|
+
const planningCandidates = [
|
|
3116
|
+
config.roles.planning?.primary,
|
|
3117
|
+
...config.roles.planning?.fallback ?? []
|
|
3118
|
+
].filter((modelId) => modelId !== void 0);
|
|
3119
|
+
const preferredPlanningModel = planningCandidates.find((modelId) => {
|
|
3120
|
+
const info = SUPPORTED_MODELS[modelId];
|
|
3121
|
+
return info?.provider === providerName && availableModels.includes(modelId);
|
|
3122
|
+
});
|
|
3123
|
+
if (preferredPlanningModel) {
|
|
3124
|
+
return preferredPlanningModel;
|
|
3125
|
+
}
|
|
3126
|
+
const firstAvailableModel = availableModels.find((modelId) => {
|
|
3127
|
+
const info = SUPPORTED_MODELS[modelId];
|
|
3128
|
+
return info?.provider === providerName;
|
|
3129
|
+
});
|
|
3130
|
+
if (firstAvailableModel) {
|
|
3131
|
+
return firstAvailableModel;
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
return availableModels[0];
|
|
3135
|
+
}
|
|
3136
|
+
function normalizeLeadAgentSpec(specs, masterModel) {
|
|
3137
|
+
const leadSpec = specs.find((spec) => spec.agentType === "lead") ?? specs.find((spec) => spec.role === "planning") ?? specs[0];
|
|
3138
|
+
if (!leadSpec) {
|
|
3139
|
+
return specs;
|
|
3140
|
+
}
|
|
3141
|
+
const normalizedLead = {
|
|
3142
|
+
...leadSpec,
|
|
3143
|
+
name: leadSpec.name,
|
|
3144
|
+
agentType: "lead",
|
|
3145
|
+
model: masterModel,
|
|
3146
|
+
role: "planning",
|
|
3147
|
+
taskPrompt: `${leadSpec.taskPrompt}
|
|
3148
|
+
|
|
3149
|
+
You are the sponsoring master agent. Own planning, delegation, and final synthesis.`
|
|
3150
|
+
};
|
|
3151
|
+
const workerSpecs = specs.filter((spec) => spec.name !== leadSpec.name);
|
|
3152
|
+
return [normalizedLead, ...workerSpecs];
|
|
3153
|
+
}
|
|
3154
|
+
function writeAgentLauncherScript(provider, model, promptFile, launcherFile, projectRoot, shellEscapeFn, writeFileSyncFn) {
|
|
3155
|
+
if (process.platform === "win32") {
|
|
3156
|
+
return writeWindowsLauncherScript(
|
|
3157
|
+
provider,
|
|
3158
|
+
model,
|
|
3159
|
+
promptFile,
|
|
3160
|
+
launcherFile,
|
|
3161
|
+
projectRoot,
|
|
3162
|
+
writeFileSyncFn
|
|
3163
|
+
);
|
|
3164
|
+
}
|
|
3165
|
+
const cliProvider = getCliProviderForModelProvider(provider);
|
|
3166
|
+
if (cliProvider) {
|
|
3167
|
+
const startCommand = getCliProviderEntry(cliProvider).startCommand(model);
|
|
3168
|
+
const script2 = [
|
|
3169
|
+
"#!/bin/bash",
|
|
3170
|
+
`cd '${shellEscapeFn(projectRoot)}' || exit 1`,
|
|
3171
|
+
`${startCommand} "$(cat '${shellEscapeFn(promptFile)}')"`
|
|
3172
|
+
].join("\n");
|
|
3173
|
+
writeFileSyncFn(launcherFile, script2, { mode: 493 });
|
|
3174
|
+
return `bash '${shellEscapeFn(launcherFile)}'`;
|
|
3175
|
+
}
|
|
3176
|
+
const script = [
|
|
3177
|
+
"#!/bin/bash",
|
|
3178
|
+
`cd '${shellEscapeFn(projectRoot)}' || exit 1`,
|
|
3179
|
+
`export AEMEATHCLI_PROMPT_FILE='${shellEscapeFn(promptFile)}'`,
|
|
3180
|
+
`'${shellEscapeFn(process.execPath)}' '${shellEscapeFn(process.argv[1] ?? "aemeathcli")}' --model ${model}`
|
|
3181
|
+
].join("\n");
|
|
3182
|
+
writeFileSyncFn(launcherFile, script, { mode: 493 });
|
|
3183
|
+
return `bash '${shellEscapeFn(launcherFile)}'`;
|
|
3184
|
+
}
|
|
3185
|
+
function writeWindowsLauncherScript(provider, model, promptFile, launcherFile, projectRoot, writeFileSyncFn) {
|
|
3186
|
+
const ps1File = launcherFile.replace(/\.[^.]+$/, ".ps1");
|
|
3187
|
+
const cliProvider = getCliProviderForModelProvider(provider);
|
|
3188
|
+
if (cliProvider) {
|
|
3189
|
+
const startCommand = getCliProviderEntry(cliProvider).startCommand(model);
|
|
3190
|
+
const script2 = [
|
|
3191
|
+
`Set-Location -Path '${projectRoot.replace(/'/g, "''")}'`,
|
|
3192
|
+
`$prompt = Get-Content -Path '${promptFile.replace(/'/g, "''")}' -Raw`,
|
|
3193
|
+
`& ${startCommand} $prompt`
|
|
3194
|
+
].join("\r\n");
|
|
3195
|
+
writeFileSyncFn(ps1File, script2);
|
|
3196
|
+
return `powershell -ExecutionPolicy Bypass -File "${ps1File}"`;
|
|
3197
|
+
}
|
|
3198
|
+
const script = [
|
|
3199
|
+
`Set-Location -Path '${projectRoot.replace(/'/g, "''")}'`,
|
|
3200
|
+
`$env:AEMEATHCLI_PROMPT_FILE = '${promptFile.replace(/'/g, "''")}'`,
|
|
3201
|
+
`& '${process.execPath.replace(/'/g, "''")}' '${(process.argv[1] ?? "aemeathcli").replace(/'/g, "''")}' --model ${model}`
|
|
3202
|
+
].join("\r\n");
|
|
3203
|
+
writeFileSyncFn(ps1File, script);
|
|
3204
|
+
return `powershell -ExecutionPolicy Bypass -File "${ps1File}"`;
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
// src/ui/team-launcher.ts
|
|
3208
|
+
function sysMsg(setMessages, content) {
|
|
3209
|
+
setMessages((prev) => [
|
|
3210
|
+
...prev,
|
|
3211
|
+
{ id: v4Id(), role: "system", content, createdAt: /* @__PURE__ */ new Date() }
|
|
3212
|
+
]);
|
|
3213
|
+
}
|
|
3214
|
+
async function handlePromptBasedTeamCreation(input, setMessages, panel, getRegistry, currentModelId, config, swarmConfig) {
|
|
3215
|
+
const teamName = `team-${Date.now()}`;
|
|
3216
|
+
setMessages((prev) => [
|
|
3217
|
+
...prev,
|
|
3218
|
+
{ id: v4Id(), role: "user", content: input, createdAt: /* @__PURE__ */ new Date() },
|
|
3219
|
+
{ id: v4Id(), role: "system", content: "Analyzing your request to design the agent team...", createdAt: /* @__PURE__ */ new Date() }
|
|
3220
|
+
]);
|
|
3221
|
+
try {
|
|
3222
|
+
const registry = await getRegistry();
|
|
3223
|
+
const { detectInstalledProviders } = await import('./detect-providers-QICJ5U3R.js');
|
|
3224
|
+
const installedClis = detectInstalledProviders();
|
|
3225
|
+
const prioritizedMasterProviders = resolveMasterProviderPriority(swarmConfig, installedClis);
|
|
3226
|
+
const availableModels = getAvailableModelsForProviders(installedClis);
|
|
3227
|
+
if (availableModels.length === 0) {
|
|
3228
|
+
sysMsg(setMessages, "No AI CLI tools detected. Install at least one: claude, codex, gemini, kimi, or ollama.");
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
const masterLeadModel = pickLeadModel(config, prioritizedMasterProviders, availableModels);
|
|
3232
|
+
if (masterLeadModel === void 0) {
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
const designModel = registry.hasModel(currentModelId) ? currentModelId : registry.hasModel(masterLeadModel) ? masterLeadModel : void 0;
|
|
3236
|
+
if (!designModel) {
|
|
3237
|
+
sysMsg(setMessages, "Swarm team design needs at least one authenticated chat provider. Run `aemeathcli auth login` or configure an API key, then try again.");
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
const designProvider = registry.getForModel(designModel);
|
|
3241
|
+
const primaryMasterLabel = prioritizedMasterProviders[0] ? getCliProviderEntry(prioritizedMasterProviders[0]).label : "current configuration";
|
|
3242
|
+
const userPrompt = `${buildTeamDesignUserPrompt(input, availableModels)}
|
|
3243
|
+
|
|
3244
|
+
Master-agent provider priority:
|
|
3245
|
+
${prioritizedMasterProviders.map((provider) => `- ${getCliProviderEntry(provider).label}`).join("\n")}
|
|
3246
|
+
|
|
3247
|
+
The sponsoring lead agent must use ${primaryMasterLabel} when possible.`;
|
|
3248
|
+
const DESIGN_TIMEOUT_MS = 6e4;
|
|
3249
|
+
sysMsg(setMessages, `Designing team with ${designModel}...`);
|
|
3250
|
+
const designStream = designProvider.stream({
|
|
3251
|
+
model: designModel,
|
|
3252
|
+
messages: [{ id: v4Id(), role: "user", content: userPrompt, createdAt: /* @__PURE__ */ new Date() }],
|
|
3253
|
+
system: TEAM_DESIGN_SYSTEM_PROMPT,
|
|
3254
|
+
maxTokens: 4e3
|
|
3255
|
+
});
|
|
3256
|
+
let designResponse = "";
|
|
3257
|
+
const designResult = await Promise.race([
|
|
3258
|
+
(async () => {
|
|
3259
|
+
for await (const chunk of designStream) {
|
|
3260
|
+
if (chunk.type === "text" && chunk.content) designResponse += chunk.content;
|
|
3261
|
+
if (chunk.type === "error" && chunk.error) throw new Error(`Design stream error: ${chunk.error}`);
|
|
3262
|
+
}
|
|
3263
|
+
return designResponse;
|
|
3264
|
+
})(),
|
|
3265
|
+
new Promise((_resolve, reject) => {
|
|
3266
|
+
setTimeout(() => {
|
|
3267
|
+
reject(new Error(`Team design timed out after ${DESIGN_TIMEOUT_MS / 1e3}s. Check your API credentials and try again.`));
|
|
3268
|
+
}, DESIGN_TIMEOUT_MS);
|
|
3269
|
+
})
|
|
3270
|
+
]);
|
|
3271
|
+
designResponse = designResult;
|
|
3272
|
+
const teamMasterModel = availableModels.includes(currentModelId) ? currentModelId : masterLeadModel;
|
|
3273
|
+
const agentSpecs = normalizeLeadAgentSpec(
|
|
3274
|
+
parseLLMTeamDesign(designResponse, availableModels, designModel),
|
|
3275
|
+
teamMasterModel
|
|
3276
|
+
);
|
|
3277
|
+
const projectRoot = process.cwd();
|
|
3278
|
+
const isWindows = process.platform === "win32";
|
|
3279
|
+
const isWindowsTerminal = isWindows && typeof process.env["WT_SESSION"] === "string" && process.env["WT_SESSION"].length > 0;
|
|
3280
|
+
if (isWindows && !isWindowsTerminal) {
|
|
3281
|
+
await launchInProcess(
|
|
3282
|
+
agentSpecs,
|
|
3283
|
+
input,
|
|
3284
|
+
teamName,
|
|
3285
|
+
config,
|
|
3286
|
+
panel,
|
|
3287
|
+
setMessages,
|
|
3288
|
+
projectRoot
|
|
3289
|
+
);
|
|
3290
|
+
return void 0;
|
|
3291
|
+
}
|
|
3292
|
+
const fs = await import('fs');
|
|
3293
|
+
const path = await import('path');
|
|
3294
|
+
const os = await import('os');
|
|
3295
|
+
const { execa: execaPane } = await import('execa');
|
|
3296
|
+
const boardDir = path.join(process.cwd(), ".aemeathcli", "team-board");
|
|
3297
|
+
fs.mkdirSync(boardDir, { recursive: true });
|
|
3298
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "aemeathcli-team-"));
|
|
3299
|
+
const shellEscape = (s) => s.replace(/'/gu, "'\\''");
|
|
3300
|
+
const [leadSpec, ...workerSpecs] = agentSpecs;
|
|
3301
|
+
if (!leadSpec) return;
|
|
3302
|
+
const teamManifest = {
|
|
3303
|
+
teamName,
|
|
3304
|
+
task: input,
|
|
3305
|
+
boardDir,
|
|
3306
|
+
leadAgent: leadSpec.name,
|
|
3307
|
+
agents: agentSpecs.map((s) => ({
|
|
3308
|
+
name: s.name,
|
|
3309
|
+
agentType: s.agentType,
|
|
3310
|
+
model: s.model,
|
|
3311
|
+
role: s.role,
|
|
3312
|
+
outputFile: path.join(boardDir, `${s.name}.md`)
|
|
3313
|
+
})),
|
|
3314
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3315
|
+
};
|
|
3316
|
+
fs.writeFileSync(path.join(boardDir, "team-manifest.json"), JSON.stringify(teamManifest, null, 2), "utf-8");
|
|
3317
|
+
const teamRoster = agentSpecs.map((s) => ` - ${s.name} (${s.agentType}, ${s.model}) \u2014 role: ${s.role}`).join("\n");
|
|
3318
|
+
const workerCommands = [];
|
|
3319
|
+
for (const spec of workerSpecs) {
|
|
3320
|
+
const outputFile = path.join(boardDir, `${spec.name}.md`);
|
|
3321
|
+
const promptFile = path.join(tempDir, `${spec.name}.txt`);
|
|
3322
|
+
const prompt = [
|
|
3323
|
+
`# Team Role: ${spec.name}`,
|
|
3324
|
+
`Type: ${spec.agentType} | Model: ${spec.model} | Role: ${spec.role}`,
|
|
3325
|
+
"",
|
|
3326
|
+
"## Your Task",
|
|
3327
|
+
spec.taskPrompt,
|
|
3328
|
+
"",
|
|
3329
|
+
"## Team Context",
|
|
3330
|
+
`You are part of a ${agentSpecs.length}-agent team working collaboratively.`,
|
|
3331
|
+
`Each agent has a specific domain. Do NOT overlap with other agents' responsibilities.`,
|
|
3332
|
+
"",
|
|
3333
|
+
"### Team Members:",
|
|
3334
|
+
teamRoster,
|
|
3335
|
+
"",
|
|
3336
|
+
"## Shared Workspace",
|
|
3337
|
+
`Team board directory: ${boardDir}`,
|
|
3338
|
+
`Your output file: ${outputFile}`,
|
|
3339
|
+
`Team manifest: ${path.join(boardDir, "team-manifest.json")}`,
|
|
3340
|
+
"",
|
|
3341
|
+
"## Coordination Protocol",
|
|
3342
|
+
`1. Write ALL your findings, analysis, and deliverables to your output file: ${outputFile}`,
|
|
3343
|
+
`2. You can read other agents' output files in ${boardDir}/ to check their progress and avoid duplication.`,
|
|
3344
|
+
`3. Focus on YOUR specific domain. Reference other agents' work when relevant to your analysis.`,
|
|
3345
|
+
`4. Read and analyze source files in the project directory to complete your task.`,
|
|
3346
|
+
`5. Be thorough and specific. Include file paths, line numbers, and code snippets in your findings.`,
|
|
3347
|
+
"",
|
|
3348
|
+
"## Coordination with Lead",
|
|
3349
|
+
`The team lead is ${leadSpec.name}. Check their coordination plan at: ${path.join(boardDir, "coordinator.md")}`,
|
|
3350
|
+
`After completing your work, the lead agent will read your output and synthesize findings.`,
|
|
3351
|
+
"",
|
|
3352
|
+
"## User's Original Request",
|
|
3353
|
+
input
|
|
3354
|
+
].join("\n");
|
|
3355
|
+
fs.writeFileSync(promptFile, prompt, "utf-8");
|
|
3356
|
+
const modelInfo = SUPPORTED_MODELS[spec.model];
|
|
3357
|
+
const provider = modelInfo?.provider ?? "anthropic";
|
|
3358
|
+
const launcherFile = path.join(tempDir, `${spec.name}-launch.sh`);
|
|
3359
|
+
const cmd = writeAgentLauncherScript(
|
|
3360
|
+
provider,
|
|
3361
|
+
spec.model,
|
|
3362
|
+
promptFile,
|
|
3363
|
+
launcherFile,
|
|
3364
|
+
projectRoot,
|
|
3365
|
+
shellEscape,
|
|
3366
|
+
fs.writeFileSync
|
|
3367
|
+
);
|
|
3368
|
+
workerCommands.push({ name: spec.name, command: cmd });
|
|
3369
|
+
}
|
|
3370
|
+
const leadOutputFile = path.join(boardDir, `${leadSpec.name}.md`);
|
|
3371
|
+
const leadCoordinationTask = [
|
|
3372
|
+
`I am the team coordinator (${leadSpec.name}, ${leadSpec.model}).`,
|
|
3373
|
+
"",
|
|
3374
|
+
leadSpec.taskPrompt,
|
|
3375
|
+
"",
|
|
3376
|
+
`## My Responsibilities`,
|
|
3377
|
+
`1. Break down the user's request into clear subtasks for each team member.`,
|
|
3378
|
+
`2. Write my coordination plan and task assignments to: ${path.join(boardDir, "coordinator.md")}`,
|
|
3379
|
+
`3. After completing my own analysis, read other agents' output files in ${boardDir}/ to check progress.`,
|
|
3380
|
+
`4. Synthesize findings from all agents into a final team summary at: ${path.join(boardDir, "SUMMARY.md")}`,
|
|
3381
|
+
`5. Flag any conflicts, gaps, or overlaps between agents' work.`,
|
|
3382
|
+
"",
|
|
3383
|
+
`## Team Members Working in Parallel Panes`,
|
|
3384
|
+
...workerSpecs.map((s) => `- ${s.name} (${s.model}): writing to ${path.join(boardDir, s.name + ".md")}`),
|
|
3385
|
+
"",
|
|
3386
|
+
`## My Output File: ${leadOutputFile}`,
|
|
3387
|
+
"",
|
|
3388
|
+
`## User's Original Request`,
|
|
3389
|
+
input,
|
|
3390
|
+
"",
|
|
3391
|
+
`Start by reading the codebase and writing my coordination plan to ${path.join(boardDir, "coordinator.md")}.`
|
|
3392
|
+
].join("\n");
|
|
3393
|
+
const leadPromptFile = path.join(tempDir, `${leadSpec.name}.txt`);
|
|
3394
|
+
const leadLauncherFile = path.join(tempDir, `${leadSpec.name}-launch.sh`);
|
|
3395
|
+
fs.writeFileSync(leadPromptFile, leadCoordinationTask, "utf-8");
|
|
3396
|
+
const leadCommand = writeAgentLauncherScript(
|
|
3397
|
+
SUPPORTED_MODELS[leadSpec.model]?.provider ?? "anthropic",
|
|
3398
|
+
leadSpec.model,
|
|
3399
|
+
leadPromptFile,
|
|
3400
|
+
leadLauncherFile,
|
|
3401
|
+
projectRoot,
|
|
3402
|
+
shellEscape,
|
|
3403
|
+
fs.writeFileSync
|
|
3404
|
+
);
|
|
3405
|
+
const allPaneCommands = [{ name: leadSpec.name, command: leadCommand }, ...workerCommands];
|
|
3406
|
+
const formatAgentLines = (lead) => agentSpecs.map(
|
|
3407
|
+
(spec) => spec.name === lead ? ` ${spec.name} (${spec.model}) \u2014 ${spec.role} [LEAD \u2014 this pane]` : ` ${spec.name} (${spec.model}) \u2014 ${spec.role}`
|
|
3408
|
+
);
|
|
3409
|
+
const isITerm2 = process.platform === "darwin" && process.env["TERM_PROGRAM"] === "iTerm.app";
|
|
3410
|
+
if (isITerm2) {
|
|
3411
|
+
return await launchITerm2(
|
|
3412
|
+
agentSpecs,
|
|
3413
|
+
leadSpec,
|
|
3414
|
+
workerCommands,
|
|
3415
|
+
teamName,
|
|
3416
|
+
tempDir,
|
|
3417
|
+
boardDir,
|
|
3418
|
+
shellEscape,
|
|
3419
|
+
fs,
|
|
3420
|
+
path,
|
|
3421
|
+
execaPane,
|
|
3422
|
+
setMessages,
|
|
3423
|
+
formatAgentLines,
|
|
3424
|
+
leadCoordinationTask
|
|
3425
|
+
);
|
|
3426
|
+
}
|
|
3427
|
+
const isGhostty = process.platform === "darwin" && process.env["TERM_PROGRAM"] === "ghostty";
|
|
3428
|
+
if (isGhostty) {
|
|
3429
|
+
return await launchGhostty(
|
|
3430
|
+
agentSpecs,
|
|
3431
|
+
leadSpec,
|
|
3432
|
+
workerCommands,
|
|
3433
|
+
teamName,
|
|
3434
|
+
tempDir,
|
|
3435
|
+
boardDir,
|
|
3436
|
+
fs,
|
|
3437
|
+
path,
|
|
3438
|
+
execaPane,
|
|
3439
|
+
setMessages,
|
|
3440
|
+
formatAgentLines,
|
|
3441
|
+
leadCoordinationTask
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
const isTerminalApp = process.platform === "darwin" && process.env["TERM_PROGRAM"] === "Apple_Terminal";
|
|
3445
|
+
if (isTerminalApp) {
|
|
3446
|
+
return await launchTerminalApp(
|
|
3447
|
+
agentSpecs,
|
|
3448
|
+
leadSpec,
|
|
3449
|
+
workerCommands,
|
|
3450
|
+
teamName,
|
|
3451
|
+
tempDir,
|
|
3452
|
+
boardDir,
|
|
3453
|
+
fs,
|
|
3454
|
+
path,
|
|
3455
|
+
execaPane,
|
|
3456
|
+
setMessages,
|
|
3457
|
+
formatAgentLines,
|
|
3458
|
+
leadCoordinationTask
|
|
3459
|
+
);
|
|
3460
|
+
}
|
|
3461
|
+
if (isWindowsTerminal) {
|
|
3462
|
+
return await launchWindowsTerminal(
|
|
3463
|
+
agentSpecs,
|
|
3464
|
+
leadSpec,
|
|
3465
|
+
workerCommands,
|
|
3466
|
+
teamName,
|
|
3467
|
+
tempDir,
|
|
3468
|
+
boardDir,
|
|
3469
|
+
fs,
|
|
3470
|
+
execaPane,
|
|
3471
|
+
setMessages,
|
|
3472
|
+
formatAgentLines,
|
|
3473
|
+
leadCoordinationTask,
|
|
3474
|
+
projectRoot
|
|
3475
|
+
);
|
|
3476
|
+
}
|
|
3477
|
+
const { TmuxManager } = await import('./tmux-manager-57QCUVHU.js');
|
|
3478
|
+
const tmux = new TmuxManager();
|
|
3479
|
+
const tmuxAvailable = await tmux.isAvailable();
|
|
3480
|
+
if (tmuxAvailable) {
|
|
3481
|
+
const insideTmux = typeof process.env["TMUX"] === "string" && process.env["TMUX"].length > 0;
|
|
3482
|
+
if (insideTmux) {
|
|
3483
|
+
return await launchTmuxInSession(
|
|
3484
|
+
agentSpecs,
|
|
3485
|
+
leadSpec,
|
|
3486
|
+
workerCommands,
|
|
3487
|
+
teamName,
|
|
3488
|
+
tempDir,
|
|
3489
|
+
boardDir,
|
|
3490
|
+
fs,
|
|
3491
|
+
execaPane,
|
|
3492
|
+
setMessages,
|
|
3493
|
+
formatAgentLines,
|
|
3494
|
+
leadCoordinationTask
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
return await launchTmuxNewSession(
|
|
3498
|
+
agentSpecs,
|
|
3499
|
+
teamName,
|
|
3500
|
+
tempDir,
|
|
3501
|
+
boardDir,
|
|
3502
|
+
allPaneCommands,
|
|
3503
|
+
tmux,
|
|
3504
|
+
fs,
|
|
3505
|
+
setMessages,
|
|
3506
|
+
formatAgentLines,
|
|
3507
|
+
leadSpec,
|
|
3508
|
+
leadCoordinationTask
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3511
|
+
await launchInProcess(
|
|
3512
|
+
agentSpecs,
|
|
3513
|
+
input,
|
|
3514
|
+
teamName,
|
|
3515
|
+
config,
|
|
3516
|
+
panel,
|
|
3517
|
+
setMessages,
|
|
3518
|
+
projectRoot
|
|
3519
|
+
);
|
|
3520
|
+
return void 0;
|
|
3521
|
+
} catch (error) {
|
|
3522
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3523
|
+
sysMsg(setMessages, `Failed to create team: ${msg}`);
|
|
3524
|
+
return void 0;
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
async function launchITerm2(agentSpecs, leadSpec, workerCommands, teamName, tempDir, boardDir, shellEscape, fs, path, execaPane, setMessages, formatAgentLines, leadCoordinationTask) {
|
|
3528
|
+
sysMsg(setMessages, `Designed ${agentSpecs.length}-agent team. Creating iTerm2 split panes...`);
|
|
3529
|
+
const asEscape = (s) => s.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"');
|
|
3530
|
+
const scriptLines = [];
|
|
3531
|
+
scriptLines.push('tell application "iTerm2"');
|
|
3532
|
+
scriptLines.push(" tell current window");
|
|
3533
|
+
scriptLines.push(" set leaderSession to current session of current tab");
|
|
3534
|
+
for (const [i, agent] of workerCommands.entries()) {
|
|
3535
|
+
const prevVar = i === 0 ? "leaderSession" : `agent${i - 1}`;
|
|
3536
|
+
const curVar = `agent${i}`;
|
|
3537
|
+
const splitDir = i === 0 ? "vertically" : "horizontally";
|
|
3538
|
+
scriptLines.push(` tell ${prevVar}`);
|
|
3539
|
+
scriptLines.push(` set ${curVar} to (split ${splitDir} with default profile)`);
|
|
3540
|
+
scriptLines.push(" end tell");
|
|
3541
|
+
scriptLines.push(` tell ${curVar}`);
|
|
3542
|
+
scriptLines.push(` set name to "${asEscape(agent.name)}"`);
|
|
3543
|
+
scriptLines.push(` write text "${asEscape(agent.command)}"`);
|
|
3544
|
+
scriptLines.push(" end tell");
|
|
3545
|
+
}
|
|
3546
|
+
scriptLines.push(" select leaderSession");
|
|
3547
|
+
scriptLines.push(" end tell");
|
|
3548
|
+
scriptLines.push("end tell");
|
|
3549
|
+
const scriptFile = path.join(tempDir, "create-panes.applescript");
|
|
3550
|
+
fs.writeFileSync(scriptFile, scriptLines.join("\n"), "utf-8");
|
|
3551
|
+
try {
|
|
3552
|
+
await execaPane("osascript", [scriptFile]);
|
|
3553
|
+
} catch (scriptErr) {
|
|
3554
|
+
const errMsg = scriptErr instanceof Error ? scriptErr.message : String(scriptErr);
|
|
3555
|
+
sysMsg(setMessages, `Failed to create iTerm2 panes: ${errMsg.slice(0, 200)}`);
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
setActiveTeamName(teamName);
|
|
3559
|
+
setActiveTeamManager(void 0);
|
|
3560
|
+
setActiveTmuxCleanup(async () => {
|
|
3561
|
+
try {
|
|
3562
|
+
const { execFileSync } = await import('child_process');
|
|
3563
|
+
execFileSync("pkill", ["-f", tempDir], { stdio: "ignore" });
|
|
3564
|
+
} catch {
|
|
3565
|
+
}
|
|
3566
|
+
try {
|
|
3567
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
3568
|
+
fs.rmSync(boardDir, { recursive: true, force: true });
|
|
3569
|
+
} catch {
|
|
3570
|
+
}
|
|
3571
|
+
});
|
|
3572
|
+
const agentLines = formatAgentLines(leadSpec.name);
|
|
3573
|
+
sysMsg(
|
|
3574
|
+
setMessages,
|
|
3575
|
+
`Team "${teamName}" \u2014 ${agentSpecs.length} agents active.
|
|
3576
|
+
${workerCommands.length} worker panes in iTerm2 + lead coordinating here.
|
|
3577
|
+
${agentLines.join("\n")}
|
|
3578
|
+
/team stop to shut down all agents.`
|
|
3579
|
+
);
|
|
3580
|
+
return leadCoordinationTask;
|
|
3581
|
+
}
|
|
3582
|
+
async function launchGhostty(agentSpecs, leadSpec, workerCommands, teamName, tempDir, boardDir, fs, path, execaPane, setMessages, formatAgentLines, leadCoordinationTask) {
|
|
3583
|
+
sysMsg(setMessages, `Designed ${agentSpecs.length}-agent team. Creating Ghostty split panes...`);
|
|
3584
|
+
const asEscape = (s) => s.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"');
|
|
3585
|
+
const scriptLines = [];
|
|
3586
|
+
scriptLines.push('tell application "Ghostty" to activate');
|
|
3587
|
+
scriptLines.push("delay 0.5");
|
|
3588
|
+
scriptLines.push('tell application "System Events"');
|
|
3589
|
+
for (const [i, agent] of workerCommands.entries()) {
|
|
3590
|
+
if (i === 0) {
|
|
3591
|
+
scriptLines.push(' keystroke "d" using {command down}');
|
|
3592
|
+
} else {
|
|
3593
|
+
scriptLines.push(' keystroke "d" using {command down, shift down}');
|
|
3594
|
+
}
|
|
3595
|
+
scriptLines.push(" delay 1.0");
|
|
3596
|
+
const escapedCmd = asEscape(agent.command);
|
|
3597
|
+
scriptLines.push(` keystroke "${escapedCmd}"`);
|
|
3598
|
+
scriptLines.push(" delay 0.2");
|
|
3599
|
+
scriptLines.push(" keystroke return");
|
|
3600
|
+
scriptLines.push(" delay 0.5");
|
|
3601
|
+
}
|
|
3602
|
+
if (workerCommands.length > 0) {
|
|
3603
|
+
for (let i = 0; i < workerCommands.length; i++) {
|
|
3604
|
+
scriptLines.push(" key code 123 using {command down, option down}");
|
|
3605
|
+
scriptLines.push(" delay 0.2");
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
scriptLines.push("end tell");
|
|
3609
|
+
const scriptFile = path.join(tempDir, "create-panes.applescript");
|
|
3610
|
+
fs.writeFileSync(scriptFile, scriptLines.join("\n"), "utf-8");
|
|
3611
|
+
try {
|
|
3612
|
+
await execaPane("osascript", [scriptFile]);
|
|
3613
|
+
} catch (scriptErr) {
|
|
3614
|
+
const errMsg = scriptErr instanceof Error ? scriptErr.message : String(scriptErr);
|
|
3615
|
+
sysMsg(
|
|
3616
|
+
setMessages,
|
|
3617
|
+
`Failed to create Ghostty panes: ${errMsg.slice(0, 200)}
|
|
3618
|
+
Ensure macOS accessibility permissions are granted and Ghostty uses default split keybindings (Cmd+D = new_split:right, Cmd+Shift+D = new_split:down).`
|
|
3619
|
+
);
|
|
3620
|
+
return;
|
|
3621
|
+
}
|
|
3622
|
+
setActiveTeamName(teamName);
|
|
3623
|
+
setActiveTeamManager(void 0);
|
|
3624
|
+
setActiveTmuxCleanup(async () => {
|
|
3625
|
+
try {
|
|
3626
|
+
const { execFileSync } = await import('child_process');
|
|
3627
|
+
execFileSync("pkill", ["-f", tempDir], { stdio: "ignore" });
|
|
3628
|
+
} catch {
|
|
3629
|
+
}
|
|
3630
|
+
try {
|
|
3631
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
3632
|
+
fs.rmSync(boardDir, { recursive: true, force: true });
|
|
3633
|
+
} catch {
|
|
3634
|
+
}
|
|
3635
|
+
});
|
|
3636
|
+
const agentLines = formatAgentLines(leadSpec.name);
|
|
3637
|
+
sysMsg(
|
|
3638
|
+
setMessages,
|
|
3639
|
+
`Team "${teamName}" \u2014 ${agentSpecs.length} agents active.
|
|
3640
|
+
${workerCommands.length} worker panes in Ghostty + lead coordinating here.
|
|
3641
|
+
${agentLines.join("\n")}
|
|
3642
|
+
Keybindings:
|
|
3643
|
+
Cmd+Opt+Arrows Switch pane
|
|
3644
|
+
Cmd+Shift+Enter Zoom pane
|
|
3645
|
+
Cmd+Ctrl+= Equalize splits
|
|
3646
|
+
/team stop to shut down all agents.`
|
|
3647
|
+
);
|
|
3648
|
+
return leadCoordinationTask;
|
|
3649
|
+
}
|
|
3650
|
+
async function launchTerminalApp(agentSpecs, leadSpec, workerCommands, teamName, tempDir, boardDir, fs, path, execaPane, setMessages, formatAgentLines, leadCoordinationTask) {
|
|
3651
|
+
sysMsg(setMessages, `Designed ${agentSpecs.length}-agent team. Creating Terminal.app split panes...`);
|
|
3652
|
+
const asEscape = (s) => s.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"');
|
|
3653
|
+
const scriptLines = [];
|
|
3654
|
+
scriptLines.push('tell application "Terminal" to activate');
|
|
3655
|
+
scriptLines.push("delay 0.5");
|
|
3656
|
+
scriptLines.push('tell application "System Events"');
|
|
3657
|
+
for (const agent of workerCommands) {
|
|
3658
|
+
scriptLines.push(' keystroke "d" using {command down}');
|
|
3659
|
+
scriptLines.push(" delay 1.0");
|
|
3660
|
+
const escapedCmd = asEscape(agent.command);
|
|
3661
|
+
scriptLines.push(` keystroke "${escapedCmd}"`);
|
|
3662
|
+
scriptLines.push(" delay 0.2");
|
|
3663
|
+
scriptLines.push(" keystroke return");
|
|
3664
|
+
scriptLines.push(" delay 0.5");
|
|
3665
|
+
}
|
|
3666
|
+
scriptLines.push("end tell");
|
|
3667
|
+
const scriptFile = path.join(tempDir, "create-panes.applescript");
|
|
3668
|
+
fs.writeFileSync(scriptFile, scriptLines.join("\n"), "utf-8");
|
|
3669
|
+
try {
|
|
3670
|
+
await execaPane("osascript", [scriptFile]);
|
|
3671
|
+
} catch (scriptErr) {
|
|
3672
|
+
const errMsg = scriptErr instanceof Error ? scriptErr.message : String(scriptErr);
|
|
3673
|
+
sysMsg(
|
|
3674
|
+
setMessages,
|
|
3675
|
+
`Failed to create Terminal.app panes: ${errMsg.slice(0, 200)}
|
|
3676
|
+
Ensure macOS accessibility permissions are granted for Terminal.app.`
|
|
3677
|
+
);
|
|
3678
|
+
return;
|
|
3679
|
+
}
|
|
3680
|
+
setActiveTeamName(teamName);
|
|
3681
|
+
setActiveTeamManager(void 0);
|
|
3682
|
+
setActiveTmuxCleanup(async () => {
|
|
3683
|
+
try {
|
|
3684
|
+
const { execFileSync } = await import('child_process');
|
|
3685
|
+
execFileSync("pkill", ["-f", tempDir], { stdio: "ignore" });
|
|
3686
|
+
} catch {
|
|
3687
|
+
}
|
|
3688
|
+
try {
|
|
3689
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
3690
|
+
fs.rmSync(boardDir, { recursive: true, force: true });
|
|
3691
|
+
} catch {
|
|
3692
|
+
}
|
|
3693
|
+
});
|
|
3694
|
+
const agentLines = formatAgentLines(leadSpec.name);
|
|
3695
|
+
sysMsg(
|
|
3696
|
+
setMessages,
|
|
3697
|
+
`Team "${teamName}" \u2014 ${agentSpecs.length} agents active.
|
|
3698
|
+
${workerCommands.length} worker panes in Terminal.app + lead coordinating here.
|
|
3699
|
+
${agentLines.join("\n")}
|
|
3700
|
+
Click the lead pane to switch back. Shift+Cmd+D to close a split pane.
|
|
3701
|
+
/team stop to shut down all agents.`
|
|
3702
|
+
);
|
|
3703
|
+
return leadCoordinationTask;
|
|
3704
|
+
}
|
|
3705
|
+
async function launchWindowsTerminal(agentSpecs, leadSpec, workerCommands, teamName, tempDir, boardDir, fs, execaPane, setMessages, formatAgentLines, leadCoordinationTask, projectRoot) {
|
|
3706
|
+
sysMsg(setMessages, `Designed ${agentSpecs.length}-agent team. Creating Windows Terminal split panes...`);
|
|
3707
|
+
for (const [i, agent] of workerCommands.entries()) {
|
|
3708
|
+
const splitDir = i === 0 ? "-V" : "-H";
|
|
3709
|
+
const args = ["-w", "0", "sp", splitDir];
|
|
3710
|
+
if (i === 0) args.push("-s", "0.5");
|
|
3711
|
+
args.push("-d", projectRoot, "--title", agent.name);
|
|
3712
|
+
args.push("cmd", "/c", agent.command);
|
|
3713
|
+
try {
|
|
3714
|
+
await execaPane("wt", args);
|
|
3715
|
+
} catch (err) {
|
|
3716
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
3717
|
+
sysMsg(setMessages, `Failed to create WT pane for ${agent.name}: ${errMsg.slice(0, 200)}`);
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
try {
|
|
3721
|
+
await execaPane("wt", ["-w", "0", "mf", "first"]);
|
|
3722
|
+
} catch {
|
|
3723
|
+
}
|
|
3724
|
+
setActiveTeamName(teamName);
|
|
3725
|
+
setActiveTeamManager(void 0);
|
|
3726
|
+
setActiveTmuxCleanup(async () => {
|
|
3727
|
+
try {
|
|
3728
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
3729
|
+
fs.rmSync(boardDir, { recursive: true, force: true });
|
|
3730
|
+
} catch {
|
|
3731
|
+
}
|
|
3732
|
+
});
|
|
3733
|
+
const agentLines = formatAgentLines(leadSpec.name);
|
|
3734
|
+
sysMsg(
|
|
3735
|
+
setMessages,
|
|
3736
|
+
`Team "${teamName}" \u2014 ${agentSpecs.length} agents active.
|
|
3737
|
+
${workerCommands.length} worker panes in Windows Terminal + lead coordinating here.
|
|
3738
|
+
${agentLines.join("\n")}
|
|
3739
|
+
Keybindings:
|
|
3740
|
+
Alt+Arrow Switch pane
|
|
3741
|
+
Alt+Shift+Arrow Resize pane
|
|
3742
|
+
/team stop to shut down all agents.`
|
|
3743
|
+
);
|
|
3744
|
+
return leadCoordinationTask;
|
|
3745
|
+
}
|
|
3746
|
+
async function launchTmuxInSession(agentSpecs, leadSpec, workerCommands, teamName, tempDir, boardDir, fs, execaPane, setMessages, formatAgentLines, leadCoordinationTask) {
|
|
3747
|
+
sysMsg(setMessages, `Designed ${agentSpecs.length}-agent team. Creating tmux split panes...`);
|
|
3748
|
+
const currentResult = await execaPane("tmux", ["display-message", "-p", "#{pane_id}"]);
|
|
3749
|
+
const leaderPaneId = currentResult.stdout.trim();
|
|
3750
|
+
const agentPaneIds = [];
|
|
3751
|
+
for (const [i, agent] of workerCommands.entries()) {
|
|
3752
|
+
const splitResult = await execaPane("tmux", [
|
|
3753
|
+
"split-window",
|
|
3754
|
+
i === 0 ? "-h" : "-v",
|
|
3755
|
+
"-P",
|
|
3756
|
+
"-F",
|
|
3757
|
+
"#{pane_id}"
|
|
3758
|
+
]);
|
|
3759
|
+
const newPaneId = splitResult.stdout.trim();
|
|
3760
|
+
agentPaneIds.push(newPaneId);
|
|
3761
|
+
await execaPane("tmux", ["send-keys", "-t", newPaneId, agent.command, "Enter"]);
|
|
3762
|
+
}
|
|
3763
|
+
if (workerCommands.length > 0) {
|
|
3764
|
+
try {
|
|
3765
|
+
await execaPane("tmux", ["select-layout", "main-vertical"]);
|
|
3766
|
+
} catch {
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
try {
|
|
3770
|
+
await execaPane("tmux", ["select-pane", "-t", leaderPaneId]);
|
|
3771
|
+
} catch {
|
|
3772
|
+
}
|
|
3773
|
+
setActiveTeamName(teamName);
|
|
3774
|
+
setActiveTeamManager(void 0);
|
|
3775
|
+
setActiveTmuxCleanup(async () => {
|
|
3776
|
+
const { execa: ex } = await import('execa');
|
|
3777
|
+
for (const pid of agentPaneIds) {
|
|
3778
|
+
try {
|
|
3779
|
+
await ex("tmux", ["kill-pane", "-t", pid]);
|
|
3780
|
+
} catch {
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
try {
|
|
3784
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
3785
|
+
fs.rmSync(boardDir, { recursive: true, force: true });
|
|
3786
|
+
} catch {
|
|
3787
|
+
}
|
|
3788
|
+
});
|
|
3789
|
+
const agentLines = formatAgentLines(leadSpec.name);
|
|
3790
|
+
sysMsg(
|
|
3791
|
+
setMessages,
|
|
3792
|
+
`Team "${teamName}" \u2014 ${agentSpecs.length} agents active.
|
|
3793
|
+
${workerCommands.length} worker panes in tmux + lead coordinating here.
|
|
3794
|
+
${agentLines.join("\n")}
|
|
3795
|
+
/team stop to shut down all agents.`
|
|
3796
|
+
);
|
|
3797
|
+
return leadCoordinationTask;
|
|
3798
|
+
}
|
|
3799
|
+
async function launchTmuxNewSession(agentSpecs, teamName, tempDir, boardDir, allPaneCommands, tmux, fs, setMessages, formatAgentLines, leadSpec, leadCoordinationTask) {
|
|
3800
|
+
sysMsg(setMessages, `Designed ${agentSpecs.length}-agent team. Opening tmux split-panel view...`);
|
|
3801
|
+
const sessionName = await tmux.createSession(teamName);
|
|
3802
|
+
const paneConfigs = agentSpecs.map((spec, i) => ({
|
|
3803
|
+
paneId: `pane-${i}`,
|
|
3804
|
+
agentName: spec.name,
|
|
3805
|
+
model: spec.model,
|
|
3806
|
+
role: spec.role,
|
|
3807
|
+
title: `${spec.name} (${spec.model})`
|
|
3808
|
+
}));
|
|
3809
|
+
const layoutConfig = {
|
|
3810
|
+
layout: "hub-spoke",
|
|
3811
|
+
panes: paneConfigs,
|
|
3812
|
+
maxPanes: paneConfigs.length
|
|
3813
|
+
};
|
|
3814
|
+
await tmux.createPanes(layoutConfig);
|
|
3815
|
+
for (const [i, agent] of allPaneCommands.entries()) {
|
|
3816
|
+
await tmux.sendCommand(`pane-${i}`, agent.command);
|
|
3817
|
+
}
|
|
3818
|
+
setActiveTeamName(teamName);
|
|
3819
|
+
setActiveTeamManager(void 0);
|
|
3820
|
+
setActiveTmuxCleanup(async () => {
|
|
3821
|
+
await tmux.destroy();
|
|
3822
|
+
try {
|
|
3823
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
3824
|
+
fs.rmSync(boardDir, { recursive: true, force: true });
|
|
3825
|
+
} catch {
|
|
3826
|
+
}
|
|
3827
|
+
});
|
|
3828
|
+
sysMsg(
|
|
3829
|
+
setMessages,
|
|
3830
|
+
`Attaching to tmux session "${sessionName}" with ${agentSpecs.length} agents\u2026
|
|
3831
|
+
|
|
3832
|
+
Inside tmux:
|
|
3833
|
+
Ctrl+B \u2192/\u2190/\u2191/\u2193 Switch pane
|
|
3834
|
+
Ctrl+B d Detach (return to aemeath)
|
|
3835
|
+
Ctrl+B z Zoom pane fullscreen`
|
|
3836
|
+
);
|
|
3837
|
+
const { execFileSync } = await import('child_process');
|
|
3838
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
3839
|
+
process.stdin.setRawMode(false);
|
|
3840
|
+
}
|
|
3841
|
+
process.stdin.pause();
|
|
3842
|
+
try {
|
|
3843
|
+
execFileSync("tmux", ["attach-session", "-t", sessionName], { stdio: "inherit" });
|
|
3844
|
+
} catch {
|
|
3845
|
+
}
|
|
3846
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
3847
|
+
process.stdin.setRawMode(true);
|
|
3848
|
+
}
|
|
3849
|
+
process.stdin.resume();
|
|
3850
|
+
const agentLines = formatAgentLines(leadSpec.name);
|
|
3851
|
+
sysMsg(
|
|
3852
|
+
setMessages,
|
|
3853
|
+
`Detached from tmux. Agents may still be running.
|
|
3854
|
+
|
|
3855
|
+
Agents:
|
|
3856
|
+
${agentLines.join("\n")}
|
|
3857
|
+
|
|
3858
|
+
Keybindings (inside tmux):
|
|
3859
|
+
Ctrl+B \u2192 \u2190 \u2191 \u2193 Switch between panes
|
|
3860
|
+
Ctrl+B d Detach (return here)
|
|
3861
|
+
Ctrl+B z Zoom active pane (fullscreen toggle)
|
|
3862
|
+
Ctrl+B x Close active pane
|
|
3863
|
+
|
|
3864
|
+
Re-attach: tmux attach -t ${sessionName}
|
|
3865
|
+
/team stop Shut down all agents and kill session`
|
|
3866
|
+
);
|
|
3867
|
+
return leadCoordinationTask;
|
|
3868
|
+
}
|
|
3869
|
+
async function launchInProcess(agentSpecs, input, teamName, config, panel, setMessages, projectRoot) {
|
|
3870
|
+
sysMsg(
|
|
3871
|
+
setMessages,
|
|
3872
|
+
`Designed ${agentSpecs.length}-agent team. Starting agents (in-process mode)\u2026
|
|
3873
|
+
|
|
3874
|
+
Keybindings:
|
|
3875
|
+
Tab Switch to next agent
|
|
3876
|
+
Ctrl+1-${agentSpecs.length} Jump to agent N
|
|
3877
|
+
/team stop Exit team mode`
|
|
3878
|
+
);
|
|
3879
|
+
const agentDefinitions = agentSpecs.map((spec) => {
|
|
3880
|
+
const modelInfo = SUPPORTED_MODELS[spec.model];
|
|
3881
|
+
const provider = modelInfo?.provider ?? "anthropic";
|
|
3882
|
+
return { name: spec.name, agentType: spec.agentType, model: spec.model, provider, role: spec.role };
|
|
3883
|
+
});
|
|
3884
|
+
const { TeamManager: TM } = await import('./team-manager-2VSMALAA.js');
|
|
3885
|
+
const manager = new TM();
|
|
3886
|
+
setActiveTeamManager(manager);
|
|
3887
|
+
setActiveTeamName(teamName);
|
|
3888
|
+
setActiveTmuxCleanup(void 0);
|
|
3889
|
+
const agentAllowedPaths = Array.from(/* @__PURE__ */ new Set([projectRoot, ...config.permissions.allowedPaths]));
|
|
3890
|
+
const teamConfig = manager.createTeam(teamName, {
|
|
3891
|
+
description: `Agent team for: ${input.slice(0, 120)}`,
|
|
3892
|
+
agents: agentDefinitions,
|
|
3893
|
+
agentEnv: {
|
|
3894
|
+
AEMEATHCLI_TOOL_PROJECT_ROOT: projectRoot,
|
|
3895
|
+
AEMEATHCLI_TOOL_WORKING_DIRECTORY: projectRoot,
|
|
3896
|
+
AEMEATHCLI_TOOL_PERMISSION_MODE: config.permissions.mode,
|
|
3897
|
+
AEMEATHCLI_TOOL_ALLOWED_PATHS: JSON.stringify(agentAllowedPaths),
|
|
3898
|
+
AEMEATHCLI_TOOL_BLOCKED_COMMANDS: JSON.stringify(config.permissions.blockedCommands)
|
|
3899
|
+
}
|
|
3900
|
+
});
|
|
3901
|
+
const agentStates = teamConfig.members.map((member) => ({
|
|
3902
|
+
config: member,
|
|
3903
|
+
status: "idle"
|
|
3904
|
+
}));
|
|
3905
|
+
panel.setAgents(agentStates);
|
|
3906
|
+
panel.activate();
|
|
3907
|
+
for (const member of teamConfig.members) {
|
|
3908
|
+
panel.appendOutput(member.agentId, `[${member.name}] Starting (${member.model})...
|
|
3909
|
+
`, { immediate: true });
|
|
3910
|
+
}
|
|
3911
|
+
manager.onAgentMessages(teamName, (_agentName, method, params) => {
|
|
3912
|
+
if (method === "agent.streamChunk") {
|
|
3913
|
+
const agentId = typeof params["agentId"] === "string" ? params["agentId"] : "";
|
|
3914
|
+
const content = typeof params["content"] === "string" ? params["content"] : "";
|
|
3915
|
+
if (agentId && content) panel.appendOutput(agentId, content);
|
|
3916
|
+
}
|
|
3917
|
+
if (method === "agent.taskUpdate") {
|
|
3918
|
+
const agentId = typeof params["agentId"] === "string" ? params["agentId"] : "";
|
|
3919
|
+
const rawStatus = typeof params["status"] === "string" ? params["status"] : "";
|
|
3920
|
+
if (agentId && rawStatus) {
|
|
3921
|
+
const statusMap = { in_progress: "active", completed: "idle" };
|
|
3922
|
+
const mapped = statusMap[rawStatus];
|
|
3923
|
+
if (mapped) panel.updateAgentStatus(agentId, mapped);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
});
|
|
3927
|
+
try {
|
|
3928
|
+
await manager.startAgents(teamName);
|
|
3929
|
+
} catch (startErr) {
|
|
3930
|
+
const errMsg = startErr instanceof Error ? startErr.message : String(startErr);
|
|
3931
|
+
sysMsg(setMessages, `Warning: Some agents failed to start: ${errMsg.slice(0, 200)}`);
|
|
3932
|
+
}
|
|
3933
|
+
const agentStatesAfterStart = manager.getAgentStates(teamName);
|
|
3934
|
+
const aliveCount = agentStatesAfterStart.filter((s) => s.status !== "error" && s.status !== "shutdown").length;
|
|
3935
|
+
if (aliveCount === 0) {
|
|
3936
|
+
sysMsg(setMessages, "All agents failed to start. Check your API credentials and try again.");
|
|
3937
|
+
return void 0;
|
|
3938
|
+
}
|
|
3939
|
+
for (const [i, member] of teamConfig.members.entries()) {
|
|
3940
|
+
const spec = agentSpecs[i];
|
|
3941
|
+
const taskId = v4Id();
|
|
3942
|
+
const prompt = spec ? `You are ${spec.name} (${spec.agentType}).
|
|
3943
|
+
|
|
3944
|
+
${spec.taskPrompt}
|
|
3945
|
+
|
|
3946
|
+
User request:
|
|
3947
|
+
${input}` : `Work on the following task:
|
|
3948
|
+
|
|
3949
|
+
${input}`;
|
|
3950
|
+
manager.assignTask(teamName, member.name, taskId, `${member.role}: ${input.slice(0, 80)}`, prompt);
|
|
3951
|
+
}
|
|
3952
|
+
const agentLines = agentSpecs.map((spec) => ` ${spec.name} (${spec.model}) \u2014 ${spec.role}`);
|
|
3953
|
+
sysMsg(
|
|
3954
|
+
setMessages,
|
|
3955
|
+
`Team "${teamName}" created with ${teamConfig.members.length} agents \u2014 split-panel active.
|
|
3956
|
+
${agentLines.join("\n")}
|
|
3957
|
+
Use Tab to switch agents. /team stop to return to single-pane.`
|
|
3958
|
+
);
|
|
3959
|
+
return void 0;
|
|
3960
|
+
}
|
|
3961
|
+
|
|
3962
|
+
// src/ui/app-helpers.ts
|
|
3963
|
+
var DEFAULT_SYSTEM_PROMPT = "You are AemeathCLI (aemeathcli), a multi-model AI agent orchestrator. You help users with coding tasks, code review, debugging, refactoring, and project management. You are NOT Claude Code, Codex, or Gemini CLI \u2014 you are Aemeath Agent Swarm, an independent tool that orchestrates multiple AI providers (Anthropic, OpenAI, Google, Kimi, Ollama).\n\nAnswer concisely and directly. Key features:\n- Multi-model: /model to switch between Claude, GPT, Gemini, Kimi, Ollama\n- Agent orchestration: use Shift+Tab to switch into swarm mode inside the TUI\n - swarm mode designs and coordinates multi-agent teams from natural language\n - the master agent owns the left pane, worker agents stream on the right\n - follow the configured master-agent provider preference when building teams\n- Skills: $review, $commit, $plan, $debug, $test, $refactor\n- Commands: /help, /model, /role, /history, /resume, /cost\n\nWhen users ask about agent swarm, multi-agent, team mode, or orchestration, keep them in the current TUI session and tell them to switch modes with Shift+Tab. Do not redirect them to a separate `launch` command.";
|
|
3964
|
+
function getCandidateModels(config, resolution, activeModelId) {
|
|
3965
|
+
const candidates = [activeModelId];
|
|
3966
|
+
if (resolution.source !== "user_override" && resolution.role) {
|
|
3967
|
+
const roleConfig = config.roles[resolution.role];
|
|
3968
|
+
if (roleConfig) {
|
|
3969
|
+
candidates.push(roleConfig.primary, ...roleConfig.fallback);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3973
|
+
const unique = [];
|
|
3974
|
+
for (const modelId of candidates) {
|
|
3975
|
+
if (!seen.has(modelId)) {
|
|
3976
|
+
seen.add(modelId);
|
|
3977
|
+
unique.push(modelId);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
return unique;
|
|
3981
|
+
}
|
|
3982
|
+
function cliProviderListsEqual(left, right) {
|
|
3983
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
3984
|
+
}
|
|
3985
|
+
function normalizeSwarmConfig(swarm, detectedProviders) {
|
|
3986
|
+
const primaryMasterProvider = swarm.primaryMasterProvider !== void 0 && detectedProviders.includes(swarm.primaryMasterProvider) ? swarm.primaryMasterProvider : void 0;
|
|
3987
|
+
const fallbackMasterProviders = swarm.fallbackMasterProviders.filter(
|
|
3988
|
+
(provider) => provider !== primaryMasterProvider && detectedProviders.includes(provider)
|
|
3989
|
+
);
|
|
3990
|
+
return {
|
|
3991
|
+
onboardingComplete: swarm.onboardingComplete && primaryMasterProvider !== void 0,
|
|
3992
|
+
detectedProviders: [...detectedProviders],
|
|
3993
|
+
primaryMasterProvider,
|
|
3994
|
+
fallbackMasterProviders
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
3997
|
+
function swarmConfigsEqual(left, right) {
|
|
3998
|
+
return left.onboardingComplete === right.onboardingComplete && left.primaryMasterProvider === right.primaryMasterProvider && cliProviderListsEqual(left.detectedProviders, right.detectedProviders) && cliProviderListsEqual(left.fallbackMasterProviders, right.fallbackMasterProviders);
|
|
3999
|
+
}
|
|
4000
|
+
function App({ config, options }) {
|
|
4001
|
+
const { resolution, modelId, switchModel, switchRole } = useModel(
|
|
4002
|
+
config,
|
|
4003
|
+
options.model,
|
|
4004
|
+
options.role
|
|
4005
|
+
);
|
|
4006
|
+
const { state: streamState, startStream, cancelStream, reset: resetStream } = useStream();
|
|
4007
|
+
const { totalCost, totalTokens, record } = useCost(config.cost);
|
|
4008
|
+
const totalCostRef = useRef(totalCost);
|
|
4009
|
+
totalCostRef.current = totalCost;
|
|
4010
|
+
const totalTokensRef = useRef(totalTokens);
|
|
4011
|
+
totalTokensRef.current = totalTokens;
|
|
4012
|
+
const panel = usePanel();
|
|
4013
|
+
const [messages, setMessages] = useState([]);
|
|
4014
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
4015
|
+
const messagesRef = useRef([]);
|
|
4016
|
+
messagesRef.current = messages;
|
|
4017
|
+
const [gitBranch, setGitBranch] = useState();
|
|
4018
|
+
const [thinkingValue, setThinkingValue] = useState("medium");
|
|
4019
|
+
const [selectionMode, setSelectionMode] = useState({ type: "none" });
|
|
4020
|
+
const [modelDisplayOrder, setModelDisplayOrder] = useState(void 0);
|
|
4021
|
+
const [persistentHistory, setPersistentHistory] = useState([]);
|
|
4022
|
+
const [inputMode, setInputMode] = useState("agent-swarm");
|
|
4023
|
+
const [swarmConfig, setSwarmConfig] = useState(config.swarm);
|
|
4024
|
+
const [swarmOnboardingDeferred, setSwarmOnboardingDeferred] = useState(false);
|
|
4025
|
+
const registryRef = useRef(void 0);
|
|
4026
|
+
const getRegistry = useCallback(async () => {
|
|
4027
|
+
if (registryRef.current !== void 0) return registryRef.current;
|
|
4028
|
+
const { createDefaultRegistry } = await import('./registry-MVNSXCEF.js');
|
|
4029
|
+
registryRef.current = await createDefaultRegistry();
|
|
4030
|
+
return registryRef.current;
|
|
4031
|
+
}, []);
|
|
4032
|
+
const projectRootRef = useRef(process.cwd());
|
|
4033
|
+
useEffect(() => {
|
|
4034
|
+
void import('./pathResolver-UVAB2FCW.js').then(({ findProjectRoot }) => {
|
|
4035
|
+
projectRootRef.current = findProjectRoot();
|
|
4036
|
+
}).catch(() => {
|
|
4037
|
+
});
|
|
4038
|
+
}, []);
|
|
4039
|
+
useEffect(() => {
|
|
4040
|
+
void (async () => {
|
|
4041
|
+
try {
|
|
4042
|
+
const { findProjectRoot } = await import('./pathResolver-UVAB2FCW.js');
|
|
4043
|
+
const root = findProjectRoot();
|
|
4044
|
+
projectRootRef.current = root;
|
|
4045
|
+
const { loadInputHistory } = await import('./input-history-BEICE7PT.js');
|
|
4046
|
+
const history = await loadInputHistory(root);
|
|
4047
|
+
if (history.length > 0) setPersistentHistory(history);
|
|
4048
|
+
} catch {
|
|
4049
|
+
}
|
|
4050
|
+
})();
|
|
4051
|
+
}, []);
|
|
4052
|
+
useEffect(() => {
|
|
4053
|
+
void import('child_process').then(({ execFile }) => {
|
|
4054
|
+
execFile("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 2e3 }, (error, stdout) => {
|
|
4055
|
+
if (!error && stdout) setGitBranch(stdout.trim());
|
|
4056
|
+
});
|
|
4057
|
+
}).catch(() => {
|
|
4058
|
+
});
|
|
4059
|
+
}, []);
|
|
4060
|
+
useEffect(() => {
|
|
4061
|
+
if (options.isAgentPane) return;
|
|
4062
|
+
void import('./detect-providers-QICJ5U3R.js').then(({ detectInstalledProviders }) => {
|
|
4063
|
+
const detected = detectInstalledProviders();
|
|
4064
|
+
if (detected.length > 0) setSwarmOnboardingDeferred(false);
|
|
4065
|
+
setSwarmConfig((prev) => {
|
|
4066
|
+
const next = normalizeSwarmConfig(prev, detected);
|
|
4067
|
+
return swarmConfigsEqual(prev, next) ? prev : next;
|
|
4068
|
+
});
|
|
4069
|
+
}).catch(() => {
|
|
4070
|
+
});
|
|
4071
|
+
}, [options.isAgentPane]);
|
|
4072
|
+
useEffect(() => {
|
|
4073
|
+
void (async () => {
|
|
4074
|
+
try {
|
|
4075
|
+
const { findProjectRoot } = await import('./pathResolver-UVAB2FCW.js');
|
|
4076
|
+
const projectRoot = findProjectRoot();
|
|
4077
|
+
projectRootRef.current = projectRoot;
|
|
4078
|
+
const { default: fg } = await import('fast-glob');
|
|
4079
|
+
const pathModule = await import('path');
|
|
4080
|
+
const fileRefs = await fg(["**/*"], {
|
|
4081
|
+
cwd: projectRoot,
|
|
4082
|
+
onlyFiles: true,
|
|
4083
|
+
dot: false,
|
|
4084
|
+
followSymbolicLinks: false,
|
|
4085
|
+
unique: true,
|
|
4086
|
+
suppressErrors: true,
|
|
4087
|
+
ignore: [".git/**", "node_modules/**", "dist/**", "coverage/**", ".aemeathcli/**", ".agents/**", "reference-gemini-cli/**", "cli-agent-orchestrator/**"]
|
|
4088
|
+
});
|
|
4089
|
+
const autocompleteItems = fileRefs.sort((left, right) => left.length !== right.length ? left.length - right.length : left.localeCompare(right)).slice(0, 400).map((filePath) => ({
|
|
4090
|
+
label: `@${filePath}`,
|
|
4091
|
+
description: pathModule.dirname(filePath) === "." ? "project root file" : pathModule.dirname(filePath)
|
|
4092
|
+
}));
|
|
4093
|
+
registerDynamicFileRefs(autocompleteItems);
|
|
4094
|
+
} catch {
|
|
4095
|
+
registerDynamicFileRefs([]);
|
|
4096
|
+
}
|
|
4097
|
+
})();
|
|
4098
|
+
}, []);
|
|
4099
|
+
useEffect(() => {
|
|
4100
|
+
void (async () => {
|
|
4101
|
+
try {
|
|
4102
|
+
const { SkillRegistry } = await import('./registry-LRURZVUL.js');
|
|
4103
|
+
const { findProjectRoot } = await import('./pathResolver-UVAB2FCW.js');
|
|
4104
|
+
const registry = new SkillRegistry();
|
|
4105
|
+
await registry.initialize(findProjectRoot());
|
|
4106
|
+
const all = registry.listAll();
|
|
4107
|
+
if (all.length > 0) {
|
|
4108
|
+
registerDynamicSkills(all.map((s) => ({ label: `$${s.name}`, description: s.description })));
|
|
4109
|
+
}
|
|
4110
|
+
} catch {
|
|
4111
|
+
}
|
|
4112
|
+
})();
|
|
4113
|
+
}, []);
|
|
4114
|
+
useEffect(() => {
|
|
4115
|
+
void (async () => {
|
|
4116
|
+
try {
|
|
4117
|
+
const { discoverModels, getDisplayOrder } = await import('./model-discovery-AAJDHRFO.js');
|
|
4118
|
+
await discoverModels();
|
|
4119
|
+
setModelDisplayOrder(getDisplayOrder());
|
|
4120
|
+
} catch {
|
|
4121
|
+
}
|
|
4122
|
+
})();
|
|
4123
|
+
}, []);
|
|
4124
|
+
const handleSubmit = useCallback(
|
|
4125
|
+
async (input) => {
|
|
4126
|
+
if (input.startsWith("/")) {
|
|
4127
|
+
await handleInternalCommand(input, switchModel, switchRole, {
|
|
4128
|
+
totalCost: totalCostRef.current,
|
|
4129
|
+
totalTokens: totalTokensRef.current,
|
|
4130
|
+
setMessages,
|
|
4131
|
+
modelId,
|
|
4132
|
+
thinkingValue,
|
|
4133
|
+
setThinkingValue,
|
|
4134
|
+
setSelectionMode,
|
|
4135
|
+
resolution,
|
|
4136
|
+
panel: { setAgents: panel.setAgents, activate: panel.activate, deactivate: panel.deactivate, appendOutput: panel.appendOutput },
|
|
4137
|
+
getRegistry,
|
|
4138
|
+
projectRoot: projectRootRef.current
|
|
4139
|
+
});
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
if (input.startsWith("$")) {
|
|
4143
|
+
await handleSkillInvocation(input, setMessages);
|
|
4144
|
+
return;
|
|
4145
|
+
}
|
|
4146
|
+
const activeTeamName = getActiveTeamName();
|
|
4147
|
+
const activeTeamManager = getActiveTeamManager();
|
|
4148
|
+
if (inputMode === "agent-swarm" && !options.isAgentPane && !panel.isSplitPanelActive && !activeTeamName) {
|
|
4149
|
+
setIsProcessing(true);
|
|
4150
|
+
try {
|
|
4151
|
+
const leaderTask = await handlePromptBasedTeamCreation(
|
|
4152
|
+
input,
|
|
4153
|
+
setMessages,
|
|
4154
|
+
panel,
|
|
4155
|
+
getRegistry,
|
|
4156
|
+
modelId,
|
|
4157
|
+
config,
|
|
4158
|
+
swarmConfig
|
|
4159
|
+
);
|
|
4160
|
+
if (leaderTask) setTimeout(() => void handleSubmit(leaderTask), 1500);
|
|
4161
|
+
} finally {
|
|
4162
|
+
setIsProcessing(false);
|
|
4163
|
+
}
|
|
4164
|
+
return;
|
|
4165
|
+
}
|
|
4166
|
+
if (panel.isSplitPanelActive && activeTeamManager && activeTeamName) {
|
|
4167
|
+
const targetAgent = panel.agents[panel.activeAgentIndex] ?? panel.agents[0];
|
|
4168
|
+
if (!targetAgent) {
|
|
4169
|
+
setMessages((prev) => [...prev, { id: v4Id(), role: "system", content: "No active swarm agent is available to receive input.", createdAt: /* @__PURE__ */ new Date() }]);
|
|
4170
|
+
return;
|
|
4171
|
+
}
|
|
4172
|
+
const taskId = v4Id();
|
|
4173
|
+
activeTeamManager.assignTask(activeTeamName, targetAgent.config.name, taskId, `User steering: ${input.slice(0, 72)}`, input);
|
|
4174
|
+
panel.appendOutput(targetAgent.config.agentId, `
|
|
4175
|
+
[User] ${input}
|
|
4176
|
+
`);
|
|
4177
|
+
panel.updateAgentStatus(targetAgent.config.agentId, "active");
|
|
4178
|
+
return;
|
|
4179
|
+
}
|
|
4180
|
+
const userMessage = { id: v4Id(), role: "user", content: input, createdAt: /* @__PURE__ */ new Date() };
|
|
4181
|
+
import('./input-history-BEICE7PT.js').then(({ appendInputHistory }) => appendInputHistory(projectRootRef.current, input)).catch(() => {
|
|
4182
|
+
});
|
|
4183
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
4184
|
+
setIsProcessing(true);
|
|
4185
|
+
resetStream();
|
|
4186
|
+
try {
|
|
4187
|
+
const registry = await getRegistry();
|
|
4188
|
+
const allMessages = [...messagesRef.current, userMessage].filter((m) => m.role !== "system");
|
|
4189
|
+
const candidateModels = getCandidateModels(config, resolution, modelId);
|
|
4190
|
+
let responseModel = modelId;
|
|
4191
|
+
let responseProvider = resolution.provider;
|
|
4192
|
+
let fullContent = "";
|
|
4193
|
+
let completed = false;
|
|
4194
|
+
let lastError;
|
|
4195
|
+
for (const candidateModel of candidateModels) {
|
|
4196
|
+
const providerOrUndefined = registry.hasModel(candidateModel) ? registry.getForModel(candidateModel) : void 0;
|
|
4197
|
+
if (!providerOrUndefined) {
|
|
4198
|
+
lastError = new Error(`No provider available for model "${candidateModel}"`);
|
|
4199
|
+
continue;
|
|
4200
|
+
}
|
|
4201
|
+
const candidateProvider = providerOrUndefined;
|
|
4202
|
+
let candidateContent = "";
|
|
4203
|
+
let caughtError;
|
|
4204
|
+
const stream = candidateProvider.stream({
|
|
4205
|
+
model: candidateModel,
|
|
4206
|
+
messages: allMessages,
|
|
4207
|
+
system: options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT,
|
|
4208
|
+
maxTokens: 16e3
|
|
4209
|
+
});
|
|
4210
|
+
async function* instrumentedStream(source) {
|
|
4211
|
+
try {
|
|
4212
|
+
for await (const chunk of source) {
|
|
4213
|
+
if (chunk.type === "text" && chunk.content) candidateContent += chunk.content;
|
|
4214
|
+
if (chunk.type === "usage" && chunk.usage) {
|
|
4215
|
+
record(candidateProvider.name, candidateModel, chunk.usage.inputTokens, chunk.usage.outputTokens, resolution.role);
|
|
4216
|
+
}
|
|
4217
|
+
if (chunk.type === "error") caughtError = new Error(chunk.error ?? `Model "${candidateModel}" stream failed`);
|
|
4218
|
+
yield chunk;
|
|
4219
|
+
}
|
|
4220
|
+
} catch (err) {
|
|
4221
|
+
caughtError = err;
|
|
4222
|
+
throw err;
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
resetStream();
|
|
4226
|
+
await startStream(instrumentedStream(stream));
|
|
4227
|
+
if (caughtError !== void 0) {
|
|
4228
|
+
lastError = caughtError;
|
|
4229
|
+
continue;
|
|
4230
|
+
}
|
|
4231
|
+
responseModel = candidateModel;
|
|
4232
|
+
responseProvider = candidateProvider.name;
|
|
4233
|
+
fullContent = candidateContent;
|
|
4234
|
+
completed = true;
|
|
4235
|
+
break;
|
|
4236
|
+
}
|
|
4237
|
+
if (!completed) {
|
|
4238
|
+
throw lastError instanceof Error ? lastError : new Error(typeof lastError === "string" ? lastError : "No model could produce a response");
|
|
4239
|
+
}
|
|
4240
|
+
if (fullContent.length > 0) {
|
|
4241
|
+
const assistantMessage = {
|
|
4242
|
+
id: v4Id(),
|
|
4243
|
+
role: "assistant",
|
|
4244
|
+
content: fullContent,
|
|
4245
|
+
model: responseModel,
|
|
4246
|
+
provider: responseProvider,
|
|
4247
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
4248
|
+
};
|
|
4249
|
+
setMessages((prev) => {
|
|
4250
|
+
const output = [...prev];
|
|
4251
|
+
if (responseModel !== modelId) {
|
|
4252
|
+
output.push({ id: v4Id(), role: "system", content: `Primary model "${modelId}" failed. Switched to fallback "${responseModel}".`, createdAt: /* @__PURE__ */ new Date() });
|
|
4253
|
+
}
|
|
4254
|
+
output.push(assistantMessage);
|
|
4255
|
+
return output;
|
|
4256
|
+
});
|
|
4257
|
+
void persistMessages(projectRootRef.current, userMessage, assistantMessage, responseModel, responseProvider);
|
|
4258
|
+
}
|
|
4259
|
+
} catch (error) {
|
|
4260
|
+
const errorContent = error instanceof Error ? error.message : String(error);
|
|
4261
|
+
setMessages((prev) => [...prev, { id: v4Id(), role: "assistant", content: `Error: ${errorContent}`, model: modelId, createdAt: /* @__PURE__ */ new Date() }]);
|
|
4262
|
+
} finally {
|
|
4263
|
+
setIsProcessing(false);
|
|
4264
|
+
}
|
|
4265
|
+
},
|
|
4266
|
+
[config, modelId, resolution, options.systemPrompt, record, switchModel, switchRole, startStream, resetStream, getRegistry, panel, thinkingValue, inputMode, options.isAgentPane, swarmConfig]
|
|
4267
|
+
);
|
|
4268
|
+
useEffect(() => {
|
|
4269
|
+
if (options.initialMessage) void handleSubmit(options.initialMessage);
|
|
4270
|
+
}, [handleSubmit, options.initialMessage]);
|
|
4271
|
+
const handleSubmitSync = useCallback((input) => {
|
|
4272
|
+
void handleSubmit(input);
|
|
4273
|
+
}, [handleSubmit]);
|
|
4274
|
+
const handleCancel = useCallback(() => {
|
|
4275
|
+
cancelStream();
|
|
4276
|
+
setIsProcessing(false);
|
|
4277
|
+
}, [cancelStream]);
|
|
4278
|
+
const handleModelSelected = useCallback((selectedModelId) => {
|
|
4279
|
+
const thinkingCfg = getThinkingConfigForModel(selectedModelId);
|
|
4280
|
+
if (thinkingCfg) {
|
|
4281
|
+
setSelectionMode({ type: "thinking", modelId: selectedModelId });
|
|
4282
|
+
} else {
|
|
4283
|
+
switchModel(selectedModelId);
|
|
4284
|
+
setSelectionMode({ type: "none" });
|
|
4285
|
+
const info = SUPPORTED_MODELS[selectedModelId];
|
|
4286
|
+
setMessages((prev) => [...prev, { id: v4Id(), role: "system", content: `Switched to model: ${info?.name ?? selectedModelId}`, createdAt: /* @__PURE__ */ new Date() }]);
|
|
4287
|
+
}
|
|
4288
|
+
}, [switchModel]);
|
|
4289
|
+
const handleThinkingSelected = useCallback((value) => {
|
|
4290
|
+
if (selectionMode.type !== "thinking") return;
|
|
4291
|
+
const { modelId: selectedModelId } = selectionMode;
|
|
4292
|
+
switchModel(selectedModelId);
|
|
4293
|
+
setThinkingValue(value);
|
|
4294
|
+
setSelectionMode({ type: "none" });
|
|
4295
|
+
const info = SUPPORTED_MODELS[selectedModelId];
|
|
4296
|
+
const cfg = getThinkingConfigForModel(selectedModelId);
|
|
4297
|
+
const methodLabel = cfg ? formatThinkingMethod(cfg.method) : "Thinking";
|
|
4298
|
+
const optionLabel = cfg?.options.find((o) => o.value === value)?.label ?? value;
|
|
4299
|
+
setMessages((prev) => [...prev, { id: v4Id(), role: "system", content: `Switched to model: ${info?.name ?? selectedModelId}
|
|
4300
|
+
${methodLabel}: ${optionLabel}`, createdAt: /* @__PURE__ */ new Date() }]);
|
|
4301
|
+
}, [selectionMode, switchModel]);
|
|
4302
|
+
const handleSelectionCancel = useCallback(() => {
|
|
4303
|
+
setSelectionMode({ type: "none" });
|
|
4304
|
+
}, []);
|
|
4305
|
+
const handleLoginSelected = useCallback(async (provider) => {
|
|
4306
|
+
setSelectionMode({ type: "none" });
|
|
4307
|
+
const loginMsg = provider === "gemini" ? "Logging in to gemini... A new terminal window will open for authentication." : `Logging in to ${provider}...`;
|
|
4308
|
+
setMessages((prev) => [...prev, { id: v4Id(), role: "system", content: loginMsg, createdAt: /* @__PURE__ */ new Date() }]);
|
|
4309
|
+
try {
|
|
4310
|
+
const loginModule = await loadLoginModuleForSlash(provider);
|
|
4311
|
+
await loginModule.login();
|
|
4312
|
+
setMessages((prev) => [...prev, { id: v4Id(), role: "system", content: `Successfully logged in to ${provider}`, createdAt: /* @__PURE__ */ new Date() }]);
|
|
4313
|
+
} catch (error) {
|
|
4314
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
4315
|
+
setMessages((prev) => [...prev, { id: v4Id(), role: "system", content: `Login failed: ${msg}`, createdAt: /* @__PURE__ */ new Date() }]);
|
|
4316
|
+
}
|
|
4317
|
+
}, []);
|
|
4318
|
+
const handleSwarmOnboardingSelected = useCallback(async (primaryProvider) => {
|
|
4319
|
+
const nextSwarmConfig = {
|
|
4320
|
+
onboardingComplete: true,
|
|
4321
|
+
detectedProviders: swarmConfig.detectedProviders,
|
|
4322
|
+
primaryMasterProvider: primaryProvider,
|
|
4323
|
+
fallbackMasterProviders: swarmConfig.detectedProviders.filter((p) => p !== primaryProvider)
|
|
4324
|
+
};
|
|
4325
|
+
try {
|
|
4326
|
+
const { ConfigStore } = await import('./config-store-NF56VHFU.js');
|
|
4327
|
+
const store = new ConfigStore();
|
|
4328
|
+
const currentConfig = store.loadGlobal();
|
|
4329
|
+
const nextProviders = { ...currentConfig.providers };
|
|
4330
|
+
for (const provider of nextSwarmConfig.detectedProviders) {
|
|
4331
|
+
const entry = getCliProviderEntry(provider);
|
|
4332
|
+
nextProviders[entry.provider] = { ...nextProviders[entry.provider], enabled: true };
|
|
4333
|
+
}
|
|
4334
|
+
store.saveGlobal({ ...currentConfig, providers: nextProviders, swarm: nextSwarmConfig });
|
|
4335
|
+
setSwarmOnboardingDeferred(false);
|
|
4336
|
+
setSwarmConfig(nextSwarmConfig);
|
|
4337
|
+
setSelectionMode({ type: "none" });
|
|
4338
|
+
} catch (error) {
|
|
4339
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4340
|
+
setMessages((prev) => [...prev, { id: v4Id(), role: "system", content: `Failed to save swarm setup: ${message}`, createdAt: /* @__PURE__ */ new Date() }]);
|
|
4341
|
+
}
|
|
4342
|
+
}, [swarmConfig.detectedProviders]);
|
|
4343
|
+
const handleSwarmOnboardingSkip = useCallback(() => {
|
|
4344
|
+
setSwarmOnboardingDeferred(true);
|
|
4345
|
+
setSelectionMode({ type: "none" });
|
|
4346
|
+
}, []);
|
|
4347
|
+
useEffect(() => {
|
|
4348
|
+
if (options.isAgentPane || panel.isSplitPanelActive || messages.length > 0 || isProcessing) return;
|
|
4349
|
+
if (selectionMode.type !== "none" || swarmOnboardingDeferred) return;
|
|
4350
|
+
if (swarmConfig.primaryMasterProvider === void 0) setSelectionMode({ type: "swarm-onboarding" });
|
|
4351
|
+
}, [isProcessing, messages.length, options.isAgentPane, panel.isSplitPanelActive, selectionMode.type, swarmConfig.primaryMasterProvider, swarmOnboardingDeferred]);
|
|
4352
|
+
if (selectionMode.type === "swarm-onboarding") {
|
|
4353
|
+
return /* @__PURE__ */ jsx(
|
|
4354
|
+
SwarmOnboarding,
|
|
4355
|
+
{
|
|
4356
|
+
detectedProviders: swarmConfig.detectedProviders,
|
|
4357
|
+
currentPrimaryProvider: swarmConfig.primaryMasterProvider,
|
|
4358
|
+
onSelect: (provider) => void handleSwarmOnboardingSelected(provider),
|
|
4359
|
+
onSkip: handleSwarmOnboardingSkip
|
|
4360
|
+
}
|
|
4361
|
+
);
|
|
4362
|
+
}
|
|
4363
|
+
if (selectionMode.type === "login") {
|
|
4364
|
+
return /* @__PURE__ */ jsx(LoginSelector, { onSelect: (provider) => void handleLoginSelected(provider), onCancel: handleSelectionCancel });
|
|
4365
|
+
}
|
|
4366
|
+
if (selectionMode.type === "model") {
|
|
4367
|
+
return /* @__PURE__ */ jsx(ModelSelector, { currentModelId: modelId, onSelect: handleModelSelected, onCancel: handleSelectionCancel, modelOrder: modelDisplayOrder ?? PROVIDER_MODEL_ORDER });
|
|
4368
|
+
}
|
|
4369
|
+
if (selectionMode.type === "thinking") {
|
|
4370
|
+
const selectedInfo = SUPPORTED_MODELS[selectionMode.modelId];
|
|
4371
|
+
return /* @__PURE__ */ jsx(ThinkingSelector, { modelId: selectionMode.modelId, modelName: selectedInfo?.name ?? selectionMode.modelId, currentValue: thinkingValue, onSelect: handleThinkingSelected, onBack: handleSelectionCancel });
|
|
4372
|
+
}
|
|
4373
|
+
if (panel.isSplitPanelActive) {
|
|
4374
|
+
return /* @__PURE__ */ jsx(
|
|
4375
|
+
SplitPane,
|
|
4376
|
+
{
|
|
4377
|
+
agents: panel.agents,
|
|
4378
|
+
activeAgentIndex: panel.activeAgentIndex,
|
|
4379
|
+
onSelectAgent: panel.selectAgent,
|
|
4380
|
+
agentOutputs: panel.agentOutputs,
|
|
4381
|
+
isProcessing,
|
|
4382
|
+
onSubmit: handleSubmitSync,
|
|
4383
|
+
onCancel: handleCancel,
|
|
4384
|
+
model: modelId,
|
|
4385
|
+
role: resolution.role,
|
|
4386
|
+
tokenCount: totalTokens,
|
|
4387
|
+
cost: totalCost,
|
|
4388
|
+
gitBranch
|
|
4389
|
+
}
|
|
4390
|
+
);
|
|
4391
|
+
}
|
|
4392
|
+
return /* @__PURE__ */ jsx(
|
|
4393
|
+
SinglePane,
|
|
4394
|
+
{
|
|
4395
|
+
messages,
|
|
4396
|
+
isProcessing,
|
|
4397
|
+
onSubmit: handleSubmitSync,
|
|
4398
|
+
onCancel: handleCancel,
|
|
4399
|
+
model: modelId,
|
|
4400
|
+
role: resolution.role,
|
|
4401
|
+
tokenCount: totalTokens,
|
|
4402
|
+
cost: totalCost,
|
|
4403
|
+
gitBranch,
|
|
4404
|
+
streamingContent: streamState.content,
|
|
4405
|
+
activity: streamState.activity,
|
|
4406
|
+
initialHistory: persistentHistory,
|
|
4407
|
+
mode: inputMode,
|
|
4408
|
+
onModeChange: setInputMode
|
|
4409
|
+
}
|
|
4410
|
+
);
|
|
4411
|
+
}
|
|
4412
|
+
async function startChatSession(options) {
|
|
4413
|
+
let config;
|
|
4414
|
+
try {
|
|
4415
|
+
const { ConfigStore } = await import('./config-store-NF56VHFU.js');
|
|
4416
|
+
const store = new ConfigStore();
|
|
4417
|
+
config = store.loadGlobal();
|
|
4418
|
+
} catch {
|
|
4419
|
+
config = DEFAULT_CONFIG;
|
|
4420
|
+
}
|
|
4421
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx(App, { config, options }));
|
|
4422
|
+
await waitUntilExit();
|
|
4423
|
+
}
|
|
4424
|
+
async function runFirstRunSetup() {
|
|
4425
|
+
const { runFirstRunSetup: runCliFirstRunSetup } = await import('./first-run-ADROZVYF.js');
|
|
4426
|
+
await runCliFirstRunSetup();
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
export { runFirstRunSetup, startChatSession };
|
|
4430
|
+
//# sourceMappingURL=App-JQ622M66.js.map
|
|
4431
|
+
//# sourceMappingURL=App-JQ622M66.js.map
|