skydive-cli 0.4.1-beta.6 → 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-B48inW5D.mjs → boot-BuxaHgFi.mjs} +241 -74
- package/dist/js/client-BuU34IVE.mjs +5 -0
- package/dist/js/{client-mykp1DVb.mjs → client-DabRpc_T.mjs} +187 -118
- package/dist/js/{daemon-CfbpCjAw.mjs → daemon-CYYE2BTu.mjs} +179 -28
- package/dist/js/{daemon-D4ZX3wNG.mjs → daemon-Dk5LeS1S.mjs} +3 -2
- package/dist/js/{daemon-client-6xtta_n2.mjs → daemon-client-3A9afTFC.mjs} +35 -10
- 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-ZV88PqDu.mjs → print-share-CKLPmsg0.mjs} +5 -2
- package/dist/js/{profiler-94qrV53c.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-NBj0KZSP.mjs +0 -4
- package/dist/js/daemon-client-BHehCLrO.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";
|
|
@@ -109,6 +110,7 @@ const initialPortalUi = {
|
|
|
109
110
|
machineName: null,
|
|
110
111
|
error: null,
|
|
111
112
|
grantedAgentIds: [],
|
|
113
|
+
declinedAgentIds: [],
|
|
112
114
|
prompt: null
|
|
113
115
|
};
|
|
114
116
|
function initialChatTitle(screen) {
|
|
@@ -181,12 +183,13 @@ const useStore = create((set, get) => ({
|
|
|
181
183
|
chatTitle: null
|
|
182
184
|
}),
|
|
183
185
|
setPortalClient: (portalClient) => set({ portalClient }),
|
|
184
|
-
syncPortal: ({ status, machineName, error, grantedAgentIds }) => set((state) => ({ portal: {
|
|
186
|
+
syncPortal: ({ status, machineName, error, grantedAgentIds, declinedAgentIds }) => set((state) => ({ portal: {
|
|
185
187
|
...state.portal,
|
|
186
188
|
status,
|
|
187
189
|
machineName,
|
|
188
190
|
error,
|
|
189
|
-
grantedAgentIds
|
|
191
|
+
grantedAgentIds,
|
|
192
|
+
declinedAgentIds
|
|
190
193
|
} })),
|
|
191
194
|
setPortalPrompt: (prompt) => set((state) => ({ portal: {
|
|
192
195
|
...state.portal,
|
|
@@ -210,7 +213,17 @@ const useStore = create((set, get) => ({
|
|
|
210
213
|
/**
|
|
211
214
|
* Decide the screen the chat TUI boots into.
|
|
212
215
|
*
|
|
213
|
-
* 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.
|
|
214
227
|
*
|
|
215
228
|
* With `--agent` (`skydive chat --agent grace`, no `-p`) we resolve it
|
|
216
229
|
* against the org roster and open that agent's conversation list, skipping
|
|
@@ -226,20 +239,31 @@ const useStore = create((set, get) => ({
|
|
|
226
239
|
* `--agent`. When both are given, the conversation wins (it pins the agent
|
|
227
240
|
* anyway).
|
|
228
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
|
+
*
|
|
229
249
|
* Kept out of the React component so it's unit-testable under Node (the
|
|
230
250
|
* component itself can't render without Bun + OpenTUI).
|
|
231
251
|
*/
|
|
232
|
-
|
|
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 }) {
|
|
233
258
|
if (conversationId) {
|
|
234
|
-
const
|
|
259
|
+
const conversation = await rest.getConversation({ conversationId }).catch((err) => {
|
|
235
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.`);
|
|
236
261
|
throw err;
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
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
|
+
});
|
|
243
267
|
return {
|
|
244
268
|
kind: "chat",
|
|
245
269
|
agent,
|
|
@@ -260,26 +284,59 @@ async function resolveInitialScreen({ rest, agentSelector, conversationId, newCo
|
|
|
260
284
|
}
|
|
261
285
|
};
|
|
262
286
|
}
|
|
263
|
-
if (!agentSelector && !newConversation) return { kind: "agent-picker" };
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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 {
|
|
272
305
|
kind: "chat",
|
|
273
|
-
agent
|
|
306
|
+
agent,
|
|
274
307
|
conversation: {
|
|
275
308
|
kind: "new",
|
|
276
|
-
agentId:
|
|
309
|
+
agentId: agent.id,
|
|
277
310
|
nonce: crypto.randomUUID()
|
|
278
311
|
}
|
|
279
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);
|
|
280
327
|
}
|
|
281
|
-
const agent = resolveAgent(
|
|
282
|
-
|
|
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 {
|
|
283
340
|
kind: "chat",
|
|
284
341
|
agent,
|
|
285
342
|
conversation: {
|
|
@@ -288,10 +345,17 @@ async function resolveInitialScreen({ rest, agentSelector, conversationId, newCo
|
|
|
288
345
|
nonce: crypto.randomUUID()
|
|
289
346
|
}
|
|
290
347
|
};
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
+
}
|
|
295
359
|
}
|
|
296
360
|
|
|
297
361
|
//#endregion
|
|
@@ -317,21 +381,28 @@ function createPortalClient(opts) {
|
|
|
317
381
|
if (portalDaemonDisabled()) {
|
|
318
382
|
const client = new PortalClient({
|
|
319
383
|
appUrl: opts.appUrl,
|
|
320
|
-
|
|
384
|
+
credentials: () => ({
|
|
385
|
+
sessionToken: opts.sessionToken,
|
|
386
|
+
deviceToken: null
|
|
387
|
+
}),
|
|
321
388
|
resolveCwd: () => process.cwd(),
|
|
322
389
|
onState: (state) => opts.onState({
|
|
323
390
|
status: state.status,
|
|
324
391
|
machineName: state.machineName,
|
|
325
392
|
friendlyName: state.friendlyName,
|
|
326
393
|
error: state.error,
|
|
327
|
-
grantedAgentIds: state.grantedAgentIds
|
|
394
|
+
grantedAgentIds: state.grantedAgentIds,
|
|
395
|
+
declinedAgentIds: []
|
|
328
396
|
})
|
|
329
397
|
});
|
|
330
398
|
return {
|
|
331
399
|
start: () => Promise.resolve(),
|
|
332
400
|
enable: () => client.enable(),
|
|
333
401
|
disable: () => client.disable(),
|
|
334
|
-
grantAgent: (agentId) => client.grantAgent(agentId),
|
|
402
|
+
grantAgent: (agentId, conversationId) => client.grantAgent(agentId, conversationId),
|
|
403
|
+
decline: (_agentId, declined) => {
|
|
404
|
+
if (declined && client.grantedAgentIds().length === 0) client.disable();
|
|
405
|
+
},
|
|
335
406
|
isGranted: (agentId) => client.isGranted(agentId),
|
|
336
407
|
bind: () => {},
|
|
337
408
|
dispose: () => client.dispose()
|
|
@@ -370,7 +441,8 @@ function usePortal({ appUrl, sessionToken, shareMachine, enabled = true }) {
|
|
|
370
441
|
status: state.status,
|
|
371
442
|
machineName: state.machineName,
|
|
372
443
|
error: state.error,
|
|
373
|
-
grantedAgentIds: state.grantedAgentIds
|
|
444
|
+
grantedAgentIds: state.grantedAgentIds,
|
|
445
|
+
declinedAgentIds: state.declinedAgentIds
|
|
374
446
|
})
|
|
375
447
|
});
|
|
376
448
|
setPortalClient(client);
|
|
@@ -4519,20 +4591,23 @@ function RenderItem({ item }) {
|
|
|
4519
4591
|
children: [item.text ? /* @__PURE__ */ jsx("text", {
|
|
4520
4592
|
fg: theme.user,
|
|
4521
4593
|
children: item.text
|
|
4522
|
-
}) : null, item.attachments
|
|
4594
|
+
}) : null, item.attachments.length > 0 ? /* @__PURE__ */ jsxs("text", {
|
|
4523
4595
|
fg: theme.muted,
|
|
4524
4596
|
children: ["⎘ ", item.attachments.join(" · ")]
|
|
4525
4597
|
}) : null]
|
|
4526
4598
|
});
|
|
4527
|
-
case "pending-steer": return /* @__PURE__ */
|
|
4599
|
+
case "pending-steer": return /* @__PURE__ */ jsxs(Row, {
|
|
4528
4600
|
barColor: theme.dim,
|
|
4529
|
-
children: /* @__PURE__ */ jsxs("text", {
|
|
4601
|
+
children: [/* @__PURE__ */ jsxs("text", {
|
|
4530
4602
|
fg: theme.muted,
|
|
4531
4603
|
children: [item.text, /* @__PURE__ */ jsx("span", {
|
|
4532
4604
|
fg: theme.dim,
|
|
4533
4605
|
children: " · steering… (esc to cancel)"
|
|
4534
4606
|
})]
|
|
4535
|
-
})
|
|
4607
|
+
}), item.attachments.length > 0 ? /* @__PURE__ */ jsxs("text", {
|
|
4608
|
+
fg: theme.dim,
|
|
4609
|
+
children: ["⎘ ", item.attachments.join(" · ")]
|
|
4610
|
+
}) : null]
|
|
4536
4611
|
});
|
|
4537
4612
|
case "assistant-text":
|
|
4538
4613
|
if (item.done) return /* @__PURE__ */ jsx(Row, {
|
|
@@ -5267,7 +5342,8 @@ function applyChunk(items, chunk, agentName = null) {
|
|
|
5267
5342
|
return items.map((m) => m.kind === "pending-steer" && ids.has(m.id) ? {
|
|
5268
5343
|
kind: "user",
|
|
5269
5344
|
id: m.id,
|
|
5270
|
-
text: m.text
|
|
5345
|
+
text: m.text,
|
|
5346
|
+
attachments: m.attachments
|
|
5271
5347
|
} : m);
|
|
5272
5348
|
}
|
|
5273
5349
|
case "data-anyone-render-spec": {
|
|
@@ -5508,7 +5584,7 @@ function uiMessagesToItems(messages) {
|
|
|
5508
5584
|
kind: "user",
|
|
5509
5585
|
id: m.id,
|
|
5510
5586
|
text,
|
|
5511
|
-
|
|
5587
|
+
attachments
|
|
5512
5588
|
});
|
|
5513
5589
|
continue;
|
|
5514
5590
|
}
|
|
@@ -6009,6 +6085,58 @@ function createResponsePreview() {
|
|
|
6009
6085
|
};
|
|
6010
6086
|
}
|
|
6011
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
|
+
|
|
6012
6140
|
//#endregion
|
|
6013
6141
|
//#region src/chat/tui/prompt-history.ts
|
|
6014
6142
|
/**
|
|
@@ -10149,6 +10277,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10149
10277
|
const portalClient = useStore((s) => s.portalClient);
|
|
10150
10278
|
const setPortalPrompt = useStore((s) => s.setPortalPrompt);
|
|
10151
10279
|
const declinedRef = useRef(/* @__PURE__ */ new Set());
|
|
10280
|
+
const grantInFlightRef = useRef(/* @__PURE__ */ new Set());
|
|
10152
10281
|
const grantPrompt = portal.prompt && portal.prompt.agentId === agent.id ? portal.prompt : null;
|
|
10153
10282
|
const isShared = portal.status !== "off";
|
|
10154
10283
|
const initialConversationId = isNewConversation(conversation) ? null : conversation.id;
|
|
@@ -10426,7 +10555,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10426
10555
|
setItems((prev) => prev.map((m) => m.kind === "pending-steer" ? {
|
|
10427
10556
|
kind: "user",
|
|
10428
10557
|
id: m.id,
|
|
10429
|
-
text: m.text
|
|
10558
|
+
text: m.text,
|
|
10559
|
+
attachments: m.attachments
|
|
10430
10560
|
} : m));
|
|
10431
10561
|
setRun({ kind: "idle" });
|
|
10432
10562
|
agentHost.transition({
|
|
@@ -10503,7 +10633,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10503
10633
|
kind: "user",
|
|
10504
10634
|
id: optimisticId,
|
|
10505
10635
|
text: trimmed,
|
|
10506
|
-
|
|
10636
|
+
attachments: stagedNames
|
|
10507
10637
|
}]);
|
|
10508
10638
|
const wasStreaming = runRef.current.kind === "streaming";
|
|
10509
10639
|
if (!wasStreaming) setRun({
|
|
@@ -10552,7 +10682,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10552
10682
|
if (directiveId) setItems((prev) => prev.map((m) => m.id === optimisticId ? {
|
|
10553
10683
|
kind: "pending-steer",
|
|
10554
10684
|
id: directiveId,
|
|
10555
|
-
text: trimmed
|
|
10685
|
+
text: trimmed,
|
|
10686
|
+
attachments: m.kind === "user" || m.kind === "pending-steer" ? m.attachments : []
|
|
10556
10687
|
} : m));
|
|
10557
10688
|
const current = runRef.current;
|
|
10558
10689
|
if (!(current.kind === "streaming" && current.runId === result.runId)) attachToRun(result.runId, result.agentName ?? null);
|
|
@@ -10599,12 +10730,14 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10599
10730
|
});
|
|
10600
10731
|
(async () => {
|
|
10601
10732
|
if (autoGrantMachine && portalClient && portalStatus === "connected" && !portalClient.isGranted(agent.id)) try {
|
|
10602
|
-
|
|
10733
|
+
grantInFlightRef.current.add(agent.id);
|
|
10734
|
+
await portalClient.grantAgent(agent.id, null);
|
|
10603
10735
|
useStore.getState().showToast({
|
|
10604
|
-
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}"`,
|
|
10605
10737
|
durationMs: 1e4
|
|
10606
10738
|
});
|
|
10607
10739
|
} catch (err) {
|
|
10740
|
+
grantInFlightRef.current.delete(agent.id);
|
|
10608
10741
|
useStore.getState().showToast({
|
|
10609
10742
|
message: `Couldn't grant machine access automatically (${errorMessage(err)}). The agent will ask in chat instead.`,
|
|
10610
10743
|
variant: "warning"
|
|
@@ -10668,9 +10801,12 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10668
10801
|
if (!portalClient) throw new Error("portal is unavailable in this session");
|
|
10669
10802
|
const justEnabled = portal.status === "off";
|
|
10670
10803
|
if (justEnabled) portalClient.enable();
|
|
10804
|
+
grantInFlightRef.current.add(action.agentId);
|
|
10805
|
+
if (useStore.getState().portal.prompt?.agentId === action.agentId) setPortalPrompt(null);
|
|
10671
10806
|
try {
|
|
10672
|
-
await portalClient.grantAgent(action.agentId);
|
|
10807
|
+
await portalClient.grantAgent(action.agentId, action.conversationId);
|
|
10673
10808
|
} catch (grantErr) {
|
|
10809
|
+
grantInFlightRef.current.delete(action.agentId);
|
|
10674
10810
|
if (justEnabled) portalClient.disable();
|
|
10675
10811
|
throw grantErr;
|
|
10676
10812
|
}
|
|
@@ -10731,7 +10867,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10731
10867
|
appUrl,
|
|
10732
10868
|
updateCard,
|
|
10733
10869
|
portalClient,
|
|
10734
|
-
portal.status
|
|
10870
|
+
portal.status,
|
|
10871
|
+
setPortalPrompt
|
|
10735
10872
|
]);
|
|
10736
10873
|
const promptForCards = useCallback(() => {
|
|
10737
10874
|
const targets = itemsRef.current.filter(isActionableCard);
|
|
@@ -11056,7 +11193,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11056
11193
|
setItems((prev) => prev.map((m) => m.kind === "pending-steer" ? {
|
|
11057
11194
|
kind: "user",
|
|
11058
11195
|
id: m.id,
|
|
11059
|
-
text: m.text
|
|
11196
|
+
text: m.text,
|
|
11197
|
+
attachments: m.attachments
|
|
11060
11198
|
} : m));
|
|
11061
11199
|
useStore.getState().showToast({ message: "run cancelled" });
|
|
11062
11200
|
}, [rest]);
|
|
@@ -11234,7 +11372,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11234
11372
|
rest,
|
|
11235
11373
|
agentSelector: null,
|
|
11236
11374
|
conversationId: target,
|
|
11237
|
-
newConversation: false
|
|
11375
|
+
newConversation: false,
|
|
11376
|
+
defaultAgentSelector: null
|
|
11238
11377
|
}));
|
|
11239
11378
|
} catch (err) {
|
|
11240
11379
|
setItems((prev) => [...prev, {
|
|
@@ -11255,6 +11394,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11255
11394
|
}
|
|
11256
11395
|
if (portal.status === "off") {
|
|
11257
11396
|
declinedRef.current.delete(agent.id);
|
|
11397
|
+
portalClient.decline(agent.id, false);
|
|
11258
11398
|
portalClient.enable();
|
|
11259
11399
|
if (conversationId) portalClient.bind(conversationId, shellSessionRef.current?.cwd ?? process.cwd());
|
|
11260
11400
|
} else {
|
|
@@ -11272,25 +11412,35 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11272
11412
|
if (!portalClient) return;
|
|
11273
11413
|
const agentName = agent.name;
|
|
11274
11414
|
setPortalPrompt(null);
|
|
11275
|
-
|
|
11415
|
+
grantInFlightRef.current.add(agent.id);
|
|
11416
|
+
portalClient.grantAgent(agent.id, conversationId).then(() => useStore.getState().showToast({
|
|
11276
11417
|
variant: "success",
|
|
11277
11418
|
message: `shared this machine with ${agentName}`
|
|
11278
|
-
})).catch((err) =>
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
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
|
+
});
|
|
11283
11427
|
}, [
|
|
11284
11428
|
portalClient,
|
|
11285
11429
|
agent.id,
|
|
11286
11430
|
agent.name,
|
|
11287
|
-
setPortalPrompt
|
|
11431
|
+
setPortalPrompt,
|
|
11432
|
+
conversationId
|
|
11288
11433
|
]);
|
|
11289
11434
|
const declineGrant = useCallback(() => {
|
|
11290
11435
|
declinedRef.current.add(agent.id);
|
|
11436
|
+
portalClient?.decline(agent.id, true);
|
|
11291
11437
|
setPortalPrompt(null);
|
|
11292
11438
|
useStore.getState().showToast({ message: "kept this machine private" });
|
|
11293
|
-
}, [
|
|
11439
|
+
}, [
|
|
11440
|
+
portalClient,
|
|
11441
|
+
agent.id,
|
|
11442
|
+
setPortalPrompt
|
|
11443
|
+
]);
|
|
11294
11444
|
const openPortalAccess = useCallback(() => {
|
|
11295
11445
|
if (!portalClient) {
|
|
11296
11446
|
useStore.getState().showToast({ message: useStore.getState().authKind === "api-key" ? "local access needs a browser session; run `skydive auth login --web`" : "local access is unavailable in this session" });
|
|
@@ -11299,6 +11449,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11299
11449
|
switch (portal.status) {
|
|
11300
11450
|
case "off":
|
|
11301
11451
|
declinedRef.current.delete(agent.id);
|
|
11452
|
+
portalClient.decline(agent.id, false);
|
|
11302
11453
|
portalClient.enable();
|
|
11303
11454
|
useStore.getState().showToast({ message: "enabling local access on this machine…" });
|
|
11304
11455
|
break;
|
|
@@ -11311,6 +11462,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11311
11462
|
break;
|
|
11312
11463
|
}
|
|
11313
11464
|
declinedRef.current.delete(agent.id);
|
|
11465
|
+
portalClient.decline(agent.id, false);
|
|
11314
11466
|
setPortalPrompt({
|
|
11315
11467
|
agentId: agent.id,
|
|
11316
11468
|
agentName: agent.name
|
|
@@ -11515,7 +11667,9 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11515
11667
|
useEffect(() => {
|
|
11516
11668
|
if (!portalClient || portal.status !== "connected") return;
|
|
11517
11669
|
if (portal.grantedAgentIds.includes(agent.id)) return;
|
|
11670
|
+
if (portal.declinedAgentIds.includes(agent.id)) return;
|
|
11518
11671
|
if (declinedRef.current.has(agent.id)) return;
|
|
11672
|
+
if (grantInFlightRef.current.has(agent.id)) return;
|
|
11519
11673
|
if (portal.prompt?.agentId === agent.id) return;
|
|
11520
11674
|
setPortalPrompt({
|
|
11521
11675
|
agentId: agent.id,
|
|
@@ -11532,12 +11686,21 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11532
11686
|
portalClient,
|
|
11533
11687
|
portal.status,
|
|
11534
11688
|
portal.grantedAgentIds,
|
|
11689
|
+
portal.declinedAgentIds,
|
|
11535
11690
|
portal.prompt,
|
|
11536
11691
|
agent.id,
|
|
11537
11692
|
agent.name,
|
|
11538
11693
|
setPortalPrompt,
|
|
11539
11694
|
agentHost
|
|
11540
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
|
+
]);
|
|
11541
11704
|
useKeyboard((key) => {
|
|
11542
11705
|
if (reviewOpen) {
|
|
11543
11706
|
if (key.name === "g" && key.ctrl) {
|
|
@@ -11879,13 +12042,11 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11879
12042
|
children: [!historyLoaded ? /* @__PURE__ */ jsx("text", {
|
|
11880
12043
|
fg: theme.muted,
|
|
11881
12044
|
children: "loading history…"
|
|
11882
|
-
}) : items.length === 0 ? /* @__PURE__ */
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
" below."
|
|
11888
|
-
]
|
|
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)
|
|
11889
12050
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [transcriptWindow.hiddenCount > 0 ? /* @__PURE__ */ jsx("box", {
|
|
11890
12051
|
style: {
|
|
11891
12052
|
paddingLeft: 1,
|
|
@@ -12339,9 +12500,12 @@ function ToastView() {
|
|
|
12339
12500
|
|
|
12340
12501
|
//#endregion
|
|
12341
12502
|
//#region src/chat/tui/app.tsx
|
|
12342
|
-
function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation }) {
|
|
12503
|
+
function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation, defaultAgentSelector }) {
|
|
12343
12504
|
const renderer = useRenderer();
|
|
12344
12505
|
const screen = useStore((s) => s.screen);
|
|
12506
|
+
useEffect(() => {
|
|
12507
|
+
if (screen.kind === "chat") recordDefaultAgent(screen.agent.id);
|
|
12508
|
+
}, [screen]);
|
|
12345
12509
|
const chatTitle = useStore((s) => s.chatTitle);
|
|
12346
12510
|
useStore((s) => s.themeId);
|
|
12347
12511
|
const rest = useStore((s) => s.rest);
|
|
@@ -12424,7 +12588,8 @@ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath,
|
|
|
12424
12588
|
rest,
|
|
12425
12589
|
agentSelector,
|
|
12426
12590
|
conversationId,
|
|
12427
|
-
newConversation: newConversation || useStore.getState().seedPrompt !== null
|
|
12591
|
+
newConversation: newConversation || useStore.getState().seedPrompt !== null,
|
|
12592
|
+
defaultAgentSelector
|
|
12428
12593
|
});
|
|
12429
12594
|
if (cancelled) return;
|
|
12430
12595
|
goTo(screen);
|
|
@@ -12447,6 +12612,7 @@ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath,
|
|
|
12447
12612
|
agentSelector,
|
|
12448
12613
|
conversationId,
|
|
12449
12614
|
newConversation,
|
|
12615
|
+
defaultAgentSelector,
|
|
12450
12616
|
goTo,
|
|
12451
12617
|
setClients,
|
|
12452
12618
|
fail
|
|
@@ -13025,7 +13191,7 @@ function onRenderCommit(id, phase, actualDuration, baseDuration, startTime, comm
|
|
|
13025
13191
|
* module — and the heavy OpenTUI dependency graph it pulls in — is
|
|
13026
13192
|
* dynamically imported and never loaded on the Node management path.
|
|
13027
13193
|
*/
|
|
13028
|
-
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 }) {
|
|
13029
13195
|
await registerGrammars();
|
|
13030
13196
|
const watchdog = startMemoryWatchdog();
|
|
13031
13197
|
installCrashHandler({ memoryProvider: () => watchdog.snapshot() });
|
|
@@ -13066,7 +13232,8 @@ async function runChat({ appUrl, sessionToken, authKind = "session", shareMachin
|
|
|
13066
13232
|
notifications,
|
|
13067
13233
|
agentSelector: agentSelector ?? null,
|
|
13068
13234
|
conversationId: conversationId ?? null,
|
|
13069
|
-
newConversation
|
|
13235
|
+
newConversation,
|
|
13236
|
+
defaultAgentSelector: defaultAgentSelector ?? null
|
|
13070
13237
|
})
|
|
13071
13238
|
}));
|
|
13072
13239
|
}
|