skydive-cli 0.1.0-beta.288 → 0.1.0-beta.297

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/dist/js/bin.mjs CHANGED
@@ -16,7 +16,7 @@ import zlib from "node:zlib";
16
16
  import os from "node:os";
17
17
 
18
18
  //#region package.json
19
- var version$1 = "0.1.0-beta.288";
19
+ var version$1 = "0.1.0-beta.297";
20
20
 
21
21
  //#endregion
22
22
  //#region src/types.ts
@@ -960,7 +960,7 @@ function createRestClient({ appUrl, sessionToken }) {
960
960
  const { agent } = await post(`/api/v1/agents/${encodeURIComponent(agentId)}`, { model }, updateAgentResponseSchema, "PATCH");
961
961
  return { model: agent.model ?? null };
962
962
  },
963
- listConversations: async ({ agentId, limit }) => {
963
+ listConversations: async ({ agentId, limit, onPage }) => {
964
964
  const all = [];
965
965
  const maxConversations = limit ?? 5e3;
966
966
  let cursor;
@@ -975,6 +975,7 @@ function createRestClient({ appUrl, sessionToken }) {
975
975
  const page = await get(`/api/v1/conversations?${params.toString()}`, listConversationsResponseSchema);
976
976
  all.push(...page.conversations);
977
977
  cursor = page.nextCursor ?? void 0;
978
+ onPage?.(limit ? all.slice(0, limit) : [...all]);
978
979
  } while (cursor && all.length < maxConversations);
979
980
  return limit ? all.slice(0, limit) : all;
980
981
  },
@@ -1022,7 +1023,12 @@ function createRestClient({ appUrl, sessionToken }) {
1022
1023
  }, sendResultSchema),
1023
1024
  activeRun: async ({ conversationId }) => {
1024
1025
  const { run } = await get(`/api/v1/chat/active-run?${new URLSearchParams({ conversationId }).toString()}`, activeRunResponseSchema);
1025
- return run;
1026
+ if (!run) return null;
1027
+ return {
1028
+ runId: run.runId,
1029
+ agentId: run.agentId ?? "",
1030
+ agentName: run.agentName ?? ""
1031
+ };
1026
1032
  },
