@fastino-ai/pioneer-cli 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/package.json +2 -1
- package/src/chat/ChatApp.tsx +5 -2
- package/src/config.ts +15 -0
- package/src/index.tsx +806 -73
package/README.md
CHANGED
|
@@ -88,6 +88,8 @@ pioneer model_artifacts list
|
|
|
88
88
|
|
|
89
89
|
`agent` starts in interactive mode by default (standard workflow). For the only explicit alternate mode:
|
|
90
90
|
- `--mode research`: Pro mode with deeper research response style.
|
|
91
|
+
- `agent resume`: list recent conversations and resume a selected session.
|
|
92
|
+
- `agent sessions`: explicit alias for listing sessions and resuming one.
|
|
91
93
|
|
|
92
94
|
```bash
|
|
93
95
|
# Interactive (default)
|
|
@@ -98,6 +100,15 @@ pioneer agent
|
|
|
98
100
|
|
|
99
101
|
# Research mode (Pro subscription required)
|
|
100
102
|
pioneer agent --mode research
|
|
103
|
+
|
|
104
|
+
# Open a session list and pick one to continue
|
|
105
|
+
pioneer agent resume
|
|
106
|
+
|
|
107
|
+
# Equivalent: explicit sessions command
|
|
108
|
+
pioneer agent sessions
|
|
109
|
+
|
|
110
|
+
# Resume a specific conversation id
|
|
111
|
+
pioneer agent resume 4f2a...
|
|
101
112
|
```
|
|
102
113
|
|
|
103
114
|
When you start any of these commands, the CLI opens a conversational prompt and keeps accepting follow-up messages in the same session.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastino-ai/pioneer-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Pioneer CLI - AI training platform with chat agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@types/react": "^18.3.0",
|
|
37
37
|
"@types/ws": "^8.18.1",
|
|
38
38
|
"bun-types": "^1.1.0",
|
|
39
|
+
"ink-testing-library": "^4.0.0",
|
|
39
40
|
"typescript": "^5.6.3"
|
|
40
41
|
},
|
|
41
42
|
"engines": {
|
package/src/chat/ChatApp.tsx
CHANGED
|
@@ -855,7 +855,8 @@ File references:
|
|
|
855
855
|
}, [client, state.isProcessing]);
|
|
856
856
|
|
|
857
857
|
// Handle keyboard shortcuts
|
|
858
|
-
useInput(
|
|
858
|
+
useInput(
|
|
859
|
+
(char, key) => {
|
|
859
860
|
if (key.ctrl && char === "c") {
|
|
860
861
|
exit();
|
|
861
862
|
return;
|
|
@@ -920,7 +921,9 @@ File references:
|
|
|
920
921
|
return;
|
|
921
922
|
}
|
|
922
923
|
}
|
|
923
|
-
|
|
924
|
+
},
|
|
925
|
+
{ isActive: isRawModeSupported }
|
|
926
|
+
);
|
|
924
927
|
|
|
925
928
|
if (!client) {
|
|
926
929
|
return (
|
package/src/config.ts
CHANGED
|
@@ -23,8 +23,12 @@ export interface Config {
|
|
|
23
23
|
// Hugging Face token for pushing datasets/models
|
|
24
24
|
hfToken?: string;
|
|
25
25
|
|
|
26
|
+
// Last agent conversation ID used for resuming chats
|
|
27
|
+
lastAgentConversationId?: string;
|
|
28
|
+
|
|
26
29
|
// Telemetry (opt-in analytics)
|
|
27
30
|
telemetry?: TelemetryConfig;
|
|
31
|
+
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
const CONFIG_DIR = path.join(os.homedir(), ".pioneer");
|
|
@@ -169,6 +173,17 @@ export function getBaseUrl(): string {
|
|
|
169
173
|
return DEFAULT_BASE_URL;
|
|
170
174
|
}
|
|
171
175
|
|
|
176
|
+
export function getLastAgentConversationId(): string | undefined {
|
|
177
|
+
return loadConfig().lastAgentConversationId;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function setLastAgentConversationId(conversationId?: string): void {
|
|
181
|
+
if (!conversationId) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
saveConfig({ lastAgentConversationId: conversationId });
|
|
185
|
+
}
|
|
186
|
+
|
|
172
187
|
export function getMleModel(): string | undefined {
|
|
173
188
|
return loadConfig().mleModel;
|
|
174
189
|
}
|
package/src/index.tsx
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
getApiKey,
|
|
16
16
|
getBaseUrl,
|
|
17
17
|
getMleModel,
|
|
18
|
+
getLastAgentConversationId,
|
|
19
|
+
setLastAgentConversationId,
|
|
18
20
|
saveConfig,
|
|
19
21
|
clearApiKey,
|
|
20
22
|
getHfToken,
|
|
@@ -274,24 +276,28 @@ interface TelemetryPromptProps {
|
|
|
274
276
|
|
|
275
277
|
const TelemetryPrompt: React.FC<TelemetryPromptProps> = ({ onComplete }) => {
|
|
276
278
|
const [selected, setSelected] = useState<"yes" | "no">("yes");
|
|
279
|
+
const { isRawModeSupported } = useStdin();
|
|
277
280
|
|
|
278
|
-
useInput(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
281
|
+
useInput(
|
|
282
|
+
(input, key) => {
|
|
283
|
+
if (key.leftArrow || key.rightArrow) {
|
|
284
|
+
setSelected((s) => (s === "yes" ? "no" : "yes"));
|
|
285
|
+
}
|
|
286
|
+
if (key.return) {
|
|
287
|
+
setTelemetryEnabled(selected === "yes");
|
|
288
|
+
onComplete();
|
|
289
|
+
}
|
|
290
|
+
if (input === "y" || input === "Y") {
|
|
291
|
+
setTelemetryEnabled(true);
|
|
292
|
+
onComplete();
|
|
293
|
+
}
|
|
294
|
+
if (input === "n" || input === "N") {
|
|
295
|
+
setTelemetryEnabled(false);
|
|
296
|
+
onComplete();
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
{ isActive: isRawModeSupported }
|
|
300
|
+
);
|
|
295
301
|
|
|
296
302
|
return (
|
|
297
303
|
<Box flexDirection="column" paddingX={1}>
|
|
@@ -725,8 +731,12 @@ function toHistoryMessages(
|
|
|
725
731
|
return items
|
|
726
732
|
.filter((item) => Boolean(item && typeof item.content === "string" && typeof item.role === "string"))
|
|
727
733
|
.map((item) => {
|
|
734
|
+
const role =
|
|
735
|
+
item.role === "user" || item.role === "assistant" || item.role === "tool"
|
|
736
|
+
? item.role
|
|
737
|
+
: "assistant";
|
|
728
738
|
const message: HistoryMessage = {
|
|
729
|
-
role
|
|
739
|
+
role,
|
|
730
740
|
content: item.content,
|
|
731
741
|
};
|
|
732
742
|
|
|
@@ -735,7 +745,20 @@ function toHistoryMessages(
|
|
|
735
745
|
message.tool_call_id = sessionMessage.tool_call_id;
|
|
736
746
|
}
|
|
737
747
|
if (sessionMessage.tool_calls && Array.isArray(sessionMessage.tool_calls)) {
|
|
738
|
-
message.tool_calls = sessionMessage.tool_calls
|
|
748
|
+
message.tool_calls = sessionMessage.tool_calls
|
|
749
|
+
.filter(
|
|
750
|
+
(call): call is { id: string; name: string; args: Record<string, unknown> } =>
|
|
751
|
+
Boolean(call) &&
|
|
752
|
+
typeof call.id === "string" &&
|
|
753
|
+
typeof call.name === "string" &&
|
|
754
|
+
typeof call.args === "object" &&
|
|
755
|
+
call.args !== null
|
|
756
|
+
)
|
|
757
|
+
.map((call) => ({
|
|
758
|
+
id: call.id,
|
|
759
|
+
name: call.name,
|
|
760
|
+
args: call.args,
|
|
761
|
+
}));
|
|
739
762
|
}
|
|
740
763
|
|
|
741
764
|
return message;
|
|
@@ -757,6 +780,11 @@ function AgentInteractiveCommand({ message, conversationId, history }: AgentInte
|
|
|
757
780
|
const finish = (code: number) => {
|
|
758
781
|
setTimeout(() => process.exit(code), 300);
|
|
759
782
|
};
|
|
783
|
+
const persistConversation = (nextConversationId?: string) => {
|
|
784
|
+
if (nextConversationId) {
|
|
785
|
+
setLastAgentConversationId(nextConversationId);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
760
788
|
const getLatestAssistantContent = (
|
|
761
789
|
messages: HistoryMessage[] | undefined
|
|
762
790
|
): string => {
|
|
@@ -838,6 +866,9 @@ function AgentInteractiveCommand({ message, conversationId, history }: AgentInte
|
|
|
838
866
|
throw new Error(lastError || result.error || "Agent fallback request failed.");
|
|
839
867
|
}
|
|
840
868
|
setStream(result.data?.answer ?? "");
|
|
869
|
+
if (result.data?.conversation_id) {
|
|
870
|
+
persistConversation(result.data.conversation_id);
|
|
871
|
+
}
|
|
841
872
|
if (!result.data?.answer) {
|
|
842
873
|
setError("Agent returned no response content.");
|
|
843
874
|
setState("error");
|
|
@@ -982,6 +1013,7 @@ function AgentInteractiveCommand({ message, conversationId, history }: AgentInte
|
|
|
982
1013
|
}
|
|
983
1014
|
doneHistory.current = messages;
|
|
984
1015
|
wsSessionId = sessionId;
|
|
1016
|
+
persistConversation(sessionId);
|
|
985
1017
|
},
|
|
986
1018
|
}, {
|
|
987
1019
|
history: mergedHistory,
|
|
@@ -1089,59 +1121,244 @@ function formatAutoAgentTurn(turn: AutoAgentTurn): string {
|
|
|
1089
1121
|
return `${prefix}${turn.content}`;
|
|
1090
1122
|
}
|
|
1091
1123
|
|
|
1092
|
-
function AutoAgentInteractiveSession({
|
|
1124
|
+
export function AutoAgentInteractiveSession({
|
|
1093
1125
|
conversationId: initialConversationId,
|
|
1094
1126
|
history,
|
|
1095
1127
|
mode,
|
|
1096
1128
|
firstMessage,
|
|
1129
|
+
allowSessionCreation = true,
|
|
1097
1130
|
}: {
|
|
1098
1131
|
conversationId?: string;
|
|
1099
1132
|
history?: api.AgentChatHistoryItem[];
|
|
1100
1133
|
mode: "standard" | "research";
|
|
1101
1134
|
firstMessage?: string;
|
|
1135
|
+
allowSessionCreation?: boolean;
|
|
1102
1136
|
}) {
|
|
1137
|
+
const { isRawModeSupported } = useStdin();
|
|
1103
1138
|
const [input, setInput] = useState("");
|
|
1104
1139
|
const [isLoading, setIsLoading] = useState(false);
|
|
1105
1140
|
const [error, setError] = useState("");
|
|
1106
1141
|
const [statusHint, setStatusHint] = useState("Start typing...");
|
|
1107
1142
|
const [conversationId, setConversationId] = useState(initialConversationId);
|
|
1108
1143
|
const [historyState, setHistoryState] = useState<api.AgentChatHistoryItem[]>(history ?? []);
|
|
1144
|
+
const normalizeTurnRole = (role: string): AutoAgentTurnRole => {
|
|
1145
|
+
const normalized = role?.trim().toLowerCase();
|
|
1146
|
+
return normalized === "user" || normalized === "human" ? "user" : "assistant";
|
|
1147
|
+
};
|
|
1148
|
+
const [isHistoryLoading, setIsHistoryLoading] = useState(
|
|
1149
|
+
Boolean(initialConversationId) && history === undefined
|
|
1150
|
+
);
|
|
1151
|
+
const [isResumeSessionUsable, setIsResumeSessionUsable] = useState(true);
|
|
1109
1152
|
const [turns, setTurns] = useState<AutoAgentTurn[]>(
|
|
1110
|
-
(history ?? []).map((entry) =>
|
|
1111
|
-
entry.role
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
)
|
|
1153
|
+
(history ?? []).map((entry) => ({
|
|
1154
|
+
role: normalizeTurnRole(entry.role),
|
|
1155
|
+
content: entry.content,
|
|
1156
|
+
}))
|
|
1115
1157
|
);
|
|
1158
|
+
const [inputHistory, setInputHistory] = useState<string[]>([]);
|
|
1159
|
+
const [inputHistoryIndex, setInputHistoryIndex] = useState(-1);
|
|
1116
1160
|
const didSeedFirstMessage = useRef(false);
|
|
1161
|
+
const setInputValue = (nextValue: string) => {
|
|
1162
|
+
setInput(nextValue);
|
|
1163
|
+
setInputHistoryIndex(-1);
|
|
1164
|
+
};
|
|
1165
|
+
const normalizeSessionUserHistory = (items: api.AgentChatHistoryItem[]) =>
|
|
1166
|
+
items
|
|
1167
|
+
.filter((message) => {
|
|
1168
|
+
const normalizedRole = message.role?.trim().toLowerCase();
|
|
1169
|
+
return (
|
|
1170
|
+
(normalizedRole === "user" || normalizedRole === "human") &&
|
|
1171
|
+
typeof message.content === "string"
|
|
1172
|
+
);
|
|
1173
|
+
})
|
|
1174
|
+
.map((message) => message.content.trim())
|
|
1175
|
+
.filter((content): content is string => content.length > 0);
|
|
1176
|
+
const mapHistoryToTurns = (items: api.AgentChatHistoryItem[]) =>
|
|
1177
|
+
items.map((entry) => ({
|
|
1178
|
+
role: normalizeTurnRole(entry.role),
|
|
1179
|
+
content: entry.content,
|
|
1180
|
+
}));
|
|
1181
|
+
|
|
1182
|
+
const commitInputToHistory = (message: string) => {
|
|
1183
|
+
const normalized = message.trim();
|
|
1184
|
+
if (!normalized) {
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
setInputHistory((previous) => {
|
|
1189
|
+
if (previous.length > 0 && previous[previous.length - 1] === normalized) {
|
|
1190
|
+
return previous;
|
|
1191
|
+
}
|
|
1192
|
+
return [...previous, normalized];
|
|
1193
|
+
});
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
useEffect(() => {
|
|
1197
|
+
const restoredHistory = history ?? [];
|
|
1198
|
+
setHistoryState(restoredHistory);
|
|
1199
|
+
setTurns(mapHistoryToTurns(restoredHistory));
|
|
1200
|
+
setInputHistory(normalizeSessionUserHistory(restoredHistory));
|
|
1201
|
+
if (history !== undefined || !initialConversationId) {
|
|
1202
|
+
setIsHistoryLoading(false);
|
|
1203
|
+
}
|
|
1204
|
+
}, [history]);
|
|
1205
|
+
|
|
1206
|
+
useEffect(() => {
|
|
1207
|
+
setTurns(mapHistoryToTurns(historyState));
|
|
1208
|
+
}, [historyState]);
|
|
1209
|
+
|
|
1210
|
+
useEffect(() => {
|
|
1211
|
+
if (!initialConversationId || history !== undefined) {
|
|
1212
|
+
setIsHistoryLoading(false);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
let isActive = true;
|
|
1217
|
+
const hydrateSessionHistory = async () => {
|
|
1218
|
+
const result = await api.getAgentSession(initialConversationId);
|
|
1219
|
+
if (!isActive) {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (!result.ok) {
|
|
1224
|
+
const lowerError = (result.error || "").toLowerCase();
|
|
1225
|
+
const notFound =
|
|
1226
|
+
result.status === 404 ||
|
|
1227
|
+
lowerError.includes("session not found") ||
|
|
1228
|
+
lowerError.includes("not found") ||
|
|
1229
|
+
lowerError.includes("does not exist");
|
|
1230
|
+
if (notFound) {
|
|
1231
|
+
setHistoryState([]);
|
|
1232
|
+
setTurns([]);
|
|
1233
|
+
setError("Could not load session history. This session could not be found on the backend. Resume is not available.");
|
|
1234
|
+
setIsResumeSessionUsable(false);
|
|
1235
|
+
setIsHistoryLoading(false);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
setError(
|
|
1239
|
+
`Could not load session history for ${initialConversationId}: ${result.error || "Unable to load conversation history."}` +
|
|
1240
|
+
" Proceeding without preloaded history."
|
|
1241
|
+
);
|
|
1242
|
+
setIsResumeSessionUsable(true);
|
|
1243
|
+
setIsHistoryLoading(false);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const restoredMessages = (result.data?.messages ?? [])
|
|
1248
|
+
.map((message) => {
|
|
1249
|
+
if (
|
|
1250
|
+
typeof message.role !== "string" ||
|
|
1251
|
+
typeof message.content !== "string"
|
|
1252
|
+
) {
|
|
1253
|
+
return undefined;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
return {
|
|
1257
|
+
role: message.role,
|
|
1258
|
+
content: message.content,
|
|
1259
|
+
} as api.AgentChatHistoryItem;
|
|
1260
|
+
})
|
|
1261
|
+
.filter((message): message is api.AgentChatHistoryItem => message !== undefined);
|
|
1262
|
+
|
|
1263
|
+
setHistoryState(restoredMessages);
|
|
1264
|
+
setTurns(mapHistoryToTurns(restoredMessages));
|
|
1265
|
+
setInputHistory(normalizeSessionUserHistory(restoredMessages));
|
|
1266
|
+
|
|
1267
|
+
setIsHistoryLoading(false);
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
void hydrateSessionHistory();
|
|
1271
|
+
|
|
1272
|
+
return () => {
|
|
1273
|
+
isActive = false;
|
|
1274
|
+
};
|
|
1275
|
+
}, [initialConversationId, history]);
|
|
1117
1276
|
|
|
1118
1277
|
const runAutoTurn = async (rawMessage: string) => {
|
|
1278
|
+
if (isHistoryLoading) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1119
1281
|
const trimmed = rawMessage.trim();
|
|
1120
1282
|
if (!trimmed || isLoading) {
|
|
1121
1283
|
return;
|
|
1122
1284
|
}
|
|
1285
|
+
commitInputToHistory(trimmed);
|
|
1286
|
+
if (!isResumeSessionUsable && !allowSessionCreation) {
|
|
1287
|
+
setError("Unable to resume this session because the conversation record is unavailable.");
|
|
1288
|
+
setIsLoading(false);
|
|
1289
|
+
setStatusHint("Ready for next message.");
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1123
1292
|
|
|
1124
1293
|
const nextHistory: api.AgentChatHistoryItem[] = [
|
|
1125
1294
|
...historyState,
|
|
1126
1295
|
{ role: "user", content: trimmed },
|
|
1127
1296
|
];
|
|
1128
1297
|
setHistoryState(nextHistory);
|
|
1129
|
-
|
|
1130
|
-
setInput("");
|
|
1298
|
+
setInputValue("");
|
|
1131
1299
|
setError("");
|
|
1132
1300
|
setIsLoading(true);
|
|
1133
1301
|
setStatusHint("Thinking...");
|
|
1134
1302
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1303
|
+
let activeConversationId = conversationId;
|
|
1304
|
+
|
|
1305
|
+
if (!activeConversationId && !allowSessionCreation) {
|
|
1306
|
+
setError("Unable to resume this session because no valid conversation id is available.");
|
|
1307
|
+
setIsLoading(false);
|
|
1308
|
+
setStatusHint("Ready for next message.");
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
if (!activeConversationId) {
|
|
1313
|
+
setStatusHint("Creating conversation...");
|
|
1314
|
+
const createdSession = await api.createAgentSession({
|
|
1315
|
+
first_message: trimmed,
|
|
1316
|
+
title: trimmed.slice(0, 80) || "New agent session",
|
|
1317
|
+
});
|
|
1318
|
+
if (!createdSession.ok) {
|
|
1319
|
+
setError(`Failed to create conversation: ${createdSession.error || "Unknown error."}`);
|
|
1320
|
+
setIsLoading(false);
|
|
1321
|
+
setStatusHint("Ready for next message.");
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
const createdSessionId = createdSession.data?.id;
|
|
1325
|
+
if (!createdSessionId) {
|
|
1326
|
+
setError("Failed to create conversation: Missing conversation id from server response.");
|
|
1327
|
+
setIsLoading(false);
|
|
1328
|
+
setStatusHint("Ready for next message.");
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
activeConversationId = createdSessionId;
|
|
1332
|
+
setConversationId(activeConversationId);
|
|
1333
|
+
persistConversation(activeConversationId);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const sendTurn = async (requestConversationId: string | undefined) =>
|
|
1337
|
+
api.agentChat({
|
|
1338
|
+
message: trimmed,
|
|
1339
|
+
...(requestConversationId ? { conversation_id: requestConversationId } : {}),
|
|
1340
|
+
...(nextHistory.length ? { history: nextHistory } : {}),
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
let result = await sendTurn(activeConversationId);
|
|
1344
|
+
const lowerError = (result.error || "").toLowerCase();
|
|
1345
|
+
const notFound =
|
|
1346
|
+
result.status === 404 ||
|
|
1347
|
+
lowerError.includes("session not found") ||
|
|
1348
|
+
lowerError.includes("not found") ||
|
|
1349
|
+
lowerError.includes("does not exist");
|
|
1140
1350
|
|
|
1141
1351
|
if (!result.ok) {
|
|
1142
|
-
if (
|
|
1352
|
+
if (lowerError.includes("failed to get session") && notFound) {
|
|
1353
|
+
if (!allowSessionCreation) {
|
|
1354
|
+
setError("Unable to resume this session. It is not available on the backend.");
|
|
1355
|
+
setIsResumeSessionUsable(false);
|
|
1356
|
+
} else {
|
|
1357
|
+
setError("Failed to resume session on the backend. Please start a new message to create a new session.");
|
|
1358
|
+
}
|
|
1359
|
+
} else if (
|
|
1143
1360
|
result.status === 403 &&
|
|
1144
|
-
|
|
1361
|
+
lowerError.includes("deep research mode")
|
|
1145
1362
|
) {
|
|
1146
1363
|
setError(
|
|
1147
1364
|
"Research mode requires a Pro subscription.\n" +
|
|
@@ -1151,29 +1368,121 @@ function AutoAgentInteractiveSession({
|
|
|
1151
1368
|
} else {
|
|
1152
1369
|
setError(result.error || "Agent request failed.");
|
|
1153
1370
|
}
|
|
1371
|
+
|
|
1154
1372
|
setIsLoading(false);
|
|
1155
1373
|
setStatusHint("Ready for next message.");
|
|
1156
1374
|
return;
|
|
1157
1375
|
}
|
|
1158
1376
|
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1377
|
+
const data = result.data;
|
|
1378
|
+
if (!data) {
|
|
1379
|
+
setError("Agent request did not return a usable response.");
|
|
1380
|
+
setIsLoading(false);
|
|
1381
|
+
setStatusHint("Ready for next message.");
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1161
1384
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1385
|
+
const answer = data.answer?.trim() || "No response content yet.";
|
|
1386
|
+
setError("");
|
|
1387
|
+
const persistedConversationId = activeConversationId ?? data.conversation_id;
|
|
1388
|
+
if (persistedConversationId) {
|
|
1389
|
+
setConversationId(persistedConversationId);
|
|
1390
|
+
persistConversation(persistedConversationId);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
const assistantTurn: api.AgentChatHistoryItem = { role: "assistant", content: answer };
|
|
1394
|
+
if (persistedConversationId) {
|
|
1395
|
+
void api.appendSessionMessages(persistedConversationId, {
|
|
1396
|
+
messages: [
|
|
1397
|
+
{ role: "user", content: trimmed },
|
|
1398
|
+
{ role: "assistant", content: answer },
|
|
1399
|
+
],
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
setHistoryState((current) => [...current, assistantTurn]);
|
|
1165
1403
|
setStatusHint("Ready for next message.");
|
|
1166
1404
|
setIsLoading(false);
|
|
1167
1405
|
};
|
|
1168
1406
|
|
|
1407
|
+
useInput(
|
|
1408
|
+
(character, key) => {
|
|
1409
|
+
if (key.return) {
|
|
1410
|
+
void runAutoTurn(input);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
if (key.backspace || key.delete) {
|
|
1415
|
+
setInputValue(input.slice(0, -1));
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if (key.upArrow) {
|
|
1420
|
+
if (!inputHistory.length) {
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
const nextIndex =
|
|
1424
|
+
inputHistoryIndex === -1 ? inputHistory.length - 1 : Math.max(0, inputHistoryIndex - 1);
|
|
1425
|
+
setInputHistoryIndex(nextIndex);
|
|
1426
|
+
setInput(inputHistory[nextIndex] ?? "");
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (key.downArrow) {
|
|
1431
|
+
if (inputHistoryIndex === -1) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const nextIndex = inputHistoryIndex + 1;
|
|
1435
|
+
if (nextIndex >= inputHistory.length) {
|
|
1436
|
+
setInputHistoryIndex(-1);
|
|
1437
|
+
setInput("");
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
setInputHistoryIndex(nextIndex);
|
|
1441
|
+
setInput(inputHistory[nextIndex] ?? "");
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (
|
|
1446
|
+
key.tab ||
|
|
1447
|
+
key.leftArrow ||
|
|
1448
|
+
key.rightArrow ||
|
|
1449
|
+
key.escape ||
|
|
1450
|
+
key.ctrl ||
|
|
1451
|
+
key.meta
|
|
1452
|
+
) {
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
if (character) {
|
|
1457
|
+
setInputValue(input + character);
|
|
1458
|
+
}
|
|
1459
|
+
},
|
|
1460
|
+
{ isActive: isRawModeSupported && !isLoading }
|
|
1461
|
+
);
|
|
1462
|
+
|
|
1463
|
+
const persistConversation = (nextConversationId?: string) => {
|
|
1464
|
+
if (nextConversationId) {
|
|
1465
|
+
setLastAgentConversationId(nextConversationId);
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
useEffect(() => {
|
|
1470
|
+
return () => {
|
|
1471
|
+
persistConversation(conversationId);
|
|
1472
|
+
};
|
|
1473
|
+
}, [conversationId]);
|
|
1474
|
+
|
|
1169
1475
|
useEffect(() => {
|
|
1170
1476
|
if (!firstMessage || didSeedFirstMessage.current) {
|
|
1171
1477
|
return;
|
|
1172
1478
|
}
|
|
1479
|
+
if (isHistoryLoading) {
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1173
1482
|
|
|
1174
1483
|
didSeedFirstMessage.current = true;
|
|
1175
1484
|
void runAutoTurn(firstMessage);
|
|
1176
|
-
}, [firstMessage]);
|
|
1485
|
+
}, [firstMessage, isHistoryLoading]);
|
|
1177
1486
|
|
|
1178
1487
|
return (
|
|
1179
1488
|
<Box flexDirection="column">
|
|
@@ -1182,6 +1491,7 @@ function AutoAgentInteractiveSession({
|
|
|
1182
1491
|
Mode: {mode === "research" ? "research" : "standard"}{" "}
|
|
1183
1492
|
{conversationId ? `(conversation ${conversationId})` : "(new conversation)"}
|
|
1184
1493
|
</Text>
|
|
1494
|
+
{isHistoryLoading ? <Loading message="Loading conversation history..." /> : null}
|
|
1185
1495
|
<Box flexDirection="column" marginTop={1}>
|
|
1186
1496
|
{turns.map((entry, idx) => (
|
|
1187
1497
|
<Text key={`agent-turn-${idx}`} dimColor={entry.role === "user"}>
|
|
@@ -1194,8 +1504,9 @@ function AutoAgentInteractiveSession({
|
|
|
1194
1504
|
{isLoading ? null : (
|
|
1195
1505
|
<TextInput
|
|
1196
1506
|
value={input}
|
|
1197
|
-
|
|
1198
|
-
|
|
1507
|
+
focus={false}
|
|
1508
|
+
onChange={() => {}}
|
|
1509
|
+
onSubmit={() => {}}
|
|
1199
1510
|
/>
|
|
1200
1511
|
)}
|
|
1201
1512
|
</Box>
|
|
@@ -1209,21 +1520,372 @@ function AutoAgentInteractiveSession({
|
|
|
1209
1520
|
);
|
|
1210
1521
|
}
|
|
1211
1522
|
|
|
1523
|
+
export function AgentResumeCommand({
|
|
1524
|
+
mode,
|
|
1525
|
+
}: {
|
|
1526
|
+
mode?: "standard" | "research";
|
|
1527
|
+
}) {
|
|
1528
|
+
const { isRawModeSupported } = useStdin();
|
|
1529
|
+
const [isReady, setReady] = useState(false);
|
|
1530
|
+
const [selectedSession, setSelectedSession] = useState<
|
|
1531
|
+
{
|
|
1532
|
+
id: string;
|
|
1533
|
+
} | undefined
|
|
1534
|
+
>();
|
|
1535
|
+
const [defaultSessionId] = useState(() => getLastAgentConversationId());
|
|
1536
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
1537
|
+
const [highlightIndex, setHighlightIndex] = useState(0);
|
|
1538
|
+
const [sessions, setSessions] = useState<
|
|
1539
|
+
Array<{
|
|
1540
|
+
id: string;
|
|
1541
|
+
title: string;
|
|
1542
|
+
updated_at: string;
|
|
1543
|
+
is_archived: boolean;
|
|
1544
|
+
}>
|
|
1545
|
+
>([]);
|
|
1546
|
+
const [error, setError] = useState("");
|
|
1547
|
+
const [statusHint, setStatusHint] = useState("Loading sessions...");
|
|
1548
|
+
const [isSessionLoading, setSessionLoading] = useState(false);
|
|
1549
|
+
const [sessionError, setSessionError] = useState("");
|
|
1550
|
+
const [selectedSessionHistory, setSelectedSessionHistory] = useState<
|
|
1551
|
+
api.AgentChatHistoryItem[] | undefined
|
|
1552
|
+
>(undefined);
|
|
1553
|
+
|
|
1554
|
+
useEffect(() => {
|
|
1555
|
+
if (!selectedSession?.id) {
|
|
1556
|
+
setSelectedSessionHistory(undefined);
|
|
1557
|
+
setSessionError("");
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
let isActive = true;
|
|
1562
|
+
const loadSessionHistory = async () => {
|
|
1563
|
+
setSessionLoading(true);
|
|
1564
|
+
setSessionError("");
|
|
1565
|
+
|
|
1566
|
+
const result = await api.getAgentSession(selectedSession.id);
|
|
1567
|
+
if (!isActive) {
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
if (!result.ok) {
|
|
1572
|
+
setSessionError(
|
|
1573
|
+
`Could not load selected session ${selectedSession.id}: ${result.error || "Unknown error."}`
|
|
1574
|
+
);
|
|
1575
|
+
setSelectedSessionHistory(undefined);
|
|
1576
|
+
setSessionLoading(false);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
const restoredMessages = (result.data?.messages ?? [])
|
|
1581
|
+
.map((message) => {
|
|
1582
|
+
if (typeof message?.role !== "string" || typeof message?.content !== "string") {
|
|
1583
|
+
return undefined;
|
|
1584
|
+
}
|
|
1585
|
+
return {
|
|
1586
|
+
role: message.role,
|
|
1587
|
+
content: message.content,
|
|
1588
|
+
} as api.AgentChatHistoryItem;
|
|
1589
|
+
})
|
|
1590
|
+
.filter((message): message is api.AgentChatHistoryItem => message !== undefined);
|
|
1591
|
+
|
|
1592
|
+
setSelectedSessionHistory(restoredMessages);
|
|
1593
|
+
setSessionLoading(false);
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
void loadSessionHistory();
|
|
1597
|
+
|
|
1598
|
+
return () => {
|
|
1599
|
+
isActive = false;
|
|
1600
|
+
setSessionLoading(false);
|
|
1601
|
+
};
|
|
1602
|
+
}, [selectedSession?.id]);
|
|
1603
|
+
|
|
1604
|
+
useEffect(() => {
|
|
1605
|
+
let isActive = true;
|
|
1606
|
+
const loadSessions = async () => {
|
|
1607
|
+
try {
|
|
1608
|
+
const result = await api.listAgentSessions();
|
|
1609
|
+
if (!isActive) {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
if (!result.ok) {
|
|
1613
|
+
setError(result.error || "Could not load agent sessions.");
|
|
1614
|
+
setStatusHint("");
|
|
1615
|
+
setReady(true);
|
|
1616
|
+
setSessions([]);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
const remoteSessions = (result.data?.sessions ?? []).map((session) => ({
|
|
1620
|
+
id: session.id,
|
|
1621
|
+
title: session.title || "(untitled)",
|
|
1622
|
+
updated_at: session.updated_at || new Date().toISOString(),
|
|
1623
|
+
is_archived: session.is_archived,
|
|
1624
|
+
}));
|
|
1625
|
+
remoteSessions.sort((a, b) => {
|
|
1626
|
+
const aTime = Date.parse(a.updated_at);
|
|
1627
|
+
const bTime = Date.parse(b.updated_at);
|
|
1628
|
+
if (Number.isNaN(aTime) || Number.isNaN(bTime)) {
|
|
1629
|
+
return 0;
|
|
1630
|
+
}
|
|
1631
|
+
return bTime - aTime;
|
|
1632
|
+
});
|
|
1633
|
+
if (defaultSessionId) {
|
|
1634
|
+
const lastIndex = remoteSessions.findIndex((session) => session.id === defaultSessionId);
|
|
1635
|
+
if (lastIndex >= 0) {
|
|
1636
|
+
setHighlightIndex(lastIndex);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
setSessions(remoteSessions);
|
|
1640
|
+
setStatusHint("");
|
|
1641
|
+
setReady(true);
|
|
1642
|
+
return;
|
|
1643
|
+
} catch (err) {
|
|
1644
|
+
if (!isActive) {
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
const message = err instanceof Error ? err.message : "Could not load agent sessions.";
|
|
1648
|
+
setError(message);
|
|
1649
|
+
setStatusHint("");
|
|
1650
|
+
setSessions([]);
|
|
1651
|
+
setReady(true);
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
void loadSessions();
|
|
1655
|
+
|
|
1656
|
+
return () => {
|
|
1657
|
+
isActive = false;
|
|
1658
|
+
};
|
|
1659
|
+
}, []);
|
|
1660
|
+
|
|
1661
|
+
const normalizeSessionLabel = (session: {
|
|
1662
|
+
id: string;
|
|
1663
|
+
title: string;
|
|
1664
|
+
is_archived: boolean;
|
|
1665
|
+
updated_at: string;
|
|
1666
|
+
}) => `${session.title || "(untitled)"} ${session.is_archived ? "[archived] " : ""}(updated ${session.updated_at})`;
|
|
1667
|
+
|
|
1668
|
+
const matchingSessions = sessions.filter((session) => {
|
|
1669
|
+
const searchable = `${session.title} ${session.id}`.toLowerCase();
|
|
1670
|
+
const normalizedQuery = searchQuery.trim().toLowerCase();
|
|
1671
|
+
return normalizedQuery.length === 0 || searchable.includes(normalizedQuery);
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
useInput((_, key) => {
|
|
1675
|
+
if (!matchingSessions.length) return;
|
|
1676
|
+
if (key.upArrow) {
|
|
1677
|
+
setHighlightIndex((idx) => (idx === 0 ? matchingSessions.length - 1 : idx - 1));
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
if (key.downArrow) {
|
|
1681
|
+
setHighlightIndex((idx) => (idx === matchingSessions.length - 1 ? 0 : idx + 1));
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
if (key.return) {
|
|
1685
|
+
const selected = matchingSessions[highlightIndex];
|
|
1686
|
+
if (!selected) {
|
|
1687
|
+
setError("No matching sessions found.");
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
setError("");
|
|
1691
|
+
setSelectedSession({ id: selected.id });
|
|
1692
|
+
}
|
|
1693
|
+
}, { isActive: isRawModeSupported });
|
|
1694
|
+
|
|
1695
|
+
if (!isRawModeSupported) {
|
|
1696
|
+
return (
|
|
1697
|
+
<Box flexDirection="column">
|
|
1698
|
+
<ErrorMessage error="Interactive input is not supported in this terminal." />
|
|
1699
|
+
<Text>Use interactive mode for this environment:</Text>
|
|
1700
|
+
<Text dimColor> agent --help</Text>
|
|
1701
|
+
</Box>
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
if (error && !sessions.length) {
|
|
1706
|
+
return <ErrorMessage error={error} />;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
if (!isReady) {
|
|
1710
|
+
return <Loading message={statusHint || "Loading sessions..."} />;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
if (!sessions.length) {
|
|
1714
|
+
return (
|
|
1715
|
+
<ErrorMessage
|
|
1716
|
+
error="No agent sessions found. Start a new conversation with `pioneer agent` and try again."
|
|
1717
|
+
/>
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (selectedSession?.id) {
|
|
1722
|
+
if (isSessionLoading) {
|
|
1723
|
+
return <Loading message={`Loading conversation ${selectedSession.id}...`} />;
|
|
1724
|
+
}
|
|
1725
|
+
if (sessionError) {
|
|
1726
|
+
return <ErrorMessage error={sessionError} />;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
return (
|
|
1730
|
+
<AutoAgentInteractiveSession
|
|
1731
|
+
conversationId={selectedSession.id}
|
|
1732
|
+
mode={mode ?? "standard"}
|
|
1733
|
+
history={selectedSessionHistory}
|
|
1734
|
+
allowSessionCreation={false}
|
|
1735
|
+
/>
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
return (
|
|
1740
|
+
<Box flexDirection="column">
|
|
1741
|
+
<Text bold>Agent sessions:</Text>
|
|
1742
|
+
<Text>
|
|
1743
|
+
{defaultSessionId ? `Last session: ${defaultSessionId}. ` : ""}
|
|
1744
|
+
Type to filter. Use ↑/↓ to navigate and Enter to select.
|
|
1745
|
+
</Text>
|
|
1746
|
+
<Text dimColor>Search title or session ID.</Text>
|
|
1747
|
+
<Text> </Text>
|
|
1748
|
+
{matchingSessions.length === 0 ? (
|
|
1749
|
+
<Text color="yellow">No sessions match "{searchQuery}".</Text>
|
|
1750
|
+
) : (
|
|
1751
|
+
matchingSessions.slice(0, 12).map((session, index) => {
|
|
1752
|
+
const isSelected = index === highlightIndex;
|
|
1753
|
+
return (
|
|
1754
|
+
<Text key={session.id} color={isSelected ? "cyan" : undefined}>
|
|
1755
|
+
{`${isSelected ? ">" : " "} ${normalizeSessionLabel(session)} (${session.id})`}
|
|
1756
|
+
</Text>
|
|
1757
|
+
);
|
|
1758
|
+
})
|
|
1759
|
+
)}
|
|
1760
|
+
<Box marginTop={1}>
|
|
1761
|
+
<Text color="cyan">> </Text>
|
|
1762
|
+
<TextInput
|
|
1763
|
+
value={searchQuery}
|
|
1764
|
+
onChange={setSearchQuery}
|
|
1765
|
+
onSubmit={(rawValue) => {
|
|
1766
|
+
const trimmed = rawValue.trim().toLowerCase();
|
|
1767
|
+
if (!trimmed && !matchingSessions.length) {
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
const exactMatch = matchingSessions.find(
|
|
1771
|
+
(session) =>
|
|
1772
|
+
session.id.toLowerCase() === trimmed ||
|
|
1773
|
+
session.title.toLowerCase() === trimmed
|
|
1774
|
+
);
|
|
1775
|
+
const selected = exactMatch ?? matchingSessions[highlightIndex];
|
|
1776
|
+
if (!selected?.id) {
|
|
1777
|
+
setError("No matching session found.");
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
setError("");
|
|
1781
|
+
setSelectedSession({ id: selected.id });
|
|
1782
|
+
}}
|
|
1783
|
+
/>
|
|
1784
|
+
</Box>
|
|
1785
|
+
{error && <ErrorMessage error={error} />}
|
|
1786
|
+
</Box>
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1212
1790
|
function AgentInteractivePrompt({
|
|
1213
1791
|
conversationId,
|
|
1214
1792
|
history,
|
|
1215
1793
|
mode,
|
|
1794
|
+
allowSessionCreation = true,
|
|
1216
1795
|
}: {
|
|
1217
1796
|
conversationId?: string;
|
|
1218
1797
|
history?: api.AgentChatHistoryItem[];
|
|
1219
1798
|
mode?: "standard" | "research";
|
|
1799
|
+
allowSessionCreation?: boolean;
|
|
1220
1800
|
}) {
|
|
1221
1801
|
const [input, setInput] = useState("");
|
|
1802
|
+
const [inputHistory, setInputHistory] = useState<string[]>([]);
|
|
1803
|
+
const [inputHistoryIndex, setInputHistoryIndex] = useState(-1);
|
|
1222
1804
|
const [isReady, setReady] = useState(false);
|
|
1223
1805
|
const [message, setMessage] = useState("");
|
|
1224
1806
|
const { isRawModeSupported } = useStdin();
|
|
1225
1807
|
const shouldExitImmediately = !isRawModeSupported;
|
|
1226
1808
|
|
|
1809
|
+
const commitInputToHistory = (value: string) => {
|
|
1810
|
+
const normalized = value.trim();
|
|
1811
|
+
if (!normalized) {
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
setInputHistory((previous) => {
|
|
1815
|
+
if (previous.length > 0 && previous[previous.length - 1] === normalized) {
|
|
1816
|
+
return previous;
|
|
1817
|
+
}
|
|
1818
|
+
return [...previous, normalized];
|
|
1819
|
+
});
|
|
1820
|
+
};
|
|
1821
|
+
|
|
1822
|
+
const setInputValue = (nextValue: string) => {
|
|
1823
|
+
setInput(nextValue);
|
|
1824
|
+
setInputHistoryIndex(-1);
|
|
1825
|
+
};
|
|
1826
|
+
|
|
1827
|
+
useInput(
|
|
1828
|
+
(character, key) => {
|
|
1829
|
+
if (key.return) {
|
|
1830
|
+
const trimmed = input.trim();
|
|
1831
|
+
if (!trimmed) {
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
commitInputToHistory(trimmed);
|
|
1835
|
+
setMessage(trimmed);
|
|
1836
|
+
setReady(true);
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
if (key.backspace || key.delete) {
|
|
1841
|
+
setInputValue(input.slice(0, -1));
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
if (key.upArrow) {
|
|
1846
|
+
if (!inputHistory.length) {
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
const nextIndex =
|
|
1850
|
+
inputHistoryIndex === -1 ? inputHistory.length - 1 : Math.max(0, inputHistoryIndex - 1);
|
|
1851
|
+
setInputHistoryIndex(nextIndex);
|
|
1852
|
+
setInput(inputHistory[nextIndex] ?? "");
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
if (key.downArrow) {
|
|
1857
|
+
if (inputHistoryIndex === -1) {
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
const nextIndex = inputHistoryIndex + 1;
|
|
1861
|
+
if (nextIndex >= inputHistory.length) {
|
|
1862
|
+
setInputHistoryIndex(-1);
|
|
1863
|
+
setInput("");
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
setInputHistoryIndex(nextIndex);
|
|
1867
|
+
setInput(inputHistory[nextIndex] ?? "");
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (
|
|
1872
|
+
key.tab ||
|
|
1873
|
+
key.leftArrow ||
|
|
1874
|
+
key.rightArrow ||
|
|
1875
|
+
key.escape ||
|
|
1876
|
+
key.ctrl ||
|
|
1877
|
+
key.meta
|
|
1878
|
+
) {
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
if (character) {
|
|
1883
|
+
setInputValue(input + character);
|
|
1884
|
+
}
|
|
1885
|
+
},
|
|
1886
|
+
{ isActive: isRawModeSupported && !isReady }
|
|
1887
|
+
);
|
|
1888
|
+
|
|
1227
1889
|
useEffect(() => {
|
|
1228
1890
|
if (!shouldExitImmediately) {
|
|
1229
1891
|
return;
|
|
@@ -1256,15 +1918,9 @@ function AgentInteractivePrompt({
|
|
|
1256
1918
|
<Text color="cyan">> </Text>
|
|
1257
1919
|
<TextInput
|
|
1258
1920
|
value={input}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
if (!trimmed) {
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
setMessage(trimmed);
|
|
1266
|
-
setReady(true);
|
|
1267
|
-
}}
|
|
1921
|
+
focus={false}
|
|
1922
|
+
onChange={() => {}}
|
|
1923
|
+
onSubmit={() => {}}
|
|
1268
1924
|
/>
|
|
1269
1925
|
</Box>
|
|
1270
1926
|
<Text dimColor>Type Ctrl+C to cancel.</Text>
|
|
@@ -1278,6 +1934,7 @@ function AgentInteractivePrompt({
|
|
|
1278
1934
|
history={history}
|
|
1279
1935
|
mode={mode === "research" ? "research" : "standard"}
|
|
1280
1936
|
firstMessage={message}
|
|
1937
|
+
allowSessionCreation={allowSessionCreation}
|
|
1281
1938
|
/>
|
|
1282
1939
|
);
|
|
1283
1940
|
}
|
|
@@ -1304,6 +1961,7 @@ function ModelCreateInteractive({
|
|
|
1304
1961
|
const [loading, setLoading] = useState(true);
|
|
1305
1962
|
const [error, setError] = useState("");
|
|
1306
1963
|
const [selectedModelId, setSelectedModelId] = useState<string | null>(null);
|
|
1964
|
+
const { isRawModeSupported } = useStdin();
|
|
1307
1965
|
const [highlightIndex, setHighlightIndex] = useState(0);
|
|
1308
1966
|
|
|
1309
1967
|
useEffect(() => {
|
|
@@ -1352,25 +2010,28 @@ function ModelCreateInteractive({
|
|
|
1352
2010
|
}
|
|
1353
2011
|
}, [highlightIndex, topMatches.length]);
|
|
1354
2012
|
|
|
1355
|
-
useInput(
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
}
|
|
1361
|
-
if (key.downArrow) {
|
|
1362
|
-
setHighlightIndex((idx) => (idx === topMatches.length - 1 ? 0 : idx + 1));
|
|
1363
|
-
return;
|
|
1364
|
-
}
|
|
1365
|
-
if (key.return) {
|
|
1366
|
-
const resolvedModel = resolveModelId(query);
|
|
1367
|
-
if (!resolvedModel) {
|
|
1368
|
-
setError("No matching models found. Refine your search and try again.");
|
|
2013
|
+
useInput(
|
|
2014
|
+
(_, key) => {
|
|
2015
|
+
if (!topMatches.length) return;
|
|
2016
|
+
if (key.upArrow) {
|
|
2017
|
+
setHighlightIndex((idx) => (idx === 0 ? topMatches.length - 1 : idx - 1));
|
|
1369
2018
|
return;
|
|
1370
2019
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
2020
|
+
if (key.downArrow) {
|
|
2021
|
+
setHighlightIndex((idx) => (idx === topMatches.length - 1 ? 0 : idx + 1));
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
if (key.return) {
|
|
2025
|
+
const resolvedModel = resolveModelId(query);
|
|
2026
|
+
if (!resolvedModel) {
|
|
2027
|
+
setError("No matching models found. Refine your search and try again.");
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
setSelectedModelId(resolvedModel);
|
|
2031
|
+
}
|
|
2032
|
+
},
|
|
2033
|
+
{ isActive: isRawModeSupported }
|
|
2034
|
+
);
|
|
1374
2035
|
|
|
1375
2036
|
if (error) {
|
|
1376
2037
|
return (
|
|
@@ -2837,7 +3498,9 @@ type HelpContext =
|
|
|
2837
3498
|
| "eval"
|
|
2838
3499
|
| "benchmark"
|
|
2839
3500
|
| "inference"
|
|
2840
|
-
| "agent"
|
|
3501
|
+
| "agent"
|
|
3502
|
+
| "model-endpoints"
|
|
3503
|
+
| "model-artifacts";
|
|
2841
3504
|
|
|
2842
3505
|
interface HelpProps {
|
|
2843
3506
|
context?: HelpContext;
|
|
@@ -3188,6 +3851,8 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
3188
3851
|
<Text> --mode {"<research>"} --mode research uses Pro workflow</Text>
|
|
3189
3852
|
<Text> </Text>
|
|
3190
3853
|
<Text> Omit --mode to use the default standard interactive mode.</Text>
|
|
3854
|
+
<Text> agent sessions List and resume previous sessions</Text>
|
|
3855
|
+
<Text> agent resume {"[conversation-id]"} List sessions, then resume a selected conversation</Text>
|
|
3191
3856
|
<Text> --conversation-id {"<id>"} Continue an existing conversation</Text>
|
|
3192
3857
|
<Text> --filters {"<json>"} Reserved for future query filters</Text>
|
|
3193
3858
|
<Text> --history {"<json>"} Optional message history JSON</Text>
|
|
@@ -3196,6 +3861,8 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
3196
3861
|
<Text dimColor>Example: pioneer agent --mode research</Text>
|
|
3197
3862
|
<Text dimColor>Then type: Analyze failures and propose retraining plan</Text>
|
|
3198
3863
|
<Text dimColor>Then type: Draft a short status summary</Text>
|
|
3864
|
+
<Text dimColor>Example: pioneer agent resume</Text>
|
|
3865
|
+
<Text dimColor>Example: pioneer agent resume b042f7a1-0e7e-4f78-96df-a1cc2d4afcdf</Text>
|
|
3199
3866
|
</Box>
|
|
3200
3867
|
);
|
|
3201
3868
|
}
|
|
@@ -3269,6 +3936,45 @@ const App: React.FC<AppProps> = ({ command, flags, parseErrors }) => {
|
|
|
3269
3936
|
}
|
|
3270
3937
|
|
|
3271
3938
|
if (hasParseErrors && !isModelCreateMissingModel) {
|
|
3939
|
+
const missingValueHints: Record<string, string> = {
|
|
3940
|
+
"--model": "<base-model-id>",
|
|
3941
|
+
"--mode":
|
|
3942
|
+
group === "agent" || group === "agents"
|
|
3943
|
+
? "<research>"
|
|
3944
|
+
: "<value>",
|
|
3945
|
+
"--conversation-id": "<session-id>",
|
|
3946
|
+
"--conversation": "<session-id>",
|
|
3947
|
+
"--history": "<json>",
|
|
3948
|
+
"--filters": "<json>",
|
|
3949
|
+
"--format": "<format>",
|
|
3950
|
+
"--text": "<text>",
|
|
3951
|
+
"--prompt": "<text>",
|
|
3952
|
+
"--name": "<name>",
|
|
3953
|
+
"--repo": "<url>",
|
|
3954
|
+
"--icon": "<icon>",
|
|
3955
|
+
"--description": "<text>",
|
|
3956
|
+
"--api-key": "<key>",
|
|
3957
|
+
"--api-url": "<url>",
|
|
3958
|
+
"--message": "<text>",
|
|
3959
|
+
"--inputs": "<json>",
|
|
3960
|
+
"--labels": "<json-array>",
|
|
3961
|
+
"--label-column": "<column>",
|
|
3962
|
+
"--text-column": "<column>",
|
|
3963
|
+
"--dataset-ids": "<comma-separated-ids>",
|
|
3964
|
+
"--output": "<path>",
|
|
3965
|
+
"--format-results": "<true|false>",
|
|
3966
|
+
"--include-confidence": "<true|false>",
|
|
3967
|
+
"--include-spans": "<true|false>",
|
|
3968
|
+
"--reasoning-trace": "<true|false>",
|
|
3969
|
+
"--reasoning-effort": "<low|medium|high>",
|
|
3970
|
+
};
|
|
3971
|
+
const getValueHint = (flag: string) => {
|
|
3972
|
+
if (flag === "--mode" && group === "agent") {
|
|
3973
|
+
return "<research> (default is standard when omitted)";
|
|
3974
|
+
}
|
|
3975
|
+
return missingValueHints[flag] ?? "<value>";
|
|
3976
|
+
};
|
|
3977
|
+
|
|
3272
3978
|
if (isModelEndpointsDeployMissingJob) {
|
|
3273
3979
|
const errorMessage = rest[1]
|
|
3274
3980
|
? `Training job ID required: model endpoints deploy ${rest[1]} --job <training-job-id>`
|
|
@@ -3296,7 +4002,7 @@ const App: React.FC<AppProps> = ({ command, flags, parseErrors }) => {
|
|
|
3296
4002
|
<ErrorMessage error="One or more flags are missing values. Please provide values for: " />
|
|
3297
4003
|
{parseErrors.map((flag) => (
|
|
3298
4004
|
<Text dimColor key={flag}>
|
|
3299
|
-
|
|
4005
|
+
- {flag} {getValueHint(flag)}
|
|
3300
4006
|
</Text>
|
|
3301
4007
|
))}
|
|
3302
4008
|
</Box>
|
|
@@ -4951,13 +5657,15 @@ const App: React.FC<AppProps> = ({ command, flags, parseErrors }) => {
|
|
|
4951
5657
|
return <Help context="agent" />;
|
|
4952
5658
|
}
|
|
4953
5659
|
|
|
4954
|
-
if (action && !action.startsWith("-")) {
|
|
5660
|
+
if (action && action !== "resume" && action !== "sessions" && !action.startsWith("-")) {
|
|
4955
5661
|
return (
|
|
4956
5662
|
<ErrorMessage
|
|
4957
5663
|
error={
|
|
4958
5664
|
'Invalid agent command syntax. Use one of:\n' +
|
|
4959
5665
|
"pioneer agent\n" +
|
|
4960
|
-
"pioneer agent --mode research"
|
|
5666
|
+
"pioneer agent --mode research\n" +
|
|
5667
|
+
"pioneer agent sessions\n" +
|
|
5668
|
+
"pioneer agent resume [conversation-id]"
|
|
4961
5669
|
}
|
|
4962
5670
|
/>
|
|
4963
5671
|
);
|
|
@@ -4992,11 +5700,34 @@ const App: React.FC<AppProps> = ({ command, flags, parseErrors }) => {
|
|
|
4992
5700
|
return <ErrorMessage error='--filters is not supported for /auto-agent/clarify. Omit this flag for now.' />;
|
|
4993
5701
|
}
|
|
4994
5702
|
|
|
5703
|
+
if (action === "resume" || action === "sessions") {
|
|
5704
|
+
if (!isRawModeSupported) {
|
|
5705
|
+
return (
|
|
5706
|
+
<ErrorMessage
|
|
5707
|
+
error="Interactive input is not supported in this terminal.\nUse interactive mode for this environment: agent --help"
|
|
5708
|
+
/>
|
|
5709
|
+
);
|
|
5710
|
+
}
|
|
5711
|
+
if (rest[0] || flags["conversation-id"]) {
|
|
5712
|
+
const resumeId = rest[0] ?? flags["conversation-id"];
|
|
5713
|
+
return (
|
|
5714
|
+
<AutoAgentInteractiveSession
|
|
5715
|
+
conversationId={resumeId}
|
|
5716
|
+
history={history}
|
|
5717
|
+
mode={mode}
|
|
5718
|
+
allowSessionCreation={false}
|
|
5719
|
+
/>
|
|
5720
|
+
);
|
|
5721
|
+
}
|
|
5722
|
+
return <AgentResumeCommand mode={mode} />;
|
|
5723
|
+
}
|
|
5724
|
+
|
|
4995
5725
|
return (
|
|
4996
5726
|
<AgentInteractivePrompt
|
|
4997
5727
|
conversationId={flags["conversation-id"]}
|
|
4998
5728
|
history={history}
|
|
4999
5729
|
mode={mode}
|
|
5730
|
+
allowSessionCreation={true}
|
|
5000
5731
|
/>
|
|
5001
5732
|
);
|
|
5002
5733
|
}
|
|
@@ -5027,4 +5758,6 @@ async function main() {
|
|
|
5027
5758
|
await render(<App command={command} flags={flags} parseErrors={parseErrors} />).waitUntilExit();
|
|
5028
5759
|
}
|
|
5029
5760
|
|
|
5030
|
-
|
|
5761
|
+
if (process.env.PIONEER_SKIP_AUTORUN !== "true") {
|
|
5762
|
+
main();
|
|
5763
|
+
}
|