dsh-code 1.0.0 → 1.0.1
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.en.md +278 -265
- package/README.md +277 -264
- package/cordis.patch.yml +7 -0
- package/lib/index.mjs +923 -223
- package/lib/types/app.d.ts +18 -2
- package/lib/types/attachments.d.ts +13 -0
- package/lib/types/authorization-panel.d.ts +22 -0
- package/lib/types/authorization.d.ts +36 -0
- package/lib/types/keyboard.d.ts +1 -3
- package/lib/types/mentions.d.ts +2 -0
- package/lib/types/models.d.ts +3 -1
- package/lib/types/permissions.d.ts +4 -14
- package/lib/types/presets.d.ts +4 -17
- package/lib/types/render/status.d.ts +1 -1
- package/package.json +49 -43
- package/src/app.ts +4667 -4378
- package/src/attachments.ts +86 -2
- package/src/authorization-panel.ts +285 -0
- package/src/authorization.ts +147 -0
- package/src/index.ts +34 -21
- package/src/internals.ts +3 -3
- package/src/keyboard.ts +33 -32
- package/src/mentions.ts +6 -0
- package/src/models.ts +20 -14
- package/src/permissions.ts +5 -13
- package/src/presets.ts +5 -18
- package/src/provider-settings.ts +1 -1
- package/src/render/animations.ts +420 -420
- package/src/render/projection.ts +7 -3
- package/src/render/status.ts +2 -2
- package/src/session-directory.ts +1 -1
package/lib/index.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import { randomUUID } from "node:crypto";
|
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import { readFileSync, realpathSync } from "node:fs";
|
|
6
6
|
import os, { homedir } from "node:os";
|
|
7
|
-
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
8
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
+
import { mkdir, open, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
8
|
+
import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
9
9
|
import z from "@deepseek-ai/schemastery";
|
|
10
10
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
11
11
|
import { MessageId, ReasoningEffortId, assertNever, boundContextSummary, createUserMessage, normalizeApiKey } from "@deepseek-ai/dsh-llm";
|
|
@@ -14,11 +14,14 @@ import { PassThrough, Stream } from "node:stream";
|
|
|
14
14
|
import process$1, { cwd, env } from "node:process";
|
|
15
15
|
import { EventEmitter } from "node:events";
|
|
16
16
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
17
|
+
import { execFile, spawn } from "node:child_process";
|
|
18
|
+
import { credentialKeyId, credentialKeyScope } from "@deepseek-ai/dsh-credentials";
|
|
19
|
+
import { AuthorizationDeclinedError } from "@deepseek-ai/dsh-authorization";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
17
21
|
import { DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES, DEFAULT_FILE_SEARCH_MAX_ENTRIES, DEFAULT_FILE_SEARCH_MAX_RESULTS, WorkspaceFileSearch } from "@deepseek-ai/dsh-file-reference-local";
|
|
18
22
|
import { formatSessionReferenceMention, parseSessionReferenceText } from "@deepseek-ai/dsh-session-reference";
|
|
19
23
|
import { UserQuestionError } from "@deepseek-ai/dsh-user-questions";
|
|
20
24
|
import { isUserInvocable } from "@deepseek-ai/dsh-skill";
|
|
21
|
-
import { execFile, spawn } from "node:child_process";
|
|
22
25
|
//#region node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
|
|
23
26
|
/**
|
|
24
27
|
* @license React
|
|
@@ -24919,7 +24922,9 @@ function imageLabels(images) {
|
|
|
24919
24922
|
if (images === void 0 || images.length === 0) return "";
|
|
24920
24923
|
return images.map((image, index) => {
|
|
24921
24924
|
const rawName = image.name?.trim() || `image ${index + 1}`;
|
|
24922
|
-
|
|
24925
|
+
const name = rawName.length <= 80 ? rawName : `${rawName.slice(0, 79)}…`;
|
|
24926
|
+
const original = image.originalDimensions;
|
|
24927
|
+
return `[image: ${name} · ${original === void 0 ? `${image.width}×${image.height}` : `${image.width}×${image.height} · original ${original.width}×${original.height}`} · ${image.bytes} B]`;
|
|
24923
24928
|
}).join("\n");
|
|
24924
24929
|
}
|
|
24925
24930
|
/** Prompt text with its durable image labels, without exposing local paths or bytes. */
|
|
@@ -27112,7 +27117,7 @@ function mergeSessionTitles(rows, observations) {
|
|
|
27112
27117
|
const titles = /* @__PURE__ */ new Map();
|
|
27113
27118
|
for (const observation of observations) {
|
|
27114
27119
|
if (observation.status !== "fulfilled") continue;
|
|
27115
|
-
const title = observation.value?.title?.title
|
|
27120
|
+
const title = observation.value?.title?.title;
|
|
27116
27121
|
if (title !== void 0 && title.trim() !== "") titles.set(observation.sessionId, title);
|
|
27117
27122
|
}
|
|
27118
27123
|
return rows.map((row) => titles.has(row.id) ? {
|
|
@@ -27253,10 +27258,10 @@ function formatRate(n) {
|
|
|
27253
27258
|
/**
|
|
27254
27259
|
* Cache-hit share of billed prompt-side input.
|
|
27255
27260
|
* @param usage - cumulative token totals.
|
|
27256
|
-
* @returns rounded
|
|
27261
|
+
* @returns percent rounded to one decimal place, or null when no input was billed.
|
|
27257
27262
|
*/
|
|
27258
27263
|
function cacheHitPercent(usage) {
|
|
27259
|
-
return usage.inputTokens === 0 ? null : Math.round(usage.cacheReadTokens / usage.inputTokens *
|
|
27264
|
+
return usage.inputTokens === 0 ? null : Math.round(usage.cacheReadTokens / usage.inputTokens * 1e3) / 10;
|
|
27260
27265
|
}
|
|
27261
27266
|
/** Separator between trailing state spans. */
|
|
27262
27267
|
const STATUS_ITEM_SEPARATOR = " · ";
|
|
@@ -28398,7 +28403,7 @@ const PASTE_END_MARKER = "[201~";
|
|
|
28398
28403
|
* "[200~"/"[201~" markers Ink leaves after stripping the ESC byte.
|
|
28399
28404
|
*/
|
|
28400
28405
|
function stripPasteMarkers(text) {
|
|
28401
|
-
return text.replaceAll(PASTE_START_MARKER, "").replaceAll(PASTE_END_MARKER, "");
|
|
28406
|
+
return text.replaceAll(`\x1b${PASTE_START_MARKER}`, "").replaceAll(`\x1b${PASTE_END_MARKER}`, "").replaceAll(PASTE_START_MARKER, "").replaceAll(PASTE_END_MARKER, "");
|
|
28402
28407
|
}
|
|
28403
28408
|
/** Match one CSI-u sequence (code, optional ;modifiers, then :event or ;alternate). */
|
|
28404
28409
|
const CSI_U_SOURCE = "\x1B\\[(\\d+)(?:;(\\d+))?(?:[:;](\\d+))?u";
|
|
@@ -28409,7 +28414,6 @@ function legacyForKey(key) {
|
|
|
28409
28414
|
const alt = (bits & 2) !== 0;
|
|
28410
28415
|
const ctrl = (bits & 4) !== 0;
|
|
28411
28416
|
if (key.code === 13) {
|
|
28412
|
-
if (shift) return "\x1B[13;2u";
|
|
28413
28417
|
if (ctrl) return "\n";
|
|
28414
28418
|
if (alt) return "\x1B\r";
|
|
28415
28419
|
return "\r";
|
|
@@ -29446,6 +29450,504 @@ function recallNewer(state) {
|
|
|
29446
29450
|
};
|
|
29447
29451
|
}
|
|
29448
29452
|
//#endregion
|
|
29453
|
+
//#region src/authorization.ts
|
|
29454
|
+
/** Terminal adapter over the Harness provider-authorization and credential-record seams. */
|
|
29455
|
+
/** Record scope used by the upstream pi-ai adapter for provider logins. */
|
|
29456
|
+
const PI_AI_RECORD_SCOPE = "llm-pi-ai";
|
|
29457
|
+
/** Load only model-provider flows; unrelated future authorization domains stay out of `/model`. */
|
|
29458
|
+
async function loadProviderAuthorizations(ctx) {
|
|
29459
|
+
const authorization = ctx.get("authorization");
|
|
29460
|
+
const credentials = ctx.get("credentials");
|
|
29461
|
+
if (authorization === void 0 || credentials === void 0) return {
|
|
29462
|
+
rows: [],
|
|
29463
|
+
failures: []
|
|
29464
|
+
};
|
|
29465
|
+
const entries = authorization.list().filter((entry) => credentialKeyScope(entry.key) === PI_AI_RECORD_SCOPE);
|
|
29466
|
+
const failures = [];
|
|
29467
|
+
return {
|
|
29468
|
+
rows: await Promise.all(entries.map(async (entry) => {
|
|
29469
|
+
let record;
|
|
29470
|
+
try {
|
|
29471
|
+
record = await credentials.describeRecord(entry.key);
|
|
29472
|
+
} catch (error) {
|
|
29473
|
+
failures.push(`${entry.label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
29474
|
+
record = {
|
|
29475
|
+
configured: false,
|
|
29476
|
+
writable: false
|
|
29477
|
+
};
|
|
29478
|
+
}
|
|
29479
|
+
return {
|
|
29480
|
+
key: entry.key,
|
|
29481
|
+
provider: credentialKeyId(entry.key),
|
|
29482
|
+
label: entry.label,
|
|
29483
|
+
methods: entry.methods,
|
|
29484
|
+
inFlight: entry.inFlight,
|
|
29485
|
+
record
|
|
29486
|
+
};
|
|
29487
|
+
})),
|
|
29488
|
+
failures
|
|
29489
|
+
};
|
|
29490
|
+
}
|
|
29491
|
+
/** Subscribe to login settlement and credential-record changes. */
|
|
29492
|
+
function subscribeProviderAuthorizations(ctx, listener) {
|
|
29493
|
+
const settled = ctx.on("authorization/settled", (key) => {
|
|
29494
|
+
if (credentialKeyScope(key) === PI_AI_RECORD_SCOPE) listener();
|
|
29495
|
+
});
|
|
29496
|
+
const records = ctx.on("credentials/record-updated", (key) => {
|
|
29497
|
+
if (credentialKeyScope(key) === PI_AI_RECORD_SCOPE) listener();
|
|
29498
|
+
});
|
|
29499
|
+
return () => {
|
|
29500
|
+
settled();
|
|
29501
|
+
records();
|
|
29502
|
+
};
|
|
29503
|
+
}
|
|
29504
|
+
/** Begin one provider login through the interaction surface owned by the caller. */
|
|
29505
|
+
async function beginProviderAuthorization(ctx, row, method, interaction, signal) {
|
|
29506
|
+
const authorization = ctx.get("authorization");
|
|
29507
|
+
if (authorization === void 0) throw new Error("provider login is unavailable in this profile");
|
|
29508
|
+
return (await authorization.begin({
|
|
29509
|
+
key: row.key,
|
|
29510
|
+
method,
|
|
29511
|
+
interaction,
|
|
29512
|
+
signal
|
|
29513
|
+
})).status;
|
|
29514
|
+
}
|
|
29515
|
+
/** Cancel the attempt currently serving this provider, if any. */
|
|
29516
|
+
function cancelProviderAuthorization(ctx, key) {
|
|
29517
|
+
ctx.get("authorization")?.cancel(key);
|
|
29518
|
+
}
|
|
29519
|
+
/** Remove an authorization record without changing the provider's settings profile. */
|
|
29520
|
+
async function logoutProviderAuthorization(ctx, row) {
|
|
29521
|
+
const credentials = ctx.get("credentials");
|
|
29522
|
+
if (credentials === void 0) throw new Error("credential storage is unavailable in this profile");
|
|
29523
|
+
const current = await credentials.describeRecord(row.key);
|
|
29524
|
+
if (!current.configured) return;
|
|
29525
|
+
if (!current.writable) throw new Error("this login record is read-only");
|
|
29526
|
+
await credentials.deleteRecord(row.key);
|
|
29527
|
+
}
|
|
29528
|
+
/** Open an authorization URL with the platform default browser, without invoking a shell. */
|
|
29529
|
+
function openAuthorizationUrl(raw) {
|
|
29530
|
+
let url;
|
|
29531
|
+
try {
|
|
29532
|
+
url = new URL(raw);
|
|
29533
|
+
} catch {
|
|
29534
|
+
return false;
|
|
29535
|
+
}
|
|
29536
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return false;
|
|
29537
|
+
const target = url.toString();
|
|
29538
|
+
try {
|
|
29539
|
+
const child = process.platform === "win32" ? spawn("explorer.exe", [target], {
|
|
29540
|
+
detached: true,
|
|
29541
|
+
stdio: "ignore"
|
|
29542
|
+
}) : process.platform === "darwin" ? spawn("/usr/bin/open", [target], {
|
|
29543
|
+
detached: true,
|
|
29544
|
+
stdio: "ignore"
|
|
29545
|
+
}) : spawn("xdg-open", [target], {
|
|
29546
|
+
detached: true,
|
|
29547
|
+
stdio: "ignore"
|
|
29548
|
+
});
|
|
29549
|
+
child.once("error", () => {});
|
|
29550
|
+
child.unref();
|
|
29551
|
+
return true;
|
|
29552
|
+
} catch {
|
|
29553
|
+
return false;
|
|
29554
|
+
}
|
|
29555
|
+
}
|
|
29556
|
+
/** Compact value-free status for the provider list. */
|
|
29557
|
+
function providerAuthorizationStatus(row) {
|
|
29558
|
+
if (row === void 0) return "login unavailable";
|
|
29559
|
+
if (row.inFlight) return "login in progress";
|
|
29560
|
+
if (!row.record.configured) return "not logged in";
|
|
29561
|
+
return row.record.kind === "grant" ? "OAuth" : "interactive API key";
|
|
29562
|
+
}
|
|
29563
|
+
/** Find a provider's login flow from a previously loaded directory. */
|
|
29564
|
+
function authorizationForProvider(directory, provider) {
|
|
29565
|
+
return directory?.rows.find((row) => row.provider === provider);
|
|
29566
|
+
}
|
|
29567
|
+
//#endregion
|
|
29568
|
+
//#region src/authorization-panel.ts
|
|
29569
|
+
/** Bounded Ink surfaces for provider login and logout. */
|
|
29570
|
+
/** Run one upstream authorization flow without letting notices or prompts exceed the panel budget. */
|
|
29571
|
+
function ProviderAuthorizationPanel(props) {
|
|
29572
|
+
const stdout = useStdout().stdout;
|
|
29573
|
+
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
29574
|
+
const [phase, setPhase] = (0, import_react.useState)("methods");
|
|
29575
|
+
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
29576
|
+
const [notices, setNotices] = (0, import_react.useState)([]);
|
|
29577
|
+
const [prompt, setPrompt] = (0, import_react.useState)(void 0);
|
|
29578
|
+
const [draft, setDraft] = (0, import_react.useState)("");
|
|
29579
|
+
const [promptCursor, setPromptCursor] = (0, import_react.useState)(0);
|
|
29580
|
+
const [error, setError] = (0, import_react.useState)(void 0);
|
|
29581
|
+
const [copyState, setCopyState] = (0, import_react.useState)(void 0);
|
|
29582
|
+
const controllerRef = (0, import_react.useRef)(void 0);
|
|
29583
|
+
const replyRef = (0, import_react.useRef)(void 0);
|
|
29584
|
+
const openedUrls = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
29585
|
+
const clearReply = () => {
|
|
29586
|
+
replyRef.current?.detach();
|
|
29587
|
+
replyRef.current = void 0;
|
|
29588
|
+
setPrompt(void 0);
|
|
29589
|
+
setDraft("");
|
|
29590
|
+
setPromptCursor(0);
|
|
29591
|
+
};
|
|
29592
|
+
const decline = () => {
|
|
29593
|
+
const reply = replyRef.current;
|
|
29594
|
+
clearReply();
|
|
29595
|
+
reply?.reject(new AuthorizationDeclinedError());
|
|
29596
|
+
};
|
|
29597
|
+
const stop = () => {
|
|
29598
|
+
controllerRef.current?.abort();
|
|
29599
|
+
controllerRef.current = void 0;
|
|
29600
|
+
props.cancel(props.row.key);
|
|
29601
|
+
decline();
|
|
29602
|
+
};
|
|
29603
|
+
(0, import_react.useEffect)(() => () => {
|
|
29604
|
+
controllerRef.current?.abort();
|
|
29605
|
+
props.cancel(props.row.key);
|
|
29606
|
+
const reply = replyRef.current;
|
|
29607
|
+
replyRef.current = void 0;
|
|
29608
|
+
reply?.detach();
|
|
29609
|
+
reply?.reject(new AuthorizationDeclinedError());
|
|
29610
|
+
}, [props.row.key]);
|
|
29611
|
+
const start = (method) => {
|
|
29612
|
+
setPhase("running");
|
|
29613
|
+
setError(void 0);
|
|
29614
|
+
setNotices([]);
|
|
29615
|
+
setCopyState(void 0);
|
|
29616
|
+
const controller = new AbortController();
|
|
29617
|
+
controllerRef.current = controller;
|
|
29618
|
+
props.begin(props.row, method, {
|
|
29619
|
+
notify(notice) {
|
|
29620
|
+
setNotices((current) => [...current.slice(-19), notice]);
|
|
29621
|
+
if (notice.url !== void 0 && !openedUrls.current.has(notice.url)) {
|
|
29622
|
+
openedUrls.current.add(notice.url);
|
|
29623
|
+
props.openUrl(notice.url);
|
|
29624
|
+
}
|
|
29625
|
+
},
|
|
29626
|
+
prompt(next) {
|
|
29627
|
+
return new Promise((resolve, reject) => {
|
|
29628
|
+
const onWithdraw = () => {
|
|
29629
|
+
if (replyRef.current?.reject !== reject) return;
|
|
29630
|
+
clearReply();
|
|
29631
|
+
reject(/* @__PURE__ */ new Error("authorization prompt was withdrawn"));
|
|
29632
|
+
};
|
|
29633
|
+
next.signal?.addEventListener("abort", onWithdraw, { once: true });
|
|
29634
|
+
replyRef.current = {
|
|
29635
|
+
resolve,
|
|
29636
|
+
reject,
|
|
29637
|
+
detach: () => next.signal?.removeEventListener("abort", onWithdraw)
|
|
29638
|
+
};
|
|
29639
|
+
setPrompt(next);
|
|
29640
|
+
setDraft("");
|
|
29641
|
+
setPromptCursor(0);
|
|
29642
|
+
});
|
|
29643
|
+
}
|
|
29644
|
+
}, controller.signal).then((status) => {
|
|
29645
|
+
controllerRef.current = void 0;
|
|
29646
|
+
clearReply();
|
|
29647
|
+
if (status === "authorized") props.done();
|
|
29648
|
+
else props.back();
|
|
29649
|
+
}, (reason) => {
|
|
29650
|
+
controllerRef.current = void 0;
|
|
29651
|
+
clearReply();
|
|
29652
|
+
if (controller.signal.aborted) {
|
|
29653
|
+
props.back();
|
|
29654
|
+
return;
|
|
29655
|
+
}
|
|
29656
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
29657
|
+
setPhase("methods");
|
|
29658
|
+
});
|
|
29659
|
+
};
|
|
29660
|
+
const answer = (value) => {
|
|
29661
|
+
const reply = replyRef.current;
|
|
29662
|
+
clearReply();
|
|
29663
|
+
reply?.resolve(value);
|
|
29664
|
+
};
|
|
29665
|
+
useInput((input, key) => {
|
|
29666
|
+
if (phase === "methods") {
|
|
29667
|
+
if (key.escape || input === "q") {
|
|
29668
|
+
props.back();
|
|
29669
|
+
return;
|
|
29670
|
+
}
|
|
29671
|
+
if (props.row.methods.length === 0) return;
|
|
29672
|
+
if (key.upArrow) {
|
|
29673
|
+
setCursor((current) => (current + props.row.methods.length - 1) % props.row.methods.length);
|
|
29674
|
+
return;
|
|
29675
|
+
}
|
|
29676
|
+
if (key.downArrow) {
|
|
29677
|
+
setCursor((current) => (current + 1) % props.row.methods.length);
|
|
29678
|
+
return;
|
|
29679
|
+
}
|
|
29680
|
+
if (key.return) start(props.row.methods[cursor]?.id ?? props.row.methods[0].id);
|
|
29681
|
+
return;
|
|
29682
|
+
}
|
|
29683
|
+
if (key.escape) {
|
|
29684
|
+
stop();
|
|
29685
|
+
props.back();
|
|
29686
|
+
return;
|
|
29687
|
+
}
|
|
29688
|
+
const copyValue = notices.at(-1)?.code ?? notices.at(-1)?.url;
|
|
29689
|
+
if ((input === "c" || input === "C") && copyValue !== void 0) {
|
|
29690
|
+
props.copy(copyValue).then(() => setCopyState("copied"), (reason) => setCopyState(`copy failed: ${reason instanceof Error ? reason.message : String(reason)}`));
|
|
29691
|
+
return;
|
|
29692
|
+
}
|
|
29693
|
+
if (prompt === void 0) return;
|
|
29694
|
+
if (prompt.kind === "select") {
|
|
29695
|
+
if (prompt.options.length === 0) return;
|
|
29696
|
+
if (key.upArrow) {
|
|
29697
|
+
setPromptCursor((current) => (current + prompt.options.length - 1) % prompt.options.length);
|
|
29698
|
+
return;
|
|
29699
|
+
}
|
|
29700
|
+
if (key.downArrow) {
|
|
29701
|
+
setPromptCursor((current) => (current + 1) % prompt.options.length);
|
|
29702
|
+
return;
|
|
29703
|
+
}
|
|
29704
|
+
if (key.return) answer(prompt.options[promptCursor]?.id ?? prompt.options[0].id);
|
|
29705
|
+
return;
|
|
29706
|
+
}
|
|
29707
|
+
if (key.backspace || key.delete) {
|
|
29708
|
+
setDraft((current) => [...current].slice(0, -1).join(""));
|
|
29709
|
+
return;
|
|
29710
|
+
}
|
|
29711
|
+
if (key.return) {
|
|
29712
|
+
if (draft.trim() !== "") answer(draft);
|
|
29713
|
+
return;
|
|
29714
|
+
}
|
|
29715
|
+
if (input !== "" && !key.ctrl && !key.meta) setDraft((current) => current + input);
|
|
29716
|
+
});
|
|
29717
|
+
if (viewport.maxHeight === 0) return (0, import_react.createElement)(Box, { display: "none" });
|
|
29718
|
+
if (viewport.compact) return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns("provider login · esc cancel", viewport.contentColumns));
|
|
29719
|
+
const rows = [];
|
|
29720
|
+
if (phase === "methods") {
|
|
29721
|
+
if (error !== void 0) rows.push({
|
|
29722
|
+
key: "error",
|
|
29723
|
+
text: ` ${singleLineText(error)}`,
|
|
29724
|
+
color: inkColor(getPalette().error)
|
|
29725
|
+
});
|
|
29726
|
+
props.row.methods.forEach((method, index) => {
|
|
29727
|
+
rows.push({
|
|
29728
|
+
key: method.id,
|
|
29729
|
+
text: `${index === cursor ? "› " : " "}${displayText(method.label)}`,
|
|
29730
|
+
color: inkColor(index === cursor ? getPalette().brandBright : getPalette().dim)
|
|
29731
|
+
});
|
|
29732
|
+
});
|
|
29733
|
+
} else {
|
|
29734
|
+
notices.forEach((notice, index) => {
|
|
29735
|
+
rows.push({
|
|
29736
|
+
key: `notice-${index}`,
|
|
29737
|
+
text: ` ${displayText(notice.message)}`
|
|
29738
|
+
});
|
|
29739
|
+
if (notice.url !== void 0) rows.push({
|
|
29740
|
+
key: `url-${index}`,
|
|
29741
|
+
text: ` ${displayText(notice.url)}`,
|
|
29742
|
+
color: inkColor(getPalette().brandBright)
|
|
29743
|
+
});
|
|
29744
|
+
if (notice.code !== void 0) rows.push({
|
|
29745
|
+
key: `code-${index}`,
|
|
29746
|
+
text: ` code ${displayText(notice.code)}`,
|
|
29747
|
+
color: inkColor(getPalette().success),
|
|
29748
|
+
bold: true
|
|
29749
|
+
});
|
|
29750
|
+
});
|
|
29751
|
+
if (prompt !== void 0) {
|
|
29752
|
+
rows.push({
|
|
29753
|
+
key: "prompt",
|
|
29754
|
+
text: ` ${displayText(prompt.message)}`,
|
|
29755
|
+
color: inkColor(getPalette().brandBright)
|
|
29756
|
+
});
|
|
29757
|
+
if (prompt.kind === "select") prompt.options.forEach((option, index) => rows.push({
|
|
29758
|
+
key: `option-${option.id}`,
|
|
29759
|
+
text: `${index === promptCursor ? "› " : " "}${displayText(option.label)}${option.description === void 0 ? "" : ` · ${displayText(option.description)}`}`,
|
|
29760
|
+
color: inkColor(index === promptCursor ? getPalette().brandBright : getPalette().dim)
|
|
29761
|
+
}));
|
|
29762
|
+
else {
|
|
29763
|
+
const shown = prompt.kind === "secret" ? "•".repeat([...draft].length) : displayText(draft);
|
|
29764
|
+
rows.push({
|
|
29765
|
+
key: "draft",
|
|
29766
|
+
text: ` ${shown}▏`,
|
|
29767
|
+
color: inkColor(getPalette().text)
|
|
29768
|
+
});
|
|
29769
|
+
}
|
|
29770
|
+
} else rows.push({
|
|
29771
|
+
key: "waiting",
|
|
29772
|
+
text: " waiting for provider…",
|
|
29773
|
+
color: inkColor(getPalette().dim)
|
|
29774
|
+
});
|
|
29775
|
+
if (copyState !== void 0) rows.push({
|
|
29776
|
+
key: "copy",
|
|
29777
|
+
text: ` ${singleLineText(copyState)}`,
|
|
29778
|
+
color: inkColor(copyState === "copied" ? getPalette().success : getPalette().error)
|
|
29779
|
+
});
|
|
29780
|
+
}
|
|
29781
|
+
const visible = rows.slice(Math.max(0, rows.length - viewport.bodyRows));
|
|
29782
|
+
const footer = phase === "methods" ? "↑↓ choose · enter continue · esc/q back" : "enter answer · c copy URL/code · esc cancel login";
|
|
29783
|
+
return (0, import_react.createElement)(Box, {
|
|
29784
|
+
flexDirection: "column",
|
|
29785
|
+
width: viewport.outerColumns,
|
|
29786
|
+
paddingX: 1,
|
|
29787
|
+
borderStyle: "round",
|
|
29788
|
+
borderColor: inkColor(getPalette().brand)
|
|
29789
|
+
}, (0, import_react.createElement)(Text, {
|
|
29790
|
+
color: inkColor(getPalette().brand),
|
|
29791
|
+
bold: true,
|
|
29792
|
+
wrap: "truncate-end"
|
|
29793
|
+
}, truncateColumns(`/model · login ${displayText(props.row.label)}`, viewport.contentColumns)), ...visible.map((row) => (0, import_react.createElement)(Text, {
|
|
29794
|
+
key: row.key,
|
|
29795
|
+
color: row.color,
|
|
29796
|
+
bold: row.bold,
|
|
29797
|
+
wrap: "truncate-end"
|
|
29798
|
+
}, truncateColumns(row.text, viewport.contentColumns))), (0, import_react.createElement)(Text, {
|
|
29799
|
+
color: inkColor(getPalette().dim),
|
|
29800
|
+
wrap: "truncate-end"
|
|
29801
|
+
}, truncateColumns(footer, viewport.contentColumns)));
|
|
29802
|
+
}
|
|
29803
|
+
function ProviderAuthorizationLogoutPanel({ row, confirm, done, back }) {
|
|
29804
|
+
const stdout = useStdout().stdout;
|
|
29805
|
+
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
29806
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
29807
|
+
const [error, setError] = (0, import_react.useState)(void 0);
|
|
29808
|
+
useInput((input, key) => {
|
|
29809
|
+
if (busy) return;
|
|
29810
|
+
if (key.escape || input === "n" || input === "N") {
|
|
29811
|
+
back();
|
|
29812
|
+
return;
|
|
29813
|
+
}
|
|
29814
|
+
if (input !== "y" && input !== "Y") return;
|
|
29815
|
+
setBusy(true);
|
|
29816
|
+
setError(void 0);
|
|
29817
|
+
confirm(row).then(done, (reason) => {
|
|
29818
|
+
setBusy(false);
|
|
29819
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
29820
|
+
});
|
|
29821
|
+
});
|
|
29822
|
+
if (viewport.maxHeight === 0) return (0, import_react.createElement)(Box, { display: "none" });
|
|
29823
|
+
if (viewport.compact) return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns("y logout · n/esc back", viewport.contentColumns));
|
|
29824
|
+
return (0, import_react.createElement)(Box, {
|
|
29825
|
+
flexDirection: "column",
|
|
29826
|
+
width: viewport.outerColumns,
|
|
29827
|
+
paddingX: 1,
|
|
29828
|
+
borderStyle: "round",
|
|
29829
|
+
borderColor: inkColor(getPalette().warn)
|
|
29830
|
+
}, (0, import_react.createElement)(Text, {
|
|
29831
|
+
color: inkColor(getPalette().warn),
|
|
29832
|
+
bold: true,
|
|
29833
|
+
wrap: "truncate-end"
|
|
29834
|
+
}, truncateColumns("/model · logout provider", viewport.contentColumns)), (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns(` remove ${displayText(row.label)} login record`, viewport.contentColumns)), (0, import_react.createElement)(Text, {
|
|
29835
|
+
color: inkColor(getPalette().dim),
|
|
29836
|
+
wrap: "truncate-end"
|
|
29837
|
+
}, truncateColumns(" provider endpoint and model configuration stay unchanged", viewport.contentColumns)), error === void 0 ? void 0 : (0, import_react.createElement)(Text, {
|
|
29838
|
+
color: inkColor(getPalette().error),
|
|
29839
|
+
wrap: "truncate-end"
|
|
29840
|
+
}, truncateColumns(` ${singleLineText(error)}`, viewport.contentColumns)), (0, import_react.createElement)(Text, {
|
|
29841
|
+
color: inkColor(getPalette().dim),
|
|
29842
|
+
wrap: "truncate-end"
|
|
29843
|
+
}, truncateColumns(busy ? "working…" : "y confirm · n/esc back", viewport.contentColumns)));
|
|
29844
|
+
}
|
|
29845
|
+
//#endregion
|
|
29846
|
+
//#region src/attachments.ts
|
|
29847
|
+
/** Terminal image-file adapter over the Harness durable attachment service. */
|
|
29848
|
+
const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
29849
|
+
".png",
|
|
29850
|
+
".jpg",
|
|
29851
|
+
".jpeg",
|
|
29852
|
+
".webp",
|
|
29853
|
+
".gif"
|
|
29854
|
+
]);
|
|
29855
|
+
/** Detect the supported encoded raster formats from bytes, never from a path suffix. */
|
|
29856
|
+
function detectImageMediaType(data) {
|
|
29857
|
+
if (data.length >= 8 && data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71 && data[4] === 13 && data[5] === 10 && data[6] === 26 && data[7] === 10) return "image/png";
|
|
29858
|
+
if (data.length >= 3 && data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
29859
|
+
if (data.length >= 6) {
|
|
29860
|
+
const signature = String.fromCharCode(...data.subarray(0, 6));
|
|
29861
|
+
if (signature === "GIF87a" || signature === "GIF89a") return "image/gif";
|
|
29862
|
+
}
|
|
29863
|
+
if (data.length >= 12 && String.fromCharCode(...data.subarray(0, 4)) === "RIFF" && String.fromCharCode(...data.subarray(8, 12)) === "WEBP") return "image/webp";
|
|
29864
|
+
}
|
|
29865
|
+
/** Whether a path-like token is worth probing as an image attachment. */
|
|
29866
|
+
function looksLikeImagePath(path) {
|
|
29867
|
+
return IMAGE_EXTENSIONS.has(extname(path).toLowerCase());
|
|
29868
|
+
}
|
|
29869
|
+
/** Parse a terminal paste/drop containing only one or more image paths. */
|
|
29870
|
+
function parsePastedImagePaths(input) {
|
|
29871
|
+
const text = input.trim();
|
|
29872
|
+
if (text === "") return [];
|
|
29873
|
+
const tokens = [];
|
|
29874
|
+
for (const match of text.matchAll(/"([^"]+)"|'([^']+)'|(\S+)/gu)) {
|
|
29875
|
+
const token = match[1] ?? match[2] ?? match[3];
|
|
29876
|
+
if (token === void 0) continue;
|
|
29877
|
+
let path = token;
|
|
29878
|
+
if (path.startsWith("file://")) try {
|
|
29879
|
+
path = fileURLToPath(path);
|
|
29880
|
+
} catch {
|
|
29881
|
+
return [];
|
|
29882
|
+
}
|
|
29883
|
+
if (!looksLikeImagePath(path)) return [];
|
|
29884
|
+
tokens.push(path);
|
|
29885
|
+
}
|
|
29886
|
+
return tokens;
|
|
29887
|
+
}
|
|
29888
|
+
/** Validate path, byte size and encoded signature without writing an attachment object. */
|
|
29889
|
+
async function inspectImagePaths(paths, attachments, cwd = process.cwd()) {
|
|
29890
|
+
if (paths.length === 0) return [];
|
|
29891
|
+
if (attachments === void 0) throw new Error("image attachments are unavailable in this profile");
|
|
29892
|
+
if (paths.length > attachments.imageLimits.maxImagesPerMessage) throw new Error(`too many images (${paths.length}; limit ${attachments.imageLimits.maxImagesPerMessage})`);
|
|
29893
|
+
const inspected = [];
|
|
29894
|
+
let totalBytes = 0;
|
|
29895
|
+
for (const raw of paths) {
|
|
29896
|
+
const path = isAbsolute(raw) ? resolve(raw) : resolve(cwd, raw);
|
|
29897
|
+
let facts;
|
|
29898
|
+
try {
|
|
29899
|
+
facts = await stat(path);
|
|
29900
|
+
} catch (error) {
|
|
29901
|
+
throw new Error(`cannot read image "${raw}": ${error instanceof Error ? error.message : String(error)}`);
|
|
29902
|
+
}
|
|
29903
|
+
if (!facts.isFile()) throw new Error(`image path is not a file: "${raw}"`);
|
|
29904
|
+
if (facts.size > attachments.imageLimits.maxImageBytes) throw new Error(`image "${basename(path)}" is ${facts.size} bytes; limit ${attachments.imageLimits.maxImageBytes}`);
|
|
29905
|
+
totalBytes += facts.size;
|
|
29906
|
+
if (totalBytes > attachments.imageLimits.maxMessageImageBytes) throw new Error(`image batch is ${totalBytes} bytes; limit ${attachments.imageLimits.maxMessageImageBytes}`);
|
|
29907
|
+
const handle = await open(path, "r");
|
|
29908
|
+
try {
|
|
29909
|
+
const signature = /* @__PURE__ */ new Uint8Array(16);
|
|
29910
|
+
const { bytesRead } = await handle.read(signature, 0, signature.length, 0);
|
|
29911
|
+
const mediaType = detectImageMediaType(signature.subarray(0, bytesRead));
|
|
29912
|
+
if (mediaType === void 0 || !attachments.imageLimits.mediaTypes.includes(mediaType)) throw new Error(`unsupported image file "${raw}" (expected PNG, JPEG, WebP, or GIF)`);
|
|
29913
|
+
inspected.push({
|
|
29914
|
+
path,
|
|
29915
|
+
name: basename(path),
|
|
29916
|
+
mediaType,
|
|
29917
|
+
bytes: facts.size
|
|
29918
|
+
});
|
|
29919
|
+
} finally {
|
|
29920
|
+
await handle.close();
|
|
29921
|
+
}
|
|
29922
|
+
}
|
|
29923
|
+
return inspected;
|
|
29924
|
+
}
|
|
29925
|
+
/** Read, validate, and persist an ordered image path list as model content blocks. */
|
|
29926
|
+
async function saveImagePaths(paths, attachments) {
|
|
29927
|
+
if (paths.length === 0) return [];
|
|
29928
|
+
if (attachments === void 0) throw new Error("image attachments are unavailable in this profile");
|
|
29929
|
+
const inputs = [];
|
|
29930
|
+
for (const path of paths) {
|
|
29931
|
+
let data;
|
|
29932
|
+
try {
|
|
29933
|
+
data = await readFile(path);
|
|
29934
|
+
} catch (error) {
|
|
29935
|
+
throw new Error(`cannot read image "${path}": ${error instanceof Error ? error.message : String(error)}`);
|
|
29936
|
+
}
|
|
29937
|
+
const mediaType = detectImageMediaType(data);
|
|
29938
|
+
if (mediaType === void 0) throw new Error(`unsupported image file "${path}" (expected PNG, JPEG, WebP, or GIF)`);
|
|
29939
|
+
inputs.push({
|
|
29940
|
+
data,
|
|
29941
|
+
mediaType,
|
|
29942
|
+
name: basename(path)
|
|
29943
|
+
});
|
|
29944
|
+
}
|
|
29945
|
+
return (await attachments.saveImages(inputs)).map((attachment) => ({
|
|
29946
|
+
type: "image",
|
|
29947
|
+
attachment
|
|
29948
|
+
}));
|
|
29949
|
+
}
|
|
29950
|
+
//#endregion
|
|
29449
29951
|
//#region src/app.ts
|
|
29450
29952
|
/**
|
|
29451
29953
|
* The Ink terminal app: whale-and-wordmark header in DeepSeek blue, the live
|
|
@@ -29490,6 +29992,106 @@ function readSettledRowCap() {
|
|
|
29490
29992
|
const PASTE_BRACKET_TIMEOUT_MS = 1e3;
|
|
29491
29993
|
/** Release the held frame after Ink has replayed the source-backed Static rows. */
|
|
29492
29994
|
const SYNCHRONIZED_UPDATE_END = "\x1B[?2026l";
|
|
29995
|
+
/** One source of truth for TUI-owned slash commands in completion and `/help`. */
|
|
29996
|
+
const LOCAL_COMMANDS = [
|
|
29997
|
+
{
|
|
29998
|
+
label: "/help",
|
|
29999
|
+
description: "show this overlay"
|
|
30000
|
+
},
|
|
30001
|
+
{
|
|
30002
|
+
label: "/model",
|
|
30003
|
+
description: "switch the model and manage providers"
|
|
30004
|
+
},
|
|
30005
|
+
{
|
|
30006
|
+
label: "/effort",
|
|
30007
|
+
description: "adjust reasoning effort for the current model"
|
|
30008
|
+
},
|
|
30009
|
+
{
|
|
30010
|
+
label: "/mode",
|
|
30011
|
+
description: "inspect or select the agent preset (/mode [preset])"
|
|
30012
|
+
},
|
|
30013
|
+
{
|
|
30014
|
+
label: "/permission",
|
|
30015
|
+
description: "inspect or select the permission preset (/permission [preset])"
|
|
30016
|
+
},
|
|
30017
|
+
{
|
|
30018
|
+
label: "/new",
|
|
30019
|
+
description: "create and switch to a fresh session (/new [preset])"
|
|
30020
|
+
},
|
|
30021
|
+
{
|
|
30022
|
+
label: "/fork",
|
|
30023
|
+
description: "fork at the latest completed turn (/fork [event-seq])"
|
|
30024
|
+
},
|
|
30025
|
+
{
|
|
30026
|
+
label: "/resume",
|
|
30027
|
+
description: "browse or switch root sessions (/resume [id|prefix])"
|
|
30028
|
+
},
|
|
30029
|
+
{
|
|
30030
|
+
label: "/plugin",
|
|
30031
|
+
description: "inspect the live plugin composition"
|
|
30032
|
+
},
|
|
30033
|
+
{
|
|
30034
|
+
label: "/jobs",
|
|
30035
|
+
description: "inspect background jobs"
|
|
30036
|
+
},
|
|
30037
|
+
{
|
|
30038
|
+
label: "/statusline",
|
|
30039
|
+
description: "customize the status line items"
|
|
30040
|
+
},
|
|
30041
|
+
{
|
|
30042
|
+
label: "/theme",
|
|
30043
|
+
description: "switch the color theme"
|
|
30044
|
+
},
|
|
30045
|
+
{
|
|
30046
|
+
label: "/history",
|
|
30047
|
+
description: "search and recall past prompts"
|
|
30048
|
+
},
|
|
30049
|
+
{
|
|
30050
|
+
label: "/agents",
|
|
30051
|
+
description: "inspect subagent sessions of this conversation"
|
|
30052
|
+
},
|
|
30053
|
+
{
|
|
30054
|
+
label: "/todos",
|
|
30055
|
+
description: "inspect the full todo list"
|
|
30056
|
+
},
|
|
30057
|
+
{
|
|
30058
|
+
label: "/subagent",
|
|
30059
|
+
description: "choose the model delegated subagents run on"
|
|
30060
|
+
},
|
|
30061
|
+
{
|
|
30062
|
+
label: "/delete",
|
|
30063
|
+
description: "delete a session and its subagent threads"
|
|
30064
|
+
},
|
|
30065
|
+
{
|
|
30066
|
+
label: "/clear",
|
|
30067
|
+
description: "clear the screen"
|
|
30068
|
+
},
|
|
30069
|
+
{
|
|
30070
|
+
label: "/export",
|
|
30071
|
+
description: "export the transcript to markdown (/export [path])"
|
|
30072
|
+
},
|
|
30073
|
+
{
|
|
30074
|
+
label: "/title",
|
|
30075
|
+
description: "rename this session (/title <text>)"
|
|
30076
|
+
},
|
|
30077
|
+
{
|
|
30078
|
+
label: "/copy",
|
|
30079
|
+
description: "copy the latest assistant response"
|
|
30080
|
+
},
|
|
30081
|
+
{
|
|
30082
|
+
label: "/diff",
|
|
30083
|
+
description: "inspect Git changes (/diff [--staged|ref])"
|
|
30084
|
+
},
|
|
30085
|
+
{
|
|
30086
|
+
label: "/review",
|
|
30087
|
+
description: "review Git changes under read-only permissions"
|
|
30088
|
+
},
|
|
30089
|
+
{
|
|
30090
|
+
label: "/quit",
|
|
30091
|
+
description: "exit"
|
|
30092
|
+
}
|
|
30093
|
+
];
|
|
30094
|
+
const LOCAL_COMMAND_NAMES = new Set(LOCAL_COMMANDS.map((command) => command.label.slice(1)));
|
|
29493
30095
|
/** Pad text with spaces to a visible-column target (menu name column). */
|
|
29494
30096
|
function padColumns(text, width) {
|
|
29495
30097
|
const clipped = truncateColumns(singleLineText(text), width);
|
|
@@ -30544,7 +31146,8 @@ function ModelPanel({ directory, error, current, onSelect, onProviders, onRetry,
|
|
|
30544
31146
|
wrap: "truncate-end"
|
|
30545
31147
|
}, truncateColumns(`/model — select model${rows.length === 0 ? "" : ` · ${cursor + 1}/${rows.length}`}`, viewport.contentColumns)), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), ...visibleStateRows, ...visible.map((row) => {
|
|
30546
31148
|
const index = rows.indexOf(row);
|
|
30547
|
-
const
|
|
31149
|
+
const capability = row.inputModalities?.includes("image") === true ? " · image" : "";
|
|
31150
|
+
const label = displayText(`${row.providerName} · ${row.modelName}${capability}`);
|
|
30548
31151
|
return (0, import_react.createElement)(Text, {
|
|
30549
31152
|
key: `${row.provider}/${row.model}`,
|
|
30550
31153
|
color: index === cursor ? inkColor(getPalette().brandBright) : inkColor(getPalette().dim),
|
|
@@ -30567,7 +31170,7 @@ function providerStateLabel(row) {
|
|
|
30567
31170
|
return `${route} · ${row.configured ? "provider auth" : "not configured"}`;
|
|
30568
31171
|
}
|
|
30569
31172
|
/** The provider-management stage reached from /model with `a`. */
|
|
30570
|
-
function ProviderPanel({ directory, error, onCredential, onConfigure, onUnset, onRemove, onRetry, onBack }) {
|
|
31173
|
+
function ProviderPanel({ directory, error, authorizations, authorizationError, onCredential, onConfigure, onUnset, onRemove, onLogin, onLogout, onRetry, onBack }) {
|
|
30571
31174
|
const stdout = useStdout().stdout;
|
|
30572
31175
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
30573
31176
|
const rows = directory?.rows ?? [];
|
|
@@ -30630,6 +31233,19 @@ function ProviderPanel({ directory, error, onCredential, onConfigure, onUnset, o
|
|
|
30630
31233
|
else onRemove(target);
|
|
30631
31234
|
return;
|
|
30632
31235
|
}
|
|
31236
|
+
const authorization = authorizationForProvider(authorizations, target.provider);
|
|
31237
|
+
if (input === "l" || input === "L") {
|
|
31238
|
+
if (authorization === void 0) setActionError("this provider offers no interactive login flow");
|
|
31239
|
+
else if (authorization.inFlight) setActionError("a login attempt is already running for this provider");
|
|
31240
|
+
else onLogin(target, authorization);
|
|
31241
|
+
return;
|
|
31242
|
+
}
|
|
31243
|
+
if (input === "o" || input === "O") {
|
|
31244
|
+
if (authorization === void 0 || !authorization.record.configured) setActionError("this provider has no login record to remove");
|
|
31245
|
+
else if (!authorization.record.writable) setActionError("this login record is read-only");
|
|
31246
|
+
else onLogout(target, authorization);
|
|
31247
|
+
return;
|
|
31248
|
+
}
|
|
30633
31249
|
if (key.return) {
|
|
30634
31250
|
if (target.settingsNs.length === 0) setActionError("this provider is not managed by Harness settings");
|
|
30635
31251
|
else if (target.credential?.kind === "error") setActionError("credential status is unavailable; retry before writing");
|
|
@@ -30659,6 +31275,16 @@ function ProviderPanel({ directory, error, onCredential, onConfigure, onUnset, o
|
|
|
30659
31275
|
color: inkColor(getPalette().warn),
|
|
30660
31276
|
wrap: "truncate-end"
|
|
30661
31277
|
}, truncateColumns(` ${singleLineText(failure)}`, viewport.contentColumns))),
|
|
31278
|
+
...authorizationError === void 0 ? [] : [(0, import_react.createElement)(Text, {
|
|
31279
|
+
key: "authorization-error",
|
|
31280
|
+
color: inkColor(getPalette().warn),
|
|
31281
|
+
wrap: "truncate-end"
|
|
31282
|
+
}, truncateColumns(` login status unavailable: ${singleLineText(authorizationError)}`, viewport.contentColumns))],
|
|
31283
|
+
...(authorizations?.failures ?? []).map((failure, index) => (0, import_react.createElement)(Text, {
|
|
31284
|
+
key: `authorization-failure-${index}`,
|
|
31285
|
+
color: inkColor(getPalette().warn),
|
|
31286
|
+
wrap: "truncate-end"
|
|
31287
|
+
}, truncateColumns(` ${singleLineText(failure)}`, viewport.contentColumns))),
|
|
30662
31288
|
...rows.length === 0 ? [(0, import_react.createElement)(Text, {
|
|
30663
31289
|
key: "empty",
|
|
30664
31290
|
color: inkColor(getPalette().dim),
|
|
@@ -30680,7 +31306,10 @@ function ProviderPanel({ directory, error, onCredential, onConfigure, onUnset, o
|
|
|
30680
31306
|
wrap: "truncate-end"
|
|
30681
31307
|
}, truncateColumns(`/model — providers${rows.length === 0 ? "" : ` · ${cursor + 1}/${rows.length}`}`, viewport.contentColumns)), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), ...visibleStateRows, ...visible.map((row) => {
|
|
30682
31308
|
const index = rows.indexOf(row);
|
|
30683
|
-
const
|
|
31309
|
+
const identity = row.displayName === row.provider ? row.provider : `${row.displayName} (${row.provider})`;
|
|
31310
|
+
const authorization = authorizationForProvider(authorizations, row.provider);
|
|
31311
|
+
const authLabel = !(row.credential?.kind === "facts" && row.credential.configured) || authorization?.record.configured === true || authorization?.inFlight === true ? ` · ${providerAuthorizationStatus(authorization)}` : "";
|
|
31312
|
+
const label = `${identity} · ${providerStateLabel(row)}${authLabel}${row.removable ? " · custom" : ""}`;
|
|
30684
31313
|
return (0, import_react.createElement)(Text, {
|
|
30685
31314
|
key: row.provider,
|
|
30686
31315
|
color: index === cursor ? inkColor(getPalette().brandBright) : inkColor(getPalette().dim),
|
|
@@ -30689,7 +31318,7 @@ function ProviderPanel({ directory, error, onCredential, onConfigure, onUnset, o
|
|
|
30689
31318
|
}), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 }), (0, import_react.createElement)(Text, {
|
|
30690
31319
|
color: inkColor(getPalette().dim),
|
|
30691
31320
|
wrap: "truncate-end"
|
|
30692
|
-
}, truncateColumns("↑↓ move ·
|
|
31321
|
+
}, truncateColumns("↑↓ move · enter key · l login · o logout · tab configure · d remove key · x remove provider · r retry · esc back", viewport.contentColumns)));
|
|
30693
31322
|
}
|
|
30694
31323
|
/** Provider configuration editor: only explicit models are written to settings. */
|
|
30695
31324
|
function ProviderConfigurationPanel({ target, catalog, save, done, back }) {
|
|
@@ -31002,7 +31631,7 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
31002
31631
|
key: "key-submit",
|
|
31003
31632
|
dimColor: true,
|
|
31004
31633
|
wrap: "truncate-end"
|
|
31005
|
-
}, " enter submit ·
|
|
31634
|
+
}, " enter submit · up/down history · tab complete"),
|
|
31006
31635
|
(0, import_react.createElement)(Text, {
|
|
31007
31636
|
key: "key-mentions",
|
|
31008
31637
|
dimColor: true,
|
|
@@ -31039,31 +31668,8 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
31039
31668
|
color: inkColor(getPalette().error),
|
|
31040
31669
|
wrap: "truncate-end"
|
|
31041
31670
|
}, truncateColumns(` command catalog unavailable: ${singleLineText(commandError)}`, viewport.contentColumns))],
|
|
31042
|
-
(0, import_react.createElement)(Box, { key:
|
|
31043
|
-
(0, import_react.createElement)(
|
|
31044
|
-
(0, import_react.createElement)(Box, { key: "local-effort" }, row("/effort", "adjust reasoning effort for the current model")),
|
|
31045
|
-
(0, import_react.createElement)(Box, { key: "local-mode" }, row("/mode", "inspect or select the agent preset (/mode [preset])")),
|
|
31046
|
-
(0, import_react.createElement)(Box, { key: "local-permission" }, row("/permission", "inspect or select the permission preset (/permission [preset])")),
|
|
31047
|
-
(0, import_react.createElement)(Box, { key: "local-new" }, row("/new", "create and switch to a fresh session (/new [preset])")),
|
|
31048
|
-
(0, import_react.createElement)(Box, { key: "local-fork" }, row("/fork", "fork at the latest completed turn (/fork [event-seq])")),
|
|
31049
|
-
(0, import_react.createElement)(Box, { key: "local-resume" }, row("/resume", "browse or switch root sessions (/resume [id|prefix])")),
|
|
31050
|
-
(0, import_react.createElement)(Box, { key: "local-plugin" }, row("/plugin", "inspect the live plugin composition")),
|
|
31051
|
-
(0, import_react.createElement)(Box, { key: "local-jobs" }, row("/jobs", "inspect background jobs")),
|
|
31052
|
-
(0, import_react.createElement)(Box, { key: "local-statusline" }, row("/statusline", "customize the status line items")),
|
|
31053
|
-
(0, import_react.createElement)(Box, { key: "local-theme" }, row("/theme", "switch the color theme")),
|
|
31054
|
-
(0, import_react.createElement)(Box, { key: "local-history" }, row("/history", "search and recall past prompts")),
|
|
31055
|
-
(0, import_react.createElement)(Box, { key: "local-agents" }, row("/agents", "inspect subagent sessions of this conversation")),
|
|
31056
|
-
(0, import_react.createElement)(Box, { key: "local-todos" }, row("/todos", "inspect the full todo list")),
|
|
31057
|
-
(0, import_react.createElement)(Box, { key: "local-subagent" }, row("/subagent", "choose the model delegated subagents run on")),
|
|
31058
|
-
(0, import_react.createElement)(Box, { key: "local-delete" }, row("/delete", "delete a session and its subagent threads")),
|
|
31059
|
-
(0, import_react.createElement)(Box, { key: "local-clear" }, row("/clear", "clear the screen")),
|
|
31060
|
-
(0, import_react.createElement)(Box, { key: "local-export" }, row("/export", "export the transcript to markdown (/export [path])")),
|
|
31061
|
-
(0, import_react.createElement)(Box, { key: "local-title" }, row("/title", "rename this session (/title <text>)")),
|
|
31062
|
-
(0, import_react.createElement)(Box, { key: "local-copy" }, row("/copy", "copy the latest assistant response")),
|
|
31063
|
-
(0, import_react.createElement)(Box, { key: "local-diff" }, row("/diff", "inspect Git changes (/diff [--staged|ref])")),
|
|
31064
|
-
(0, import_react.createElement)(Box, { key: "local-review" }, row("/review", "review Git changes under read-only permissions")),
|
|
31065
|
-
(0, import_react.createElement)(Box, { key: "local-quit" }, row("/quit", "exit")),
|
|
31066
|
-
...descriptors.map((descriptor) => (0, import_react.createElement)(Text, {
|
|
31671
|
+
...LOCAL_COMMANDS.map((command) => (0, import_react.createElement)(Box, { key: `local-${command.label.slice(1)}` }, row(command.label, command.description))),
|
|
31672
|
+
...descriptors.filter((descriptor) => !LOCAL_COMMAND_NAMES.has(descriptor.name)).map((descriptor) => (0, import_react.createElement)(Text, {
|
|
31067
31673
|
key: `command-${descriptor.name}`,
|
|
31068
31674
|
dimColor: true,
|
|
31069
31675
|
wrap: "truncate-end"
|
|
@@ -31332,130 +31938,11 @@ const MemoStaticTranscript = (0, import_react.memo)(StaticTranscript);
|
|
|
31332
31938
|
function completionCandidates(value, descriptors, skills) {
|
|
31333
31939
|
if (!value.startsWith("/")) return [];
|
|
31334
31940
|
const prefix = value.slice(1).split(" ")[0] ?? "";
|
|
31335
|
-
const local =
|
|
31336
|
-
|
|
31337
|
-
|
|
31338
|
-
|
|
31339
|
-
|
|
31340
|
-
},
|
|
31341
|
-
{
|
|
31342
|
-
label: "/model",
|
|
31343
|
-
description: "switch the model",
|
|
31344
|
-
origin: "command"
|
|
31345
|
-
},
|
|
31346
|
-
{
|
|
31347
|
-
label: "/effort",
|
|
31348
|
-
description: "adjust reasoning effort for the current model",
|
|
31349
|
-
origin: "command"
|
|
31350
|
-
},
|
|
31351
|
-
{
|
|
31352
|
-
label: "/mode",
|
|
31353
|
-
description: "select the agent preset",
|
|
31354
|
-
origin: "command"
|
|
31355
|
-
},
|
|
31356
|
-
{
|
|
31357
|
-
label: "/permission",
|
|
31358
|
-
description: "inspect or select the permission preset",
|
|
31359
|
-
origin: "command"
|
|
31360
|
-
},
|
|
31361
|
-
{
|
|
31362
|
-
label: "/new",
|
|
31363
|
-
description: "start a fresh session",
|
|
31364
|
-
origin: "command"
|
|
31365
|
-
},
|
|
31366
|
-
{
|
|
31367
|
-
label: "/fork",
|
|
31368
|
-
description: "fork at a completed turn",
|
|
31369
|
-
origin: "command"
|
|
31370
|
-
},
|
|
31371
|
-
{
|
|
31372
|
-
label: "/resume",
|
|
31373
|
-
description: "browse or switch sessions",
|
|
31374
|
-
origin: "command"
|
|
31375
|
-
},
|
|
31376
|
-
{
|
|
31377
|
-
label: "/plugin",
|
|
31378
|
-
description: "inspect the plugin composition",
|
|
31379
|
-
origin: "command"
|
|
31380
|
-
},
|
|
31381
|
-
{
|
|
31382
|
-
label: "/jobs",
|
|
31383
|
-
description: "inspect background jobs",
|
|
31384
|
-
origin: "command"
|
|
31385
|
-
},
|
|
31386
|
-
{
|
|
31387
|
-
label: "/statusline",
|
|
31388
|
-
description: "customize the status line",
|
|
31389
|
-
origin: "command"
|
|
31390
|
-
},
|
|
31391
|
-
{
|
|
31392
|
-
label: "/theme",
|
|
31393
|
-
description: "switch the color theme",
|
|
31394
|
-
origin: "command"
|
|
31395
|
-
},
|
|
31396
|
-
{
|
|
31397
|
-
label: "/history",
|
|
31398
|
-
description: "search and recall past prompts",
|
|
31399
|
-
origin: "command"
|
|
31400
|
-
},
|
|
31401
|
-
{
|
|
31402
|
-
label: "/agents",
|
|
31403
|
-
description: "inspect subagent sessions of this conversation",
|
|
31404
|
-
origin: "command"
|
|
31405
|
-
},
|
|
31406
|
-
{
|
|
31407
|
-
label: "/todos",
|
|
31408
|
-
description: "inspect the full todo list",
|
|
31409
|
-
origin: "command"
|
|
31410
|
-
},
|
|
31411
|
-
{
|
|
31412
|
-
label: "/subagent",
|
|
31413
|
-
description: "choose the model delegated subagents run on",
|
|
31414
|
-
origin: "command"
|
|
31415
|
-
},
|
|
31416
|
-
{
|
|
31417
|
-
label: "/delete",
|
|
31418
|
-
description: "delete a session and its subagent threads",
|
|
31419
|
-
origin: "command"
|
|
31420
|
-
},
|
|
31421
|
-
{
|
|
31422
|
-
label: "/clear",
|
|
31423
|
-
description: "clear the screen",
|
|
31424
|
-
origin: "command"
|
|
31425
|
-
},
|
|
31426
|
-
{
|
|
31427
|
-
label: "/export",
|
|
31428
|
-
description: "export the transcript to markdown",
|
|
31429
|
-
origin: "command"
|
|
31430
|
-
},
|
|
31431
|
-
{
|
|
31432
|
-
label: "/title",
|
|
31433
|
-
description: "rename this session",
|
|
31434
|
-
origin: "command"
|
|
31435
|
-
},
|
|
31436
|
-
{
|
|
31437
|
-
label: "/copy",
|
|
31438
|
-
description: "copy the latest assistant response",
|
|
31439
|
-
origin: "command"
|
|
31440
|
-
},
|
|
31441
|
-
{
|
|
31442
|
-
label: "/diff",
|
|
31443
|
-
description: "inspect Git changes",
|
|
31444
|
-
origin: "command"
|
|
31445
|
-
},
|
|
31446
|
-
{
|
|
31447
|
-
label: "/review",
|
|
31448
|
-
description: "review changes read-only",
|
|
31449
|
-
origin: "command"
|
|
31450
|
-
},
|
|
31451
|
-
{
|
|
31452
|
-
label: "/quit",
|
|
31453
|
-
description: "exit",
|
|
31454
|
-
origin: "command"
|
|
31455
|
-
}
|
|
31456
|
-
];
|
|
31457
|
-
const localNames = new Set(local.map((candidate) => candidate.label.slice(1)));
|
|
31458
|
-
const registry = descriptors.filter((descriptor) => !localNames.has(descriptor.name)).map((descriptor) => ({
|
|
31941
|
+
const local = LOCAL_COMMANDS.map((command) => ({
|
|
31942
|
+
...command,
|
|
31943
|
+
origin: "command"
|
|
31944
|
+
}));
|
|
31945
|
+
const registry = descriptors.filter((descriptor) => !LOCAL_COMMAND_NAMES.has(descriptor.name)).map((descriptor) => ({
|
|
31459
31946
|
label: `/${descriptor.name}`,
|
|
31460
31947
|
description: descriptor.description,
|
|
31461
31948
|
origin: "command"
|
|
@@ -31534,11 +32021,19 @@ function CompletionMenu({ active, mention, index, rows }) {
|
|
|
31534
32021
|
* While a modal (approval / question / model panel) owns the keys, the
|
|
31535
32022
|
* box passes every key through untouched.
|
|
31536
32023
|
*/
|
|
31537
|
-
function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openJobs, openStatusline, openTheme, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, waveTier, waveStyle, maxRows, onEditorRows }) {
|
|
32024
|
+
function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openJobs, openStatusline, openTheme, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, inspectImages, prepareImages, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, waveTier, waveStyle, maxRows, onEditorRows }) {
|
|
31538
32025
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
31539
32026
|
const stdin = useStdin().stdin;
|
|
31540
32027
|
const [value, setValue] = (0, import_react.useState)("");
|
|
31541
32028
|
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
32029
|
+
const valueRef = (0, import_react.useRef)(value);
|
|
32030
|
+
const cursorRef = (0, import_react.useRef)(cursor);
|
|
32031
|
+
valueRef.current = value;
|
|
32032
|
+
cursorRef.current = cursor;
|
|
32033
|
+
const [draftImages, setDraftImages] = (0, import_react.useState)([]);
|
|
32034
|
+
const draftImagesRef = (0, import_react.useRef)(draftImages);
|
|
32035
|
+
draftImagesRef.current = draftImages;
|
|
32036
|
+
const [preparingImages, setPreparingImages] = (0, import_react.useState)(false);
|
|
31542
32037
|
const killRef = (0, import_react.useRef)("");
|
|
31543
32038
|
const preferredColumnRef = (0, import_react.useRef)(null);
|
|
31544
32039
|
const editorScrollRef = (0, import_react.useRef)(0);
|
|
@@ -31551,6 +32046,8 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31551
32046
|
(0, import_react.useEffect)(() => {
|
|
31552
32047
|
if (historyFill === void 0) return;
|
|
31553
32048
|
const safe = sanitizeDraftText(historyFill.text);
|
|
32049
|
+
draftImagesRef.current = [];
|
|
32050
|
+
setDraftImages([]);
|
|
31554
32051
|
setValue(safe);
|
|
31555
32052
|
setCursor(safe.length);
|
|
31556
32053
|
preferredColumnRef.current = null;
|
|
@@ -31567,6 +32064,13 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31567
32064
|
recallSpace,
|
|
31568
32065
|
historyConsumed
|
|
31569
32066
|
]);
|
|
32067
|
+
(0, import_react.useEffect)(() => {
|
|
32068
|
+
setDraftImages((current) => {
|
|
32069
|
+
const next = current.filter((image) => value.includes(image.marker));
|
|
32070
|
+
draftImagesRef.current = next;
|
|
32071
|
+
return next.length === current.length ? current : next;
|
|
32072
|
+
});
|
|
32073
|
+
}, [value]);
|
|
31570
32074
|
(0, import_react.useEffect)(() => {
|
|
31571
32075
|
if (stdin === void 0) return;
|
|
31572
32076
|
const originalRead = stdin.read.bind(stdin);
|
|
@@ -31602,6 +32106,64 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31602
32106
|
};
|
|
31603
32107
|
const mentionActive = mentionToken !== void 0;
|
|
31604
32108
|
const [mentionRows, setMentionRows] = (0, import_react.useState)([]);
|
|
32109
|
+
const sameImagePath = (left, right) => process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
32110
|
+
const uniqueImageMarker = (name, source, reserved = []) => {
|
|
32111
|
+
const safeName = singleLineText(sanitizeDraftText(name));
|
|
32112
|
+
let marker = source === "mention" ? `@${safeName}` : `[image: ${safeName}]`;
|
|
32113
|
+
let suffix = 2;
|
|
32114
|
+
while (valueRef.current.includes(marker) || draftImagesRef.current.some((image) => image.marker === marker) || reserved.includes(marker)) {
|
|
32115
|
+
marker = source === "mention" ? `@${safeName} (${suffix})` : `[image: ${safeName} ${suffix}]`;
|
|
32116
|
+
suffix += 1;
|
|
32117
|
+
}
|
|
32118
|
+
return marker;
|
|
32119
|
+
};
|
|
32120
|
+
const registerDraftImage = (inspection, marker) => {
|
|
32121
|
+
if (draftImagesRef.current.some((image) => sameImagePath(image.path, inspection.path))) {
|
|
32122
|
+
notify(`${inspection.name} is already attached`, "warning");
|
|
32123
|
+
return false;
|
|
32124
|
+
}
|
|
32125
|
+
const next = [...draftImagesRef.current, {
|
|
32126
|
+
...inspection,
|
|
32127
|
+
marker
|
|
32128
|
+
}];
|
|
32129
|
+
draftImagesRef.current = next;
|
|
32130
|
+
setDraftImages(next);
|
|
32131
|
+
return true;
|
|
32132
|
+
};
|
|
32133
|
+
const insertDroppedImages = (paths) => {
|
|
32134
|
+
notify(`checking ${paths.length} image${paths.length === 1 ? "" : "s"}…`);
|
|
32135
|
+
inspectImages(paths).then((inspected) => {
|
|
32136
|
+
const additions = [];
|
|
32137
|
+
const markers = [];
|
|
32138
|
+
for (const inspection of inspected) {
|
|
32139
|
+
if ([...draftImagesRef.current, ...additions].some((image) => sameImagePath(image.path, inspection.path))) continue;
|
|
32140
|
+
const marker = uniqueImageMarker(inspection.name, "drop", markers);
|
|
32141
|
+
additions.push({
|
|
32142
|
+
...inspection,
|
|
32143
|
+
marker
|
|
32144
|
+
});
|
|
32145
|
+
markers.push(marker);
|
|
32146
|
+
}
|
|
32147
|
+
if (additions.length === 0) {
|
|
32148
|
+
notify("those images are already attached", "warning");
|
|
32149
|
+
return;
|
|
32150
|
+
}
|
|
32151
|
+
const at = cursorRef.current;
|
|
32152
|
+
const current = valueRef.current;
|
|
32153
|
+
const insertion = `${at > 0 && !/\s$/u.test(current.slice(0, at)) ? " " : ""}${markers.join(" ")}${current.slice(at) === "" ? "" : " "}`;
|
|
32154
|
+
const next = current.slice(0, at) + insertion + current.slice(at);
|
|
32155
|
+
valueRef.current = next;
|
|
32156
|
+
cursorRef.current = at + insertion.length;
|
|
32157
|
+
setValue(next);
|
|
32158
|
+
setCursor(cursorRef.current);
|
|
32159
|
+
const nextImages = [...draftImagesRef.current, ...additions];
|
|
32160
|
+
draftImagesRef.current = nextImages;
|
|
32161
|
+
setDraftImages(nextImages);
|
|
32162
|
+
notify(`${additions.length} image${additions.length === 1 ? "" : "s"} ready for the next message`);
|
|
32163
|
+
}, (reason) => {
|
|
32164
|
+
notify(`image attachment failed: ${reason instanceof Error ? reason.message : String(reason)}`, "error");
|
|
32165
|
+
});
|
|
32166
|
+
};
|
|
31605
32167
|
(0, import_react.useEffect)(() => {
|
|
31606
32168
|
if (!active || !mentionActive) {
|
|
31607
32169
|
setMentionRows([]);
|
|
@@ -31629,6 +32191,41 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31629
32191
|
if (mentionActive && mentionToken !== void 0) {
|
|
31630
32192
|
const row = mentionRows[completionIndex % mentionRows.length];
|
|
31631
32193
|
if (row !== void 0) {
|
|
32194
|
+
if (row.kind === "file" && row.path !== void 0 && looksLikeImagePath(row.path)) {
|
|
32195
|
+
const tokenText = value.slice(mentionToken.start, cursor);
|
|
32196
|
+
const start = mentionToken.start;
|
|
32197
|
+
notify(`checking image ${basename(row.path)}…`);
|
|
32198
|
+
inspectImages([row.path]).then((inspected) => {
|
|
32199
|
+
const inspection = inspected[0];
|
|
32200
|
+
if (inspection === void 0) return;
|
|
32201
|
+
const current = valueRef.current;
|
|
32202
|
+
if (current.slice(start, start + tokenText.length) !== tokenText) return;
|
|
32203
|
+
if (draftImagesRef.current.some((image) => sameImagePath(image.path, inspection.path))) {
|
|
32204
|
+
const next = current.slice(0, start) + current.slice(start + tokenText.length);
|
|
32205
|
+
valueRef.current = next;
|
|
32206
|
+
cursorRef.current = start;
|
|
32207
|
+
setValue(next);
|
|
32208
|
+
setCursor(start);
|
|
32209
|
+
setDismissedMenuValue(next);
|
|
32210
|
+
notify(`${inspection.name} is already attached`, "warning");
|
|
32211
|
+
return;
|
|
32212
|
+
}
|
|
32213
|
+
const marker = uniqueImageMarker(inspection.name, "mention");
|
|
32214
|
+
const next = current.slice(0, start) + marker + current.slice(start + tokenText.length);
|
|
32215
|
+
valueRef.current = next;
|
|
32216
|
+
cursorRef.current = start + marker.length;
|
|
32217
|
+
setValue(next);
|
|
32218
|
+
setCursor(cursorRef.current);
|
|
32219
|
+
setDismissedMenuValue(next);
|
|
32220
|
+
registerDraftImage(inspection, marker);
|
|
32221
|
+
notify(`${inspection.name} ready for the next message`);
|
|
32222
|
+
}, (reason) => {
|
|
32223
|
+
notify(`image attachment failed: ${reason instanceof Error ? reason.message : String(reason)}`, "error");
|
|
32224
|
+
});
|
|
32225
|
+
setCompletionIndex(0);
|
|
32226
|
+
setDismissedMenuValue(void 0);
|
|
32227
|
+
return;
|
|
32228
|
+
}
|
|
31632
32229
|
const insertion = row.label.startsWith("@") ? row.label : `@${row.label}${row.kind === "directory" ? "/" : ""}`;
|
|
31633
32230
|
setValue(value.slice(0, mentionToken.start) + insertion + value.slice(cursor));
|
|
31634
32231
|
setCursor(mentionToken.start + insertion.length);
|
|
@@ -31660,6 +32257,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31660
32257
|
};
|
|
31661
32258
|
useInput((input, key) => {
|
|
31662
32259
|
if (!active) return;
|
|
32260
|
+
if (preparingImages) return;
|
|
31663
32261
|
if (deleteConfirm !== void 0) {
|
|
31664
32262
|
if (input === "y" || input === "Y") confirmDelete();
|
|
31665
32263
|
else cancelDelete();
|
|
@@ -31687,6 +32285,8 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31687
32285
|
else if (value !== "") {
|
|
31688
32286
|
setValue("");
|
|
31689
32287
|
setCursor(0);
|
|
32288
|
+
draftImagesRef.current = [];
|
|
32289
|
+
setDraftImages([]);
|
|
31690
32290
|
setCompletionIndex(0);
|
|
31691
32291
|
setDismissedMenuValue(void 0);
|
|
31692
32292
|
} else quit();
|
|
@@ -31717,18 +32317,11 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31717
32317
|
cancelQueued(queued[queued.length - 1].messageId);
|
|
31718
32318
|
return;
|
|
31719
32319
|
}
|
|
31720
|
-
|
|
31721
|
-
if (shiftEnterSequence || key.return) {
|
|
32320
|
+
if (key.return) {
|
|
31722
32321
|
if (pasteBracketRef.current) {
|
|
31723
32322
|
applyEdit(insertText(value, cursor, "\n"));
|
|
31724
32323
|
return;
|
|
31725
32324
|
}
|
|
31726
|
-
if (shiftEnterSequence || key.shift || key.meta || key.ctrl && input === "j") {
|
|
31727
|
-
setValue(value.slice(0, cursor) + "\n" + value.slice(cursor));
|
|
31728
|
-
setCursor(cursor + 1);
|
|
31729
|
-
setDismissedMenuValue(void 0);
|
|
31730
|
-
return;
|
|
31731
|
-
}
|
|
31732
32325
|
if (menuActive) {
|
|
31733
32326
|
if (!(!mentionActive && candidates.some((candidate) => candidate.label === value))) {
|
|
31734
32327
|
acceptMenuCandidate();
|
|
@@ -31736,6 +32329,34 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31736
32329
|
}
|
|
31737
32330
|
}
|
|
31738
32331
|
const text = value.trim();
|
|
32332
|
+
if (draftImagesRef.current.length > 0) {
|
|
32333
|
+
setPreparingImages(true);
|
|
32334
|
+
notify(`processing ${draftImagesRef.current.length} image${draftImagesRef.current.length === 1 ? "" : "s"}…`);
|
|
32335
|
+
const snapshot = draftImagesRef.current;
|
|
32336
|
+
prepareImages(snapshot.map((image) => image.path)).then((images) => {
|
|
32337
|
+
setPreparingImages(false);
|
|
32338
|
+
valueRef.current = "";
|
|
32339
|
+
cursorRef.current = 0;
|
|
32340
|
+
setValue("");
|
|
32341
|
+
setCursor(0);
|
|
32342
|
+
draftImagesRef.current = [];
|
|
32343
|
+
setDraftImages([]);
|
|
32344
|
+
setCompletionIndex(0);
|
|
32345
|
+
setDismissedMenuValue(void 0);
|
|
32346
|
+
dismissNotice();
|
|
32347
|
+
if (text !== "") {
|
|
32348
|
+
recordLocal(text);
|
|
32349
|
+
recordHistory(text);
|
|
32350
|
+
}
|
|
32351
|
+
recall.current = beginRecall(recallSpace, "");
|
|
32352
|
+
if (busy) steer(text, images);
|
|
32353
|
+
else dispatch(text, images);
|
|
32354
|
+
}, (reason) => {
|
|
32355
|
+
setPreparingImages(false);
|
|
32356
|
+
notify(`image submission failed: ${reason instanceof Error ? reason.message : String(reason)}`, "error");
|
|
32357
|
+
});
|
|
32358
|
+
return;
|
|
32359
|
+
}
|
|
31739
32360
|
setValue("");
|
|
31740
32361
|
setCursor(0);
|
|
31741
32362
|
setCompletionIndex(0);
|
|
@@ -31863,6 +32484,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
31863
32484
|
dispatch(text);
|
|
31864
32485
|
return;
|
|
31865
32486
|
}
|
|
32487
|
+
if (input === "\n" || input === "\r") return;
|
|
31866
32488
|
if (menuActive && key.upArrow) {
|
|
31867
32489
|
setCompletionIndex((index) => (index + menuRows.length - 1) % menuRows.length);
|
|
31868
32490
|
return;
|
|
@@ -32011,6 +32633,11 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32011
32633
|
text = text.replaceAll(PASTE_END_MARKER, "");
|
|
32012
32634
|
}
|
|
32013
32635
|
if (text === "") return;
|
|
32636
|
+
const droppedPaths = text.length > 1 ? parsePastedImagePaths(text) : [];
|
|
32637
|
+
if (droppedPaths.length > 0) {
|
|
32638
|
+
insertDroppedImages(droppedPaths);
|
|
32639
|
+
return;
|
|
32640
|
+
}
|
|
32014
32641
|
applyEdit(insertText(value, cursor, text));
|
|
32015
32642
|
}
|
|
32016
32643
|
});
|
|
@@ -32429,6 +33056,8 @@ function App(props) {
|
|
|
32429
33056
|
const [modelError, setModelError] = (0, import_react.useState)(void 0);
|
|
32430
33057
|
const [providerDirectory, setProviderDirectory] = (0, import_react.useState)(void 0);
|
|
32431
33058
|
const [providerError, setProviderError] = (0, import_react.useState)(void 0);
|
|
33059
|
+
const [authorizationDirectory, setAuthorizationDirectory] = (0, import_react.useState)(void 0);
|
|
33060
|
+
const [authorizationError, setAuthorizationError] = (0, import_react.useState)(void 0);
|
|
32432
33061
|
const [modelLoadEpoch, setModelLoadEpoch] = (0, import_react.useState)(0);
|
|
32433
33062
|
const [notice, setNotice] = (0, import_react.useState)(void 0);
|
|
32434
33063
|
const notify = (0, import_react.useCallback)((text, tone = "info") => {
|
|
@@ -32476,6 +33105,24 @@ function App(props) {
|
|
|
32476
33105
|
modelLoadEpoch,
|
|
32477
33106
|
props.loadModelProviders
|
|
32478
33107
|
]);
|
|
33108
|
+
(0, import_react.useEffect)(() => {
|
|
33109
|
+
if (!modelOpen || props.loadProviderAuthorizations === void 0) return;
|
|
33110
|
+
let cancelled = false;
|
|
33111
|
+
setAuthorizationDirectory(void 0);
|
|
33112
|
+
setAuthorizationError(void 0);
|
|
33113
|
+
Promise.resolve().then(() => props.loadProviderAuthorizations()).then((loaded) => {
|
|
33114
|
+
if (!cancelled) setAuthorizationDirectory(loaded);
|
|
33115
|
+
}, (error) => {
|
|
33116
|
+
if (!cancelled) setAuthorizationError(error instanceof Error ? error.message : String(error));
|
|
33117
|
+
});
|
|
33118
|
+
return () => {
|
|
33119
|
+
cancelled = true;
|
|
33120
|
+
};
|
|
33121
|
+
}, [
|
|
33122
|
+
modelOpen,
|
|
33123
|
+
modelLoadEpoch,
|
|
33124
|
+
props.loadProviderAuthorizations
|
|
33125
|
+
]);
|
|
32479
33126
|
(0, import_react.useEffect)(() => {
|
|
32480
33127
|
const subscribe = props.subscribeModelProviders;
|
|
32481
33128
|
if (!modelOpen || subscribe === void 0) return;
|
|
@@ -32485,6 +33132,15 @@ function App(props) {
|
|
|
32485
33132
|
setProviderError(error instanceof Error ? error.message : String(error));
|
|
32486
33133
|
}
|
|
32487
33134
|
}, [modelOpen, props.subscribeModelProviders]);
|
|
33135
|
+
(0, import_react.useEffect)(() => {
|
|
33136
|
+
const subscribe = props.subscribeProviderAuthorizations;
|
|
33137
|
+
if (!modelOpen || subscribe === void 0) return;
|
|
33138
|
+
try {
|
|
33139
|
+
return subscribe(() => setModelLoadEpoch((epoch) => epoch + 1));
|
|
33140
|
+
} catch (error) {
|
|
33141
|
+
setAuthorizationError(error instanceof Error ? error.message : String(error));
|
|
33142
|
+
}
|
|
33143
|
+
}, [modelOpen, props.subscribeProviderAuthorizations]);
|
|
32488
33144
|
const busy = view.busy;
|
|
32489
33145
|
const [showReasoning, setShowReasoning] = (0, import_react.useState)(false);
|
|
32490
33146
|
const [verboseOpen, setVerboseOpen] = (0, import_react.useState)(false);
|
|
@@ -32681,13 +33337,16 @@ function App(props) {
|
|
|
32681
33337
|
busy,
|
|
32682
33338
|
streamingActive
|
|
32683
33339
|
]);
|
|
33340
|
+
const sessionHasImages = (0, import_react.useMemo)(() => view.entries.some((entry) => (entry.kind === "user" || entry.kind === "pending") && (entry.images?.length ?? 0) > 0), [view.entries]);
|
|
32684
33341
|
/** Apply one /model pick: record the selection, close the panel, report via notice. */
|
|
32685
33342
|
const applyModel = (row, effortId) => {
|
|
32686
33343
|
try {
|
|
32687
33344
|
const label = props.selectModel(row, effortId);
|
|
32688
33345
|
setModelLabel(label);
|
|
32689
33346
|
setEffortLabel(effortId);
|
|
32690
|
-
|
|
33347
|
+
const selected = `${label}${effortId === void 0 || effortId === "" ? "" : `@${effortId}`}`;
|
|
33348
|
+
if (sessionHasImages && row.inputModalities !== void 0 && !row.inputModalities.includes("image")) notify(`model → ${selected} · image history will be sent as text placeholders`, "warning");
|
|
33349
|
+
else notify(`model → next step uses ${selected}`);
|
|
32691
33350
|
setModelOpen(false);
|
|
32692
33351
|
setProviderOpen(false);
|
|
32693
33352
|
setProviderAction(void 0);
|
|
@@ -32707,7 +33366,37 @@ function App(props) {
|
|
|
32707
33366
|
};
|
|
32708
33367
|
let modelSurface;
|
|
32709
33368
|
if (modelOpen && !approvalPending && !questionPending) {
|
|
32710
|
-
if (providerAction?.kind === "
|
|
33369
|
+
if (providerAction?.kind === "login" && props.beginProviderAuthorization !== void 0 && props.cancelProviderAuthorization !== void 0 && props.openAuthorizationUrl !== void 0 && props.copyTextValue !== void 0) modelSurface = (0, import_react.createElement)(ProviderAuthorizationPanel, {
|
|
33370
|
+
row: providerAction.authorization,
|
|
33371
|
+
begin: props.beginProviderAuthorization,
|
|
33372
|
+
cancel: () => props.cancelProviderAuthorization(providerAction.authorization),
|
|
33373
|
+
openUrl: props.openAuthorizationUrl,
|
|
33374
|
+
copy: props.copyTextValue,
|
|
33375
|
+
done: () => {
|
|
33376
|
+
const authorization = providerAction.authorization;
|
|
33377
|
+
setProviderAction(void 0);
|
|
33378
|
+
setProviderOpen(false);
|
|
33379
|
+
reloadModelSurfaces();
|
|
33380
|
+
notify(`logged in to ${authorization.label}; select a model`);
|
|
33381
|
+
},
|
|
33382
|
+
back: () => {
|
|
33383
|
+
setProviderAction(void 0);
|
|
33384
|
+
setProviderOpen(true);
|
|
33385
|
+
}
|
|
33386
|
+
});
|
|
33387
|
+
else if (providerAction?.kind === "logout" && props.logoutProviderAuthorization !== void 0) modelSurface = (0, import_react.createElement)(ProviderAuthorizationLogoutPanel, {
|
|
33388
|
+
row: providerAction.authorization,
|
|
33389
|
+
confirm: props.logoutProviderAuthorization,
|
|
33390
|
+
done: () => {
|
|
33391
|
+
const authorization = providerAction.authorization;
|
|
33392
|
+
setProviderAction(void 0);
|
|
33393
|
+
setProviderOpen(true);
|
|
33394
|
+
reloadModelSurfaces();
|
|
33395
|
+
notify(`logged out from ${authorization.label}`);
|
|
33396
|
+
},
|
|
33397
|
+
back: () => setProviderAction(void 0)
|
|
33398
|
+
});
|
|
33399
|
+
else if (providerAction?.kind === "configure" && props.saveModelProviderConfiguration !== void 0) modelSurface = (0, import_react.createElement)(ProviderConfigurationPanel, {
|
|
32711
33400
|
target: providerAction.target,
|
|
32712
33401
|
catalog: directory?.rows ?? [],
|
|
32713
33402
|
save: props.saveModelProviderConfiguration,
|
|
@@ -32761,6 +33450,8 @@ function App(props) {
|
|
|
32761
33450
|
else if (providerOpen) modelSurface = (0, import_react.createElement)(ProviderPanel, {
|
|
32762
33451
|
directory: providerDirectory,
|
|
32763
33452
|
error: providerError,
|
|
33453
|
+
authorizations: authorizationDirectory,
|
|
33454
|
+
authorizationError,
|
|
32764
33455
|
onCredential: (target) => {
|
|
32765
33456
|
if (props.saveModelProviderCredential === void 0) {
|
|
32766
33457
|
notify("API key storage is unavailable in this profile", "warning");
|
|
@@ -32801,6 +33492,32 @@ function App(props) {
|
|
|
32801
33492
|
target
|
|
32802
33493
|
});
|
|
32803
33494
|
},
|
|
33495
|
+
onLogin: (target, authorization) => {
|
|
33496
|
+
if (busy) {
|
|
33497
|
+
notify("provider login is available only while the agent is idle", "warning");
|
|
33498
|
+
return;
|
|
33499
|
+
}
|
|
33500
|
+
if (props.beginProviderAuthorization === void 0 || props.cancelProviderAuthorization === void 0 || props.openAuthorizationUrl === void 0 || props.copyTextValue === void 0) {
|
|
33501
|
+
notify("provider login is unavailable in this profile", "warning");
|
|
33502
|
+
return;
|
|
33503
|
+
}
|
|
33504
|
+
setProviderAction({
|
|
33505
|
+
kind: "login",
|
|
33506
|
+
target,
|
|
33507
|
+
authorization
|
|
33508
|
+
});
|
|
33509
|
+
},
|
|
33510
|
+
onLogout: (target, authorization) => {
|
|
33511
|
+
if (props.logoutProviderAuthorization === void 0) {
|
|
33512
|
+
notify("provider logout is unavailable in this profile", "warning");
|
|
33513
|
+
return;
|
|
33514
|
+
}
|
|
33515
|
+
setProviderAction({
|
|
33516
|
+
kind: "logout",
|
|
33517
|
+
target,
|
|
33518
|
+
authorization
|
|
33519
|
+
});
|
|
33520
|
+
},
|
|
32804
33521
|
onRetry: reloadModelSurfaces,
|
|
32805
33522
|
onBack: () => setProviderOpen(false)
|
|
32806
33523
|
});
|
|
@@ -32983,6 +33700,8 @@ function App(props) {
|
|
|
32983
33700
|
setModelError(void 0);
|
|
32984
33701
|
setProviderDirectory(void 0);
|
|
32985
33702
|
setProviderError(void 0);
|
|
33703
|
+
setAuthorizationDirectory(void 0);
|
|
33704
|
+
setAuthorizationError(void 0);
|
|
32986
33705
|
setProviderOpen(false);
|
|
32987
33706
|
setProviderAction(void 0);
|
|
32988
33707
|
setEffortFor(void 0);
|
|
@@ -33070,6 +33789,8 @@ function App(props) {
|
|
|
33070
33789
|
if (!busy && !streamingActive) refreshScreen();
|
|
33071
33790
|
},
|
|
33072
33791
|
loadMentions: props.loadMentions,
|
|
33792
|
+
inspectImages: props.inspectImages,
|
|
33793
|
+
prepareImages: props.prepareImages,
|
|
33073
33794
|
cyclePermission: props.cyclePermission,
|
|
33074
33795
|
exportTranscript: props.exportTranscript,
|
|
33075
33796
|
renameTitle: props.renameTitle,
|
|
@@ -33394,14 +34115,16 @@ async function loadModelDirectory(ctx) {
|
|
|
33394
34115
|
provider: provider.id,
|
|
33395
34116
|
providerName: provider.name,
|
|
33396
34117
|
model: model.id,
|
|
33397
|
-
modelName: model.name
|
|
34118
|
+
modelName: model.name,
|
|
34119
|
+
...model.inputModalities === void 0 ? {} : { inputModalities: [...model.inputModalities] }
|
|
33398
34120
|
};
|
|
33399
34121
|
if (llmResolve.resolveModelInfo === void 0) return row;
|
|
33400
34122
|
try {
|
|
33401
34123
|
const resolved = await llmResolve.resolveModelInfo(provider.id, model.id);
|
|
33402
|
-
return
|
|
34124
|
+
return {
|
|
33403
34125
|
...row,
|
|
33404
|
-
|
|
34126
|
+
...resolved.inputModalities === void 0 ? {} : { inputModalities: [...resolved.inputModalities] },
|
|
34127
|
+
...resolved.reasoning === void 0 ? {} : { reasoning: mapReasoning(resolved.reasoning) }
|
|
33405
34128
|
};
|
|
33406
34129
|
} catch {
|
|
33407
34130
|
reasoningFailures.push(`${provider.id}/${model.id}`);
|
|
@@ -33503,7 +34226,7 @@ function deriveCredentialRef(provider) {
|
|
|
33503
34226
|
}
|
|
33504
34227
|
/** Events that invalidate the official Models provider/settings/credential join. */
|
|
33505
34228
|
const PROVIDER_SETTINGS_EVENTS = [
|
|
33506
|
-
"credentials/updated",
|
|
34229
|
+
"credentials/reference-updated",
|
|
33507
34230
|
"settings/document-updated",
|
|
33508
34231
|
"llm/adapters-updated"
|
|
33509
34232
|
];
|
|
@@ -33821,7 +34544,8 @@ function createMentions(ctx, agent, cwd) {
|
|
|
33821
34544
|
const fileRows = files.slice(0, MAX_FILE_ROWS).map((candidate) => ({
|
|
33822
34545
|
label: candidate.path,
|
|
33823
34546
|
description: candidate.kind === "directory" ? "Folder" : "File",
|
|
33824
|
-
kind: candidate.kind
|
|
34547
|
+
kind: candidate.kind,
|
|
34548
|
+
...candidate.kind === "file" ? { path: isAbsolute(candidate.path) ? candidate.path : resolve(cwd, candidate.path) } : {}
|
|
33825
34549
|
}));
|
|
33826
34550
|
const sessionRows = sessions.map((candidate) => ({
|
|
33827
34551
|
label: formatSessionReferenceMention(candidate),
|
|
@@ -34284,44 +35008,6 @@ function buildExportMarkdown(view, sessionId) {
|
|
|
34284
35008
|
return out.join("\n");
|
|
34285
35009
|
}
|
|
34286
35010
|
//#endregion
|
|
34287
|
-
//#region src/attachments.ts
|
|
34288
|
-
/** Terminal image-file adapter over the Harness durable attachment service. */
|
|
34289
|
-
/** Detect the supported encoded raster formats from bytes, never from a path suffix. */
|
|
34290
|
-
function detectImageMediaType(data) {
|
|
34291
|
-
if (data.length >= 8 && data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71 && data[4] === 13 && data[5] === 10 && data[6] === 26 && data[7] === 10) return "image/png";
|
|
34292
|
-
if (data.length >= 3 && data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
34293
|
-
if (data.length >= 6) {
|
|
34294
|
-
const signature = String.fromCharCode(...data.subarray(0, 6));
|
|
34295
|
-
if (signature === "GIF87a" || signature === "GIF89a") return "image/gif";
|
|
34296
|
-
}
|
|
34297
|
-
if (data.length >= 12 && String.fromCharCode(...data.subarray(0, 4)) === "RIFF" && String.fromCharCode(...data.subarray(8, 12)) === "WEBP") return "image/webp";
|
|
34298
|
-
}
|
|
34299
|
-
/** Read, validate, and persist an ordered image path list as model content blocks. */
|
|
34300
|
-
async function saveImagePaths(paths, attachments) {
|
|
34301
|
-
if (paths.length === 0) return [];
|
|
34302
|
-
if (attachments === void 0) throw new Error("image attachments are unavailable in this profile");
|
|
34303
|
-
const inputs = [];
|
|
34304
|
-
for (const path of paths) {
|
|
34305
|
-
let data;
|
|
34306
|
-
try {
|
|
34307
|
-
data = await readFile(path);
|
|
34308
|
-
} catch (error) {
|
|
34309
|
-
throw new Error(`cannot read image "${path}": ${error instanceof Error ? error.message : String(error)}`);
|
|
34310
|
-
}
|
|
34311
|
-
const mediaType = detectImageMediaType(data);
|
|
34312
|
-
if (mediaType === void 0) throw new Error(`unsupported image file "${path}" (expected PNG, JPEG, WebP, or GIF)`);
|
|
34313
|
-
inputs.push({
|
|
34314
|
-
data,
|
|
34315
|
-
mediaType,
|
|
34316
|
-
name: basename(path)
|
|
34317
|
-
});
|
|
34318
|
-
}
|
|
34319
|
-
return (await attachments.saveImages(inputs)).map((attachment) => ({
|
|
34320
|
-
type: "image",
|
|
34321
|
-
attachment
|
|
34322
|
-
}));
|
|
34323
|
-
}
|
|
34324
|
-
//#endregion
|
|
34325
35011
|
//#region src/editor.ts
|
|
34326
35012
|
/** Host editor and clipboard adapters used by the terminal surface. */
|
|
34327
35013
|
function waitForProcess(command, args, input) {
|
|
@@ -34602,7 +35288,6 @@ function applyPendingPermission(service, session, pending) {
|
|
|
34602
35288
|
*/
|
|
34603
35289
|
function listPermissionRows(service) {
|
|
34604
35290
|
return service.names.map((id) => {
|
|
34605
|
-
if (service.optionOf === void 0) return { id };
|
|
34606
35291
|
try {
|
|
34607
35292
|
return {
|
|
34608
35293
|
id,
|
|
@@ -35216,16 +35901,16 @@ async function run(ctx, startup, io) {
|
|
|
35216
35901
|
deliverLine(line, mode, images);
|
|
35217
35902
|
};
|
|
35218
35903
|
/** Dispatch one submitted line: slash commands to the registry, other text to the agent. */
|
|
35219
|
-
const dispatch = (text) => {
|
|
35220
|
-
send(text, "followup");
|
|
35904
|
+
const dispatch = (text, images = []) => {
|
|
35905
|
+
send(text, "followup", images);
|
|
35221
35906
|
};
|
|
35222
35907
|
/**
|
|
35223
35908
|
* Submit steering: a running driver consumes the text at its next step
|
|
35224
35909
|
* boundary (the inbox delivers between steps); an idle driver just starts
|
|
35225
35910
|
* a turn, so this doubles as the busy-state submit path.
|
|
35226
35911
|
*/
|
|
35227
|
-
const steer = (text) => {
|
|
35228
|
-
send(text, "steer");
|
|
35912
|
+
const steer = (text, images = []) => {
|
|
35913
|
+
send(text, "steer", images);
|
|
35229
35914
|
};
|
|
35230
35915
|
/** Interrupt the running turn (Esc); true when a turn was actually cancelled. */
|
|
35231
35916
|
const interrupt = () => {
|
|
@@ -35637,7 +36322,16 @@ async function run(ctx, startup, io) {
|
|
|
35637
36322
|
saveModelProviderConfiguration: (target, configuration) => saveProviderConfiguration(ctx, target, configuration),
|
|
35638
36323
|
unsetModelProviderCredential: (target) => unsetProviderCredential(ctx, target),
|
|
35639
36324
|
removeModelProvider: (target) => removeProviderSettings(ctx, target),
|
|
36325
|
+
loadProviderAuthorizations: () => loadProviderAuthorizations(ctx),
|
|
36326
|
+
subscribeProviderAuthorizations: (listener) => subscribeProviderAuthorizations(ctx, listener),
|
|
36327
|
+
beginProviderAuthorization: (row, method, interaction, signal) => beginProviderAuthorization(ctx, row, method, interaction, signal),
|
|
36328
|
+
cancelProviderAuthorization: (row) => cancelProviderAuthorization(ctx, row.key),
|
|
36329
|
+
logoutProviderAuthorization: (row) => logoutProviderAuthorization(ctx, row),
|
|
36330
|
+
openAuthorizationUrl,
|
|
36331
|
+
copyTextValue: copyText,
|
|
35640
36332
|
loadMentions: (query, signal) => mentions.candidates(query, signal),
|
|
36333
|
+
inspectImages: (paths) => inspectImagePaths(paths, ctx.get("attachments"), session?.header.cwd ?? cwd),
|
|
36334
|
+
prepareImages: (paths) => saveImagePaths(paths, ctx.get("attachments")),
|
|
35641
36335
|
cyclePermission: cyclePermission$1,
|
|
35642
36336
|
setPermission: setPermissionAction,
|
|
35643
36337
|
selectModel,
|
|
@@ -35687,7 +36381,13 @@ async function run(ctx, startup, io) {
|
|
|
35687
36381
|
mountRef.current?.rerender(appElement());
|
|
35688
36382
|
};
|
|
35689
36383
|
mountRef.current = io.mount(appElement());
|
|
35690
|
-
if (startup.prompt !== void 0 || (startup.images?.length ?? 0) > 0)
|
|
36384
|
+
if (startup.prompt !== void 0 || (startup.images?.length ?? 0) > 0) {
|
|
36385
|
+
if ((startup.images?.length ?? 0) > 0) bridge.notify(`processing ${startup.images.length} startup image${startup.images.length === 1 ? "" : "s"}…`);
|
|
36386
|
+
saveImagePaths(startup.images ?? [], ctx.get("attachments")).then((images) => {
|
|
36387
|
+
if (images.length > 0) bridge.notify(`${images.length} startup image${images.length === 1 ? "" : "s"} attached`);
|
|
36388
|
+
send(startup.prompt ?? "", "followup", images);
|
|
36389
|
+
}, (error) => bridge.notify(`initial prompt failed: ${error instanceof Error ? error.message : String(error)}`, "error"));
|
|
36390
|
+
}
|
|
35691
36391
|
async function copyLastResponse() {
|
|
35692
36392
|
const text = latestAssistantText(store.getView());
|
|
35693
36393
|
if (text === void 0) return "nothing to copy yet";
|