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.
@@ -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-DHQhmoSF.mjs";
3
- import { B as saveTheme, D as getSavedTheme, E as getReviewStateDir, L as resolveWebUrl, 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 } from "./print-CbayCa87.mjs";
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-BY2nADw5.mjs";
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-Dd5sMXPv.mjs";
8
- import "./daemon-C6yadKp2.mjs";
9
- import { t as SandboxStream } from "./client-Cn2af31H.mjs";
10
- import { t as PortalDaemonClient } from "./daemon-client-Rr6PJvRg.mjs";
11
- import { t as runRawPtyPassthrough } from "./raw-pty-B6mAroiI.mjs";
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, exactly as before.
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
- async function resolveInitialScreen({ rest, agentSelector, conversationId, newConversation }) {
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 [conversation, agents] = await Promise.all([rest.getConversation({ conversationId }).catch((err) => {
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
- }), rest.listAgents({
240
- scope: "org",
241
- onPage: null
242
- })]);
243
- const agent = agents.find((a) => a.id === conversation.agentId);
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
- const agents = await rest.listAgents({
267
- scope: "org",
268
- onPage: null
269
- });
270
- if (!agentSelector && newConversation) {
271
- const only = agents.length === 1 ? agents[0] : null;
272
- if (!only) return { kind: "agent-picker" };
273
- return {
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: only,
306
+ agent,
276
307
  conversation: {
277
308
  kind: "new",
278
- agentId: only.id,
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(agents, agentSelector);
284
- if (newConversation) return {
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
- return {
294
- kind: "conversation-picker",
295
- agent
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 && item.attachments.length > 0 ? /* @__PURE__ */ jsxs("text", {
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__ */ jsx(Row, {
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
- ...attachments.length > 0 ? { attachments } : {}
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 atBottom = box.scrollTop + viewport >= box.scrollHeight - 1;
10280
- setScrolledUp((prev) => prev === !atBottom ? prev : !atBottom);
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
- ...stagedNames.length > 0 ? { attachments: stagedNames } : {}
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
- await portalClient.grantAgent(agent.id);
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
- portalClient.grantAgent(agent.id).then(() => useStore.getState().showToast({
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) => setItems((prev) => [...prev, {
11290
- kind: "error",
11291
- id: crypto.randomUUID(),
11292
- text: `couldn't share machine: ${errorDetail(err)}`
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__ */ jsxs("text", {
11903
- fg: theme.muted,
11904
- children: [
11905
- "say hello to ",
11906
- agent.name,
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
  }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { t as PortalClient } from "./client-DabRpc_T.mjs";
3
+ import "./api-DG5W6iwx.mjs";
4
+
5
+ export { PortalClient };