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.
@@ -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-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-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-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, 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;
@@ -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
- ...stagedNames.length > 0 ? { attachments: stagedNames } : {}
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
- await portalClient.grantAgent(agent.id);
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
- portalClient.grantAgent(agent.id).then(() => useStore.getState().showToast({
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) => setItems((prev) => [...prev, {
11290
- kind: "error",
11291
- id: crypto.randomUUID(),
11292
- text: `couldn't share machine: ${errorDetail(err)}`
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__ */ jsxs("text", {
11903
- fg: theme.muted,
11904
- children: [
11905
- "say hello to ",
11906
- agent.name,
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
  }
@@ -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 };