skydive-cli 0.1.0-beta.291 → 0.1.0-beta.311
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/dist/js/bin.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import zlib from "node:zlib";
|
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
|
|
18
18
|
//#region package.json
|
|
19
|
-
var version$1 = "0.1.0-beta.
|
|
19
|
+
var version$1 = "0.1.0-beta.311";
|
|
20
20
|
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region src/types.ts
|
|
@@ -1023,7 +1023,12 @@ function createRestClient({ appUrl, sessionToken }) {
|
|
|
1023
1023
|
}, sendResultSchema),
|
|
1024
1024
|
activeRun: async ({ conversationId }) => {
|
|
1025
1025
|
const { run } = await get(`/api/v1/chat/active-run?${new URLSearchParams({ conversationId }).toString()}`, activeRunResponseSchema);
|
|
1026
|
-
return
|
|
1026
|
+
if (!run) return null;
|
|
1027
|
+
return {
|
|
1028
|
+
runId: run.runId,
|
|
1029
|
+
agentId: run.agentId ?? "",
|
|
1030
|
+
agentName: run.agentName ?? ""
|
|
1031
|
+
};
|
|
1027
1032
|
},
|
|
1028
1033
|
cancelRun: async ({ runId }) => {
|
|
1029
1034
|
await post(`/api/v1/chat/runs/${encodeURIComponent(runId)}/cancel`, {}, z.object({ ok: z.boolean() }));
|
|
@@ -1187,7 +1192,11 @@ const uiMessagePartSchema = z.union([
|
|
|
1187
1192
|
const uiMessageSchema = z.object({
|
|
1188
1193
|
id: z.string(),
|
|
1189
1194
|
role: z.string(),
|
|
1190
|
-
parts: z.array(uiMessagePartSchema)
|
|
1195
|
+
parts: z.array(uiMessagePartSchema),
|
|
1196
|
+
metadata: z.object({ custom: z.object({
|
|
1197
|
+
agentId: z.string().nullish(),
|
|
1198
|
+
agentName: z.string().nullish()
|
|
1199
|
+
}).passthrough().optional() }).passthrough().optional()
|
|
1191
1200
|
});
|
|
1192
1201
|
const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nullable() });
|
|
1193
1202
|
const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
|
|
@@ -1196,6 +1205,8 @@ const sendResultSchema = z.object({
|
|
|
1196
1205
|
messageId: z.string().uuid().nullish(),
|
|
1197
1206
|
conversationId: z.string().uuid(),
|
|
1198
1207
|
isNewConversation: z.boolean(),
|
|
1208
|
+
agentId: z.string().nullish(),
|
|
1209
|
+
agentName: z.string().nullish(),
|
|
1199
1210
|
steered: z.boolean().optional(),
|
|
1200
1211
|
directive: z.object({ id: z.string() }).passthrough().optional()
|
|
1201
1212
|
});
|
|
@@ -1210,7 +1221,11 @@ const finalizeResponseSchema = z.object({
|
|
|
1210
1221
|
mediaType: z.string(),
|
|
1211
1222
|
sizeBytes: z.number().nullable().optional()
|
|
1212
1223
|
});
|
|
1213
|
-
const activeRunResponseSchema = z.object({ run: z.object({
|
|
1224
|
+
const activeRunResponseSchema = z.object({ run: z.object({
|
|
1225
|
+
runId: z.string(),
|
|
1226
|
+
agentId: z.string().nullish(),
|
|
1227
|
+
agentName: z.string().nullish()
|
|
1228
|
+
}).nullable() });
|
|
1214
1229
|
const oauthConnectResponseSchema = z.object({ connectLink: z.string() }).passthrough();
|
|
1215
1230
|
const externalOauthConnectResponseSchema = z.object({ authorizationUrl: z.string().optional() }).passthrough();
|
|
1216
1231
|
const runStreamEventSchema = z.union([z.object({
|
|
@@ -2592,7 +2607,7 @@ const chatCommand = {
|
|
|
2592
2607
|
printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
|
|
2593
2608
|
process.exit(1);
|
|
2594
2609
|
}
|
|
2595
|
-
const { runChat } = await import("./boot-
|
|
2610
|
+
const { runChat } = await import("./boot-DEX6CwVM.mjs");
|
|
2596
2611
|
await runChat({
|
|
2597
2612
|
appUrl,
|
|
2598
2613
|
sessionToken: session.value.sessionToken,
|
|
@@ -3375,7 +3390,7 @@ const switchCommand = {
|
|
|
3375
3390
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
3376
3391
|
process.exit(1);
|
|
3377
3392
|
}
|
|
3378
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
3393
|
+
const { runWorkspacePicker } = await import("./boot-DEX6CwVM.mjs");
|
|
3379
3394
|
await runWorkspacePicker(session);
|
|
3380
3395
|
return;
|
|
3381
3396
|
}
|
|
@@ -1096,7 +1096,8 @@ function AgentCreateScreen() {
|
|
|
1096
1096
|
agent,
|
|
1097
1097
|
conversation: {
|
|
1098
1098
|
kind: "new",
|
|
1099
|
-
agentId: agent.id
|
|
1099
|
+
agentId: agent.id,
|
|
1100
|
+
nonce: crypto.randomUUID()
|
|
1100
1101
|
}
|
|
1101
1102
|
});
|
|
1102
1103
|
} catch (err) {
|
|
@@ -1406,7 +1407,8 @@ function ConversationList({ agent, conversations, complete, onPick }) {
|
|
|
1406
1407
|
agent,
|
|
1407
1408
|
conversation: {
|
|
1408
1409
|
kind: "new",
|
|
1409
|
-
agentId: agent.id
|
|
1410
|
+
agentId: agent.id,
|
|
1411
|
+
nonce: crypto.randomUUID()
|
|
1410
1412
|
}
|
|
1411
1413
|
});
|
|
1412
1414
|
else onPick({
|
|
@@ -2905,6 +2907,19 @@ function RenderItem({ item }) {
|
|
|
2905
2907
|
barColor: null,
|
|
2906
2908
|
children: /* @__PURE__ */ jsx(CardItem, { item })
|
|
2907
2909
|
});
|
|
2910
|
+
case "info": return /* @__PURE__ */ jsx(Row, {
|
|
2911
|
+
barColor: null,
|
|
2912
|
+
children: /* @__PURE__ */ jsxs("box", {
|
|
2913
|
+
style: { flexDirection: "column" },
|
|
2914
|
+
children: [/* @__PURE__ */ jsx("text", {
|
|
2915
|
+
fg: theme.muted,
|
|
2916
|
+
children: /* @__PURE__ */ jsx("b", { children: item.title })
|
|
2917
|
+
}), /* @__PURE__ */ jsx("text", {
|
|
2918
|
+
fg: theme.muted,
|
|
2919
|
+
children: item.text
|
|
2920
|
+
})]
|
|
2921
|
+
})
|
|
2922
|
+
});
|
|
2908
2923
|
case "recap": return /* @__PURE__ */ jsx(Row, {
|
|
2909
2924
|
barColor: null,
|
|
2910
2925
|
children: /* @__PURE__ */ jsxs("text", {
|
|
@@ -3027,16 +3042,23 @@ function str(chunk, key) {
|
|
|
3027
3042
|
const value = chunk[key];
|
|
3028
3043
|
return typeof value === "string" ? value : null;
|
|
3029
3044
|
}
|
|
3030
|
-
|
|
3045
|
+
/**
|
|
3046
|
+
* `agentName` is the display name of the agent producing this run's stream.
|
|
3047
|
+
* It's stamped onto each assistant-origin item so a multi-agent conversation
|
|
3048
|
+
* can attribute live turns; pass null in a solo thread to leave items
|
|
3049
|
+
* un-attributed (the chat screen only renders the name when the thread has
|
|
3050
|
+
* more than one agent).
|
|
3051
|
+
*/
|
|
3052
|
+
function applyChunk(items, chunk, agentName = null) {
|
|
3031
3053
|
const type = str(chunk, "type");
|
|
3032
3054
|
if (!type) return [...items];
|
|
3033
3055
|
switch (type) {
|
|
3034
|
-
case "text-start": return appendAssistantTextStart(items, str(chunk, "id") ?? cryptoId());
|
|
3056
|
+
case "text-start": return appendAssistantTextStart(items, str(chunk, "id") ?? cryptoId(), agentName);
|
|
3035
3057
|
case "text-delta": {
|
|
3036
3058
|
const id = str(chunk, "id");
|
|
3037
3059
|
const delta = str(chunk, "delta") ?? str(chunk, "text") ?? "";
|
|
3038
3060
|
if (!id || !delta) return [...items];
|
|
3039
|
-
return appendAssistantTextDelta(items, id, delta);
|
|
3061
|
+
return appendAssistantTextDelta(items, id, delta, agentName);
|
|
3040
3062
|
}
|
|
3041
3063
|
case "text-end": {
|
|
3042
3064
|
const id = str(chunk, "id");
|
|
@@ -3049,14 +3071,15 @@ function applyChunk(items, chunk) {
|
|
|
3049
3071
|
kind: "reasoning",
|
|
3050
3072
|
id: bubbleId,
|
|
3051
3073
|
text: "",
|
|
3052
|
-
done: false
|
|
3074
|
+
done: false,
|
|
3075
|
+
agentName
|
|
3053
3076
|
}];
|
|
3054
3077
|
}
|
|
3055
3078
|
case "reasoning-delta": {
|
|
3056
3079
|
const id = str(chunk, "id");
|
|
3057
3080
|
const delta = str(chunk, "delta") ?? str(chunk, "text") ?? "";
|
|
3058
3081
|
if (!id || !delta) return [...items];
|
|
3059
|
-
return appendReasoningDelta(items, id, delta);
|
|
3082
|
+
return appendReasoningDelta(items, id, delta, agentName);
|
|
3060
3083
|
}
|
|
3061
3084
|
case "reasoning-end": {
|
|
3062
3085
|
const id = str(chunk, "id");
|
|
@@ -3079,7 +3102,8 @@ function applyChunk(items, chunk) {
|
|
|
3079
3102
|
inputText: "",
|
|
3080
3103
|
input: null,
|
|
3081
3104
|
output: null,
|
|
3082
|
-
state: "streaming"
|
|
3105
|
+
state: "streaming",
|
|
3106
|
+
agentName
|
|
3083
3107
|
}];
|
|
3084
3108
|
}
|
|
3085
3109
|
case "tool-input-delta": {
|
|
@@ -3108,7 +3132,8 @@ function applyChunk(items, chunk) {
|
|
|
3108
3132
|
inputText: "",
|
|
3109
3133
|
input,
|
|
3110
3134
|
output: null,
|
|
3111
|
-
state: "input-ready"
|
|
3135
|
+
state: "input-ready",
|
|
3136
|
+
agentName
|
|
3112
3137
|
}];
|
|
3113
3138
|
}
|
|
3114
3139
|
case "tool-output-available": {
|
|
@@ -3165,7 +3190,8 @@ function applyChunk(items, chunk) {
|
|
|
3165
3190
|
id,
|
|
3166
3191
|
card,
|
|
3167
3192
|
status: "idle",
|
|
3168
|
-
detail: null
|
|
3193
|
+
detail: null,
|
|
3194
|
+
agentName
|
|
3169
3195
|
};
|
|
3170
3196
|
if (items.some((m) => m.kind === "card" && m.id === id)) return items.map((m) => m.kind === "card" && m.id === id ? next : m);
|
|
3171
3197
|
return [...items, next];
|
|
@@ -3206,16 +3232,17 @@ function openTextIndex(items, id, kind) {
|
|
|
3206
3232
|
}
|
|
3207
3233
|
return -1;
|
|
3208
3234
|
}
|
|
3209
|
-
function appendAssistantTextStart(items, id) {
|
|
3235
|
+
function appendAssistantTextStart(items, id, agentName) {
|
|
3210
3236
|
const bubbleId = freshTextId(items, id, "assistant-text");
|
|
3211
3237
|
return [...items, {
|
|
3212
3238
|
kind: "assistant-text",
|
|
3213
3239
|
id: bubbleId,
|
|
3214
3240
|
text: "",
|
|
3215
|
-
done: false
|
|
3241
|
+
done: false,
|
|
3242
|
+
agentName
|
|
3216
3243
|
}];
|
|
3217
3244
|
}
|
|
3218
|
-
function appendAssistantTextDelta(items, id, delta) {
|
|
3245
|
+
function appendAssistantTextDelta(items, id, delta, agentName) {
|
|
3219
3246
|
const idx = openTextIndex(items, id, "assistant-text");
|
|
3220
3247
|
if (idx === -1) {
|
|
3221
3248
|
const bubbleId = freshTextId(items, id, "assistant-text");
|
|
@@ -3223,7 +3250,8 @@ function appendAssistantTextDelta(items, id, delta) {
|
|
|
3223
3250
|
kind: "assistant-text",
|
|
3224
3251
|
id: bubbleId,
|
|
3225
3252
|
text: delta,
|
|
3226
|
-
done: false
|
|
3253
|
+
done: false,
|
|
3254
|
+
agentName
|
|
3227
3255
|
}];
|
|
3228
3256
|
}
|
|
3229
3257
|
return items.map((m, i) => i === idx && m.kind === "assistant-text" ? {
|
|
@@ -3231,7 +3259,7 @@ function appendAssistantTextDelta(items, id, delta) {
|
|
|
3231
3259
|
text: m.text + delta
|
|
3232
3260
|
} : m);
|
|
3233
3261
|
}
|
|
3234
|
-
function appendReasoningDelta(items, id, delta) {
|
|
3262
|
+
function appendReasoningDelta(items, id, delta, agentName) {
|
|
3235
3263
|
const idx = openTextIndex(items, id, "reasoning");
|
|
3236
3264
|
if (idx === -1) {
|
|
3237
3265
|
const bubbleId = freshTextId(items, id, "reasoning");
|
|
@@ -3239,7 +3267,8 @@ function appendReasoningDelta(items, id, delta) {
|
|
|
3239
3267
|
kind: "reasoning",
|
|
3240
3268
|
id: bubbleId,
|
|
3241
3269
|
text: delta,
|
|
3242
|
-
done: false
|
|
3270
|
+
done: false,
|
|
3271
|
+
agentName
|
|
3243
3272
|
}];
|
|
3244
3273
|
}
|
|
3245
3274
|
return items.map((m, i) => i === idx && m.kind === "reasoning" ? {
|
|
@@ -3269,6 +3298,59 @@ function cryptoId() {
|
|
|
3269
3298
|
return crypto.randomUUID();
|
|
3270
3299
|
}
|
|
3271
3300
|
|
|
3301
|
+
//#endregion
|
|
3302
|
+
//#region src/chat/tui/chat/attribution.ts
|
|
3303
|
+
/**
|
|
3304
|
+
* The agent that authored an item, or null for items with no agent (the user's
|
|
3305
|
+
* own turns, errors, the recap).
|
|
3306
|
+
*/
|
|
3307
|
+
function itemAgentName(m) {
|
|
3308
|
+
return "agentName" in m ? m.agentName ?? null : null;
|
|
3309
|
+
}
|
|
3310
|
+
/**
|
|
3311
|
+
* Items that end an agent's turn: the human speaking or acting. The next
|
|
3312
|
+
* assistant item after one of these starts a new turn and is re-headed with its
|
|
3313
|
+
* author, even when the same agent replies again — in a multi-agent thread the
|
|
3314
|
+
* reader can't be asked to scan back past their own message to work out who is
|
|
3315
|
+
* answering.
|
|
3316
|
+
*/
|
|
3317
|
+
function isTurnSeparator(m) {
|
|
3318
|
+
return m.kind === "user" || m.kind === "pending-steer" || m.kind === "shell";
|
|
3319
|
+
}
|
|
3320
|
+
/**
|
|
3321
|
+
* Every agent that has spoken in the transcript, plus the agent on screen.
|
|
3322
|
+
* Attribution renders only when this holds more than one name, mirroring web's
|
|
3323
|
+
* group-chat gate so a solo thread carries no attribution chrome at all.
|
|
3324
|
+
*/
|
|
3325
|
+
function conversationAgentNames(items, onScreenAgentName) {
|
|
3326
|
+
const names = new Set([onScreenAgentName]);
|
|
3327
|
+
for (const item of items) {
|
|
3328
|
+
const name = itemAgentName(item);
|
|
3329
|
+
if (name) names.add(name);
|
|
3330
|
+
}
|
|
3331
|
+
return names;
|
|
3332
|
+
}
|
|
3333
|
+
/**
|
|
3334
|
+
* The agent-name label to render above `items[i]`, or null for no label. Every
|
|
3335
|
+
* assistant turn is headed with its author's name. Within one turn the items
|
|
3336
|
+
* (text, reasoning, tool calls) share the single header, so a turn isn't
|
|
3337
|
+
* re-labeled on each tool row; `error` and `recap` items are transparent and
|
|
3338
|
+
* don't break that grouping.
|
|
3339
|
+
*/
|
|
3340
|
+
function agentLabelFor(items, i) {
|
|
3341
|
+
const current = items[i];
|
|
3342
|
+
const name = current ? itemAgentName(current) : null;
|
|
3343
|
+
if (!name) return null;
|
|
3344
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
3345
|
+
const prevItem = items[j];
|
|
3346
|
+
if (!prevItem) continue;
|
|
3347
|
+
if (isTurnSeparator(prevItem)) return name;
|
|
3348
|
+
const prev = itemAgentName(prevItem);
|
|
3349
|
+
if (prev !== null) return prev === name ? null : name;
|
|
3350
|
+
}
|
|
3351
|
+
return name;
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3272
3354
|
//#endregion
|
|
3273
3355
|
//#region src/chat/tui/chat/history.ts
|
|
3274
3356
|
function isDynamicTool(part) {
|
|
@@ -3297,6 +3379,7 @@ function uiMessagesToItems(messages) {
|
|
|
3297
3379
|
continue;
|
|
3298
3380
|
}
|
|
3299
3381
|
if (m.role === "assistant") {
|
|
3382
|
+
const agentName = m.metadata?.custom?.agentName ?? null;
|
|
3300
3383
|
let textBuf = "";
|
|
3301
3384
|
let textIdx = 0;
|
|
3302
3385
|
for (const part of m.parts) {
|
|
@@ -3309,7 +3392,8 @@ function uiMessagesToItems(messages) {
|
|
|
3309
3392
|
kind: "assistant-text",
|
|
3310
3393
|
id: `${m.id}#t${textIdx++}`,
|
|
3311
3394
|
text: textBuf,
|
|
3312
|
-
done: true
|
|
3395
|
+
done: true,
|
|
3396
|
+
agentName
|
|
3313
3397
|
});
|
|
3314
3398
|
textBuf = "";
|
|
3315
3399
|
}
|
|
@@ -3318,7 +3402,8 @@ function uiMessagesToItems(messages) {
|
|
|
3318
3402
|
kind: "reasoning",
|
|
3319
3403
|
id: `${m.id}#r${textIdx++}`,
|
|
3320
3404
|
text: "text" in part && typeof part.text === "string" ? part.text : "",
|
|
3321
|
-
done: true
|
|
3405
|
+
done: true,
|
|
3406
|
+
agentName
|
|
3322
3407
|
});
|
|
3323
3408
|
continue;
|
|
3324
3409
|
}
|
|
@@ -3332,7 +3417,8 @@ function uiMessagesToItems(messages) {
|
|
|
3332
3417
|
id: toolCallId ?? `${m.id}#c${textIdx++}`,
|
|
3333
3418
|
card,
|
|
3334
3419
|
status: "idle",
|
|
3335
|
-
detail: null
|
|
3420
|
+
detail: null,
|
|
3421
|
+
agentName
|
|
3336
3422
|
});
|
|
3337
3423
|
}
|
|
3338
3424
|
continue;
|
|
@@ -3352,7 +3438,8 @@ function uiMessagesToItems(messages) {
|
|
|
3352
3438
|
kind: "ok",
|
|
3353
3439
|
value: part.output
|
|
3354
3440
|
} : null,
|
|
3355
|
-
state
|
|
3441
|
+
state,
|
|
3442
|
+
agentName
|
|
3356
3443
|
});
|
|
3357
3444
|
}
|
|
3358
3445
|
}
|
|
@@ -3360,7 +3447,8 @@ function uiMessagesToItems(messages) {
|
|
|
3360
3447
|
kind: "assistant-text",
|
|
3361
3448
|
id: `${m.id}#t${textIdx++}`,
|
|
3362
3449
|
text: textBuf,
|
|
3363
|
-
done: true
|
|
3450
|
+
done: true,
|
|
3451
|
+
agentName
|
|
3364
3452
|
});
|
|
3365
3453
|
}
|
|
3366
3454
|
}
|
|
@@ -3381,7 +3469,7 @@ function mapToolState(raw) {
|
|
|
3381
3469
|
function formatChatFooterStatus({ runKind, ctrlCArmed, isShared, hasPendingSteer, hasActionableCard }) {
|
|
3382
3470
|
const connectHint = hasActionableCard ? "ctrl+r connect · " : "";
|
|
3383
3471
|
switch (runKind) {
|
|
3384
|
-
case "idle": return ctrlCArmed ? "press ctrl+c again to quit" : `${connectHint}↵ send · esc back · ctrl+c quit · ctrl+t ${isShared ? "revoke access" : "local access"} · ? keybinds`;
|
|
3472
|
+
case "idle": return ctrlCArmed ? "press ctrl+c again to quit" : `${connectHint}↵ send · / commands · esc back · ctrl+c quit · ctrl+t ${isShared ? "revoke access" : "local access"} · ? keybinds`;
|
|
3385
3473
|
case "sending": return "sending…";
|
|
3386
3474
|
case "streaming": return hasPendingSteer ? `${connectHint}↵ steer · esc cancel steer · ctrl+c cancel run` : `${connectHint}↵ steer · ctrl+c cancel`;
|
|
3387
3475
|
}
|
|
@@ -3391,6 +3479,31 @@ function formatModelHint(model) {
|
|
|
3391
3479
|
return `ctrl+p · ${model ?? "default"}`;
|
|
3392
3480
|
}
|
|
3393
3481
|
|
|
3482
|
+
//#endregion
|
|
3483
|
+
//#region src/chat/tui/chat/status-info.ts
|
|
3484
|
+
/**
|
|
3485
|
+
* Body text for the `/status` transcript block: the session facts the status
|
|
3486
|
+
* bar and footer can't fit, one aligned `label value` row each. Pure so the
|
|
3487
|
+
* layout is testable without a renderer.
|
|
3488
|
+
*/
|
|
3489
|
+
function formatStatusInfo({ appUrl, workspaceName, agentName, conversationId, model, share }) {
|
|
3490
|
+
const rows = [
|
|
3491
|
+
["app", appUrl ?? "unknown"],
|
|
3492
|
+
["workspace", workspaceName ?? "unknown"],
|
|
3493
|
+
["agent", agentName],
|
|
3494
|
+
["conversation", conversationId ?? "new — created when you send the first message"],
|
|
3495
|
+
["model", model ?? "default"],
|
|
3496
|
+
["local access", formatShare(share)]
|
|
3497
|
+
];
|
|
3498
|
+
const width = Math.max(...rows.map(([label]) => label.length));
|
|
3499
|
+
return rows.map(([label, value]) => `${label.padEnd(width)} ${value}`).join("\n");
|
|
3500
|
+
}
|
|
3501
|
+
function formatShare(share) {
|
|
3502
|
+
if (share.status === "off") return "off — ctrl+t to share this machine";
|
|
3503
|
+
if (share.status !== "connected") return share.status;
|
|
3504
|
+
return share.grantedToAgent ? "shared — this agent can run commands here" : "shared — this agent is not granted";
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3394
3507
|
//#endregion
|
|
3395
3508
|
//#region src/chat/notification-preview.ts
|
|
3396
3509
|
const MAX_NOTIFICATION_PREVIEW_CHARS = 240;
|
|
@@ -3677,12 +3790,37 @@ const keybindGroups = [
|
|
|
3677
3790
|
keys: "pgup / pgdn",
|
|
3678
3791
|
action: "scroll transcript"
|
|
3679
3792
|
},
|
|
3793
|
+
{
|
|
3794
|
+
keys: "/",
|
|
3795
|
+
action: "open the command menu (start of message)"
|
|
3796
|
+
},
|
|
3680
3797
|
{
|
|
3681
3798
|
keys: "?",
|
|
3682
3799
|
action: "open this help (composer empty)"
|
|
3683
3800
|
}
|
|
3684
3801
|
]
|
|
3685
3802
|
},
|
|
3803
|
+
{
|
|
3804
|
+
title: "command menu (type / to open)",
|
|
3805
|
+
binds: [
|
|
3806
|
+
{
|
|
3807
|
+
keys: "↑ / ↓",
|
|
3808
|
+
action: "move selection"
|
|
3809
|
+
},
|
|
3810
|
+
{
|
|
3811
|
+
keys: "tab",
|
|
3812
|
+
action: "complete the command"
|
|
3813
|
+
},
|
|
3814
|
+
{
|
|
3815
|
+
keys: "↵",
|
|
3816
|
+
action: "run (or complete) the selected command"
|
|
3817
|
+
},
|
|
3818
|
+
{
|
|
3819
|
+
keys: "esc",
|
|
3820
|
+
action: "dismiss the menu"
|
|
3821
|
+
}
|
|
3822
|
+
]
|
|
3823
|
+
},
|
|
3686
3824
|
{
|
|
3687
3825
|
title: "agent picker",
|
|
3688
3826
|
binds: [
|
|
@@ -3933,6 +4071,182 @@ function HelpModal({ onClose }) {
|
|
|
3933
4071
|
});
|
|
3934
4072
|
}
|
|
3935
4073
|
|
|
4074
|
+
//#endregion
|
|
4075
|
+
//#region src/chat/tui/autocomplete/completion-menu.tsx
|
|
4076
|
+
const MAX_COMPLETION_ROWS = 6;
|
|
4077
|
+
/**
|
|
4078
|
+
* Rows the menu occupies in the chat screen's fixed layout budget: the
|
|
4079
|
+
* windowed items (min 1 for the empty row) plus the border. The chat screen
|
|
4080
|
+
* subtracts this from the transcript height, mirroring the composer and
|
|
4081
|
+
* staged-attachment panels.
|
|
4082
|
+
*/
|
|
4083
|
+
function completionMenuHeight(itemCount) {
|
|
4084
|
+
return Math.min(Math.max(itemCount, 1), MAX_COMPLETION_ROWS) + 2;
|
|
4085
|
+
}
|
|
4086
|
+
/**
|
|
4087
|
+
* The inline autocomplete dropdown, rendered directly above the composer
|
|
4088
|
+
* (never as a screen overlay — the composer must stay mounted and focused).
|
|
4089
|
+
* Pure presentation: the chat screen owns the open/highlight state and the
|
|
4090
|
+
* keyboard handling.
|
|
4091
|
+
*/
|
|
4092
|
+
function CompletionMenu({ items, highlight }) {
|
|
4093
|
+
const start = windowStart(highlight, items.length, MAX_COMPLETION_ROWS);
|
|
4094
|
+
const windowed = items.slice(start, start + MAX_COMPLETION_ROWS);
|
|
4095
|
+
return /* @__PURE__ */ jsx("box", {
|
|
4096
|
+
style: {
|
|
4097
|
+
border: true,
|
|
4098
|
+
borderColor: theme.faint,
|
|
4099
|
+
paddingLeft: 1,
|
|
4100
|
+
paddingRight: 1,
|
|
4101
|
+
flexShrink: 0,
|
|
4102
|
+
marginTop: 1,
|
|
4103
|
+
flexDirection: "column"
|
|
4104
|
+
},
|
|
4105
|
+
children: windowed.length === 0 ? /* @__PURE__ */ jsx("text", {
|
|
4106
|
+
fg: theme.muted,
|
|
4107
|
+
children: "no matching command — ↵ sends as a message"
|
|
4108
|
+
}) : windowed.map((item, i) => {
|
|
4109
|
+
const selected = start + i === highlight;
|
|
4110
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
4111
|
+
fg: selected ? theme.accent : theme.fg,
|
|
4112
|
+
children: [
|
|
4113
|
+
selected ? "› " : " ",
|
|
4114
|
+
item.label,
|
|
4115
|
+
item.hint ? /* @__PURE__ */ jsx("span", {
|
|
4116
|
+
fg: theme.dim,
|
|
4117
|
+
children: ` ${item.hint}`
|
|
4118
|
+
}) : null,
|
|
4119
|
+
/* @__PURE__ */ jsx("span", {
|
|
4120
|
+
fg: theme.muted,
|
|
4121
|
+
children: ` ${item.description}`
|
|
4122
|
+
})
|
|
4123
|
+
]
|
|
4124
|
+
}, item.id);
|
|
4125
|
+
})
|
|
4126
|
+
});
|
|
4127
|
+
}
|
|
4128
|
+
|
|
4129
|
+
//#endregion
|
|
4130
|
+
//#region src/chat/slash-commands.ts
|
|
4131
|
+
const slashCommands = [
|
|
4132
|
+
{
|
|
4133
|
+
name: "sandbox",
|
|
4134
|
+
aliases: ["sbx"],
|
|
4135
|
+
argsHint: "[command]",
|
|
4136
|
+
description: "run in the agent's sandbox (no command: open a terminal)",
|
|
4137
|
+
action: { kind: "sandbox" }
|
|
4138
|
+
},
|
|
4139
|
+
{
|
|
4140
|
+
name: "new",
|
|
4141
|
+
aliases: [],
|
|
4142
|
+
argsHint: null,
|
|
4143
|
+
description: "start a new conversation with this agent",
|
|
4144
|
+
action: { kind: "new-conversation" }
|
|
4145
|
+
},
|
|
4146
|
+
{
|
|
4147
|
+
name: "model",
|
|
4148
|
+
aliases: [],
|
|
4149
|
+
argsHint: null,
|
|
4150
|
+
description: "switch the model for the next run",
|
|
4151
|
+
action: { kind: "model-picker" }
|
|
4152
|
+
},
|
|
4153
|
+
{
|
|
4154
|
+
name: "theme",
|
|
4155
|
+
aliases: [],
|
|
4156
|
+
argsHint: null,
|
|
4157
|
+
description: "switch the color theme",
|
|
4158
|
+
action: { kind: "theme-picker" }
|
|
4159
|
+
},
|
|
4160
|
+
{
|
|
4161
|
+
name: "status",
|
|
4162
|
+
aliases: [],
|
|
4163
|
+
argsHint: null,
|
|
4164
|
+
description: "show connection and session status",
|
|
4165
|
+
action: { kind: "status" }
|
|
4166
|
+
},
|
|
4167
|
+
{
|
|
4168
|
+
name: "share",
|
|
4169
|
+
aliases: [],
|
|
4170
|
+
argsHint: null,
|
|
4171
|
+
description: "local access: grant / revoke this machine",
|
|
4172
|
+
action: { kind: "share" }
|
|
4173
|
+
},
|
|
4174
|
+
{
|
|
4175
|
+
name: "browser",
|
|
4176
|
+
aliases: [],
|
|
4177
|
+
argsHint: null,
|
|
4178
|
+
description: "open this conversation in the browser",
|
|
4179
|
+
action: { kind: "browser" }
|
|
4180
|
+
},
|
|
4181
|
+
{
|
|
4182
|
+
name: "connect",
|
|
4183
|
+
aliases: [],
|
|
4184
|
+
argsHint: null,
|
|
4185
|
+
description: "run the newest connect card (auth cards)",
|
|
4186
|
+
action: { kind: "connect" }
|
|
4187
|
+
},
|
|
4188
|
+
{
|
|
4189
|
+
name: "help",
|
|
4190
|
+
aliases: [],
|
|
4191
|
+
argsHint: null,
|
|
4192
|
+
description: "show keybinds and commands",
|
|
4193
|
+
action: { kind: "help" }
|
|
4194
|
+
},
|
|
4195
|
+
{
|
|
4196
|
+
name: "exit",
|
|
4197
|
+
aliases: ["quit"],
|
|
4198
|
+
argsHint: null,
|
|
4199
|
+
description: "quit the chat",
|
|
4200
|
+
action: { kind: "quit" }
|
|
4201
|
+
}
|
|
4202
|
+
];
|
|
4203
|
+
/**
|
|
4204
|
+
* Match a submitted line against the registry: `/<name>` or `/<name> <rest>`,
|
|
4205
|
+
* case-insensitive on the name. Returns the command plus the trimmed rest,
|
|
4206
|
+
* or null when the line isn't a known slash command.
|
|
4207
|
+
*
|
|
4208
|
+
* An argument-less command only matches as the exact line — `/help me write
|
|
4209
|
+
* a plan` stays an ordinary message rather than swallowing the user's text
|
|
4210
|
+
* into the help modal. Only `sandbox` accepts a rest.
|
|
4211
|
+
*/
|
|
4212
|
+
function matchSlashCommand(line) {
|
|
4213
|
+
if (!line.startsWith("/")) return null;
|
|
4214
|
+
const sp = line.indexOf(" ");
|
|
4215
|
+
const name = (sp === -1 ? line.slice(1) : line.slice(1, sp)).toLowerCase();
|
|
4216
|
+
const command = slashCommands.find((c) => c.name === name || c.aliases.includes(name));
|
|
4217
|
+
if (!command) return null;
|
|
4218
|
+
const rest = sp === -1 ? "" : line.slice(sp + 1).trim();
|
|
4219
|
+
if (command.argsHint === null && rest !== "") return null;
|
|
4220
|
+
return {
|
|
4221
|
+
command,
|
|
4222
|
+
rest
|
|
4223
|
+
};
|
|
4224
|
+
}
|
|
4225
|
+
/**
|
|
4226
|
+
* The in-progress command token the completion menu should open for: the
|
|
4227
|
+
* composer text is a lone `/<partial>` with no whitespace yet (whitespace
|
|
4228
|
+
* means the name is settled and the user is typing arguments — or prose).
|
|
4229
|
+
* Returns the partial name ('' right after `/`), or null when no menu
|
|
4230
|
+
* should show.
|
|
4231
|
+
*/
|
|
4232
|
+
function detectCommandTrigger(text) {
|
|
4233
|
+
if (!text.startsWith("/")) return null;
|
|
4234
|
+
if (/\s/.test(text)) return null;
|
|
4235
|
+
return text.slice(1);
|
|
4236
|
+
}
|
|
4237
|
+
/**
|
|
4238
|
+
* Menu ordering: prefix matches (registry order) ahead of substring matches,
|
|
4239
|
+
* checked against names and aliases. Empty query lists everything.
|
|
4240
|
+
*/
|
|
4241
|
+
function filterSlashCommands(query) {
|
|
4242
|
+
const q = query.trim().toLowerCase();
|
|
4243
|
+
if (!q) return [...slashCommands];
|
|
4244
|
+
const names = (c) => [c.name, ...c.aliases];
|
|
4245
|
+
const prefix = slashCommands.filter((c) => names(c).some((n) => n.startsWith(q)));
|
|
4246
|
+
const substring = slashCommands.filter((c) => !prefix.includes(c) && names(c).some((n) => n.includes(q)));
|
|
4247
|
+
return [...prefix, ...substring];
|
|
4248
|
+
}
|
|
4249
|
+
|
|
3936
4250
|
//#endregion
|
|
3937
4251
|
//#region src/chat/tui/screens/model-picker.tsx
|
|
3938
4252
|
/**
|
|
@@ -4759,28 +5073,35 @@ function routeInput(raw) {
|
|
|
4759
5073
|
command
|
|
4760
5074
|
} : { kind: "noop" };
|
|
4761
5075
|
}
|
|
4762
|
-
const
|
|
4763
|
-
if (
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
5076
|
+
const match = matchSlashCommand(line);
|
|
5077
|
+
if (match) {
|
|
5078
|
+
const { action } = match.command;
|
|
5079
|
+
switch (action.kind) {
|
|
5080
|
+
case "sandbox": return match.rest ? {
|
|
5081
|
+
kind: "sandbox-exec",
|
|
5082
|
+
command: match.rest
|
|
5083
|
+
} : { kind: "sandbox-pty" };
|
|
5084
|
+
case "new-conversation":
|
|
5085
|
+
case "model-picker":
|
|
5086
|
+
case "theme-picker":
|
|
5087
|
+
case "status":
|
|
5088
|
+
case "share":
|
|
5089
|
+
case "browser":
|
|
5090
|
+
case "connect":
|
|
5091
|
+
case "help":
|
|
5092
|
+
case "quit": return { kind: action.kind };
|
|
5093
|
+
default: {
|
|
5094
|
+
const exhaustive = action;
|
|
5095
|
+
throw new Error(`unhandled slash action: ${JSON.stringify(exhaustive)}`);
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
4767
5099
|
if (line.trim().toLowerCase() === "exit") return { kind: "quit" };
|
|
4768
5100
|
return {
|
|
4769
5101
|
kind: "message",
|
|
4770
5102
|
content: raw
|
|
4771
5103
|
};
|
|
4772
5104
|
}
|
|
4773
|
-
/**
|
|
4774
|
-
* If `line` is `/<name>` or `/<name> <rest>` for one of `names`, return the
|
|
4775
|
-
* trimmed rest (possibly ''); otherwise null. Case-insensitive on the name.
|
|
4776
|
-
*/
|
|
4777
|
-
function matchSlash(line, names) {
|
|
4778
|
-
if (!line.startsWith("/")) return null;
|
|
4779
|
-
const sp = line.indexOf(" ");
|
|
4780
|
-
const name = (sp === -1 ? line.slice(1) : line.slice(1, sp)).toLowerCase();
|
|
4781
|
-
if (!names.includes(name)) return null;
|
|
4782
|
-
return sp === -1 ? "" : line.slice(sp + 1).trim();
|
|
4783
|
-
}
|
|
4784
5105
|
/** Max characters of `!` output we forward to the agent as context. */
|
|
4785
5106
|
const SHELL_CONTEXT_OUTPUT_CAP = 16e3;
|
|
4786
5107
|
/**
|
|
@@ -5066,6 +5387,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5066
5387
|
const sessionToken = useStore((s) => s.sessionToken);
|
|
5067
5388
|
const goTo = useStore((s) => s.goTo);
|
|
5068
5389
|
const setChatTitle = useStore((s) => s.setChatTitle);
|
|
5390
|
+
const workspaceName = useStore((s) => s.workspaceName);
|
|
5069
5391
|
const portal = useStore((s) => s.portal);
|
|
5070
5392
|
const portalClient = useStore((s) => s.portalClient);
|
|
5071
5393
|
const setPortalPrompt = useStore((s) => s.setPortalPrompt);
|
|
@@ -5089,6 +5411,12 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5089
5411
|
const uploadsRef = useRef(/* @__PURE__ */ new Map());
|
|
5090
5412
|
const [credPrompt, setCredPrompt] = useState(null);
|
|
5091
5413
|
const credPromptOpen = credPrompt !== null;
|
|
5414
|
+
const [menuDismissed, setMenuDismissed] = useState(false);
|
|
5415
|
+
const [menuHighlight, setMenuHighlight] = useState(0);
|
|
5416
|
+
const commandQuery = grantPrompt || credPrompt ? null : detectCommandTrigger(input);
|
|
5417
|
+
const menuCommands = commandQuery === null ? [] : filterSlashCommands(commandQuery);
|
|
5418
|
+
const menuOpen = commandQuery !== null && !menuDismissed;
|
|
5419
|
+
const menuHighlightClamped = Math.min(menuHighlight, Math.max(0, menuCommands.length - 1));
|
|
5092
5420
|
const runRef = useRef(run);
|
|
5093
5421
|
runRef.current = run;
|
|
5094
5422
|
const itemsRef = useRef(items);
|
|
@@ -5118,7 +5446,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5118
5446
|
const pendingRows = pendingVisible ? attachments.length + 2 : 0;
|
|
5119
5447
|
const credPromptHeight = 4 + (credPrompt && (credPrompt.error || credPrompt.submitting) ? 1 : 0);
|
|
5120
5448
|
const bottomBoxHeight = credPrompt ? credPromptHeight : composerBoxHeight;
|
|
5121
|
-
const
|
|
5449
|
+
const menuRows = menuOpen ? completionMenuHeight(menuCommands.length) : 0;
|
|
5450
|
+
const scrollHeight = Math.max(3, height - 5 - bottomBoxHeight - pendingRows - menuRows);
|
|
5122
5451
|
useEffect(() => {
|
|
5123
5452
|
const composer = composerRef.current;
|
|
5124
5453
|
if (!composer) return;
|
|
@@ -5168,10 +5497,11 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5168
5497
|
cancelled = true;
|
|
5169
5498
|
};
|
|
5170
5499
|
}, [rest, initialConversationId]);
|
|
5171
|
-
const attachToRun = useCallback((runId) => {
|
|
5500
|
+
const attachToRun = useCallback((runId, runAgentName) => {
|
|
5172
5501
|
if (!rest) return;
|
|
5173
5502
|
const abort = new AbortController();
|
|
5174
5503
|
const responsePreview = createResponsePreview();
|
|
5504
|
+
const streamAgentName = runAgentName ?? agent.name;
|
|
5175
5505
|
setRun({
|
|
5176
5506
|
kind: "streaming",
|
|
5177
5507
|
runId,
|
|
@@ -5183,7 +5513,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5183
5513
|
onEvent: (event) => {
|
|
5184
5514
|
if (event.kind === "chunk") {
|
|
5185
5515
|
responsePreview.addChunk(event.chunk);
|
|
5186
|
-
setItems((prev) => applyChunk(prev, event.chunk));
|
|
5516
|
+
setItems((prev) => applyChunk(prev, event.chunk, streamAgentName));
|
|
5187
5517
|
return;
|
|
5188
5518
|
}
|
|
5189
5519
|
const errorText = event.error;
|
|
@@ -5221,6 +5551,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5221
5551
|
agent.name,
|
|
5222
5552
|
agentHost
|
|
5223
5553
|
]);
|
|
5554
|
+
const multiAgent = useMemo(() => conversationAgentNames(items, agent.name), [items, agent.name]).size > 1;
|
|
5224
5555
|
useEffect(() => {
|
|
5225
5556
|
if (!rest || !conversationId) return;
|
|
5226
5557
|
const abort = new AbortController();
|
|
@@ -5244,7 +5575,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5244
5575
|
try {
|
|
5245
5576
|
const active = await rest.activeRun({ conversationId });
|
|
5246
5577
|
if (cancelled || !active) return;
|
|
5247
|
-
attachToRun(active.runId);
|
|
5578
|
+
attachToRun(active.runId, active.agentName || null);
|
|
5248
5579
|
} catch (_error) {}
|
|
5249
5580
|
})();
|
|
5250
5581
|
return () => {
|
|
@@ -5311,10 +5642,10 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5311
5642
|
text: trimmed
|
|
5312
5643
|
} : m));
|
|
5313
5644
|
const current = runRef.current;
|
|
5314
|
-
if (!(current.kind === "streaming" && current.runId === result.runId)) attachToRun(result.runId);
|
|
5645
|
+
if (!(current.kind === "streaming" && current.runId === result.runId)) attachToRun(result.runId, result.agentName ?? null);
|
|
5315
5646
|
return;
|
|
5316
5647
|
}
|
|
5317
|
-
attachToRun(result.runId);
|
|
5648
|
+
attachToRun(result.runId, result.agentName ?? null);
|
|
5318
5649
|
} catch (err) {
|
|
5319
5650
|
setItems((prev) => [...prev.filter((m) => m.id !== optimisticId), {
|
|
5320
5651
|
kind: "error",
|
|
@@ -5449,6 +5780,12 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5449
5780
|
portalClient,
|
|
5450
5781
|
portal.status
|
|
5451
5782
|
]);
|
|
5783
|
+
const runNewestCard = useCallback(() => {
|
|
5784
|
+
const target = [...itemsRef.current].reverse().find(isActionableCard);
|
|
5785
|
+
if (!target) return false;
|
|
5786
|
+
runCardAction(target);
|
|
5787
|
+
return true;
|
|
5788
|
+
}, [runCardAction]);
|
|
5452
5789
|
const handleCredInput = useCallback((displayed) => {
|
|
5453
5790
|
setCredPrompt((prev) => {
|
|
5454
5791
|
if (!prev || prev.submitting) return prev;
|
|
@@ -5625,42 +5962,12 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5625
5962
|
agentHost,
|
|
5626
5963
|
renderer
|
|
5627
5964
|
]);
|
|
5628
|
-
const submit = useCallback(() => {
|
|
5629
|
-
const content = input.trim();
|
|
5630
|
-
const staged = attachments.filter((a) => a.status !== "error");
|
|
5631
|
-
if (!content && staged.length === 0) return;
|
|
5632
|
-
const routed = routeInput(content);
|
|
5633
|
-
if (routed.kind !== "message") {
|
|
5634
|
-
history.append(content);
|
|
5635
|
-
setInput("");
|
|
5636
|
-
composerRef.current?.clear();
|
|
5637
|
-
setComposerRows(1);
|
|
5638
|
-
if (routed.kind === "local-shell") runLocalShell(routed.command);
|
|
5639
|
-
else if (routed.kind === "sandbox-exec") runSandboxExec(routed.command);
|
|
5640
|
-
else if (routed.kind === "sandbox-pty") runSandboxPty();
|
|
5641
|
-
else if (routed.kind === "quit") quit();
|
|
5642
|
-
return;
|
|
5643
|
-
}
|
|
5644
|
-
history.append(content);
|
|
5645
|
-
setInput("");
|
|
5646
|
-
composerRef.current?.clear();
|
|
5647
|
-
setComposerRows(1);
|
|
5648
|
-
setAttachments([]);
|
|
5649
|
-
sendContent(content, staged);
|
|
5650
|
-
}, [
|
|
5651
|
-
input,
|
|
5652
|
-
attachments,
|
|
5653
|
-
sendContent,
|
|
5654
|
-
history,
|
|
5655
|
-
runLocalShell,
|
|
5656
|
-
runSandboxExec,
|
|
5657
|
-
runSandboxPty,
|
|
5658
|
-
quit
|
|
5659
|
-
]);
|
|
5660
5965
|
const handleComposerChange = useCallback(() => {
|
|
5661
5966
|
const composer = composerRef.current;
|
|
5662
5967
|
if (!composer) return;
|
|
5663
5968
|
setInput(composer.plainText);
|
|
5969
|
+
setMenuHighlight(0);
|
|
5970
|
+
if (detectCommandTrigger(composer.plainText) === null) setMenuDismissed(false);
|
|
5664
5971
|
setComposerRows(Math.min(Math.max(composer.editorView.getTotalVirtualLineCount(), 1), maxComposerRows));
|
|
5665
5972
|
}, []);
|
|
5666
5973
|
/**
|
|
@@ -5840,6 +6147,126 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5840
6147
|
setPortalPrompt(null);
|
|
5841
6148
|
useStore.getState().showToast({ message: "kept this machine private" });
|
|
5842
6149
|
}, [agent.id, setPortalPrompt]);
|
|
6150
|
+
const executeLine = useCallback((content, staged) => {
|
|
6151
|
+
if (!content && staged.length === 0) return;
|
|
6152
|
+
const routed = routeInput(content);
|
|
6153
|
+
if (routed.kind !== "message") {
|
|
6154
|
+
history.append(content);
|
|
6155
|
+
setInput("");
|
|
6156
|
+
composerRef.current?.clear();
|
|
6157
|
+
setComposerRows(1);
|
|
6158
|
+
switch (routed.kind) {
|
|
6159
|
+
case "local-shell":
|
|
6160
|
+
runLocalShell(routed.command);
|
|
6161
|
+
break;
|
|
6162
|
+
case "sandbox-exec":
|
|
6163
|
+
runSandboxExec(routed.command);
|
|
6164
|
+
break;
|
|
6165
|
+
case "sandbox-pty":
|
|
6166
|
+
runSandboxPty();
|
|
6167
|
+
break;
|
|
6168
|
+
case "new-conversation":
|
|
6169
|
+
if (runRef.current.kind !== "idle") {
|
|
6170
|
+
useStore.getState().showToast({ message: "finish or cancel the run first (ctrl+c)" });
|
|
6171
|
+
break;
|
|
6172
|
+
}
|
|
6173
|
+
goTo({
|
|
6174
|
+
kind: "chat",
|
|
6175
|
+
agent,
|
|
6176
|
+
conversation: {
|
|
6177
|
+
kind: "new",
|
|
6178
|
+
agentId: agent.id,
|
|
6179
|
+
nonce: crypto.randomUUID()
|
|
6180
|
+
}
|
|
6181
|
+
});
|
|
6182
|
+
break;
|
|
6183
|
+
case "model-picker":
|
|
6184
|
+
setModelPickerOpen(true);
|
|
6185
|
+
break;
|
|
6186
|
+
case "theme-picker":
|
|
6187
|
+
setThemePickerOpen(true);
|
|
6188
|
+
break;
|
|
6189
|
+
case "status":
|
|
6190
|
+
setItems((prev) => [...prev, {
|
|
6191
|
+
kind: "info",
|
|
6192
|
+
id: crypto.randomUUID(),
|
|
6193
|
+
title: "status",
|
|
6194
|
+
text: formatStatusInfo({
|
|
6195
|
+
appUrl,
|
|
6196
|
+
workspaceName,
|
|
6197
|
+
agentName: agent.name,
|
|
6198
|
+
conversationId,
|
|
6199
|
+
model,
|
|
6200
|
+
share: {
|
|
6201
|
+
status: portal.status,
|
|
6202
|
+
grantedToAgent: portal.grantedAgentIds.includes(agent.id)
|
|
6203
|
+
}
|
|
6204
|
+
})
|
|
6205
|
+
}]);
|
|
6206
|
+
break;
|
|
6207
|
+
case "share":
|
|
6208
|
+
toggleShare();
|
|
6209
|
+
break;
|
|
6210
|
+
case "browser":
|
|
6211
|
+
openInBrowser();
|
|
6212
|
+
break;
|
|
6213
|
+
case "connect":
|
|
6214
|
+
if (!runNewestCard()) useStore.getState().showToast({ message: "no connect card waiting for action" });
|
|
6215
|
+
break;
|
|
6216
|
+
case "help":
|
|
6217
|
+
setHelpOpen(true);
|
|
6218
|
+
break;
|
|
6219
|
+
case "quit":
|
|
6220
|
+
quit();
|
|
6221
|
+
break;
|
|
6222
|
+
case "noop": break;
|
|
6223
|
+
default: {
|
|
6224
|
+
const exhaustive = routed;
|
|
6225
|
+
throw new Error(`unhandled route: ${JSON.stringify(exhaustive)}`);
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
return;
|
|
6229
|
+
}
|
|
6230
|
+
history.append(content);
|
|
6231
|
+
setInput("");
|
|
6232
|
+
composerRef.current?.clear();
|
|
6233
|
+
setComposerRows(1);
|
|
6234
|
+
setAttachments([]);
|
|
6235
|
+
sendContent(content, staged);
|
|
6236
|
+
}, [
|
|
6237
|
+
history,
|
|
6238
|
+
runLocalShell,
|
|
6239
|
+
runSandboxExec,
|
|
6240
|
+
runSandboxPty,
|
|
6241
|
+
toggleShare,
|
|
6242
|
+
openInBrowser,
|
|
6243
|
+
quit,
|
|
6244
|
+
sendContent,
|
|
6245
|
+
goTo,
|
|
6246
|
+
agent,
|
|
6247
|
+
appUrl,
|
|
6248
|
+
workspaceName,
|
|
6249
|
+
conversationId,
|
|
6250
|
+
model,
|
|
6251
|
+
portal.status,
|
|
6252
|
+
portal.grantedAgentIds,
|
|
6253
|
+
runNewestCard
|
|
6254
|
+
]);
|
|
6255
|
+
const submit = useCallback(() => {
|
|
6256
|
+
const staged = attachments.filter((a) => a.status !== "error");
|
|
6257
|
+
executeLine(input.trim(), staged);
|
|
6258
|
+
}, [
|
|
6259
|
+
executeLine,
|
|
6260
|
+
input,
|
|
6261
|
+
attachments
|
|
6262
|
+
]);
|
|
6263
|
+
const acceptCompletion = useCallback((command, execute) => {
|
|
6264
|
+
if (execute && command.argsHint === null) {
|
|
6265
|
+
executeLine(`/${command.name}`, []);
|
|
6266
|
+
return;
|
|
6267
|
+
}
|
|
6268
|
+
applyComposerText(`/${command.name} `, "end");
|
|
6269
|
+
}, [executeLine, applyComposerText]);
|
|
5843
6270
|
useEffect(() => {
|
|
5844
6271
|
if (!portalClient || portal.status !== "connected") return;
|
|
5845
6272
|
if (portal.grantedAgentIds.includes(agent.id)) return;
|
|
@@ -5885,6 +6312,38 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5885
6312
|
}
|
|
5886
6313
|
if (!(key.name === "c" && key.ctrl || key.name === "pageup" || key.name === "pgup" || key.name === "pagedown" || key.name === "pgdn")) return;
|
|
5887
6314
|
}
|
|
6315
|
+
if (menuOpen) {
|
|
6316
|
+
if (key.name === "escape") {
|
|
6317
|
+
setMenuDismissed(true);
|
|
6318
|
+
return;
|
|
6319
|
+
}
|
|
6320
|
+
const selected = menuCommands[menuHighlightClamped];
|
|
6321
|
+
if (selected) {
|
|
6322
|
+
if (key.name === "up") {
|
|
6323
|
+
setMenuHighlight((h) => Math.max(0, Math.min(h, menuCommands.length - 1) - 1));
|
|
6324
|
+
key.preventDefault();
|
|
6325
|
+
return;
|
|
6326
|
+
}
|
|
6327
|
+
if (key.name === "down") {
|
|
6328
|
+
setMenuHighlight((h) => Math.min(menuCommands.length - 1, h + 1));
|
|
6329
|
+
key.preventDefault();
|
|
6330
|
+
return;
|
|
6331
|
+
}
|
|
6332
|
+
if (key.name === "tab") {
|
|
6333
|
+
acceptCompletion(selected, false);
|
|
6334
|
+
key.preventDefault();
|
|
6335
|
+
return;
|
|
6336
|
+
}
|
|
6337
|
+
if ((key.name === "return" || key.name === "kpenter") && !key.shift) {
|
|
6338
|
+
const query = (commandQuery ?? "").toLowerCase();
|
|
6339
|
+
if (!(query === selected.name || selected.aliases.includes(query)) || selected.argsHint === null) {
|
|
6340
|
+
acceptCompletion(selected, true);
|
|
6341
|
+
key.preventDefault();
|
|
6342
|
+
return;
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
5888
6347
|
const composer = composerRef.current;
|
|
5889
6348
|
if (composer && !grantPrompt && !credPrompt && (key.name === "up" || key.name === "down")) {
|
|
5890
6349
|
if (key.name === "up") {
|
|
@@ -5898,6 +6357,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5898
6357
|
const text = history.previous(composer.plainText);
|
|
5899
6358
|
if (text === null) return;
|
|
5900
6359
|
applyComposerText(text, "start");
|
|
6360
|
+
if (detectCommandTrigger(text) !== null) setMenuDismissed(true);
|
|
5901
6361
|
key.preventDefault();
|
|
5902
6362
|
return;
|
|
5903
6363
|
}
|
|
@@ -5912,6 +6372,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5912
6372
|
const text = history.next(composer.plainText);
|
|
5913
6373
|
if (text === null) return;
|
|
5914
6374
|
applyComposerText(text, "end");
|
|
6375
|
+
if (detectCommandTrigger(text) !== null) setMenuDismissed(true);
|
|
5915
6376
|
key.preventDefault();
|
|
5916
6377
|
return;
|
|
5917
6378
|
}
|
|
@@ -5920,8 +6381,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
5920
6381
|
return;
|
|
5921
6382
|
}
|
|
5922
6383
|
if (key.name === "r" && key.ctrl) {
|
|
5923
|
-
|
|
5924
|
-
if (target) runCardAction(target);
|
|
6384
|
+
runNewestCard();
|
|
5925
6385
|
return;
|
|
5926
6386
|
}
|
|
5927
6387
|
if (key.name === "t" && key.ctrl) {
|
|
@@ -6045,7 +6505,22 @@ function ChatScreen({ agent, conversation }) {
|
|
|
6045
6505
|
agent.name,
|
|
6046
6506
|
" below."
|
|
6047
6507
|
]
|
|
6048
|
-
}) : /* @__PURE__ */ jsx(Fragment, { children: items.map((item
|
|
6508
|
+
}) : /* @__PURE__ */ jsx(Fragment, { children: items.map((item, i) => {
|
|
6509
|
+
const label = multiAgent ? agentLabelFor(items, i) : null;
|
|
6510
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
6511
|
+
style: { flexDirection: "column" },
|
|
6512
|
+
children: [label ? /* @__PURE__ */ jsx("box", {
|
|
6513
|
+
style: {
|
|
6514
|
+
paddingLeft: 3,
|
|
6515
|
+
paddingRight: 1
|
|
6516
|
+
},
|
|
6517
|
+
children: /* @__PURE__ */ jsx("text", {
|
|
6518
|
+
fg: theme.muted,
|
|
6519
|
+
children: /* @__PURE__ */ jsx("b", { children: label })
|
|
6520
|
+
})
|
|
6521
|
+
}) : null, /* @__PURE__ */ jsx(RenderItem, { item })]
|
|
6522
|
+
}, item.id);
|
|
6523
|
+
}) }), run.kind !== "idle" ? /* @__PURE__ */ jsx(ActivityRow, { label: run.kind === "sending" ? "sending" : "working" }) : null]
|
|
6049
6524
|
})
|
|
6050
6525
|
}),
|
|
6051
6526
|
pendingVisible ? /* @__PURE__ */ jsxs("box", {
|
|
@@ -6071,6 +6546,15 @@ function ChatScreen({ agent, conversation }) {
|
|
|
6071
6546
|
children: "ctrl+x remove last"
|
|
6072
6547
|
})]
|
|
6073
6548
|
}) : null,
|
|
6549
|
+
menuOpen ? /* @__PURE__ */ jsx(CompletionMenu, {
|
|
6550
|
+
items: menuCommands.map((c) => ({
|
|
6551
|
+
id: c.name,
|
|
6552
|
+
label: `/${c.name}`,
|
|
6553
|
+
hint: c.argsHint,
|
|
6554
|
+
description: c.description
|
|
6555
|
+
})),
|
|
6556
|
+
highlight: menuHighlightClamped
|
|
6557
|
+
}) : null,
|
|
6074
6558
|
credPrompt ? /* @__PURE__ */ jsx(CredentialPromptBox, {
|
|
6075
6559
|
prompt: credPrompt,
|
|
6076
6560
|
height: credPromptHeight,
|
|
@@ -6441,7 +6925,7 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
|
|
|
6441
6925
|
screen.kind === "chat" && rest && /* @__PURE__ */ jsx(ChatScreen, {
|
|
6442
6926
|
agent: screen.agent,
|
|
6443
6927
|
conversation: screen.conversation
|
|
6444
|
-
})
|
|
6928
|
+
}, "id" in screen.conversation ? screen.conversation.id : screen.conversation.nonce)
|
|
6445
6929
|
]
|
|
6446
6930
|
})
|
|
6447
6931
|
]
|