1027
1033
  cancelRun: async ({ runId }) => {
1028
1034
  await post(`/api/v1/chat/runs/${encodeURIComponent(runId)}/cancel`, {}, z.object({ ok: z.boolean() }));
@@ -1186,7 +1192,11 @@ const uiMessagePartSchema = z.union([
1186
1192
  const uiMessageSchema = z.object({
1187
1193
  id: z.string(),
1188
1194
  role: z.string(),
1189
- parts: z.array(uiMessagePartSchema)
1195
+ parts: z.array(uiMessagePartSchema),
1196
+ metadata: z.object({ custom: z.object({
1197
+ agentId: z.string().nullish(),
1198
+ agentName: z.string().nullish()
1199
+ }).passthrough().optional() }).passthrough().optional()
1190
1200
  });
1191
1201
  const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nullable() });
1192
1202
  const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
@@ -1195,6 +1205,8 @@ const sendResultSchema = z.object({
1195
1205
  messageId: z.string().uuid().nullish(),
1196
1206
  conversationId: z.string().uuid(),
1197
1207
  isNewConversation: z.boolean(),
1208
+ agentId: z.string().nullish(),
1209
+ agentName: z.string().nullish(),
1198
1210
  steered: z.boolean().optional(),
1199
1211
  directive: z.object({ id: z.string() }).passthrough().optional()
1200
1212
  });
@@ -1209,7 +1221,11 @@ const finalizeResponseSchema = z.object({
1209
1221
  mediaType: z.string(),
1210
1222
  sizeBytes: z.number().nullable().optional()
1211
1223
  });
1212
- const activeRunResponseSchema = z.object({ run: z.object({ runId: z.string() }).nullable() });
1224
+ const activeRunResponseSchema = z.object({ run: z.object({
1225
+ runId: z.string(),
1226
+ agentId: z.string().nullish(),
1227
+ agentName: z.string().nullish()
1228
+ }).nullable() });
1213
1229
  const oauthConnectResponseSchema = z.object({ connectLink: z.string() }).passthrough();
1214
1230
  const externalOauthConnectResponseSchema = z.object({ authorizationUrl: z.string().optional() }).passthrough();
1215
1231
  const runStreamEventSchema = z.union([z.object({
@@ -2591,7 +2607,7 @@ const chatCommand = {
2591
2607
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
2592
2608
  process.exit(1);
2593
2609
  }
2594
- const { runChat } = await import("./boot-CWiCh_T9.mjs");
2610
+ const { runChat } = await import("./boot-DYzw1U0y.mjs");
2595
2611
  await runChat({
2596
2612
  appUrl,
2597
2613
  sessionToken: session.value.sessionToken,
@@ -3236,7 +3252,8 @@ const listCommand$1 = {
3236
3252
  }), argv.agent ?? null);
3237
3253
  const conversations = await client.listConversations({
3238
3254
  agentId: agent.id,
3239
- limit: argv.limit
3255
+ limit: argv.limit,
3256
+ onPage: null
3240
3257
  });
3241
3258
  if (argv.json) {
3242
3259
  output(argv, conversations);
@@ -3373,7 +3390,7 @@ const switchCommand = {
3373
3390
  printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
3374
3391
  process.exit(1);
3375
3392
  }
3376
- const { runWorkspacePicker } = await import("./boot-CWiCh_T9.mjs");
3393
+ const { runWorkspacePicker } = await import("./boot-DYzw1U0y.mjs");
3377
3394
  await runWorkspacePicker(session);
3378
3395
  return;
3379
3396
  }
@@ -1341,12 +1341,19 @@ function ConversationPickerScreen({ agent }) {
1341
1341
  try {
1342
1342
  const conversations = await rest.listConversations({
1343
1343
  agentId: agent.id,
1344
- limit: 50
1344
+ onPage: (partial) => {
1345
+ if (!cancelled) setState({
1346
+ kind: "ready",
1347
+ conversations: partial,
1348
+ complete: false
1349
+ });
1350
+ }
1345
1351
  });
1346
1352
  if (cancelled) return;
1347
1353
  setState({
1348
1354
  kind: "ready",
1349
- conversations
1355
+ conversations,
1356
+ complete: true
1350
1357
  });
1351
1358
  } catch (err) {
1352
1359
  if (cancelled) return;
@@ -1371,17 +1378,19 @@ function ConversationPickerScreen({ agent }) {
1371
1378
  return /* @__PURE__ */ jsx(ConversationList, {
1372
1379
  agent,
1373
1380
  conversations: state.conversations,
1381
+ complete: state.complete,
1374
1382
  onPick: goTo
1375
1383
  });
1376
1384
  }
1377
- function ConversationList({ agent, conversations, onPick }) {
1385
+ function ConversationList({ agent, conversations, complete, onPick }) {
1378
1386
  const { width, height } = useTerminalDimensions();
1379
1387
  const rest = useStore((s) => s.rest);
1380
1388
  const [query, setQuery] = useState("");
1381
1389
  const [highlight, setHighlight] = useState(0);
1382
- const [list, setList] = useState(conversations);
1390
+ const [removedIds, setRemovedIds] = useState(/* @__PURE__ */ new Set());
1383
1391
  const [confirmId, setConfirmId] = useState(null);
1384
1392
  const [error, setError] = useState(null);
1393
+ const list = conversations.filter((c) => !removedIds.has(c.id));
1385
1394
  const hits = fuzzyFilter(query, list, conversationKeys);
1386
1395
  const rows = [{ kind: "new" }, ...hits.map((hit) => ({
1387
1396
  kind: "conv",
@@ -1408,12 +1417,15 @@ function ConversationList({ agent, conversations, onPick }) {
1408
1417
  };
1409
1418
  const deleteConversation = (id) => {
1410
1419
  if (!rest) return;
1411
- const removed = list.find((c) => c.id === id);
1412
- if (!removed) return;
1420
+ if (!list.some((c) => c.id === id)) return;
1413
1421
  setConfirmId(null);
1414
- setList((prev) => prev.filter((c) => c.id !== id));
1422
+ setRemovedIds((prev) => new Set([...prev, id]));
1415
1423
  rest.deleteConversation({ conversationId: id }).catch((err) => {
1416
- setList((prev) => prev.some((c) => c.id === id) ? prev : [removed, ...prev]);
1424
+ setRemovedIds((prev) => {
1425
+ const next = new Set(prev);
1426
+ next.delete(id);
1427
+ return next;
1428
+ });
1417
1429
  setError(err instanceof Error ? err.message : String(err));
1418
1430
  });
1419
1431
  };
@@ -1478,6 +1490,7 @@ function ConversationList({ agent, conversations, onPick }) {
1478
1490
  hits.length,
1479
1491
  "/",
1480
1492
  list.length,
1493
+ complete ? "" : " (loading…)",
1481
1494
  " · ↑/↓ move · ↵ open · ctrl+d delete · esc back"
1482
1495
  ]
1483
1496
  }),
@@ -3014,16 +3027,23 @@ function str(chunk, key) {
3014
3027
  const value = chunk[key];
3015
3028
  return typeof value === "string" ? value : null;
3016
3029
  }
3017
- function applyChunk(items, chunk) {
3030
+ /**
3031
+ * `agentName` is the display name of the agent producing this run's stream.
3032
+ * It's stamped onto each assistant-origin item so a multi-agent conversation
3033
+ * can attribute live turns; pass null in a solo thread to leave items
3034
+ * un-attributed (the chat screen only renders the name when the thread has
3035
+ * more than one agent).
3036
+ */
3037
+ function applyChunk(items, chunk, agentName = null) {
3018
3038
  const type = str(chunk, "type");
3019
3039
  if (!type) return [...items];
3020
3040
  switch (type) {
3021
- case "text-start": return appendAssistantTextStart(items, str(chunk, "id") ?? cryptoId());
3041
+ case "text-start": return appendAssistantTextStart(items, str(chunk, "id") ?? cryptoId(), agentName);
3022
3042
  case "text-delta": {
3023
3043
  const id = str(chunk, "id");
3024
3044
  const delta = str(chunk, "delta") ?? str(chunk, "text") ?? "";
3025
3045
  if (!id || !delta) return [...items];
3026
- return appendAssistantTextDelta(items, id, delta);
3046
+ return appendAssistantTextDelta(items, id, delta, agentName);
3027
3047
  }
3028
3048
  case "text-end": {
3029
3049
  const id = str(chunk, "id");
@@ -3036,14 +3056,15 @@ function applyChunk(items, chunk) {
3036
3056
  kind: "reasoning",
3037
3057
  id: bubbleId,
3038
3058
  text: "",
3039
- done: false
3059
+ done: false,
3060
+ agentName
3040
3061
  }];
3041
3062
  }
3042
3063
  case "reasoning-delta": {
3043
3064
  const id = str(chunk, "id");
3044
3065
  const delta = str(chunk, "delta") ?? str(chunk, "text") ?? "";
3045
3066
  if (!id || !delta) return [...items];
3046
- return appendReasoningDelta(items, id, delta);
3067
+ return appendReasoningDelta(items, id, delta, agentName);
3047
3068
  }
3048
3069
  case "reasoning-end": {
3049
3070
  const id = str(chunk, "id");
@@ -3066,7 +3087,8 @@ function applyChunk(items, chunk) {
3066
3087
  inputText: "",
3067
3088
  input: null,
3068
3089
  output: null,
3069
- state: "streaming"
3090
+ state: "streaming",
3091
+ agentName
3070
3092
  }];
3071
3093
  }
3072
3094
  case "tool-input-delta": {
@@ -3095,7 +3117,8 @@ function applyChunk(items, chunk) {
3095
3117
  inputText: "",
3096
3118
  input,
3097
3119
  output: null,
3098
- state: "input-ready"
3120
+ state: "input-ready",
3121
+ agentName
3099
3122
  }];
3100
3123
  }
3101
3124
  case "tool-output-available": {
@@ -3152,7 +3175,8 @@ function applyChunk(items, chunk) {
3152
3175
  id,
3153
3176
  card,
3154
3177
  status: "idle",
3155
- detail: null
3178
+ detail: null,
3179
+ agentName
3156
3180
  };
3157
3181
  if (items.some((m) => m.kind === "card" && m.id === id)) return items.map((m) => m.kind === "card" && m.id === id ? next : m);
3158
3182
  return [...items, next];
@@ -3193,16 +3217,17 @@ function openTextIndex(items, id, kind) {
3193
3217
  }
3194
3218
  return -1;
3195
3219
  }
3196
- function appendAssistantTextStart(items, id) {
3220
+ function appendAssistantTextStart(items, id, agentName) {
3197
3221
  const bubbleId = freshTextId(items, id, "assistant-text");
3198
3222
  return [...items, {
3199
3223
  kind: "assistant-text",
3200
3224
  id: bubbleId,
3201
3225
  text: "",
3202
- done: false
3226
+ done: false,
3227
+ agentName
3203
3228
  }];
3204
3229
  }
3205
- function appendAssistantTextDelta(items, id, delta) {
3230
+ function appendAssistantTextDelta(items, id, delta, agentName) {
3206
3231
  const idx = openTextIndex(items, id, "assistant-text");
3207
3232
  if (idx === -1) {
3208
3233
  const bubbleId = freshTextId(items, id, "assistant-text");
@@ -3210,7 +3235,8 @@ function appendAssistantTextDelta(items, id, delta) {
3210
3235
  kind: "assistant-text",
3211
3236
  id: bubbleId,
3212
3237
  text: delta,
3213
- done: false
3238
+ done: false,
3239
+ agentName
3214
3240
  }];
3215
3241
  }
3216
3242
  return items.map((m, i) => i === idx && m.kind === "assistant-text" ? {
@@ -3218,7 +3244,7 @@ function appendAssistantTextDelta(items, id, delta) {
3218
3244
  text: m.text + delta
3219
3245
  } : m);
3220
3246
  }
3221
- function appendReasoningDelta(items, id, delta) {
3247
+ function appendReasoningDelta(items, id, delta, agentName) {
3222
3248
  const idx = openTextIndex(items, id, "reasoning");
3223
3249
  if (idx === -1) {
3224
3250
  const bubbleId = freshTextId(items, id, "reasoning");
@@ -3226,7 +3252,8 @@ function appendReasoningDelta(items, id, delta) {
3226
3252
  kind: "reasoning",
3227
3253
  id: bubbleId,
3228
3254
  text: delta,
3229
- done: false
3255
+ done: false,
3256
+ agentName
3230
3257
  }];
3231
3258
  }
3232
3259
  return items.map((m, i) => i === idx && m.kind === "reasoning" ? {
@@ -3256,6 +3283,59 @@ function cryptoId() {
3256
3283
  return crypto.randomUUID();
3257
3284
  }
3258
3285
 
3286
+ //#endregion
3287
+ //#region src/chat/tui/chat/attribution.ts
3288
+ /**
3289
+ * The agent that authored an item, or null for items with no agent (the user's
3290
+ * own turns, errors, the recap).
3291
+ */
3292
+ function itemAgentName(m) {
3293
+ return "agentName" in m ? m.agentName ?? null : null;
3294
+ }
3295
+ /**
3296
+ * Items that end an agent's turn: the human speaking or acting. The next
3297
+ * assistant item after one of these starts a new turn and is re-headed with its
3298
+ * author, even when the same agent replies again — in a multi-agent thread the
3299
+ * reader can't be asked to scan back past their own message to work out who is
3300
+ * answering.
3301
+ */
3302
+ function isTurnSeparator(m) {
3303
+ return m.kind === "user" || m.kind === "pending-steer" || m.kind === "shell";
3304
+ }
3305
+ /**
3306
+ * Every agent that has spoken in the transcript, plus the agent on screen.
3307
+ * Attribution renders only when this holds more than one name, mirroring web's
3308
+ * group-chat gate so a solo thread carries no attribution chrome at all.
3309
+ */
3310
+ function conversationAgentNames(items, onScreenAgentName) {
3311
+ const names = new Set([onScreenAgentName]);
3312
+ for (const item of items) {
3313
+ const name = itemAgentName(item);
3314
+ if (name) names.add(name);
3315
+ }
3316
+ return names;
3317
+ }
3318
+ /**
3319
+ * The agent-name label to render above `items[i]`, or null for no label. Every
3320
+ * assistant turn is headed with its author's name. Within one turn the items
3321
+ * (text, reasoning, tool calls) share the single header, so a turn isn't
3322
+ * re-labeled on each tool row; `error` and `recap` items are transparent and
3323
+ * don't break that grouping.
3324
+ */
3325
+ function agentLabelFor(items, i) {
3326
+ const current = items[i];
3327
+ const name = current ? itemAgentName(current) : null;
3328
+ if (!name) return null;
3329
+ for (let j = i - 1; j >= 0; j--) {
3330
+ const prevItem = items[j];
3331
+ if (!prevItem) continue;
3332
+ if (isTurnSeparator(prevItem)) return name;
3333
+ const prev = itemAgentName(prevItem);
3334
+ if (prev !== null) return prev === name ? null : name;
3335
+ }
3336
+ return name;
3337
+ }
3338
+
3259
3339
  //#endregion
3260
3340
  //#region src/chat/tui/chat/history.ts
3261
3341
  function isDynamicTool(part) {
@@ -3284,6 +3364,7 @@ function uiMessagesToItems(messages) {
3284
3364
  continue;
3285
3365
  }
3286
3366
  if (m.role === "assistant") {
3367
+ const agentName = m.metadata?.custom?.agentName ?? null;
3287
3368
  let textBuf = "";
3288
3369
  let textIdx = 0;
3289
3370
  for (const part of m.parts) {
@@ -3296,7 +3377,8 @@ function uiMessagesToItems(messages) {
3296
3377
  kind: "assistant-text",
3297
3378
  id: `${m.id}#t${textIdx++}`,
3298
3379
  text: textBuf,
3299
- done: true
3380
+ done: true,
3381
+ agentName
3300
3382
  });
3301
3383
  textBuf = "";
3302
3384
  }
@@ -3305,7 +3387,8 @@ function uiMessagesToItems(messages) {
3305
3387
  kind: "reasoning",
3306
3388
  id: `${m.id}#r${textIdx++}`,
3307
3389
  text: "text" in part && typeof part.text === "string" ? part.text : "",
3308
- done: true
3390
+ done: true,
3391
+ agentName
3309
3392
  });
3310
3393
  continue;
3311
3394
  }
@@ -3319,7 +3402,8 @@ function uiMessagesToItems(messages) {
3319
3402
  id: toolCallId ?? `${m.id}#c${textIdx++}`,
3320
3403
  card,
3321
3404
  status: "idle",
3322
- detail: null
3405
+ detail: null,
3406
+ agentName
3323
3407
  });
3324
3408
  }
3325
3409
  continue;
@@ -3339,7 +3423,8 @@ function uiMessagesToItems(messages) {
3339
3423
  kind: "ok",
3340
3424
  value: part.output
3341
3425
  } : null,
3342
- state
3426
+ state,
3427
+ agentName
3343
3428
  });
3344
3429
  }
3345
3430
  }
@@ -3347,7 +3432,8 @@ function uiMessagesToItems(messages) {
3347
3432
  kind: "assistant-text",
3348
3433
  id: `${m.id}#t${textIdx++}`,
3349
3434
  text: textBuf,
3350
- done: true
3435
+ done: true,
3436
+ agentName
3351
3437
  });
3352
3438
  }
3353
3439
  }
@@ -5155,10 +5241,11 @@ function ChatScreen({ agent, conversation }) {
5155
5241
  cancelled = true;
5156
5242
  };
5157
5243
  }, [rest, initialConversationId]);
5158
- const attachToRun = useCallback((runId) => {
5244
+ const attachToRun = useCallback((runId, runAgentName) => {
5159
5245
  if (!rest) return;
5160
5246
  const abort = new AbortController();
5161
5247
  const responsePreview = createResponsePreview();
5248
+ const streamAgentName = runAgentName ?? agent.name;
5162
5249
  setRun({
5163
5250
  kind: "streaming",
5164
5251
  runId,
@@ -5170,7 +5257,7 @@ function ChatScreen({ agent, conversation }) {
5170
5257
  onEvent: (event) => {
5171
5258
  if (event.kind === "chunk") {
5172
5259
  responsePreview.addChunk(event.chunk);
5173
- setItems((prev) => applyChunk(prev, event.chunk));
5260
+ setItems((prev) => applyChunk(prev, event.chunk, streamAgentName));
5174
5261
  return;
5175
5262
  }
5176
5263
  const errorText = event.error;
@@ -5208,6 +5295,7 @@ function ChatScreen({ agent, conversation }) {
5208
5295
  agent.name,
5209
5296
  agentHost
5210
5297
  ]);
5298
+ const multiAgent = useMemo(() => conversationAgentNames(items, agent.name), [items, agent.name]).size > 1;
5211
5299
  useEffect(() => {
5212
5300
  if (!rest || !conversationId) return;
5213
5301
  const abort = new AbortController();
@@ -5231,7 +5319,7 @@ function ChatScreen({ agent, conversation }) {
5231
5319
  try {
5232
5320
  const active = await rest.activeRun({ conversationId });
5233
5321
  if (cancelled || !active) return;
5234
- attachToRun(active.runId);
5322
+ attachToRun(active.runId, active.agentName || null);
5235
5323
  } catch (_error) {}
5236
5324
  })();
5237
5325
  return () => {
@@ -5298,10 +5386,10 @@ function ChatScreen({ agent, conversation }) {
5298
5386
  text: trimmed
5299
5387
  } : m));
5300
5388
  const current = runRef.current;
5301
- if (!(current.kind === "streaming" && current.runId === result.runId)) attachToRun(result.runId);
5389
+ if (!(current.kind === "streaming" && current.runId === result.runId)) attachToRun(result.runId, result.agentName ?? null);
5302
5390
  return;
5303
5391
  }
5304
- attachToRun(result.runId);
5392
+ attachToRun(result.runId, result.agentName ?? null);
5305
5393
  } catch (err) {
5306
5394
  setItems((prev) => [...prev.filter((m) => m.id !== optimisticId), {
5307
5395
  kind: "error",
@@ -6032,7 +6120,22 @@ function ChatScreen({ agent, conversation }) {
6032
6120
  agent.name,
6033
6121
  " below."
6034
6122
  ]
6035
- }) : /* @__PURE__ */ jsx(Fragment, { children: items.map((item) => /* @__PURE__ */ jsx(RenderItem, { item }, item.id)) }), run.kind !== "idle" ? /* @__PURE__ */ jsx(ActivityRow, { label: run.kind === "sending" ? "sending" : "working" }) : null]
6123
+ }) : /* @__PURE__ */ jsx(Fragment, { children: items.map((item, i) => {
6124
+ const label = multiAgent ? agentLabelFor(items, i) : null;
6125
+ return /* @__PURE__ */ jsxs("box", {
6126
+ style: { flexDirection: "column" },
6127
+ children: [label ? /* @__PURE__ */ jsx("box", {
6128
+ style: {
6129
+ paddingLeft: 3,
6130
+ paddingRight: 1
6131
+ },
6132
+ children: /* @__PURE__ */ jsx("text", {
6133
+ fg: theme.muted,
6134
+ children: /* @__PURE__ */ jsx("b", { children: label })
6135
+ })
6136
+ }) : null, /* @__PURE__ */ jsx(RenderItem, { item })]
6137
+ }, item.id);
6138
+ }) }), run.kind !== "idle" ? /* @__PURE__ */ jsx(ActivityRow, { label: run.kind === "sending" ? "sending" : "working" }) : null]
6036
6139
  })
6037
6140
  }),
6038
6141
  pendingVisible ? /* @__PURE__ */ jsxs("box", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.1.0-beta.288",
3
+ "version": "0.1.0-beta.297",
4
4
  "description": "Skydive CLI — manage AI agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",