skydive-cli 0.1.0-beta.90 → 0.1.0-beta.96
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
|
@@ -13,7 +13,7 @@ import fs from "node:fs";
|
|
|
13
13
|
import zlib from "node:zlib";
|
|
14
14
|
|
|
15
15
|
//#region package.json
|
|
16
|
-
var version$1 = "0.1.0-beta.
|
|
16
|
+
var version$1 = "0.1.0-beta.96";
|
|
17
17
|
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/types.ts
|
|
@@ -2101,7 +2101,7 @@ const chatCommand = {
|
|
|
2101
2101
|
printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
|
|
2102
2102
|
process.exit(1);
|
|
2103
2103
|
}
|
|
2104
|
-
const { runChat } = await import("./boot-
|
|
2104
|
+
const { runChat } = await import("./boot-DRWra2vY.mjs");
|
|
2105
2105
|
await runChat({
|
|
2106
2106
|
appUrl,
|
|
2107
2107
|
sessionToken: session.value.sessionToken,
|
|
@@ -2119,7 +2119,7 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
2119
2119
|
printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
2120
2120
|
process.exit(1);
|
|
2121
2121
|
}
|
|
2122
|
-
const { runPrint, readStdin } = await import("./print-
|
|
2122
|
+
const { runPrint, readStdin } = await import("./print-CQ8b7JUC.mjs").then((n) => n.t);
|
|
2123
2123
|
let prompt = (argv.print ?? "").trim();
|
|
2124
2124
|
if (!prompt) {
|
|
2125
2125
|
if (process.stdin.isTTY) {
|
|
@@ -2218,7 +2218,7 @@ const switchCommand = {
|
|
|
2218
2218
|
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.");
|
|
2219
2219
|
process.exit(1);
|
|
2220
2220
|
}
|
|
2221
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
2221
|
+
const { runWorkspacePicker } = await import("./boot-DRWra2vY.mjs");
|
|
2222
2222
|
await runWorkspacePicker(session);
|
|
2223
2223
|
return;
|
|
2224
2224
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as
|
|
2
|
+
import { a as parseExternalOauthConnectParams, c as resolveConnectUrl, d as isRecord, f as HttpError, i as cardActionErrorMessage, l as parseConnectCard, n as resolveAgent, o as parseOauthConnectParams, p as createRestClient, r as MASK_CHAR, s as reconcileMaskedInput, u as errorMessage } from "./print-CQ8b7JUC.mjs";
|
|
3
3
|
import { _ as getSavedTheme, a as noColorRequested, c as themeMode, d as themesForMode, f as getActiveWorkspaceId, g as DEFAULT_APP_URL, h as DEFAULT_API_URL, i as monoTheme, l as themeModeFromColorFgBg, m as setActiveWorkspace, n as applyTheme, o as theme, p as listWorkspaces, r as findTheme, s as themeForMode, t as DEFAULT_THEME_ID, u as themeVersion, v as resolveWebUrl, y as saveTheme } from "./bin.mjs";
|
|
4
4
|
import path, { basename, isAbsolute, join, win32 } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -217,19 +217,6 @@ function decodeFrame(frame) {
|
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
//#endregion
|
|
221
|
-
//#region src/chat/util.ts
|
|
222
|
-
/** Narrowing helper for the many `unknown` payloads the chat stream and
|
|
223
|
-
* tool inputs/outputs carry. A type predicate (not an `as` cast), so call
|
|
224
|
-
* sites can read properties without asserting. */
|
|
225
|
-
function isRecord(value) {
|
|
226
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
227
|
-
}
|
|
228
|
-
/** Best-effort message from an unknown thrown value. */
|
|
229
|
-
function errorMessage(err) {
|
|
230
|
-
return err instanceof Error ? err.message : String(err);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
220
|
//#endregion
|
|
234
221
|
//#region src/chat/portal/machine.ts
|
|
235
222
|
/**
|
|
@@ -3253,107 +3240,6 @@ function formatElapsed(totalSeconds) {
|
|
|
3253
3240
|
return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;
|
|
3254
3241
|
}
|
|
3255
3242
|
|
|
3256
|
-
//#endregion
|
|
3257
|
-
//#region src/chat/tui/chat/card.ts
|
|
3258
|
-
const urlActionKinds = [
|
|
3259
|
-
"open_oauth",
|
|
3260
|
-
"open_external_oauth",
|
|
3261
|
-
"open_github_app",
|
|
3262
|
-
"submit_credential"
|
|
3263
|
-
];
|
|
3264
|
-
function isUrlActionKind(value) {
|
|
3265
|
-
return typeof value === "string" && urlActionKinds.includes(value);
|
|
3266
|
-
}
|
|
3267
|
-
function optionalString(value) {
|
|
3268
|
-
return typeof value === "string" && value ? value : null;
|
|
3269
|
-
}
|
|
3270
|
-
function bindStateKey(props) {
|
|
3271
|
-
const value = props.value;
|
|
3272
|
-
if (!isRecord(value)) return null;
|
|
3273
|
-
const pointer = value.$bindState;
|
|
3274
|
-
if (typeof pointer !== "string") return null;
|
|
3275
|
-
return pointer.startsWith("/") ? pointer.slice(1) : pointer;
|
|
3276
|
-
}
|
|
3277
|
-
function parseButton(element) {
|
|
3278
|
-
const props = isRecord(element.props) ? element.props : {};
|
|
3279
|
-
const label = optionalString(props.label) ?? "Connect";
|
|
3280
|
-
const on = isRecord(element.on) ? element.on : null;
|
|
3281
|
-
const press = on && isRecord(on.press) ? on.press : null;
|
|
3282
|
-
if (!press) return null;
|
|
3283
|
-
const params = isRecord(press.params) ? press.params : {};
|
|
3284
|
-
const primary = props.variant === "primary";
|
|
3285
|
-
if (press.action === "approve_portal_access") {
|
|
3286
|
-
const agentId = params.agentId;
|
|
3287
|
-
if (typeof agentId !== "string" || !agentId) return null;
|
|
3288
|
-
return {
|
|
3289
|
-
label,
|
|
3290
|
-
action: {
|
|
3291
|
-
kind: "grant_portal",
|
|
3292
|
-
agentId,
|
|
3293
|
-
deviceId: typeof params.deviceId === "string" && params.deviceId ? params.deviceId : null
|
|
3294
|
-
},
|
|
3295
|
-
primary
|
|
3296
|
-
};
|
|
3297
|
-
}
|
|
3298
|
-
if (!isUrlActionKind(press.action)) return null;
|
|
3299
|
-
const url = params.url;
|
|
3300
|
-
if (typeof url !== "string" || !url) return null;
|
|
3301
|
-
return {
|
|
3302
|
-
label,
|
|
3303
|
-
action: {
|
|
3304
|
-
kind: press.action,
|
|
3305
|
-
url
|
|
3306
|
-
},
|
|
3307
|
-
primary
|
|
3308
|
-
};
|
|
3309
|
-
}
|
|
3310
|
-
function parseConnectCard(spec) {
|
|
3311
|
-
if (!isRecord(spec)) return null;
|
|
3312
|
-
const { root, elements } = spec;
|
|
3313
|
-
if (typeof root !== "string" || !isRecord(elements)) return null;
|
|
3314
|
-
const rootEl = elements[root];
|
|
3315
|
-
if (!isRecord(rootEl) || rootEl.type !== "Card") return null;
|
|
3316
|
-
const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
|
|
3317
|
-
const title = optionalString(rootProps.title);
|
|
3318
|
-
if (!title) return null;
|
|
3319
|
-
const fields = [];
|
|
3320
|
-
const buttons = [];
|
|
3321
|
-
const children = Array.isArray(rootEl.children) ? rootEl.children : [];
|
|
3322
|
-
for (const childId of children) {
|
|
3323
|
-
if (typeof childId !== "string") continue;
|
|
3324
|
-
const el = elements[childId];
|
|
3325
|
-
if (!isRecord(el)) continue;
|
|
3326
|
-
if (el.type === "TextInput" || el.type === "SecretInput") {
|
|
3327
|
-
const props = isRecord(el.props) ? el.props : {};
|
|
3328
|
-
const key = bindStateKey(props);
|
|
3329
|
-
if (!key) continue;
|
|
3330
|
-
fields.push({
|
|
3331
|
-
key,
|
|
3332
|
-
label: optionalString(props.label) ?? key,
|
|
3333
|
-
placeholder: optionalString(props.placeholder),
|
|
3334
|
-
secret: el.type === "SecretInput"
|
|
3335
|
-
});
|
|
3336
|
-
continue;
|
|
3337
|
-
}
|
|
3338
|
-
if (el.type === "Button") {
|
|
3339
|
-
const button = parseButton(el);
|
|
3340
|
-
if (button) buttons.push(button);
|
|
3341
|
-
}
|
|
3342
|
-
}
|
|
3343
|
-
const preferred = buttons.find((b) => b.primary) ?? buttons[0] ?? null;
|
|
3344
|
-
return {
|
|
3345
|
-
title,
|
|
3346
|
-
subtitle: optionalString(rootProps.subtitle),
|
|
3347
|
-
description: optionalString(rootProps.description),
|
|
3348
|
-
fields,
|
|
3349
|
-
button: preferred ? {
|
|
3350
|
-
label: preferred.label,
|
|
3351
|
-
action: preferred.action
|
|
3352
|
-
} : null,
|
|
3353
|
-
state: isRecord(spec.state) ? spec.state : {}
|
|
3354
|
-
};
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
3243
|
//#endregion
|
|
3358
3244
|
//#region src/chat/tui/chat/reducer.ts
|
|
3359
3245
|
function str(chunk, key) {
|
|
@@ -3719,94 +3605,6 @@ function formatModelHint(model) {
|
|
|
3719
3605
|
return `ctrl+p · ${model ?? "default"}`;
|
|
3720
3606
|
}
|
|
3721
3607
|
|
|
3722
|
-
//#endregion
|
|
3723
|
-
//#region src/chat/tui/chat/card-actions.ts
|
|
3724
|
-
function safeUrl(url) {
|
|
3725
|
-
try {
|
|
3726
|
-
return new URL(url, "http://localhost");
|
|
3727
|
-
} catch (_error) {
|
|
3728
|
-
return null;
|
|
3729
|
-
}
|
|
3730
|
-
}
|
|
3731
|
-
/**
|
|
3732
|
-
* Resolve a connect URL to an absolute one the OS can open in a browser.
|
|
3733
|
-
*
|
|
3734
|
-
* The server builds lazy connect links as server-relative paths (e.g.
|
|
3735
|
-
* `/api/v1/oauth/start/slack?connect_session_token=...`). The web client
|
|
3736
|
-
* resolves these against its own origin implicitly; the TUI runs outside a
|
|
3737
|
-
* browser, so `open()` on a scheme-less path is a silent no-op — the browser
|
|
3738
|
-
* never launches and the connect card sits in `launched` forever (the Slack
|
|
3739
|
-
* channel-connect deadlock). Resolve against `appUrl` first, mirroring the
|
|
3740
|
-
* `open_github_app` / `fulfillCredential` branches, so both the browser we
|
|
3741
|
-
* launch and the URL shown on the card are absolute. An already-absolute URL
|
|
3742
|
-
* passes through unchanged; if `appUrl` is missing we return the input as-is.
|
|
3743
|
-
*/
|
|
3744
|
-
function resolveConnectUrl(url, appUrl) {
|
|
3745
|
-
if (!appUrl) return url;
|
|
3746
|
-
try {
|
|
3747
|
-
return new URL(url, appUrl).toString();
|
|
3748
|
-
} catch (_error) {
|
|
3749
|
-
return url;
|
|
3750
|
-
}
|
|
3751
|
-
}
|
|
3752
|
-
function parseOauthConnectParams(url) {
|
|
3753
|
-
const parsed = safeUrl(url);
|
|
3754
|
-
if (!parsed) return null;
|
|
3755
|
-
const integrationKey = parsed.searchParams.get("integration");
|
|
3756
|
-
const agentId = parsed.searchParams.get("agent_id");
|
|
3757
|
-
const authConfigId = parsed.searchParams.get("auth_config_id");
|
|
3758
|
-
const conversationId = parsed.searchParams.get("conversation_id");
|
|
3759
|
-
if (!integrationKey || !agentId || !authConfigId || !conversationId) return null;
|
|
3760
|
-
return {
|
|
3761
|
-
integrationKey,
|
|
3762
|
-
agentId,
|
|
3763
|
-
authConfigId,
|
|
3764
|
-
conversationId,
|
|
3765
|
-
scopes: parsed.searchParams.get("scopes")
|
|
3766
|
-
};
|
|
3767
|
-
}
|
|
3768
|
-
function parseExternalOauthConnectParams(url) {
|
|
3769
|
-
const parsed = safeUrl(url);
|
|
3770
|
-
if (!parsed) return null;
|
|
3771
|
-
const agentId = parsed.searchParams.get("agent_id");
|
|
3772
|
-
const conversationId = parsed.searchParams.get("conversation_id");
|
|
3773
|
-
const serverUrl = parsed.searchParams.get("server_url");
|
|
3774
|
-
if (!agentId || !conversationId || !serverUrl) return null;
|
|
3775
|
-
return {
|
|
3776
|
-
agentId,
|
|
3777
|
-
conversationId,
|
|
3778
|
-
serverUrl
|
|
3779
|
-
};
|
|
3780
|
-
}
|
|
3781
|
-
/**
|
|
3782
|
-
* A short human line for a failed card action. Fulfill/connect endpoints
|
|
3783
|
-
* return `{ error: string }` bodies (e.g. "already fulfilled") — prefer that
|
|
3784
|
-
* over the generic HttpError message.
|
|
3785
|
-
*/
|
|
3786
|
-
function cardActionErrorMessage(err) {
|
|
3787
|
-
if (err instanceof HttpError) {
|
|
3788
|
-
try {
|
|
3789
|
-
const parsed = JSON.parse(err.body);
|
|
3790
|
-
if (isRecord(parsed) && typeof parsed.error === "string") return parsed.error;
|
|
3791
|
-
} catch (_error) {}
|
|
3792
|
-
return `request failed (HTTP ${err.status})`;
|
|
3793
|
-
}
|
|
3794
|
-
return errorMessage(err);
|
|
3795
|
-
}
|
|
3796
|
-
const MASK_CHAR = "•";
|
|
3797
|
-
/**
|
|
3798
|
-
* Recover the real secret from the masked input's displayed text. The input
|
|
3799
|
-
* is controlled: after every edit we render bullets, which forces the cursor
|
|
3800
|
-
* to the end, so the next edit is always a tail edit — the displayed text is
|
|
3801
|
-
* some prefix of the old mask (kept characters) followed by newly typed or
|
|
3802
|
-
* pasted plaintext. Characters beyond the retained bullets are the new tail.
|
|
3803
|
-
*/
|
|
3804
|
-
function reconcileMaskedInput(previousValue, displayed) {
|
|
3805
|
-
let kept = 0;
|
|
3806
|
-
while (kept < displayed.length && kept < previousValue.length && displayed[kept] === MASK_CHAR) kept++;
|
|
3807
|
-
return previousValue.slice(0, kept) + displayed.slice(kept);
|
|
3808
|
-
}
|
|
3809
|
-
|
|
3810
3608
|
//#endregion
|
|
3811
3609
|
//#region src/chat/notification-preview.ts
|
|
3812
3610
|
const MAX_NOTIFICATION_PREVIEW_CHARS = 240;
|
|
@@ -348,6 +348,293 @@ const runStreamEventSchema = z.union([z.object({
|
|
|
348
348
|
})]);
|
|
349
349
|
const streamErrorSchema = z.object({ error: z.string() });
|
|
350
350
|
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/chat/util.ts
|
|
353
|
+
/** Narrowing helper for the many `unknown` payloads the chat stream and
|
|
354
|
+
* tool inputs/outputs carry. A type predicate (not an `as` cast), so call
|
|
355
|
+
* sites can read properties without asserting. */
|
|
356
|
+
function isRecord(value) {
|
|
357
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
358
|
+
}
|
|
359
|
+
/** Best-effort message from an unknown thrown value. */
|
|
360
|
+
function errorMessage(err) {
|
|
361
|
+
return err instanceof Error ? err.message : String(err);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/chat/tui/chat/card.ts
|
|
366
|
+
const urlActionKinds = [
|
|
367
|
+
"open_oauth",
|
|
368
|
+
"open_external_oauth",
|
|
369
|
+
"open_github_app",
|
|
370
|
+
"submit_credential"
|
|
371
|
+
];
|
|
372
|
+
function isUrlActionKind(value) {
|
|
373
|
+
return typeof value === "string" && urlActionKinds.includes(value);
|
|
374
|
+
}
|
|
375
|
+
function optionalString(value) {
|
|
376
|
+
return typeof value === "string" && value ? value : null;
|
|
377
|
+
}
|
|
378
|
+
function bindStateKey(props) {
|
|
379
|
+
const value = props.value;
|
|
380
|
+
if (!isRecord(value)) return null;
|
|
381
|
+
const pointer = value.$bindState;
|
|
382
|
+
if (typeof pointer !== "string") return null;
|
|
383
|
+
return pointer.startsWith("/") ? pointer.slice(1) : pointer;
|
|
384
|
+
}
|
|
385
|
+
function parseButton(element) {
|
|
386
|
+
const props = isRecord(element.props) ? element.props : {};
|
|
387
|
+
const label = optionalString(props.label) ?? "Connect";
|
|
388
|
+
const on = isRecord(element.on) ? element.on : null;
|
|
389
|
+
const press = on && isRecord(on.press) ? on.press : null;
|
|
390
|
+
if (!press) return null;
|
|
391
|
+
const params = isRecord(press.params) ? press.params : {};
|
|
392
|
+
const primary = props.variant === "primary";
|
|
393
|
+
if (press.action === "approve_portal_access") {
|
|
394
|
+
const agentId = params.agentId;
|
|
395
|
+
if (typeof agentId !== "string" || !agentId) return null;
|
|
396
|
+
return {
|
|
397
|
+
label,
|
|
398
|
+
action: {
|
|
399
|
+
kind: "grant_portal",
|
|
400
|
+
agentId,
|
|
401
|
+
deviceId: typeof params.deviceId === "string" && params.deviceId ? params.deviceId : null
|
|
402
|
+
},
|
|
403
|
+
primary
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (!isUrlActionKind(press.action)) return null;
|
|
407
|
+
const url = params.url;
|
|
408
|
+
if (typeof url !== "string" || !url) return null;
|
|
409
|
+
return {
|
|
410
|
+
label,
|
|
411
|
+
action: {
|
|
412
|
+
kind: press.action,
|
|
413
|
+
url
|
|
414
|
+
},
|
|
415
|
+
primary
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function parseConnectCard(spec) {
|
|
419
|
+
if (!isRecord(spec)) return null;
|
|
420
|
+
const { root, elements } = spec;
|
|
421
|
+
if (typeof root !== "string" || !isRecord(elements)) return null;
|
|
422
|
+
const rootEl = elements[root];
|
|
423
|
+
if (!isRecord(rootEl) || rootEl.type !== "Card") return null;
|
|
424
|
+
const rootProps = isRecord(rootEl.props) ? rootEl.props : {};
|
|
425
|
+
const title = optionalString(rootProps.title);
|
|
426
|
+
if (!title) return null;
|
|
427
|
+
const fields = [];
|
|
428
|
+
const buttons = [];
|
|
429
|
+
const children = Array.isArray(rootEl.children) ? rootEl.children : [];
|
|
430
|
+
for (const childId of children) {
|
|
431
|
+
if (typeof childId !== "string") continue;
|
|
432
|
+
const el = elements[childId];
|
|
433
|
+
if (!isRecord(el)) continue;
|
|
434
|
+
if (el.type === "TextInput" || el.type === "SecretInput") {
|
|
435
|
+
const props = isRecord(el.props) ? el.props : {};
|
|
436
|
+
const key = bindStateKey(props);
|
|
437
|
+
if (!key) continue;
|
|
438
|
+
fields.push({
|
|
439
|
+
key,
|
|
440
|
+
label: optionalString(props.label) ?? key,
|
|
441
|
+
placeholder: optionalString(props.placeholder),
|
|
442
|
+
secret: el.type === "SecretInput"
|
|
443
|
+
});
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (el.type === "Button") {
|
|
447
|
+
const button = parseButton(el);
|
|
448
|
+
if (button) buttons.push(button);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const preferred = buttons.find((b) => b.primary) ?? buttons[0] ?? null;
|
|
452
|
+
return {
|
|
453
|
+
title,
|
|
454
|
+
subtitle: optionalString(rootProps.subtitle),
|
|
455
|
+
description: optionalString(rootProps.description),
|
|
456
|
+
fields,
|
|
457
|
+
button: preferred ? {
|
|
458
|
+
label: preferred.label,
|
|
459
|
+
action: preferred.action
|
|
460
|
+
} : null,
|
|
461
|
+
state: isRecord(spec.state) ? spec.state : {}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/chat/tui/chat/card-actions.ts
|
|
467
|
+
function safeUrl(url) {
|
|
468
|
+
try {
|
|
469
|
+
return new URL(url, "http://localhost");
|
|
470
|
+
} catch (_error) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Resolve a connect URL to an absolute one the OS can open in a browser.
|
|
476
|
+
*
|
|
477
|
+
* The server builds lazy connect links as server-relative paths (e.g.
|
|
478
|
+
* `/api/v1/oauth/start/slack?connect_session_token=...`). The web client
|
|
479
|
+
* resolves these against its own origin implicitly; the TUI runs outside a
|
|
480
|
+
* browser, so `open()` on a scheme-less path is a silent no-op — the browser
|
|
481
|
+
* never launches and the connect card sits in `launched` forever (the Slack
|
|
482
|
+
* channel-connect deadlock). Resolve against `appUrl` first, mirroring the
|
|
483
|
+
* `open_github_app` / `fulfillCredential` branches, so both the browser we
|
|
484
|
+
* launch and the URL shown on the card are absolute. An already-absolute URL
|
|
485
|
+
* passes through unchanged; if `appUrl` is missing we return the input as-is.
|
|
486
|
+
*/
|
|
487
|
+
function resolveConnectUrl(url, appUrl) {
|
|
488
|
+
if (!appUrl) return url;
|
|
489
|
+
try {
|
|
490
|
+
return new URL(url, appUrl).toString();
|
|
491
|
+
} catch (_error) {
|
|
492
|
+
return url;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function parseOauthConnectParams(url) {
|
|
496
|
+
const parsed = safeUrl(url);
|
|
497
|
+
if (!parsed) return null;
|
|
498
|
+
const integrationKey = parsed.searchParams.get("integration");
|
|
499
|
+
const agentId = parsed.searchParams.get("agent_id");
|
|
500
|
+
const authConfigId = parsed.searchParams.get("auth_config_id");
|
|
501
|
+
const conversationId = parsed.searchParams.get("conversation_id");
|
|
502
|
+
if (!integrationKey || !agentId || !authConfigId || !conversationId) return null;
|
|
503
|
+
return {
|
|
504
|
+
integrationKey,
|
|
505
|
+
agentId,
|
|
506
|
+
authConfigId,
|
|
507
|
+
conversationId,
|
|
508
|
+
scopes: parsed.searchParams.get("scopes")
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function parseExternalOauthConnectParams(url) {
|
|
512
|
+
const parsed = safeUrl(url);
|
|
513
|
+
if (!parsed) return null;
|
|
514
|
+
const agentId = parsed.searchParams.get("agent_id");
|
|
515
|
+
const conversationId = parsed.searchParams.get("conversation_id");
|
|
516
|
+
const serverUrl = parsed.searchParams.get("server_url");
|
|
517
|
+
if (!agentId || !conversationId || !serverUrl) return null;
|
|
518
|
+
return {
|
|
519
|
+
agentId,
|
|
520
|
+
conversationId,
|
|
521
|
+
serverUrl
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* A short human line for a failed card action. Fulfill/connect endpoints
|
|
526
|
+
* return `{ error: string }` bodies (e.g. "already fulfilled") — prefer that
|
|
527
|
+
* over the generic HttpError message.
|
|
528
|
+
*/
|
|
529
|
+
function cardActionErrorMessage(err) {
|
|
530
|
+
if (err instanceof HttpError) {
|
|
531
|
+
try {
|
|
532
|
+
const parsed = JSON.parse(err.body);
|
|
533
|
+
if (isRecord(parsed) && typeof parsed.error === "string") return parsed.error;
|
|
534
|
+
} catch (_error) {}
|
|
535
|
+
return `request failed (HTTP ${err.status})`;
|
|
536
|
+
}
|
|
537
|
+
return errorMessage(err);
|
|
538
|
+
}
|
|
539
|
+
const MASK_CHAR = "•";
|
|
540
|
+
/**
|
|
541
|
+
* Recover the real secret from the masked input's displayed text. The input
|
|
542
|
+
* is controlled: after every edit we render bullets, which forces the cursor
|
|
543
|
+
* to the end, so the next edit is always a tail edit — the displayed text is
|
|
544
|
+
* some prefix of the old mask (kept characters) followed by newly typed or
|
|
545
|
+
* pasted plaintext. Characters beyond the retained bullets are the new tail.
|
|
546
|
+
*/
|
|
547
|
+
function reconcileMaskedInput(previousValue, displayed) {
|
|
548
|
+
let kept = 0;
|
|
549
|
+
while (kept < displayed.length && kept < previousValue.length && displayed[kept] === MASK_CHAR) kept++;
|
|
550
|
+
return previousValue.slice(0, kept) + displayed.slice(kept);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region src/chat/connect-cards.ts
|
|
555
|
+
/**
|
|
556
|
+
* Turn a parsed ConnectCard into the headless summary, resolving any relative
|
|
557
|
+
* connect URL against the app origin so the emitted URL is directly openable.
|
|
558
|
+
*/
|
|
559
|
+
function summarizeConnectCard(card, appUrl) {
|
|
560
|
+
const base = {
|
|
561
|
+
title: card.title,
|
|
562
|
+
subtitle: card.subtitle,
|
|
563
|
+
description: card.description
|
|
564
|
+
};
|
|
565
|
+
const button = card.button;
|
|
566
|
+
if (!button) return {
|
|
567
|
+
...base,
|
|
568
|
+
action: { kind: "unsupported" }
|
|
569
|
+
};
|
|
570
|
+
const act = button.action;
|
|
571
|
+
switch (act.kind) {
|
|
572
|
+
case "open_oauth":
|
|
573
|
+
case "open_external_oauth":
|
|
574
|
+
case "open_github_app": return {
|
|
575
|
+
...base,
|
|
576
|
+
action: {
|
|
577
|
+
kind: "open_url",
|
|
578
|
+
url: resolveConnectUrl(act.url, appUrl)
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
case "submit_credential": return {
|
|
582
|
+
...base,
|
|
583
|
+
action: {
|
|
584
|
+
kind: "submit_credential",
|
|
585
|
+
url: resolveConnectUrl(act.url, appUrl),
|
|
586
|
+
fields: card.fields.map((f) => f.label)
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
case "grant_portal": return {
|
|
590
|
+
...base,
|
|
591
|
+
action: {
|
|
592
|
+
kind: "approve_portal",
|
|
593
|
+
agentId: act.agentId
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
default: return {
|
|
597
|
+
...base,
|
|
598
|
+
action: { kind: "unsupported" }
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Parse a `data-anyone-render-spec` stream chunk into a connect-card summary,
|
|
604
|
+
* or null if the chunk isn't a connect card (other render specs — training,
|
|
605
|
+
* deep-learn, compute-request — parse to null, same as the TUI).
|
|
606
|
+
*/
|
|
607
|
+
function connectCardFromChunk(chunk, appUrl) {
|
|
608
|
+
if (chunk["type"] !== "data-anyone-render-spec") return null;
|
|
609
|
+
const data = chunk["data"];
|
|
610
|
+
if (!isRecord(data)) return null;
|
|
611
|
+
const card = parseConnectCard(data["spec"]);
|
|
612
|
+
if (!card) return null;
|
|
613
|
+
return summarizeConnectCard(card, appUrl);
|
|
614
|
+
}
|
|
615
|
+
/** Render a connect-card summary as a human-readable action block. */
|
|
616
|
+
function formatConnectCard(card) {
|
|
617
|
+
const lines = [];
|
|
618
|
+
lines.push(`\n[action needed] ${card.title}`);
|
|
619
|
+
if (card.subtitle) lines.push(card.subtitle);
|
|
620
|
+
if (card.description) lines.push(card.description);
|
|
621
|
+
switch (card.action.kind) {
|
|
622
|
+
case "open_url":
|
|
623
|
+
lines.push(`Open to continue: ${card.action.url}`);
|
|
624
|
+
break;
|
|
625
|
+
case "submit_credential":
|
|
626
|
+
lines.push(`Provide credential (${card.action.fields.join(", ") || "value"}) at: ${card.action.url}`);
|
|
627
|
+
break;
|
|
628
|
+
case "approve_portal":
|
|
629
|
+
lines.push(`Approve local-machine access for agent ${card.action.agentId} in the TUI or web app.`);
|
|
630
|
+
break;
|
|
631
|
+
case "unsupported":
|
|
632
|
+
lines.push("Open this conversation in the web app to continue.");
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
return lines.join("\n");
|
|
636
|
+
}
|
|
637
|
+
|
|
351
638
|
//#endregion
|
|
352
639
|
//#region src/chat/print.ts
|
|
353
640
|
var print_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -385,6 +672,7 @@ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversat
|
|
|
385
672
|
let text = "";
|
|
386
673
|
const controller = new AbortController();
|
|
387
674
|
let streamError = null;
|
|
675
|
+
const connectCards = [];
|
|
388
676
|
await client.streamRun({
|
|
389
677
|
runId: send.runId,
|
|
390
678
|
signal: controller.signal,
|
|
@@ -401,17 +689,23 @@ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversat
|
|
|
401
689
|
if (!json) process.stdout.write(delta);
|
|
402
690
|
}
|
|
403
691
|
} else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
|
|
692
|
+
else {
|
|
693
|
+
const card = connectCardFromChunk(chunk, appUrl);
|
|
694
|
+
if (card) connectCards.push(card);
|
|
695
|
+
}
|
|
404
696
|
}
|
|
405
697
|
});
|
|
406
698
|
if (streamError) throw new Error(streamError);
|
|
407
699
|
if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
|
|
700
|
+
if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
|
|
408
701
|
return {
|
|
409
702
|
agentId: agent.id,
|
|
410
703
|
agentName: agent.name,
|
|
411
704
|
conversationId: send.conversationId,
|
|
412
705
|
isNewConversation: send.isNewConversation,
|
|
413
706
|
runId: send.runId,
|
|
414
|
-
text
|
|
707
|
+
text,
|
|
708
|
+
connectCards
|
|
415
709
|
};
|
|
416
710
|
}
|
|
417
711
|
/**
|
|
@@ -448,4 +742,4 @@ async function readStdin() {
|
|
|
448
742
|
}
|
|
449
743
|
|
|
450
744
|
//#endregion
|
|
451
|
-
export {
|
|
745
|
+
export { parseExternalOauthConnectParams as a, resolveConnectUrl as c, isRecord as d, HttpError as f, cardActionErrorMessage as i, parseConnectCard as l, resolveAgent as n, parseOauthConnectParams as o, createRestClient as p, MASK_CHAR as r, reconcileMaskedInput as s, print_exports as t, errorMessage as u };
|