skydive-cli 0.5.0-beta.1 → 0.5.0-beta.11
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 +15 -4
- package/dist/js/api-DG5W6iwx.mjs +131 -0
- package/dist/js/bin.mjs +79 -33
- package/dist/js/{boot-BvmABDme.mjs → boot-BWOK3tCN.mjs} +220 -70
- package/dist/js/client-BuU34IVE.mjs +5 -0
- package/dist/js/{client-Dd5sMXPv.mjs → client-DabRpc_T.mjs} +145 -112
- package/dist/js/{daemon-XCciQ1mS.mjs → daemon-CJNMbWMJ.mjs} +3 -2
- package/dist/js/{daemon-C6yadKp2.mjs → daemon-DfeT8rGL.mjs} +71 -5
- package/dist/js/daemon-client-CAkZTO_6.mjs +7 -0
- package/dist/js/{daemon-client-Rr6PJvRg.mjs → daemon-client-eBSEPljJ.mjs} +12 -8
- package/dist/js/forward-18QoL5dO.mjs +68 -0
- package/dist/js/{print-ba_0hiV9.mjs → print-Bo06tvfV.mjs} +2 -2
- package/dist/js/{print-CbayCa87.mjs → print-ClPkgR9z.mjs} +105 -29
- package/dist/js/{print-share-C1LPsgTP.mjs → print-share-CKLPmsg0.mjs} +1 -1
- package/dist/js/{profiler-DHQhmoSF.mjs → profiler-BXdrd1D4.mjs} +2 -2
- package/dist/js/{raw-pty-B6mAroiI.mjs → raw-pty-DY4KelZW.mjs} +1 -1
- package/dist/js/raw-pty-DmdUf4_w.mjs +5 -0
- package/dist/js/{rest-DADJh0bi.mjs → rest-D29qNkto.mjs} +1 -1
- package/dist/js/{rest-BY2nADw5.mjs → rest-I3imNduB.mjs} +6 -11
- package/package.json +2 -2
- package/dist/js/client-CKBQ8pft.mjs +0 -4
- package/dist/js/daemon-client-CgziZU3X.mjs +0 -6
- package/dist/js/raw-pty-3EkG-jjH.mjs +0 -5
- /package/dist/js/{client-Cn2af31H.mjs → client-c4c5MmgN.mjs} +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { D as setActiveWorkspace, E as listWorkspaces, S as themesForMode, _ as themeForMode, a as installCrashHandler, b as themeVersion, c as MARK_CELLS, d as DEFAULT_THEME_ID, f as applyTheme, g as theme, h as noColorRequested, i as writeArtifact, l as WORDMARK, m as monoTheme, n as profilingEnabled, o as buildCrashReport, p as findTheme, r as record, s as writeCrashReport, v as themeMode, w as getActiveWorkspaceId, y as themeModeFromColorFgBg } from "./profiler-
|
|
3
|
-
import {
|
|
2
|
+
import { D as setActiveWorkspace, E as listWorkspaces, S as themesForMode, _ as themeForMode, a as installCrashHandler, b as themeVersion, c as MARK_CELLS, d as DEFAULT_THEME_ID, f as applyTheme, g as theme, h as noColorRequested, i as writeArtifact, l as WORDMARK, m as monoTheme, n as profilingEnabled, o as buildCrashReport, p as findTheme, r as record, s as writeCrashReport, v as themeMode, w as getActiveWorkspaceId, y as themeModeFromColorFgBg } from "./profiler-BXdrd1D4.mjs";
|
|
3
|
+
import { D as getReviewStateDir, H as saveTheme, N as recordDefaultAgent, O as getSavedTheme, S as getConfigPath, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, i as resolveAgent, l as parseExternalOauthConnectParams, m as specKeyFor, p as parseConnectCard, s as MASK_CHAR, u as parseOauthConnectParams, v as DEFAULT_API_URL, y as DEFAULT_APP_URL, z as resolveWebUrl } from "./print-ClPkgR9z.mjs";
|
|
4
4
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
5
|
-
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
5
|
+
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-I3imNduB.mjs";
|
|
6
6
|
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-2wju4gC_.mjs";
|
|
7
|
-
import { t as PortalClient } from "./client-
|
|
8
|
-
import "./daemon-
|
|
9
|
-
import
|
|
10
|
-
import { t as
|
|
11
|
-
import { t as
|
|
7
|
+
import { t as PortalClient } from "./client-DabRpc_T.mjs";
|
|
8
|
+
import "./daemon-DfeT8rGL.mjs";
|
|
9
|
+
import "./api-DG5W6iwx.mjs";
|
|
10
|
+
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
11
|
+
import { t as PortalDaemonClient } from "./daemon-client-eBSEPljJ.mjs";
|
|
12
|
+
import { t as runRawPtyPassthrough } from "./raw-pty-DY4KelZW.mjs";
|
|
12
13
|
import * as os$1 from "node:os";
|
|
13
14
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
14
15
|
import path, { basename, extname, isAbsolute, join, win32 } from "node:path";
|
|
@@ -212,7 +213,17 @@ const useStore = create((set, get) => ({
|
|
|
212
213
|
/**
|
|
213
214
|
* Decide the screen the chat TUI boots into.
|
|
214
215
|
*
|
|
215
|
-
* With no selector we open the agent picker
|
|
216
|
+
* With no selector we open the agent picker — unless the config stores a
|
|
217
|
+
* `defaultAgent`, in which case a bare `skydive chat` opens a fresh
|
|
218
|
+
* conversation with that agent directly. The default is maintained
|
|
219
|
+
* automatically (every conversation the TUI opens records its agent, so a
|
|
220
|
+
* bare launch returns to whoever you talked to last — the dominant launch
|
|
221
|
+
* pattern), and `skydive config set defaultAgent` can seed or override it. A
|
|
222
|
+
* stored selector that no longer resolves degrades silently to the agent
|
|
223
|
+
* picker rather than an error screen: config goes stale (agent renamed,
|
|
224
|
+
* archived, another one claiming the same name), and unlike a mistyped
|
|
225
|
+
* `--agent` there is no command line to correct — and picking an agent
|
|
226
|
+
* there heals the default on its own.
|
|
216
227
|
*
|
|
217
228
|
* With `--agent` (`skydive chat --agent grace`, no `-p`) we resolve it
|
|
218
229
|
* against the org roster and open that agent's conversation list, skipping
|
|
@@ -228,20 +239,31 @@ const useStore = create((set, get) => ({
|
|
|
228
239
|
* `--agent`. When both are given, the conversation wins (it pins the agent
|
|
229
240
|
* anyway).
|
|
230
241
|
*
|
|
242
|
+
* None of these paths load the whole org agent roster. The only decision that
|
|
243
|
+
* needs every candidate is disambiguating a fuzzy name/slug selector; an id
|
|
244
|
+
* (or the `--resume` conversation's agentId) is exact and resolves with a
|
|
245
|
+
* single `getAgent`, and `--new` with no selector only needs "exactly one
|
|
246
|
+
* agent, or not", which a two-row page answers. A large org must never sit on
|
|
247
|
+
* the `connecting…` splash while the roster streams in.
|
|
248
|
+
*
|
|
231
249
|
* Kept out of the React component so it's unit-testable under Node (the
|
|
232
250
|
* component itself can't render without Bun + OpenTUI).
|
|
233
251
|
*/
|
|
234
|
-
|
|
252
|
+
/** A selector that is a bare uuid is an exact agent id: it can be resolved
|
|
253
|
+
* with a single `getAgent` fetch, so no boot path that carries one needs the
|
|
254
|
+
* org roster. A name/slug selector is the only case that can be ambiguous and
|
|
255
|
+
* therefore still needs the full candidate list to disambiguate. */
|
|
256
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
257
|
+
async function resolveInitialScreen({ rest, agentSelector, conversationId, newConversation, defaultAgentSelector }) {
|
|
235
258
|
if (conversationId) {
|
|
236
|
-
const
|
|
259
|
+
const conversation = await rest.getConversation({ conversationId }).catch((err) => {
|
|
237
260
|
if (err instanceof HttpError && err.status === 404) throw new Error(`No conversation with id ${conversationId}. It may have been deleted, or belong to another workspace.`);
|
|
238
261
|
throw err;
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (!agent) throw new Error(`Conversation ${conversationId} belongs to an agent you can't access.`);
|
|
262
|
+
});
|
|
263
|
+
const agent = await rest.getAgent({ agentId: conversation.agentId }).catch((err) => {
|
|
264
|
+
if (err instanceof HttpError && (err.status === 404 || err.status === 403)) throw new Error(`Conversation ${conversationId} belongs to an agent you can't access.`);
|
|
265
|
+
throw err;
|
|
266
|
+
});
|
|
245
267
|
return {
|
|
246
268
|
kind: "chat",
|
|
247
269
|
agent,
|
|
@@ -262,26 +284,59 @@ async function resolveInitialScreen({ rest, agentSelector, conversationId, newCo
|
|
|
262
284
|
}
|
|
263
285
|
};
|
|
264
286
|
}
|
|
265
|
-
if (!agentSelector && !newConversation) return { kind: "agent-picker" };
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
287
|
+
if (!agentSelector && !newConversation && !defaultAgentSelector) return { kind: "agent-picker" };
|
|
288
|
+
if (!agentSelector && defaultAgentSelector) {
|
|
289
|
+
const agent = UUID_RE.test(defaultAgentSelector) ? await rest.getAgent({ agentId: defaultAgentSelector }).catch((err) => {
|
|
290
|
+
if (err instanceof HttpError && (err.status === 404 || err.status === 403)) return null;
|
|
291
|
+
throw err;
|
|
292
|
+
}) : resolveAgentOrNull(await rest.listAgents({
|
|
293
|
+
scope: "org",
|
|
294
|
+
onPage: null
|
|
295
|
+
}), defaultAgentSelector);
|
|
296
|
+
if (agent) return newChat(agent);
|
|
297
|
+
return { kind: "agent-picker" };
|
|
298
|
+
}
|
|
299
|
+
if (agentSelector && UUID_RE.test(agentSelector)) {
|
|
300
|
+
const agent = await rest.getAgent({ agentId: agentSelector }).catch((err) => {
|
|
301
|
+
if (err instanceof HttpError && (err.status === 404 || err.status === 403)) throw new Error(`No agent with id ${agentSelector}. It may not exist, or belong to another workspace.`);
|
|
302
|
+
throw err;
|
|
303
|
+
});
|
|
304
|
+
if (newConversation) return {
|
|
274
305
|
kind: "chat",
|
|
275
|
-
agent
|
|
306
|
+
agent,
|
|
276
307
|
conversation: {
|
|
277
308
|
kind: "new",
|
|
278
|
-
agentId:
|
|
309
|
+
agentId: agent.id,
|
|
279
310
|
nonce: crypto.randomUUID()
|
|
280
311
|
}
|
|
281
312
|
};
|
|
313
|
+
return {
|
|
314
|
+
kind: "conversation-picker",
|
|
315
|
+
agent
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (!agentSelector && newConversation) {
|
|
319
|
+
const firstTwo = await rest.listAgents({
|
|
320
|
+
scope: "org",
|
|
321
|
+
onPage: null,
|
|
322
|
+
limit: 2
|
|
323
|
+
});
|
|
324
|
+
const only = firstTwo.length === 1 ? firstTwo[0] : null;
|
|
325
|
+
if (!only) return { kind: "agent-picker" };
|
|
326
|
+
return newChat(only);
|
|
282
327
|
}
|
|
283
|
-
const agent = resolveAgent(
|
|
284
|
-
|
|
328
|
+
const agent = resolveAgent(await rest.listAgents({
|
|
329
|
+
scope: "org",
|
|
330
|
+
onPage: null
|
|
331
|
+
}), agentSelector);
|
|
332
|
+
if (newConversation) return newChat(agent);
|
|
333
|
+
return {
|
|
334
|
+
kind: "conversation-picker",
|
|
335
|
+
agent
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function newChat(agent) {
|
|
339
|
+
return {
|
|
285
340
|
kind: "chat",
|
|
286
341
|
agent,
|
|
287
342
|
conversation: {
|
|
@@ -290,10 +345,17 @@ async function resolveInitialScreen({ rest, agentSelector, conversationId, newCo
|
|
|
290
345
|
nonce: crypto.randomUUID()
|
|
291
346
|
}
|
|
292
347
|
};
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
348
|
+
}
|
|
349
|
+
/** `resolveAgent` throws for a selector that matches nothing or matches
|
|
350
|
+
* ambiguously — both phrased for a human who can retype `--agent`. A stored
|
|
351
|
+
* default has no command line behind it, so both shapes just mean "no usable
|
|
352
|
+
* default". */
|
|
353
|
+
function resolveAgentOrNull(agents, selector) {
|
|
354
|
+
try {
|
|
355
|
+
return resolveAgent(agents, selector);
|
|
356
|
+
} catch {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
297
359
|
}
|
|
298
360
|
|
|
299
361
|
//#endregion
|
|
@@ -337,7 +399,7 @@ function createPortalClient(opts) {
|
|
|
337
399
|
start: () => Promise.resolve(),
|
|
338
400
|
enable: () => client.enable(),
|
|
339
401
|
disable: () => client.disable(),
|
|
340
|
-
grantAgent: (agentId) => client.grantAgent(agentId),
|
|
402
|
+
grantAgent: (agentId, conversationId) => client.grantAgent(agentId, conversationId),
|
|
341
403
|
decline: (_agentId, declined) => {
|
|
342
404
|
if (declined && client.grantedAgentIds().length === 0) client.disable();
|
|
343
405
|
},
|
|
@@ -4529,20 +4591,23 @@ function RenderItem({ item }) {
|
|
|
4529
4591
|
children: [item.text ? /* @__PURE__ */ jsx("text", {
|
|
4530
4592
|
fg: theme.user,
|
|
4531
4593
|
children: item.text
|
|
4532
|
-
}) : null, item.attachments
|
|
4594
|
+
}) : null, item.attachments.length > 0 ? /* @__PURE__ */ jsxs("text", {
|
|
4533
4595
|
fg: theme.muted,
|
|
4534
4596
|
children: ["⎘ ", item.attachments.join(" · ")]
|
|
4535
4597
|
}) : null]
|
|
4536
4598
|
});
|
|
4537
|
-
case "pending-steer": return /* @__PURE__ */
|
|
4599
|
+
case "pending-steer": return /* @__PURE__ */ jsxs(Row, {
|
|
4538
4600
|
barColor: theme.dim,
|
|
4539
|
-
children: /* @__PURE__ */ jsxs("text", {
|
|
4601
|
+
children: [/* @__PURE__ */ jsxs("text", {
|
|
4540
4602
|
fg: theme.muted,
|
|
4541
4603
|
children: [item.text, /* @__PURE__ */ jsx("span", {
|
|
4542
4604
|
fg: theme.dim,
|
|
4543
4605
|
children: " · steering… (esc to cancel)"
|
|
4544
4606
|
})]
|
|
4545
|
-
})
|
|
4607
|
+
}), item.attachments.length > 0 ? /* @__PURE__ */ jsxs("text", {
|
|
4608
|
+
fg: theme.dim,
|
|
4609
|
+
children: ["⎘ ", item.attachments.join(" · ")]
|
|
4610
|
+
}) : null]
|
|
4546
4611
|
});
|
|
4547
4612
|
case "assistant-text":
|
|
4548
4613
|
if (item.done) return /* @__PURE__ */ jsx(Row, {
|
|
@@ -5277,7 +5342,8 @@ function applyChunk(items, chunk, agentName = null) {
|
|
|
5277
5342
|
return items.map((m) => m.kind === "pending-steer" && ids.has(m.id) ? {
|
|
5278
5343
|
kind: "user",
|
|
5279
5344
|
id: m.id,
|
|
5280
|
-
text: m.text
|
|
5345
|
+
text: m.text,
|
|
5346
|
+
attachments: m.attachments
|
|
5281
5347
|
} : m);
|
|
5282
5348
|
}
|
|
5283
5349
|
case "data-anyone-render-spec": {
|
|
@@ -5518,7 +5584,7 @@ function uiMessagesToItems(messages) {
|
|
|
5518
5584
|
kind: "user",
|
|
5519
5585
|
id: m.id,
|
|
5520
5586
|
text,
|
|
5521
|
-
|
|
5587
|
+
attachments
|
|
5522
5588
|
});
|
|
5523
5589
|
continue;
|
|
5524
5590
|
}
|
|
@@ -6019,6 +6085,58 @@ function createResponsePreview() {
|
|
|
6019
6085
|
};
|
|
6020
6086
|
}
|
|
6021
6087
|
|
|
6088
|
+
//#endregion
|
|
6089
|
+
//#region src/chat/tui/new-chat-splash.tsx
|
|
6090
|
+
/** Rows the splash art needs before it's worth painting: the 6-row mark, a
|
|
6091
|
+
* spacer, the two identity/hint lines, and a row of breathing room. Below
|
|
6092
|
+
* this only the identity + hint render (small panes must keep every
|
|
6093
|
+
* transcript row). */
|
|
6094
|
+
const MARK_MIN_ROWS = 10;
|
|
6095
|
+
/** Columns the full mark + wordmark lockup spans (~68), with margin; below
|
|
6096
|
+
* this the pinwheel mark renders alone instead of wrapping the wordmark. */
|
|
6097
|
+
const FULL_MARK_MIN_COLS = 70;
|
|
6098
|
+
/**
|
|
6099
|
+
* Brand splash filling an empty conversation's transcript area: the Skydive
|
|
6100
|
+
* mark centered above the agent's identity and the "say hello" hint, with
|
|
6101
|
+
* the composer live below. The identity line matters here because nothing
|
|
6102
|
+
* else on this screen names the agent — a bare `skydive chat` boots straight
|
|
6103
|
+
* into this view via the configured default. Rendered only while the
|
|
6104
|
+
* transcript is empty, so the first submission replaces it with the regular
|
|
6105
|
+
* conversation view. Degrades by available space: full lockup → mark only →
|
|
6106
|
+
* identity + hint only.
|
|
6107
|
+
*/
|
|
6108
|
+
function NewChatSplash({ agentName, agentTitle, width, height }) {
|
|
6109
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
6110
|
+
style: {
|
|
6111
|
+
height,
|
|
6112
|
+
flexDirection: "column",
|
|
6113
|
+
justifyContent: "center",
|
|
6114
|
+
alignItems: "center"
|
|
6115
|
+
},
|
|
6116
|
+
children: [
|
|
6117
|
+
height >= MARK_MIN_ROWS ? /* @__PURE__ */ jsx("box", {
|
|
6118
|
+
style: {
|
|
6119
|
+
flexDirection: "column",
|
|
6120
|
+
alignItems: "center",
|
|
6121
|
+
marginBottom: 1
|
|
6122
|
+
},
|
|
6123
|
+
children: /* @__PURE__ */ jsx(SkydiveMark, { variant: width >= FULL_MARK_MIN_COLS ? "full" : "mark" })
|
|
6124
|
+
}) : null,
|
|
6125
|
+
/* @__PURE__ */ jsxs("text", { children: [/* @__PURE__ */ jsx("span", {
|
|
6126
|
+
fg: theme.accent,
|
|
6127
|
+
children: agentName
|
|
6128
|
+
}), agentTitle ? /* @__PURE__ */ jsxs("span", {
|
|
6129
|
+
fg: theme.muted,
|
|
6130
|
+
children: [" · ", agentTitle]
|
|
6131
|
+
}) : null] }),
|
|
6132
|
+
/* @__PURE__ */ jsx("text", {
|
|
6133
|
+
fg: theme.dim,
|
|
6134
|
+
children: "say hello below"
|
|
6135
|
+
})
|
|
6136
|
+
]
|
|
6137
|
+
});
|
|
6138
|
+
}
|
|
6139
|
+
|
|
6022
6140
|
//#endregion
|
|
6023
6141
|
//#region src/chat/tui/prompt-history.ts
|
|
6024
6142
|
/**
|
|
@@ -10159,6 +10277,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10159
10277
|
const portalClient = useStore((s) => s.portalClient);
|
|
10160
10278
|
const setPortalPrompt = useStore((s) => s.setPortalPrompt);
|
|
10161
10279
|
const declinedRef = useRef(/* @__PURE__ */ new Set());
|
|
10280
|
+
const grantInFlightRef = useRef(/* @__PURE__ */ new Set());
|
|
10162
10281
|
const grantPrompt = portal.prompt && portal.prompt.agentId === agent.id ? portal.prompt : null;
|
|
10163
10282
|
const isShared = portal.status !== "off";
|
|
10164
10283
|
const initialConversationId = isNewConversation(conversation) ? null : conversation.id;
|
|
@@ -10271,13 +10390,16 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10271
10390
|
});
|
|
10272
10391
|
setScrolledUp(false);
|
|
10273
10392
|
}, []);
|
|
10393
|
+
const scrolledUpRef = useRef(false);
|
|
10274
10394
|
useEffect(() => {
|
|
10275
10395
|
const id = setInterval(() => {
|
|
10276
10396
|
const box = scrollRef.current;
|
|
10277
10397
|
if (!box) return;
|
|
10278
10398
|
const viewport = box.viewport.height;
|
|
10279
|
-
const
|
|
10280
|
-
|
|
10399
|
+
const next = !(box.scrollTop + viewport >= box.scrollHeight - 1);
|
|
10400
|
+
if (scrolledUpRef.current === next) return;
|
|
10401
|
+
scrolledUpRef.current = next;
|
|
10402
|
+
setScrolledUp(next);
|
|
10281
10403
|
}, 150);
|
|
10282
10404
|
return () => clearInterval(id);
|
|
10283
10405
|
}, []);
|
|
@@ -10436,7 +10558,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10436
10558
|
setItems((prev) => prev.map((m) => m.kind === "pending-steer" ? {
|
|
10437
10559
|
kind: "user",
|
|
10438
10560
|
id: m.id,
|
|
10439
|
-
text: m.text
|
|
10561
|
+
text: m.text,
|
|
10562
|
+
attachments: m.attachments
|
|
10440
10563
|
} : m));
|
|
10441
10564
|
setRun({ kind: "idle" });
|
|
10442
10565
|
agentHost.transition({
|
|
@@ -10513,7 +10636,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10513
10636
|
kind: "user",
|
|
10514
10637
|
id: optimisticId,
|
|
10515
10638
|
text: trimmed,
|
|
10516
|
-
|
|
10639
|
+
attachments: stagedNames
|
|
10517
10640
|
}]);
|
|
10518
10641
|
const wasStreaming = runRef.current.kind === "streaming";
|
|
10519
10642
|
if (!wasStreaming) setRun({
|
|
@@ -10562,7 +10685,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10562
10685
|
if (directiveId) setItems((prev) => prev.map((m) => m.id === optimisticId ? {
|
|
10563
10686
|
kind: "pending-steer",
|
|
10564
10687
|
id: directiveId,
|
|
10565
|
-
text: trimmed
|
|
10688
|
+
text: trimmed,
|
|
10689
|
+
attachments: m.kind === "user" || m.kind === "pending-steer" ? m.attachments : []
|
|
10566
10690
|
} : m));
|
|
10567
10691
|
const current = runRef.current;
|
|
10568
10692
|
if (!(current.kind === "streaming" && current.runId === result.runId)) attachToRun(result.runId, result.agentName ?? null);
|
|
@@ -10609,12 +10733,14 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10609
10733
|
});
|
|
10610
10734
|
(async () => {
|
|
10611
10735
|
if (autoGrantMachine && portalClient && portalStatus === "connected" && !portalClient.isGranted(agent.id)) try {
|
|
10612
|
-
|
|
10736
|
+
grantInFlightRef.current.add(agent.id);
|
|
10737
|
+
await portalClient.grantAgent(agent.id, null);
|
|
10613
10738
|
useStore.getState().showToast({
|
|
10614
|
-
message: `Shared this machine with ${agent.name} for the import. The grant persists. Revoke anytime: skydive portal revoke --agent ${agent.name}`,
|
|
10739
|
+
message: `Shared this machine with ${agent.name} for the import. The grant persists. Revoke anytime: skydive portal revoke --agent "${agent.name}"`,
|
|
10615
10740
|
durationMs: 1e4
|
|
10616
10741
|
});
|
|
10617
10742
|
} catch (err) {
|
|
10743
|
+
grantInFlightRef.current.delete(agent.id);
|
|
10618
10744
|
useStore.getState().showToast({
|
|
10619
10745
|
message: `Couldn't grant machine access automatically (${errorMessage(err)}). The agent will ask in chat instead.`,
|
|
10620
10746
|
variant: "warning"
|
|
@@ -10678,9 +10804,12 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10678
10804
|
if (!portalClient) throw new Error("portal is unavailable in this session");
|
|
10679
10805
|
const justEnabled = portal.status === "off";
|
|
10680
10806
|
if (justEnabled) portalClient.enable();
|
|
10807
|
+
grantInFlightRef.current.add(action.agentId);
|
|
10808
|
+
if (useStore.getState().portal.prompt?.agentId === action.agentId) setPortalPrompt(null);
|
|
10681
10809
|
try {
|
|
10682
|
-
await portalClient.grantAgent(action.agentId);
|
|
10810
|
+
await portalClient.grantAgent(action.agentId, action.conversationId);
|
|
10683
10811
|
} catch (grantErr) {
|
|
10812
|
+
grantInFlightRef.current.delete(action.agentId);
|
|
10684
10813
|
if (justEnabled) portalClient.disable();
|
|
10685
10814
|
throw grantErr;
|
|
10686
10815
|
}
|
|
@@ -10741,7 +10870,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10741
10870
|
appUrl,
|
|
10742
10871
|
updateCard,
|
|
10743
10872
|
portalClient,
|
|
10744
|
-
portal.status
|
|
10873
|
+
portal.status,
|
|
10874
|
+
setPortalPrompt
|
|
10745
10875
|
]);
|
|
10746
10876
|
const promptForCards = useCallback(() => {
|
|
10747
10877
|
const targets = itemsRef.current.filter(isActionableCard);
|
|
@@ -11066,7 +11196,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11066
11196
|
setItems((prev) => prev.map((m) => m.kind === "pending-steer" ? {
|
|
11067
11197
|
kind: "user",
|
|
11068
11198
|
id: m.id,
|
|
11069
|
-
text: m.text
|
|
11199
|
+
text: m.text,
|
|
11200
|
+
attachments: m.attachments
|
|
11070
11201
|
} : m));
|
|
11071
11202
|
useStore.getState().showToast({ message: "run cancelled" });
|
|
11072
11203
|
}, [rest]);
|
|
@@ -11244,7 +11375,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11244
11375
|
rest,
|
|
11245
11376
|
agentSelector: null,
|
|
11246
11377
|
conversationId: target,
|
|
11247
|
-
newConversation: false
|
|
11378
|
+
newConversation: false,
|
|
11379
|
+
defaultAgentSelector: null
|
|
11248
11380
|
}));
|
|
11249
11381
|
} catch (err) {
|
|
11250
11382
|
setItems((prev) => [...prev, {
|
|
@@ -11283,19 +11415,24 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11283
11415
|
if (!portalClient) return;
|
|
11284
11416
|
const agentName = agent.name;
|
|
11285
11417
|
setPortalPrompt(null);
|
|
11286
|
-
|
|
11418
|
+
grantInFlightRef.current.add(agent.id);
|
|
11419
|
+
portalClient.grantAgent(agent.id, conversationId).then(() => useStore.getState().showToast({
|
|
11287
11420
|
variant: "success",
|
|
11288
11421
|
message: `shared this machine with ${agentName}`
|
|
11289
|
-
})).catch((err) =>
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11422
|
+
})).catch((err) => {
|
|
11423
|
+
grantInFlightRef.current.delete(agent.id);
|
|
11424
|
+
setItems((prev) => [...prev, {
|
|
11425
|
+
kind: "error",
|
|
11426
|
+
id: crypto.randomUUID(),
|
|
11427
|
+
text: `couldn't share machine: ${errorDetail(err)}`
|
|
11428
|
+
}]);
|
|
11429
|
+
});
|
|
11294
11430
|
}, [
|
|
11295
11431
|
portalClient,
|
|
11296
11432
|
agent.id,
|
|
11297
11433
|
agent.name,
|
|
11298
|
-
setPortalPrompt
|
|
11434
|
+
setPortalPrompt,
|
|
11435
|
+
conversationId
|
|
11299
11436
|
]);
|
|
11300
11437
|
const declineGrant = useCallback(() => {
|
|
11301
11438
|
declinedRef.current.add(agent.id);
|
|
@@ -11535,6 +11672,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11535
11672
|
if (portal.grantedAgentIds.includes(agent.id)) return;
|
|
11536
11673
|
if (portal.declinedAgentIds.includes(agent.id)) return;
|
|
11537
11674
|
if (declinedRef.current.has(agent.id)) return;
|
|
11675
|
+
if (grantInFlightRef.current.has(agent.id)) return;
|
|
11538
11676
|
if (portal.prompt?.agentId === agent.id) return;
|
|
11539
11677
|
setPortalPrompt({
|
|
11540
11678
|
agentId: agent.id,
|
|
@@ -11558,6 +11696,14 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11558
11696
|
setPortalPrompt,
|
|
11559
11697
|
agentHost
|
|
11560
11698
|
]);
|
|
11699
|
+
useEffect(() => {
|
|
11700
|
+
for (const agentId of portal.grantedAgentIds) grantInFlightRef.current.delete(agentId);
|
|
11701
|
+
if (portal.prompt && portal.grantedAgentIds.includes(portal.prompt.agentId)) setPortalPrompt(null);
|
|
11702
|
+
}, [
|
|
11703
|
+
portal.grantedAgentIds,
|
|
11704
|
+
portal.prompt,
|
|
11705
|
+
setPortalPrompt
|
|
11706
|
+
]);
|
|
11561
11707
|
useKeyboard((key) => {
|
|
11562
11708
|
if (reviewOpen) {
|
|
11563
11709
|
if (key.name === "g" && key.ctrl) {
|
|
@@ -11899,13 +12045,11 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11899
12045
|
children: [!historyLoaded ? /* @__PURE__ */ jsx("text", {
|
|
11900
12046
|
fg: theme.muted,
|
|
11901
12047
|
children: "loading history…"
|
|
11902
|
-
}) : items.length === 0 ? /* @__PURE__ */
|
|
11903
|
-
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
" below."
|
|
11908
|
-
]
|
|
12048
|
+
}) : items.length === 0 ? /* @__PURE__ */ jsx(NewChatSplash, {
|
|
12049
|
+
agentName: agent.name,
|
|
12050
|
+
agentTitle: agent.title ?? null,
|
|
12051
|
+
width: reviewView && reviewPlacement === "side" ? Math.max(1, bodyWidth - reviewWidth - 1) : bodyWidth,
|
|
12052
|
+
height: Math.max(1, scrollHeight - 1)
|
|
11909
12053
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [transcriptWindow.hiddenCount > 0 ? /* @__PURE__ */ jsx("box", {
|
|
11910
12054
|
style: {
|
|
11911
12055
|
paddingLeft: 1,
|
|
@@ -12359,9 +12503,12 @@ function ToastView() {
|
|
|
12359
12503
|
|
|
12360
12504
|
//#endregion
|
|
12361
12505
|
//#region src/chat/tui/app.tsx
|
|
12362
|
-
function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation }) {
|
|
12506
|
+
function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation, defaultAgentSelector }) {
|
|
12363
12507
|
const renderer = useRenderer();
|
|
12364
12508
|
const screen = useStore((s) => s.screen);
|
|
12509
|
+
useEffect(() => {
|
|
12510
|
+
if (screen.kind === "chat") recordDefaultAgent(screen.agent.id);
|
|
12511
|
+
}, [screen]);
|
|
12365
12512
|
const chatTitle = useStore((s) => s.chatTitle);
|
|
12366
12513
|
useStore((s) => s.themeId);
|
|
12367
12514
|
const rest = useStore((s) => s.rest);
|
|
@@ -12444,7 +12591,8 @@ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath,
|
|
|
12444
12591
|
rest,
|
|
12445
12592
|
agentSelector,
|
|
12446
12593
|
conversationId,
|
|
12447
|
-
newConversation: newConversation || useStore.getState().seedPrompt !== null
|
|
12594
|
+
newConversation: newConversation || useStore.getState().seedPrompt !== null,
|
|
12595
|
+
defaultAgentSelector
|
|
12448
12596
|
});
|
|
12449
12597
|
if (cancelled) return;
|
|
12450
12598
|
goTo(screen);
|
|
@@ -12467,6 +12615,7 @@ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath,
|
|
|
12467
12615
|
agentSelector,
|
|
12468
12616
|
conversationId,
|
|
12469
12617
|
newConversation,
|
|
12618
|
+
defaultAgentSelector,
|
|
12470
12619
|
goTo,
|
|
12471
12620
|
setClients,
|
|
12472
12621
|
fail
|
|
@@ -13045,7 +13194,7 @@ function onRenderCommit(id, phase, actualDuration, baseDuration, startTime, comm
|
|
|
13045
13194
|
* module — and the heavy OpenTUI dependency graph it pulls in — is
|
|
13046
13195
|
* dynamically imported and never loaded on the Node management path.
|
|
13047
13196
|
*/
|
|
13048
|
-
async function runChat({ appUrl, sessionToken, authKind = "session", shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, seedPrompt }) {
|
|
13197
|
+
async function runChat({ appUrl, sessionToken, authKind = "session", shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, defaultAgentSelector, seedPrompt }) {
|
|
13049
13198
|
await registerGrammars();
|
|
13050
13199
|
const watchdog = startMemoryWatchdog();
|
|
13051
13200
|
installCrashHandler({ memoryProvider: () => watchdog.snapshot() });
|
|
@@ -13086,7 +13235,8 @@ async function runChat({ appUrl, sessionToken, authKind = "session", shareMachin
|
|
|
13086
13235
|
notifications,
|
|
13087
13236
|
agentSelector: agentSelector ?? null,
|
|
13088
13237
|
conversationId: conversationId ?? null,
|
|
13089
|
-
newConversation
|
|
13238
|
+
newConversation,
|
|
13239
|
+
defaultAgentSelector: defaultAgentSelector ?? null
|
|
13090
13240
|
})
|
|
13091
13241
|
}));
|
|
13092
13242
|
}
|