skydive-cli 0.5.0-beta.1 → 0.5.0-beta.10
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-BuxaHgFi.mjs} +215 -68
- package/dist/js/client-BuU34IVE.mjs +5 -0
- package/dist/js/{client-Dd5sMXPv.mjs → client-DabRpc_T.mjs} +145 -112
- package/dist/js/{daemon-C6yadKp2.mjs → daemon-CYYE2BTu.mjs} +71 -5
- package/dist/js/{daemon-XCciQ1mS.mjs → daemon-Dk5LeS1S.mjs} +3 -2
- package/dist/js/{daemon-client-Rr6PJvRg.mjs → daemon-client-3A9afTFC.mjs} +12 -8
- package/dist/js/daemon-client-Dh7G9RxC.mjs +7 -0
- 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-Zs3BHURc.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-Zs3BHURc.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-CYYE2BTu.mjs";
|
|
9
|
+
import "./api-DG5W6iwx.mjs";
|
|
10
|
+
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
11
|
+
import { t as PortalDaemonClient } from "./daemon-client-3A9afTFC.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;
|
|
@@ -10436,7 +10555,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10436
10555
|
setItems((prev) => prev.map((m) => m.kind === "pending-steer" ? {
|
|
10437
10556
|
kind: "user",
|
|
10438
10557
|
id: m.id,
|
|
10439
|
-
text: m.text
|
|
10558
|
+
text: m.text,
|
|
10559
|
+
attachments: m.attachments
|
|
10440
10560
|
} : m));
|
|
10441
10561
|
setRun({ kind: "idle" });
|
|
10442
10562
|
agentHost.transition({
|
|
@@ -10513,7 +10633,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10513
10633
|
kind: "user",
|
|
10514
10634
|
id: optimisticId,
|
|
10515
10635
|
text: trimmed,
|
|
10516
|
-
|
|
10636
|
+
attachments: stagedNames
|
|
10517
10637
|
}]);
|
|
10518
10638
|
const wasStreaming = runRef.current.kind === "streaming";
|
|
10519
10639
|
if (!wasStreaming) setRun({
|
|
@@ -10562,7 +10682,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10562
10682
|
if (directiveId) setItems((prev) => prev.map((m) => m.id === optimisticId ? {
|
|
10563
10683
|
kind: "pending-steer",
|
|
10564
10684
|
id: directiveId,
|
|
10565
|
-
text: trimmed
|
|
10685
|
+
text: trimmed,
|
|
10686
|
+
attachments: m.kind === "user" || m.kind === "pending-steer" ? m.attachments : []
|
|
10566
10687
|
} : m));
|
|
10567
10688
|
const current = runRef.current;
|
|
10568
10689
|
if (!(current.kind === "streaming" && current.runId === result.runId)) attachToRun(result.runId, result.agentName ?? null);
|
|
@@ -10609,12 +10730,14 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10609
10730
|
});
|
|
10610
10731
|
(async () => {
|
|
10611
10732
|
if (autoGrantMachine && portalClient && portalStatus === "connected" && !portalClient.isGranted(agent.id)) try {
|
|
10612
|
-
|
|
10733
|
+
grantInFlightRef.current.add(agent.id);
|
|
10734
|
+
await portalClient.grantAgent(agent.id, null);
|
|
10613
10735
|
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}`,
|
|
10736
|
+
message: `Shared this machine with ${agent.name} for the import. The grant persists. Revoke anytime: skydive portal revoke --agent "${agent.name}"`,
|
|
10615
10737
|
durationMs: 1e4
|
|
10616
10738
|
});
|
|
10617
10739
|
} catch (err) {
|
|
10740
|
+
grantInFlightRef.current.delete(agent.id);
|
|
10618
10741
|
useStore.getState().showToast({
|
|
10619
10742
|
message: `Couldn't grant machine access automatically (${errorMessage(err)}). The agent will ask in chat instead.`,
|
|
10620
10743
|
variant: "warning"
|
|
@@ -10678,9 +10801,12 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10678
10801
|
if (!portalClient) throw new Error("portal is unavailable in this session");
|
|
10679
10802
|
const justEnabled = portal.status === "off";
|
|
10680
10803
|
if (justEnabled) portalClient.enable();
|
|
10804
|
+
grantInFlightRef.current.add(action.agentId);
|
|
10805
|
+
if (useStore.getState().portal.prompt?.agentId === action.agentId) setPortalPrompt(null);
|
|
10681
10806
|
try {
|
|
10682
|
-
await portalClient.grantAgent(action.agentId);
|
|
10807
|
+
await portalClient.grantAgent(action.agentId, action.conversationId);
|
|
10683
10808
|
} catch (grantErr) {
|
|
10809
|
+
grantInFlightRef.current.delete(action.agentId);
|
|
10684
10810
|
if (justEnabled) portalClient.disable();
|
|
10685
10811
|
throw grantErr;
|
|
10686
10812
|
}
|
|
@@ -10741,7 +10867,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10741
10867
|
appUrl,
|
|
10742
10868
|
updateCard,
|
|
10743
10869
|
portalClient,
|
|
10744
|
-
portal.status
|
|
10870
|
+
portal.status,
|
|
10871
|
+
setPortalPrompt
|
|
10745
10872
|
]);
|
|
10746
10873
|
const promptForCards = useCallback(() => {
|
|
10747
10874
|
const targets = itemsRef.current.filter(isActionableCard);
|
|
@@ -11066,7 +11193,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11066
11193
|
setItems((prev) => prev.map((m) => m.kind === "pending-steer" ? {
|
|
11067
11194
|
kind: "user",
|
|
11068
11195
|
id: m.id,
|
|
11069
|
-
text: m.text
|
|
11196
|
+
text: m.text,
|
|
11197
|
+
attachments: m.attachments
|
|
11070
11198
|
} : m));
|
|
11071
11199
|
useStore.getState().showToast({ message: "run cancelled" });
|
|
11072
11200
|
}, [rest]);
|
|
@@ -11244,7 +11372,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11244
11372
|
rest,
|
|
11245
11373
|
agentSelector: null,
|
|
11246
11374
|
conversationId: target,
|
|
11247
|
-
newConversation: false
|
|
11375
|
+
newConversation: false,
|
|
11376
|
+
defaultAgentSelector: null
|
|
11248
11377
|
}));
|
|
11249
11378
|
} catch (err) {
|
|
11250
11379
|
setItems((prev) => [...prev, {
|
|
@@ -11283,19 +11412,24 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11283
11412
|
if (!portalClient) return;
|
|
11284
11413
|
const agentName = agent.name;
|
|
11285
11414
|
setPortalPrompt(null);
|
|
11286
|
-
|
|
11415
|
+
grantInFlightRef.current.add(agent.id);
|
|
11416
|
+
portalClient.grantAgent(agent.id, conversationId).then(() => useStore.getState().showToast({
|
|
11287
11417
|
variant: "success",
|
|
11288
11418
|
message: `shared this machine with ${agentName}`
|
|
11289
|
-
})).catch((err) =>
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11419
|
+
})).catch((err) => {
|
|
11420
|
+
grantInFlightRef.current.delete(agent.id);
|
|
11421
|
+
setItems((prev) => [...prev, {
|
|
11422
|
+
kind: "error",
|
|
11423
|
+
id: crypto.randomUUID(),
|
|
11424
|
+
text: `couldn't share machine: ${errorDetail(err)}`
|
|
11425
|
+
}]);
|
|
11426
|
+
});
|
|
11294
11427
|
}, [
|
|
11295
11428
|
portalClient,
|
|
11296
11429
|
agent.id,
|
|
11297
11430
|
agent.name,
|
|
11298
|
-
setPortalPrompt
|
|
11431
|
+
setPortalPrompt,
|
|
11432
|
+
conversationId
|
|
11299
11433
|
]);
|
|
11300
11434
|
const declineGrant = useCallback(() => {
|
|
11301
11435
|
declinedRef.current.add(agent.id);
|
|
@@ -11535,6 +11669,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11535
11669
|
if (portal.grantedAgentIds.includes(agent.id)) return;
|
|
11536
11670
|
if (portal.declinedAgentIds.includes(agent.id)) return;
|
|
11537
11671
|
if (declinedRef.current.has(agent.id)) return;
|
|
11672
|
+
if (grantInFlightRef.current.has(agent.id)) return;
|
|
11538
11673
|
if (portal.prompt?.agentId === agent.id) return;
|
|
11539
11674
|
setPortalPrompt({
|
|
11540
11675
|
agentId: agent.id,
|
|
@@ -11558,6 +11693,14 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11558
11693
|
setPortalPrompt,
|
|
11559
11694
|
agentHost
|
|
11560
11695
|
]);
|
|
11696
|
+
useEffect(() => {
|
|
11697
|
+
for (const agentId of portal.grantedAgentIds) grantInFlightRef.current.delete(agentId);
|
|
11698
|
+
if (portal.prompt && portal.grantedAgentIds.includes(portal.prompt.agentId)) setPortalPrompt(null);
|
|
11699
|
+
}, [
|
|
11700
|
+
portal.grantedAgentIds,
|
|
11701
|
+
portal.prompt,
|
|
11702
|
+
setPortalPrompt
|
|
11703
|
+
]);
|
|
11561
11704
|
useKeyboard((key) => {
|
|
11562
11705
|
if (reviewOpen) {
|
|
11563
11706
|
if (key.name === "g" && key.ctrl) {
|
|
@@ -11899,13 +12042,11 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11899
12042
|
children: [!historyLoaded ? /* @__PURE__ */ jsx("text", {
|
|
11900
12043
|
fg: theme.muted,
|
|
11901
12044
|
children: "loading history…"
|
|
11902
|
-
}) : items.length === 0 ? /* @__PURE__ */
|
|
11903
|
-
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
" below."
|
|
11908
|
-
]
|
|
12045
|
+
}) : items.length === 0 ? /* @__PURE__ */ jsx(NewChatSplash, {
|
|
12046
|
+
agentName: agent.name,
|
|
12047
|
+
agentTitle: agent.title ?? null,
|
|
12048
|
+
width: reviewView && reviewPlacement === "side" ? Math.max(1, bodyWidth - reviewWidth - 1) : bodyWidth,
|
|
12049
|
+
height: Math.max(1, scrollHeight - 1)
|
|
11909
12050
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [transcriptWindow.hiddenCount > 0 ? /* @__PURE__ */ jsx("box", {
|
|
11910
12051
|
style: {
|
|
11911
12052
|
paddingLeft: 1,
|
|
@@ -12359,9 +12500,12 @@ function ToastView() {
|
|
|
12359
12500
|
|
|
12360
12501
|
//#endregion
|
|
12361
12502
|
//#region src/chat/tui/app.tsx
|
|
12362
|
-
function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation }) {
|
|
12503
|
+
function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation, defaultAgentSelector }) {
|
|
12363
12504
|
const renderer = useRenderer();
|
|
12364
12505
|
const screen = useStore((s) => s.screen);
|
|
12506
|
+
useEffect(() => {
|
|
12507
|
+
if (screen.kind === "chat") recordDefaultAgent(screen.agent.id);
|
|
12508
|
+
}, [screen]);
|
|
12365
12509
|
const chatTitle = useStore((s) => s.chatTitle);
|
|
12366
12510
|
useStore((s) => s.themeId);
|
|
12367
12511
|
const rest = useStore((s) => s.rest);
|
|
@@ -12444,7 +12588,8 @@ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath,
|
|
|
12444
12588
|
rest,
|
|
12445
12589
|
agentSelector,
|
|
12446
12590
|
conversationId,
|
|
12447
|
-
newConversation: newConversation || useStore.getState().seedPrompt !== null
|
|
12591
|
+
newConversation: newConversation || useStore.getState().seedPrompt !== null,
|
|
12592
|
+
defaultAgentSelector
|
|
12448
12593
|
});
|
|
12449
12594
|
if (cancelled) return;
|
|
12450
12595
|
goTo(screen);
|
|
@@ -12467,6 +12612,7 @@ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath,
|
|
|
12467
12612
|
agentSelector,
|
|
12468
12613
|
conversationId,
|
|
12469
12614
|
newConversation,
|
|
12615
|
+
defaultAgentSelector,
|
|
12470
12616
|
goTo,
|
|
12471
12617
|
setClients,
|
|
12472
12618
|
fail
|
|
@@ -13045,7 +13191,7 @@ function onRenderCommit(id, phase, actualDuration, baseDuration, startTime, comm
|
|
|
13045
13191
|
* module — and the heavy OpenTUI dependency graph it pulls in — is
|
|
13046
13192
|
* dynamically imported and never loaded on the Node management path.
|
|
13047
13193
|
*/
|
|
13048
|
-
async function runChat({ appUrl, sessionToken, authKind = "session", shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, seedPrompt }) {
|
|
13194
|
+
async function runChat({ appUrl, sessionToken, authKind = "session", shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, defaultAgentSelector, seedPrompt }) {
|
|
13049
13195
|
await registerGrammars();
|
|
13050
13196
|
const watchdog = startMemoryWatchdog();
|
|
13051
13197
|
installCrashHandler({ memoryProvider: () => watchdog.snapshot() });
|
|
@@ -13086,7 +13232,8 @@ async function runChat({ appUrl, sessionToken, authKind = "session", shareMachin
|
|
|
13086
13232
|
notifications,
|
|
13087
13233
|
agentSelector: agentSelector ?? null,
|
|
13088
13234
|
conversationId: conversationId ?? null,
|
|
13089
|
-
newConversation
|
|
13235
|
+
newConversation,
|
|
13236
|
+
defaultAgentSelector: defaultAgentSelector ?? null
|
|
13090
13237
|
})
|
|
13091
13238
|
}));
|
|
13092
13239
|
}
|