skydive-cli 0.1.0-beta.122 → 0.1.0-beta.180
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 +8 -7
- package/dist/js/bin.mjs +263 -62
- package/dist/js/{boot-BvkqTAxQ.mjs → boot-B-eA8M6t.mjs} +11 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,18 +142,19 @@ skydive chat -p "status?" --agent grace --json # structured envelo
|
|
|
142
142
|
browser login — sign in first with `skydive auth login --web` or set
|
|
143
143
|
`SKYDIVE_SESSION_TOKEN`.
|
|
144
144
|
- `--json` prints `{ agentId, agentName, conversationId, isNewConversation,
|
|
145
|
-
|
|
145
|
+
messageId, text }` instead of streaming the raw text.
|
|
146
146
|
- **Recovering a dropped run.** A long run's stream can be cut off at the edge
|
|
147
|
-
(a gateway 502/504) after the
|
|
148
|
-
server-side, so a blind retry would re-execute
|
|
149
|
-
`
|
|
147
|
+
(a gateway 502/504) after the message was accepted. The agent keeps working
|
|
148
|
+
server-side, so a blind retry would re-execute it. Instead, note the
|
|
149
|
+
`messageId` (always in `--json` output) and fetch the result once it's done:
|
|
150
150
|
|
|
151
151
|
```sh
|
|
152
|
-
skydive
|
|
152
|
+
skydive messages get <messageId> # prints the reply; --json for the envelope
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
`
|
|
156
|
-
still streaming or already finished.
|
|
155
|
+
`messages get` re-attaches to the exchange behind that message id and replays
|
|
156
|
+
the full reply whether it's still streaming or already finished. Message ids
|
|
157
|
+
are the handle this API works in; run ids stay server-side.
|
|
157
158
|
|
|
158
159
|
It defaults to the production API (`https://api.skydive.com`). For local
|
|
159
160
|
dev, point it at your stack:
|
package/dist/js/bin.mjs
CHANGED
|
@@ -31,7 +31,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
31
31
|
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region package.json
|
|
34
|
-
var version$1 = "0.1.0-beta.
|
|
34
|
+
var version$1 = "0.1.0-beta.180";
|
|
35
35
|
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region src/types.ts
|
|
@@ -886,7 +886,7 @@ function truncate(value, max) {
|
|
|
886
886
|
if (trimmed.length <= max) return trimmed || "-";
|
|
887
887
|
return `${trimmed.slice(0, max - 1)}\u2026`;
|
|
888
888
|
}
|
|
889
|
-
const getCommand = {
|
|
889
|
+
const getCommand$1 = {
|
|
890
890
|
command: "get <id>",
|
|
891
891
|
describe: "Get agent details",
|
|
892
892
|
builder: (y) => y.positional("id", {
|
|
@@ -956,7 +956,7 @@ const createCommand$1 = {
|
|
|
956
956
|
const agentsCommand = {
|
|
957
957
|
command: "agents",
|
|
958
958
|
describe: "Manage agents",
|
|
959
|
-
builder: (y) => y.command(listCommand$4).command(getCommand).command(createCommand$1).demandCommand(1, "Specify a subcommand: list, get, create"),
|
|
959
|
+
builder: (y) => y.command(listCommand$4).command(getCommand$1).command(createCommand$1).demandCommand(1, "Specify a subcommand: list, get, create"),
|
|
960
960
|
handler: () => {}
|
|
961
961
|
};
|
|
962
962
|
|
|
@@ -2152,7 +2152,7 @@ const chatCommand = {
|
|
|
2152
2152
|
printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
|
|
2153
2153
|
process.exit(1);
|
|
2154
2154
|
}
|
|
2155
|
-
const { runChat } = await import("./boot-
|
|
2155
|
+
const { runChat } = await import("./boot-B-eA8M6t.mjs");
|
|
2156
2156
|
await runChat({
|
|
2157
2157
|
appUrl,
|
|
2158
2158
|
sessionToken: session.value.sessionToken,
|
|
@@ -2199,6 +2199,60 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
2199
2199
|
}
|
|
2200
2200
|
}
|
|
2201
2201
|
|
|
2202
|
+
//#endregion
|
|
2203
|
+
//#region src/commands/messages.ts
|
|
2204
|
+
/**
|
|
2205
|
+
* `skydive messages get <messageId>` re-attaches to an exchange by message id
|
|
2206
|
+
* and prints the agent's reply.
|
|
2207
|
+
*
|
|
2208
|
+
* This is the recovery path for `chat -p`: a long run's stream can drop at the
|
|
2209
|
+
* edge (Cloudflare 502/504) after the message was accepted, leaving the caller
|
|
2210
|
+
* with a messageId but no result. The agent keeps working server-side, so a
|
|
2211
|
+
* blind retry would double-execute an agent that may have write access.
|
|
2212
|
+
* Instead, fetch the finished result by message id here — the server resolves
|
|
2213
|
+
* the run behind the message and replays it from its persisted event log, so
|
|
2214
|
+
* this works whether the run is still live or already done.
|
|
2215
|
+
*
|
|
2216
|
+
* Message ids are the currency of this API: `chat -p --json` reports the
|
|
2217
|
+
* `messageId` and this command consumes it. Run ids stay server-side.
|
|
2218
|
+
*/
|
|
2219
|
+
const getCommand = {
|
|
2220
|
+
command: "get <message-id>",
|
|
2221
|
+
describe: "Fetch a message by id and print the reply (recovers a timed-out -p)",
|
|
2222
|
+
builder: (y) => y.positional("message-id", {
|
|
2223
|
+
type: "string",
|
|
2224
|
+
demandOption: true,
|
|
2225
|
+
describe: "Message id (the `messageId` from a prior `chat -p --json`)"
|
|
2226
|
+
}),
|
|
2227
|
+
handler: async (argv) => {
|
|
2228
|
+
const appUrl = resolveAppUrl({ appUrl: argv["api-url"] });
|
|
2229
|
+
const session = resolveSession({ appUrl });
|
|
2230
|
+
if (session.isErr()) {
|
|
2231
|
+
printError(`${session.error.message} Run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
2232
|
+
process.exit(1);
|
|
2233
|
+
}
|
|
2234
|
+
const { messageGet } = await Promise.resolve().then(() => print_exports);
|
|
2235
|
+
try {
|
|
2236
|
+
const result = await messageGet({
|
|
2237
|
+
appUrl,
|
|
2238
|
+
sessionToken: session.value.sessionToken,
|
|
2239
|
+
messageId: argv["message-id"],
|
|
2240
|
+
json: argv.json
|
|
2241
|
+
});
|
|
2242
|
+
if (argv.json) output(argv, result);
|
|
2243
|
+
} catch (error) {
|
|
2244
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
2245
|
+
process.exit(1);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
};
|
|
2249
|
+
const messagesCommand = {
|
|
2250
|
+
command: "messages",
|
|
2251
|
+
describe: "Inspect chat messages and re-fetch agent replies",
|
|
2252
|
+
builder: (y) => y.command(getCommand).demandCommand(1, "Specify a subcommand: get"),
|
|
2253
|
+
handler: () => {}
|
|
2254
|
+
};
|
|
2255
|
+
|
|
2202
2256
|
//#endregion
|
|
2203
2257
|
//#region src/chat/api/rest.ts
|
|
2204
2258
|
var HttpError = class extends Error {
|
|
@@ -2239,6 +2293,65 @@ function createRestClient({ appUrl, sessionToken }) {
|
|
|
2239
2293
|
});
|
|
2240
2294
|
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
2241
2295
|
}
|
|
2296
|
+
const streamEvents = async ({ path, label, signal, onEvent }) => {
|
|
2297
|
+
let lastEventId = null;
|
|
2298
|
+
let finished = false;
|
|
2299
|
+
let reconnects = 0;
|
|
2300
|
+
for (;;) {
|
|
2301
|
+
if (signal.aborted) return;
|
|
2302
|
+
try {
|
|
2303
|
+
const headers = {
|
|
2304
|
+
authorization: `Bearer ${sessionToken}`,
|
|
2305
|
+
accept: "text/event-stream"
|
|
2306
|
+
};
|
|
2307
|
+
if (lastEventId) headers["last-event-id"] = lastEventId;
|
|
2308
|
+
const res = await fetch(`${appUrl}${path}`, {
|
|
2309
|
+
headers,
|
|
2310
|
+
signal
|
|
2311
|
+
});
|
|
2312
|
+
if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
2313
|
+
reconnects = 0;
|
|
2314
|
+
const parser = createParser({ onEvent: (message) => {
|
|
2315
|
+
if (message.id) lastEventId = message.id;
|
|
2316
|
+
if (message.event === "error") {
|
|
2317
|
+
const { error } = streamErrorSchema.parse(JSON.parse(message.data));
|
|
2318
|
+
throw new Error(error);
|
|
2319
|
+
}
|
|
2320
|
+
const event = runStreamEventSchema.parse(JSON.parse(message.data));
|
|
2321
|
+
if (event.kind === "finished") finished = true;
|
|
2322
|
+
onEvent(event.kind === "finished" ? {
|
|
2323
|
+
...event,
|
|
2324
|
+
error: event.error ?? null
|
|
2325
|
+
} : event);
|
|
2326
|
+
} });
|
|
2327
|
+
const decoder = new TextDecoder();
|
|
2328
|
+
const reader = res.body.getReader();
|
|
2329
|
+
try {
|
|
2330
|
+
for (;;) {
|
|
2331
|
+
const { done, value } = await reader.read();
|
|
2332
|
+
if (done) break;
|
|
2333
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
2334
|
+
if (finished) return;
|
|
2335
|
+
}
|
|
2336
|
+
} finally {
|
|
2337
|
+
try {
|
|
2338
|
+
await reader.cancel();
|
|
2339
|
+
} catch (_error) {}
|
|
2340
|
+
}
|
|
2341
|
+
} catch (err) {
|
|
2342
|
+
if (signal.aborted) return;
|
|
2343
|
+
if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
|
|
2344
|
+
reconnects += 1;
|
|
2345
|
+
if (reconnects > MAX_STREAM_RECONNECTS) throw err;
|
|
2346
|
+
await sleep(Math.min(500 * 2 ** reconnects, 5e3));
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
if (finished) return;
|
|
2350
|
+
reconnects += 1;
|
|
2351
|
+
if (reconnects > MAX_STREAM_RECONNECTS) throw new Error(`${label} stream ended unexpectedly`);
|
|
2352
|
+
await sleep(Math.min(500 * 2 ** reconnects, 5e3));
|
|
2353
|
+
}
|
|
2354
|
+
};
|
|
2242
2355
|
return {
|
|
2243
2356
|
listAgents: async ({ scope, onPage }) => {
|
|
2244
2357
|
const all = [];
|
|
@@ -2365,36 +2478,35 @@ function createRestClient({ appUrl, sessionToken }) {
|
|
|
2365
2478
|
});
|
|
2366
2479
|
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
2367
2480
|
},
|
|
2368
|
-
streamRun: async ({ runId, signal, onEvent }) => {
|
|
2369
|
-
|
|
2370
|
-
|
|
2481
|
+
streamRun: async ({ runId, signal, onEvent }) => streamEvents({
|
|
2482
|
+
path: `/api/v1/chat/runs/${encodeURIComponent(runId)}/stream`,
|
|
2483
|
+
label: "run",
|
|
2484
|
+
signal,
|
|
2485
|
+
onEvent
|
|
2486
|
+
}),
|
|
2487
|
+
streamMessage: async ({ messageId, signal, onEvent }) => streamEvents({
|
|
2488
|
+
path: `/api/v1/chat/messages/${encodeURIComponent(messageId)}/stream`,
|
|
2489
|
+
label: "message",
|
|
2490
|
+
signal,
|
|
2491
|
+
onEvent
|
|
2492
|
+
}),
|
|
2493
|
+
streamConversation: async ({ conversationId, signal, onEvent }) => {
|
|
2371
2494
|
let reconnects = 0;
|
|
2372
2495
|
for (;;) {
|
|
2373
2496
|
if (signal.aborted) return;
|
|
2374
2497
|
try {
|
|
2375
|
-
const
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
const res = await fetch(`${appUrl}/api/v1/chat/runs/${encodeURIComponent(runId)}/stream`, {
|
|
2381
|
-
headers,
|
|
2498
|
+
const res = await fetch(`${appUrl}/api/v1/chat/conversations/${encodeURIComponent(conversationId)}/stream`, {
|
|
2499
|
+
headers: {
|
|
2500
|
+
authorization: `Bearer ${sessionToken}`,
|
|
2501
|
+
accept: "text/event-stream"
|
|
2502
|
+
},
|
|
2382
2503
|
signal
|
|
2383
2504
|
});
|
|
2384
2505
|
if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
2385
2506
|
reconnects = 0;
|
|
2386
2507
|
const parser = createParser({ onEvent: (message) => {
|
|
2387
|
-
|
|
2388
|
-
if (
|
|
2389
|
-
const { error } = streamErrorSchema.parse(JSON.parse(message.data));
|
|
2390
|
-
throw new Error(error);
|
|
2391
|
-
}
|
|
2392
|
-
const event = runStreamEventSchema.parse(JSON.parse(message.data));
|
|
2393
|
-
if (event.kind === "finished") finished = true;
|
|
2394
|
-
onEvent(event.kind === "finished" ? {
|
|
2395
|
-
...event,
|
|
2396
|
-
error: event.error ?? null
|
|
2397
|
-
} : event);
|
|
2508
|
+
const parsed = conversationStreamEventSchema.safeParse(JSON.parse(message.data));
|
|
2509
|
+
if (parsed.success) onEvent(parsed.data);
|
|
2398
2510
|
} });
|
|
2399
2511
|
const decoder = new TextDecoder();
|
|
2400
2512
|
const reader = res.body.getReader();
|
|
@@ -2403,7 +2515,6 @@ function createRestClient({ appUrl, sessionToken }) {
|
|
|
2403
2515
|
const { done, value } = await reader.read();
|
|
2404
2516
|
if (done) break;
|
|
2405
2517
|
parser.feed(decoder.decode(value, { stream: true }));
|
|
2406
|
-
if (finished) return;
|
|
2407
2518
|
}
|
|
2408
2519
|
} finally {
|
|
2409
2520
|
try {
|
|
@@ -2418,9 +2529,9 @@ function createRestClient({ appUrl, sessionToken }) {
|
|
|
2418
2529
|
await sleep(Math.min(500 * 2 ** reconnects, 5e3));
|
|
2419
2530
|
continue;
|
|
2420
2531
|
}
|
|
2421
|
-
if (
|
|
2532
|
+
if (signal.aborted) return;
|
|
2422
2533
|
reconnects += 1;
|
|
2423
|
-
if (reconnects > MAX_STREAM_RECONNECTS) throw new Error("
|
|
2534
|
+
if (reconnects > MAX_STREAM_RECONNECTS) throw new Error("conversation stream ended unexpectedly");
|
|
2424
2535
|
await sleep(Math.min(500 * 2 ** reconnects, 5e3));
|
|
2425
2536
|
}
|
|
2426
2537
|
}
|
|
@@ -2509,6 +2620,7 @@ const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nul
|
|
|
2509
2620
|
const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
|
|
2510
2621
|
const sendResultSchema = z.object({
|
|
2511
2622
|
runId: z.string(),
|
|
2623
|
+
messageId: z.string().uuid().nullish(),
|
|
2512
2624
|
conversationId: z.string().uuid(),
|
|
2513
2625
|
isNewConversation: z.boolean(),
|
|
2514
2626
|
steered: z.boolean().optional(),
|
|
@@ -2537,6 +2649,11 @@ const runStreamEventSchema = z.union([z.object({
|
|
|
2537
2649
|
error: z.string().nullish()
|
|
2538
2650
|
})]);
|
|
2539
2651
|
const streamErrorSchema = z.object({ error: z.string() });
|
|
2652
|
+
const conversationStreamEventSchema = z.object({
|
|
2653
|
+
kind: z.literal("conversation"),
|
|
2654
|
+
id: z.string(),
|
|
2655
|
+
title: z.string().nullable()
|
|
2656
|
+
});
|
|
2540
2657
|
|
|
2541
2658
|
//#endregion
|
|
2542
2659
|
//#region src/chat/util.ts
|
|
@@ -2828,9 +2945,12 @@ function formatConnectCard(card) {
|
|
|
2828
2945
|
//#endregion
|
|
2829
2946
|
//#region src/chat/print.ts
|
|
2830
2947
|
var print_exports = /* @__PURE__ */ __exportAll({
|
|
2948
|
+
collectRunText: () => collectRunText,
|
|
2949
|
+
messageGet: () => messageGet,
|
|
2831
2950
|
readStdin: () => readStdin,
|
|
2832
2951
|
resolveAgent: () => resolveAgent,
|
|
2833
|
-
runPrint: () => runPrint
|
|
2952
|
+
runPrint: () => runPrint,
|
|
2953
|
+
toPrintError: () => toPrintError
|
|
2834
2954
|
});
|
|
2835
2955
|
/**
|
|
2836
2956
|
* Non-interactive chat, à la `claude -p`. Sends a single prompt to an
|
|
@@ -2852,48 +2972,129 @@ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversat
|
|
|
2852
2972
|
scope: "org",
|
|
2853
2973
|
onPage: null
|
|
2854
2974
|
}), agentSelector);
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2975
|
+
let send;
|
|
2976
|
+
try {
|
|
2977
|
+
send = await client.sendMessage({
|
|
2978
|
+
agentId: agent.id,
|
|
2979
|
+
conversationId,
|
|
2980
|
+
content: prompt,
|
|
2981
|
+
attachmentIds: [],
|
|
2982
|
+
clientSurface: "cli"
|
|
2983
|
+
});
|
|
2984
|
+
} catch (err) {
|
|
2985
|
+
throw toPrintError(err);
|
|
2986
|
+
}
|
|
2987
|
+
const { text, connectCards } = await collectRunText({
|
|
2988
|
+
client,
|
|
2989
|
+
appUrl,
|
|
2990
|
+
target: {
|
|
2991
|
+
kind: "run",
|
|
2992
|
+
id: send.runId
|
|
2993
|
+
},
|
|
2994
|
+
onText: json ? null : (delta) => process.stdout.write(delta),
|
|
2995
|
+
messageIdForHint: send.messageId ?? null
|
|
2861
2996
|
});
|
|
2997
|
+
if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
|
|
2998
|
+
if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
|
|
2999
|
+
return {
|
|
3000
|
+
agentId: agent.id,
|
|
3001
|
+
agentName: agent.name,
|
|
3002
|
+
conversationId: send.conversationId,
|
|
3003
|
+
isNewConversation: send.isNewConversation,
|
|
3004
|
+
messageId: send.messageId ?? null,
|
|
3005
|
+
text,
|
|
3006
|
+
connectCards
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
/**
|
|
3010
|
+
* Turn a transport failure into an actionable CLI error. A Cloudflare edge
|
|
3011
|
+
* 5xx (502/504) on a long run otherwise leaks a raw HTML/JSON error page to
|
|
3012
|
+
* stdout, which is impossible to act on. When a messageId is known and
|
|
3013
|
+
* recoverable we point the user at `skydive messages get <messageId>` rather
|
|
3014
|
+
* than a blind retry (a retry re-executes an agent that may have write access).
|
|
3015
|
+
*/
|
|
3016
|
+
function toPrintError(err, messageId) {
|
|
3017
|
+
if (err instanceof HttpError && err.status >= 500) {
|
|
3018
|
+
const recovery = messageId ? ` The run may still be completing server-side. Do NOT blindly retry (it would re-run the agent). Fetch the result with: skydive messages get ${messageId}` : "";
|
|
3019
|
+
return /* @__PURE__ */ new Error(`The request to Skydive timed out at the edge (HTTP ${err.status}).${recovery}`);
|
|
3020
|
+
}
|
|
3021
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
3022
|
+
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Stream a run to completion, folding text-delta chunks into the reply and
|
|
3025
|
+
* collecting any connect cards (OAuth / MCP / credential requests) the run
|
|
3026
|
+
* posts. Shared by `chat -p` (streaming the run it just created) and `messages
|
|
3027
|
+
* get` (re-attaching by message id — the server replays a finished run from its
|
|
3028
|
+
* persisted log, so this works whether the run is live or already done).
|
|
3029
|
+
*/
|
|
3030
|
+
async function collectRunText({ client, appUrl, target, onText, messageIdForHint }) {
|
|
2862
3031
|
let text = "";
|
|
2863
3032
|
const controller = new AbortController();
|
|
2864
3033
|
let streamError = null;
|
|
2865
3034
|
const connectCards = [];
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
if (delta) {
|
|
2878
|
-
text += delta;
|
|
2879
|
-
if (!json) process.stdout.write(delta);
|
|
2880
|
-
}
|
|
2881
|
-
} else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
|
|
2882
|
-
else {
|
|
2883
|
-
const card = connectCardFromChunk(chunk, appUrl);
|
|
2884
|
-
if (card) connectCards.push(card);
|
|
3035
|
+
const onEvent = (event) => {
|
|
3036
|
+
if (event.kind === "finished") {
|
|
3037
|
+
if (event.error) streamError = event.error;
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
const chunk = event.chunk;
|
|
3041
|
+
if (chunk["type"] === "text-delta") {
|
|
3042
|
+
const delta = typeof chunk["delta"] === "string" ? chunk["delta"] : typeof chunk["text"] === "string" ? chunk["text"] : "";
|
|
3043
|
+
if (delta) {
|
|
3044
|
+
text += delta;
|
|
3045
|
+
if (onText) onText(delta);
|
|
2885
3046
|
}
|
|
3047
|
+
} else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
|
|
3048
|
+
else {
|
|
3049
|
+
const card = connectCardFromChunk(chunk, appUrl);
|
|
3050
|
+
if (card) connectCards.push(card);
|
|
2886
3051
|
}
|
|
2887
|
-
}
|
|
3052
|
+
};
|
|
3053
|
+
try {
|
|
3054
|
+
if (target.kind === "message") await client.streamMessage({
|
|
3055
|
+
messageId: target.id,
|
|
3056
|
+
signal: controller.signal,
|
|
3057
|
+
onEvent
|
|
3058
|
+
});
|
|
3059
|
+
else await client.streamRun({
|
|
3060
|
+
runId: target.id,
|
|
3061
|
+
signal: controller.signal,
|
|
3062
|
+
onEvent
|
|
3063
|
+
});
|
|
3064
|
+
} catch (err) {
|
|
3065
|
+
throw toPrintError(err, messageIdForHint);
|
|
3066
|
+
}
|
|
2888
3067
|
if (streamError) throw new Error(streamError);
|
|
3068
|
+
return {
|
|
3069
|
+
text,
|
|
3070
|
+
connectCards
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
/**
|
|
3074
|
+
* Re-attach to an exchange by message id and print its reply. Backs `skydive
|
|
3075
|
+
* messages get <messageId>` — the recovery path when a `chat -p` stream dropped
|
|
3076
|
+
* at the edge after the message was accepted. The server resolves the run
|
|
3077
|
+
* behind the message and replays it from its persisted event log, so this
|
|
3078
|
+
* returns the full reply whether the run is still live or already done.
|
|
3079
|
+
*/
|
|
3080
|
+
async function messageGet({ appUrl, sessionToken, messageId, json }) {
|
|
3081
|
+
const { text, connectCards } = await collectRunText({
|
|
3082
|
+
client: createRestClient({
|
|
3083
|
+
appUrl,
|
|
3084
|
+
sessionToken
|
|
3085
|
+
}),
|
|
3086
|
+
appUrl,
|
|
3087
|
+
target: {
|
|
3088
|
+
kind: "message",
|
|
3089
|
+
id: messageId
|
|
3090
|
+
},
|
|
3091
|
+
onText: json ? null : (delta) => process.stdout.write(delta),
|
|
3092
|
+
messageIdForHint: null
|
|
3093
|
+
});
|
|
2889
3094
|
if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
|
|
2890
3095
|
if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
|
|
2891
3096
|
return {
|
|
2892
|
-
|
|
2893
|
-
agentName: agent.name,
|
|
2894
|
-
conversationId: send.conversationId,
|
|
2895
|
-
isNewConversation: send.isNewConversation,
|
|
2896
|
-
runId: send.runId,
|
|
3097
|
+
messageId,
|
|
2897
3098
|
text,
|
|
2898
3099
|
connectCards
|
|
2899
3100
|
};
|
|
@@ -3129,7 +3330,7 @@ const switchCommand = {
|
|
|
3129
3330
|
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.");
|
|
3130
3331
|
process.exit(1);
|
|
3131
3332
|
}
|
|
3132
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
3333
|
+
const { runWorkspacePicker } = await import("./boot-B-eA8M6t.mjs");
|
|
3133
3334
|
await runWorkspacePicker(session);
|
|
3134
3335
|
return;
|
|
3135
3336
|
}
|
|
@@ -3183,7 +3384,7 @@ function createCli(argv) {
|
|
|
3183
3384
|
type: "string",
|
|
3184
3385
|
global: true,
|
|
3185
3386
|
describe: "Override API base URL"
|
|
3186
|
-
}).command(authCommand).command(chatCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
|
|
3387
|
+
}).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
|
|
3187
3388
|
printError(err ? err instanceof Error ? err.message : String(err) : msg ?? "Unknown error");
|
|
3188
3389
|
process.exit(1);
|
|
3189
3390
|
});
|
|
@@ -508,7 +508,7 @@ var PortalClient = class {
|
|
|
508
508
|
this.setStatus("error", errorMessage(err));
|
|
509
509
|
}
|
|
510
510
|
if (!this.enabled || this.disposed) break;
|
|
511
|
-
await sleep
|
|
511
|
+
await sleep(backoff);
|
|
512
512
|
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
513
513
|
}
|
|
514
514
|
}
|
|
@@ -554,7 +554,7 @@ var PortalClient = class {
|
|
|
554
554
|
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
555
555
|
await this.refreshDevice();
|
|
556
556
|
if (this.deviceId) return this.deviceId;
|
|
557
|
-
await sleep
|
|
557
|
+
await sleep(300);
|
|
558
558
|
}
|
|
559
559
|
throw new Error("this machine is not connected yet");
|
|
560
560
|
}
|
|
@@ -590,7 +590,7 @@ function toBuffer(data) {
|
|
|
590
590
|
function shortBody(res) {
|
|
591
591
|
return res.text().then((text) => text.slice(0, 120)).catch(() => "");
|
|
592
592
|
}
|
|
593
|
-
function sleep
|
|
593
|
+
function sleep(ms) {
|
|
594
594
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
595
595
|
}
|
|
596
596
|
|
|
@@ -4639,9 +4639,6 @@ function ThemePicker({ onClose }) {
|
|
|
4639
4639
|
|
|
4640
4640
|
//#endregion
|
|
4641
4641
|
//#region src/chat/tui/screens/chat.tsx
|
|
4642
|
-
function sleep(ms) {
|
|
4643
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4644
|
-
}
|
|
4645
4642
|
/**
|
|
4646
4643
|
* Composer key bindings: Enter sends the message (matching the rest of the
|
|
4647
4644
|
* app), Shift+Enter inserts a hard newline. Everything else falls back to the
|
|
@@ -4858,41 +4855,18 @@ function ChatScreen({ agent, conversation }) {
|
|
|
4858
4855
|
]);
|
|
4859
4856
|
useEffect(() => {
|
|
4860
4857
|
if (!rest || !conversationId) return;
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
1500,
|
|
4868
|
-
2500,
|
|
4869
|
-
4e3,
|
|
4870
|
-
6e3
|
|
4871
|
-
];
|
|
4872
|
-
let last = null;
|
|
4873
|
-
for (const delay of delays) {
|
|
4874
|
-
if (delay) await sleep(delay);
|
|
4875
|
-
if (cancelled) return;
|
|
4876
|
-
let title;
|
|
4877
|
-
try {
|
|
4878
|
-
({title} = await rest.getConversation({ conversationId }));
|
|
4879
|
-
} catch (_error) {
|
|
4880
|
-
continue;
|
|
4881
|
-
}
|
|
4882
|
-
if (cancelled) return;
|
|
4883
|
-
if (title && title !== last) {
|
|
4884
|
-
last = title;
|
|
4885
|
-
setChatTitle(title);
|
|
4886
|
-
}
|
|
4858
|
+
const abort = new AbortController();
|
|
4859
|
+
rest.streamConversation({
|
|
4860
|
+
conversationId,
|
|
4861
|
+
signal: abort.signal,
|
|
4862
|
+
onEvent: (event) => {
|
|
4863
|
+
if (event.title) setChatTitle(event.title);
|
|
4887
4864
|
}
|
|
4888
|
-
})();
|
|
4889
|
-
return () =>
|
|
4890
|
-
cancelled = true;
|
|
4891
|
-
};
|
|
4865
|
+
}).catch(() => {});
|
|
4866
|
+
return () => abort.abort();
|
|
4892
4867
|
}, [
|
|
4893
4868
|
rest,
|
|
4894
4869
|
conversationId,
|
|
4895
|
-
initialConversationId,
|
|
4896
4870
|
setChatTitle
|
|
4897
4871
|
]);
|
|
4898
4872
|
useEffect(() => {
|