agent-relay-server 0.20.0 → 0.22.0
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/docs/openapi.json +4 -1
- package/package.json +2 -2
- package/public/index.html +698 -140
- package/runner/src/adapter.ts +1 -1
- package/scripts/install-bin-shim.cjs +16 -3
- package/src/agent-ref.ts +217 -0
- package/src/automations.ts +4 -1
- package/src/cli.ts +68 -8
- package/src/config-store.ts +35 -1
- package/src/context-router.ts +7 -7
- package/src/db.ts +111 -29
- package/src/maintenance.ts +9 -4
- package/src/managed-policy.ts +4 -1
- package/src/mcp.ts +452 -69
- package/src/routes.ts +94 -170
- package/src/runtime-tokens.ts +26 -1
- package/src/security.ts +3 -1
- package/src/workspace-actions.ts +336 -0
- package/src/workspace-phase.ts +181 -0
package/public/index.html
CHANGED
|
@@ -10136,6 +10136,37 @@ var persistImpl = (config, baseOptions) => (set, get, api) => {
|
|
|
10136
10136
|
};
|
|
10137
10137
|
var persist = persistImpl;
|
|
10138
10138
|
//#endregion
|
|
10139
|
+
//#region ../sdk/src/sse.ts
|
|
10140
|
+
/**
|
|
10141
|
+
* Parse a single SSE frame — the text between blank-line (`\n\n`) separators —
|
|
10142
|
+
* into its event name, data lines, and optional retry hint. Spec-compliant
|
|
10143
|
+
* field parsing: normalises CRLF, skips comment (`:`-prefixed) lines, splits each
|
|
10144
|
+
* line at the first colon, and strips one leading space from the value.
|
|
10145
|
+
*/
|
|
10146
|
+
function parseSseFrame(frame) {
|
|
10147
|
+
let event = "message";
|
|
10148
|
+
const data = [];
|
|
10149
|
+
let retry;
|
|
10150
|
+
for (const rawLine of frame.replace(/\r\n/g, "\n").split("\n")) {
|
|
10151
|
+
if (!rawLine || rawLine.startsWith(":")) continue;
|
|
10152
|
+
const sep = rawLine.indexOf(":");
|
|
10153
|
+
const field = sep >= 0 ? rawLine.slice(0, sep) : rawLine;
|
|
10154
|
+
let value = sep >= 0 ? rawLine.slice(sep + 1) : "";
|
|
10155
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
10156
|
+
if (field === "event") event = value;
|
|
10157
|
+
else if (field === "data") data.push(value);
|
|
10158
|
+
else if (field === "retry") {
|
|
10159
|
+
const next = Number(value);
|
|
10160
|
+
if (Number.isFinite(next) && next >= 1e3) retry = next;
|
|
10161
|
+
}
|
|
10162
|
+
}
|
|
10163
|
+
return {
|
|
10164
|
+
event,
|
|
10165
|
+
data,
|
|
10166
|
+
retry
|
|
10167
|
+
};
|
|
10168
|
+
}
|
|
10169
|
+
//#endregion
|
|
10139
10170
|
//#region src/lib/api.ts
|
|
10140
10171
|
var authToken = "";
|
|
10141
10172
|
function setAuthToken(token) {
|
|
@@ -10190,21 +10221,8 @@ function openRelayEventStream(token, handlers) {
|
|
|
10190
10221
|
reconnectTimer = setTimeout(connect, retryMs);
|
|
10191
10222
|
};
|
|
10192
10223
|
const dispatchFrame = (frame) => {
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
for (const rawLine of frame.replace(/\r\n/g, "\n").split("\n")) {
|
|
10196
|
-
if (!rawLine || rawLine.startsWith(":")) continue;
|
|
10197
|
-
const separator = rawLine.indexOf(":");
|
|
10198
|
-
const field = separator >= 0 ? rawLine.slice(0, separator) : rawLine;
|
|
10199
|
-
let value = separator >= 0 ? rawLine.slice(separator + 1) : "";
|
|
10200
|
-
if (value.startsWith(" ")) value = value.slice(1);
|
|
10201
|
-
if (field === "event") event = value;
|
|
10202
|
-
else if (field === "data") data.push(value);
|
|
10203
|
-
else if (field === "retry") {
|
|
10204
|
-
const nextRetry = Number(value);
|
|
10205
|
-
if (Number.isFinite(nextRetry) && nextRetry >= 1e3) retryMs = nextRetry;
|
|
10206
|
-
}
|
|
10207
|
-
}
|
|
10224
|
+
const { event, data, retry } = parseSseFrame(frame);
|
|
10225
|
+
if (retry) retryMs = retry;
|
|
10208
10226
|
if (data.length > 0) handlers.message(event, data.join("\n"));
|
|
10209
10227
|
};
|
|
10210
10228
|
const connect = async () => {
|
|
@@ -10330,25 +10348,27 @@ async function apiBlob(path) {
|
|
|
10330
10348
|
return response.blob();
|
|
10331
10349
|
}
|
|
10332
10350
|
//#endregion
|
|
10333
|
-
//#region src/
|
|
10351
|
+
//#region ../sdk/src/speech-text.ts
|
|
10334
10352
|
/**
|
|
10335
|
-
*
|
|
10353
|
+
* Shared text → speech preparation, imported by both the dashboard
|
|
10354
|
+
* (`dashboard/src/lib/voice.ts`) and the voice connector
|
|
10355
|
+
* (`connectors/voice/src/text.ts`). Single source of truth — these rules used to
|
|
10356
|
+
* live duplicated in both places and drifted.
|
|
10336
10357
|
*
|
|
10337
|
-
*
|
|
10338
|
-
*
|
|
10339
|
-
*
|
|
10340
|
-
*
|
|
10358
|
+
* Two stages, applied in order by {@link prepareForSpeech}:
|
|
10359
|
+
* 1. {@link speechify} — collapse markdown/code structure into prose worth hearing.
|
|
10360
|
+
* 2. {@link normalizeForSpeech} — rule-based normalization of inline tokens
|
|
10361
|
+
* (numbers, units, symbols, code identifiers, paths) so the speech engine
|
|
10362
|
+
* reads them naturally.
|
|
10341
10363
|
*
|
|
10342
|
-
*
|
|
10343
|
-
*
|
|
10344
|
-
*
|
|
10345
|
-
*
|
|
10346
|
-
*
|
|
10347
|
-
*
|
|
10348
|
-
* - The active chat preempts: if it speaks while a previous chat's audio lingers,
|
|
10349
|
-
* that audio is cancelled.
|
|
10364
|
+
* Why both, and why here: the server-side Kokoro engine (agent-speech) already
|
|
10365
|
+
* normalizes some of this internally (number ranges, decimals, currency) via its
|
|
10366
|
+
* phonemizer — but that normalization does NOT run for the browser Web Speech
|
|
10367
|
+
* fallback, and the dashboard's own sentence splitter trips on decimals before
|
|
10368
|
+
* the engine ever sees them. Normalizing here fixes the browser path and protects
|
|
10369
|
+
* the splitter; feeding already-normalized text to Kokoro is harmless (idempotent).
|
|
10350
10370
|
*/
|
|
10351
|
-
/** Collapse markdown/code into something worth hearing
|
|
10371
|
+
/** Collapse markdown/code into something worth hearing. */
|
|
10352
10372
|
function speechify(markdown) {
|
|
10353
10373
|
if (!markdown) return "";
|
|
10354
10374
|
let text = markdown.replace(/\r\n/g, "\n");
|
|
@@ -10357,6 +10377,7 @@ function speechify(markdown) {
|
|
|
10357
10377
|
return ` code block, ${lines} ${lines === 1 ? "line" : "lines"}. `;
|
|
10358
10378
|
});
|
|
10359
10379
|
text = text.replace(/```[^\n]*/g, " code block. ");
|
|
10380
|
+
text = collapseTables(text);
|
|
10360
10381
|
text = text.replace(/^#{1,6}\s+/gm, "");
|
|
10361
10382
|
text = text.replace(/^\s*[-*+]\s+/gm, ". ");
|
|
10362
10383
|
text = text.replace(/^\s*\d+\.\s+/gm, ". ");
|
|
@@ -10368,6 +10389,127 @@ function speechify(markdown) {
|
|
|
10368
10389
|
text = text.replace(/\s*\.\s*\.\s*(\.\s*)+/g, ". ");
|
|
10369
10390
|
return text.trim();
|
|
10370
10391
|
}
|
|
10392
|
+
/** A markdown table row: `| a | b |` or `a | b`. The separator row is `|---|:-:|`. */
|
|
10393
|
+
function isTableRow(line) {
|
|
10394
|
+
return /\|/.test(line) && line.trim().length > 0;
|
|
10395
|
+
}
|
|
10396
|
+
function isTableSeparator(line) {
|
|
10397
|
+
return /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)+\|?\s*$/.test(line);
|
|
10398
|
+
}
|
|
10399
|
+
function tableCells(line) {
|
|
10400
|
+
return line.trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
10401
|
+
}
|
|
10402
|
+
/**
|
|
10403
|
+
* Replace contiguous markdown tables with a spoken summary
|
|
10404
|
+
* ("table with columns A, B, C; N rows.") rather than reading pipes and cells.
|
|
10405
|
+
*/
|
|
10406
|
+
function collapseTables(text) {
|
|
10407
|
+
const lines = text.split("\n");
|
|
10408
|
+
const out = [];
|
|
10409
|
+
let i = 0;
|
|
10410
|
+
while (i < lines.length) {
|
|
10411
|
+
if (isTableRow(lines[i]) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
|
|
10412
|
+
const headers = tableCells(lines[i]);
|
|
10413
|
+
let j = i + 2;
|
|
10414
|
+
let rows = 0;
|
|
10415
|
+
while (j < lines.length && isTableRow(lines[j]) && !isTableSeparator(lines[j])) {
|
|
10416
|
+
rows++;
|
|
10417
|
+
j++;
|
|
10418
|
+
}
|
|
10419
|
+
const cols = headers.length ? `with columns ${headers.join(", ")}` : "";
|
|
10420
|
+
const rowText = `${rows} ${rows === 1 ? "row" : "rows"}`;
|
|
10421
|
+
out.push(`table ${cols ? `${cols}, ` : ""}${rowText}.`.replace(/\s+/g, " ").trim());
|
|
10422
|
+
i = j;
|
|
10423
|
+
continue;
|
|
10424
|
+
}
|
|
10425
|
+
out.push(lines[i]);
|
|
10426
|
+
i++;
|
|
10427
|
+
}
|
|
10428
|
+
return out.join("\n");
|
|
10429
|
+
}
|
|
10430
|
+
var UNIT_WORDS = {
|
|
10431
|
+
ms: "milliseconds",
|
|
10432
|
+
s: "seconds",
|
|
10433
|
+
kb: "kilobytes",
|
|
10434
|
+
mb: "megabytes",
|
|
10435
|
+
gb: "gigabytes",
|
|
10436
|
+
tb: "terabytes"
|
|
10437
|
+
};
|
|
10438
|
+
/** Spell out a fractional part digit-by-digit: "14" → "1 4" (so "3.14" → "3 point 1 4"). */
|
|
10439
|
+
function spaceDigits(frac) {
|
|
10440
|
+
return frac.split("").join(" ");
|
|
10441
|
+
}
|
|
10442
|
+
/**
|
|
10443
|
+
* Ordered normalization rules. ORDER IS LOAD-BEARING:
|
|
10444
|
+
* - URLs/paths first, before anything mangles their slashes/dots.
|
|
10445
|
+
* - `~` → "approximately" before number rules consume the digits after it.
|
|
10446
|
+
* - number ranges ("5-7") before unit expansion, so the unit attaches once: "5 to 7 seconds".
|
|
10447
|
+
* - decimals before unit expansion and before the sentence splitter sees the dot.
|
|
10448
|
+
* - unit expansion last among the number rules.
|
|
10449
|
+
*/
|
|
10450
|
+
var SPEECH_RULES = [
|
|
10451
|
+
{
|
|
10452
|
+
name: "url",
|
|
10453
|
+
pattern: /\bhttps?:\/\/\S+/gi,
|
|
10454
|
+
replace: " link "
|
|
10455
|
+
},
|
|
10456
|
+
{
|
|
10457
|
+
name: "path",
|
|
10458
|
+
pattern: /(?:\/[A-Za-z0-9._-]+){2,}\/?/g,
|
|
10459
|
+
replace: (m) => " " + m.replace(/[/._-]+/g, " ").trim() + " "
|
|
10460
|
+
},
|
|
10461
|
+
{
|
|
10462
|
+
name: "func-call",
|
|
10463
|
+
pattern: /\b([A-Za-z_$][\w$]*)\(\)/g,
|
|
10464
|
+
replace: "$1"
|
|
10465
|
+
},
|
|
10466
|
+
{
|
|
10467
|
+
name: "approx",
|
|
10468
|
+
pattern: /~(?=\s*[\d.])/g,
|
|
10469
|
+
replace: "approximately "
|
|
10470
|
+
},
|
|
10471
|
+
{
|
|
10472
|
+
name: "range",
|
|
10473
|
+
pattern: /(\d)\s*[-–—]\s*(?=\d)/g,
|
|
10474
|
+
replace: "$1 to "
|
|
10475
|
+
},
|
|
10476
|
+
{
|
|
10477
|
+
name: "decimal",
|
|
10478
|
+
pattern: /\b(\d+)\.(\d+)\b/g,
|
|
10479
|
+
replace: (_m, i, f) => `${i} point ${spaceDigits(f)}`
|
|
10480
|
+
},
|
|
10481
|
+
{
|
|
10482
|
+
name: "byte-unit",
|
|
10483
|
+
pattern: /\b(\d+)\s?(kb|mb|gb|tb)\b/gi,
|
|
10484
|
+
replace: (_m, n, u) => `${n} ${UNIT_WORDS[u.toLowerCase()]}`
|
|
10485
|
+
},
|
|
10486
|
+
{
|
|
10487
|
+
name: "ms-unit",
|
|
10488
|
+
pattern: /\b(\d{1,5})\s?ms\b/g,
|
|
10489
|
+
replace: (_m, n) => `${n} ${UNIT_WORDS.ms}`
|
|
10490
|
+
},
|
|
10491
|
+
{
|
|
10492
|
+
name: "s-unit",
|
|
10493
|
+
pattern: /\b(\d{1,3})\s?s\b/g,
|
|
10494
|
+
replace: (_m, n) => `${n} ${UNIT_WORDS.s}`
|
|
10495
|
+
}
|
|
10496
|
+
];
|
|
10497
|
+
/**
|
|
10498
|
+
* Apply the inline normalization rules in order. Pure and idempotent enough to
|
|
10499
|
+
* feed straight into either the Kokoro engine or the browser Web Speech API.
|
|
10500
|
+
*/
|
|
10501
|
+
function normalizeForSpeech(text) {
|
|
10502
|
+
if (!text) return "";
|
|
10503
|
+
let out = text;
|
|
10504
|
+
for (const rule of SPEECH_RULES) out = out.replace(rule.pattern, rule.replace);
|
|
10505
|
+
return out.replace(/[ \t]{2,}/g, " ").trim();
|
|
10506
|
+
}
|
|
10507
|
+
/** Full pipeline: markdown → prose → normalized speech text. */
|
|
10508
|
+
function prepareForSpeech(markdown) {
|
|
10509
|
+
return normalizeForSpeech(speechify(markdown));
|
|
10510
|
+
}
|
|
10511
|
+
//#endregion
|
|
10512
|
+
//#region src/lib/voice.ts
|
|
10371
10513
|
var MAX_CHUNK = 220;
|
|
10372
10514
|
/** Split into utterance-sized chunks (sentence boundaries; hard-split very long runs). */
|
|
10373
10515
|
function chunkForSpeech(text) {
|
|
@@ -10402,12 +10544,31 @@ var VoiceTts = class {
|
|
|
10402
10544
|
gen = 0;
|
|
10403
10545
|
audioEl = null;
|
|
10404
10546
|
audioUrl = null;
|
|
10547
|
+
playingKey = null;
|
|
10548
|
+
manual = false;
|
|
10549
|
+
listeners = /* @__PURE__ */ new Set();
|
|
10405
10550
|
get available() {
|
|
10406
10551
|
return synthAvailable || typeof Audio !== "undefined";
|
|
10407
10552
|
}
|
|
10408
10553
|
isEnabled() {
|
|
10409
10554
|
return this.enabled;
|
|
10410
10555
|
}
|
|
10556
|
+
/** Subscribe to manual-playback changes (useSyncExternalStore). */
|
|
10557
|
+
subscribe(fn) {
|
|
10558
|
+
this.listeners.add(fn);
|
|
10559
|
+
return () => {
|
|
10560
|
+
this.listeners.delete(fn);
|
|
10561
|
+
};
|
|
10562
|
+
}
|
|
10563
|
+
/** Id of the bubble currently played on demand, or null. */
|
|
10564
|
+
getPlayingKey() {
|
|
10565
|
+
return this.playingKey;
|
|
10566
|
+
}
|
|
10567
|
+
setPlayingKey(key) {
|
|
10568
|
+
if (key === this.playingKey) return;
|
|
10569
|
+
this.playingKey = key;
|
|
10570
|
+
for (const fn of this.listeners) fn();
|
|
10571
|
+
}
|
|
10411
10572
|
setEnabled(on) {
|
|
10412
10573
|
if (on === this.enabled) return;
|
|
10413
10574
|
this.enabled = on;
|
|
@@ -10433,9 +10594,9 @@ var VoiceTts = class {
|
|
|
10433
10594
|
/** A captured agent response turn arrived for `chatId`. */
|
|
10434
10595
|
onResponse(chatId, rawText) {
|
|
10435
10596
|
if (!this.enabled || !this.available || !chatId || chatId !== this.active) return;
|
|
10436
|
-
const text =
|
|
10597
|
+
const text = prepareForSpeech(rawText);
|
|
10437
10598
|
if (!text) return;
|
|
10438
|
-
if (this.speaking && this.currentChat && this.currentChat !== chatId) {
|
|
10599
|
+
if (this.speaking && (this.manual || this.currentChat && this.currentChat !== chatId)) {
|
|
10439
10600
|
this.queue = [];
|
|
10440
10601
|
this.cancel();
|
|
10441
10602
|
}
|
|
@@ -10445,6 +10606,30 @@ var VoiceTts = class {
|
|
|
10445
10606
|
});
|
|
10446
10607
|
this.pump();
|
|
10447
10608
|
}
|
|
10609
|
+
/**
|
|
10610
|
+
* Play an arbitrary response on demand (chat-bubble play button), independent
|
|
10611
|
+
* of which chat is active. Preempts whatever is currently playing.
|
|
10612
|
+
*/
|
|
10613
|
+
speak(rawText, key) {
|
|
10614
|
+
if (!this.available) return;
|
|
10615
|
+
const text = prepareForSpeech(rawText);
|
|
10616
|
+
if (!text) return;
|
|
10617
|
+
this.reset();
|
|
10618
|
+
this.manual = true;
|
|
10619
|
+
this.speaking = true;
|
|
10620
|
+
this.currentChat = null;
|
|
10621
|
+
this.setPlayingKey(key ?? null);
|
|
10622
|
+
const gen = ++this.gen;
|
|
10623
|
+
const chunks = chunkForSpeech(text);
|
|
10624
|
+
const done = () => {
|
|
10625
|
+
if (gen !== this.gen) return;
|
|
10626
|
+
this.speaking = false;
|
|
10627
|
+
this.manual = false;
|
|
10628
|
+
this.setPlayingKey(null);
|
|
10629
|
+
};
|
|
10630
|
+
if (this.mode === "kokoro") this.speakKokoro(chunks, 0, gen, done);
|
|
10631
|
+
else this.speakBrowser(chunks, 0, gen, done);
|
|
10632
|
+
}
|
|
10448
10633
|
/** Cut all speech immediately (e.g. when the user starts talking). */
|
|
10449
10634
|
bargeIn() {
|
|
10450
10635
|
this.reset();
|
|
@@ -10457,6 +10642,8 @@ var VoiceTts = class {
|
|
|
10457
10642
|
this.gen++;
|
|
10458
10643
|
this.currentChat = null;
|
|
10459
10644
|
this.speaking = false;
|
|
10645
|
+
this.manual = false;
|
|
10646
|
+
this.setPlayingKey(null);
|
|
10460
10647
|
try {
|
|
10461
10648
|
window.speechSynthesis.cancel();
|
|
10462
10649
|
} catch {}
|
|
@@ -10479,6 +10666,7 @@ var VoiceTts = class {
|
|
|
10479
10666
|
const item = this.queue.shift();
|
|
10480
10667
|
if (!item) return;
|
|
10481
10668
|
this.speaking = true;
|
|
10669
|
+
this.manual = false;
|
|
10482
10670
|
this.currentChat = item.chatId;
|
|
10483
10671
|
const gen = ++this.gen;
|
|
10484
10672
|
const chunks = chunkForSpeech(item.text);
|
|
@@ -11285,6 +11473,12 @@ function emptyAttention() {
|
|
|
11285
11473
|
score: 0
|
|
11286
11474
|
};
|
|
11287
11475
|
}
|
|
11476
|
+
function channelIsReady(channel) {
|
|
11477
|
+
if (!channel || channel.status === "offline") return false;
|
|
11478
|
+
if (channel.targetHealth?.status === "ok") return true;
|
|
11479
|
+
if (channel.targetHealth?.status === "error" || channel.targetHealth?.status === "warning") return false;
|
|
11480
|
+
return channel.ready;
|
|
11481
|
+
}
|
|
11288
11482
|
function channelPresence(channel) {
|
|
11289
11483
|
if (!channel) return {
|
|
11290
11484
|
label: "unknown",
|
|
@@ -11310,18 +11504,18 @@ function channelPresence(channel) {
|
|
|
11310
11504
|
icon: "PlugZap",
|
|
11311
11505
|
badges: []
|
|
11312
11506
|
};
|
|
11313
|
-
if (!channel.ready) return {
|
|
11314
|
-
label: "not ready",
|
|
11315
|
-
tone: "warning",
|
|
11316
|
-
icon: "Loader",
|
|
11317
|
-
badges: []
|
|
11318
|
-
};
|
|
11319
11507
|
if (channel.status === "busy") return {
|
|
11320
11508
|
label: "busy",
|
|
11321
11509
|
tone: "warning",
|
|
11322
11510
|
icon: "Activity",
|
|
11323
11511
|
badges: []
|
|
11324
11512
|
};
|
|
11513
|
+
if (!channelIsReady(channel)) return {
|
|
11514
|
+
label: "not ready",
|
|
11515
|
+
tone: "warning",
|
|
11516
|
+
icon: "Loader",
|
|
11517
|
+
badges: []
|
|
11518
|
+
};
|
|
11325
11519
|
return {
|
|
11326
11520
|
label: "ready",
|
|
11327
11521
|
tone: "success",
|
|
@@ -11905,9 +12099,11 @@ function isDashboardHidden() {
|
|
|
11905
12099
|
function notificationPeer(notification) {
|
|
11906
12100
|
return notification.threadPeer || notification.agentId || "";
|
|
11907
12101
|
}
|
|
12102
|
+
function isActiveVisibleChat(peer, state) {
|
|
12103
|
+
return Boolean(peer && state.view === "chat" && state.selectedInboxThread === peer && !isDashboardHidden());
|
|
12104
|
+
}
|
|
11908
12105
|
function notificationTargetsActiveChat(notification, state) {
|
|
11909
|
-
|
|
11910
|
-
return Boolean(state.view === "chat" && peer && state.selectedInboxThread === peer && !isDashboardHidden());
|
|
12106
|
+
return isActiveVisibleChat(notificationPeer(notification), state);
|
|
11911
12107
|
}
|
|
11912
12108
|
function lastInboundMessageId(messages) {
|
|
11913
12109
|
return messages.filter((m) => m.to === "user" && m.from !== "user").reduce((max, m) => Math.max(max, m.id), 0);
|
|
@@ -12641,7 +12837,15 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
12641
12837
|
if (s.view === "messages" && s.selectedAgent) path += "&for=" + encodeURIComponent(s.selectedAgent);
|
|
12642
12838
|
if (s.view === "messages" && s.channelFilter) path += "&channel=" + encodeURIComponent(s.channelFilter);
|
|
12643
12839
|
const messages = await api("GET", path);
|
|
12644
|
-
|
|
12840
|
+
const merged = mergeFetchedMessages(get().messages, messages);
|
|
12841
|
+
set({ messages: merged });
|
|
12842
|
+
const after = get();
|
|
12843
|
+
const peer = after.selectedInboxThread;
|
|
12844
|
+
if (isActiveVisibleChat(peer, after)) {
|
|
12845
|
+
let lastId = 0;
|
|
12846
|
+
for (const m of merged) if (m.id > lastId && inboxPeer(m) === peer && isHumanInboundMessage(m) && !isSessionActivityStep(m)) lastId = m.id;
|
|
12847
|
+
if (lastId) get().markInboxThreadReadTo(peer, lastId);
|
|
12848
|
+
}
|
|
12645
12849
|
} catch {}
|
|
12646
12850
|
},
|
|
12647
12851
|
async fetchThreadHistory(peer) {
|
|
@@ -12732,17 +12936,17 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
12732
12936
|
if (event === "connected") return;
|
|
12733
12937
|
if (event === "message.new") {
|
|
12734
12938
|
const msg = JSON.parse(data);
|
|
12939
|
+
if (msg.kind === "session" && msg.from !== "user") {
|
|
12940
|
+
const sess = msg.payload?.session;
|
|
12941
|
+
if (sess?.type === "response" && sess?.origin === "provider") voiceTts.onResponse(inboxPeer(msg), msg.body);
|
|
12942
|
+
}
|
|
12735
12943
|
const s = get();
|
|
12736
12944
|
if (s.messages.some((m) => m.id === msg.id)) return;
|
|
12737
12945
|
const msgs = [...s.messages, msg];
|
|
12738
12946
|
if (msgs.length > 500) msgs.splice(0, msgs.length - 500);
|
|
12739
12947
|
set({ messages: msgs });
|
|
12740
|
-
if (msg.kind === "session" && msg.from !== "user") {
|
|
12741
|
-
const sess = msg.payload?.session;
|
|
12742
|
-
if (sess?.type === "response" && sess?.origin === "provider") voiceTts.onResponse(inboxPeer(msg), msg.body);
|
|
12743
|
-
}
|
|
12744
12948
|
const peer = inboxPeer(msg);
|
|
12745
|
-
if (isHumanInboundMessage(msg) && peer
|
|
12949
|
+
if (isHumanInboundMessage(msg) && isActiveVisibleChat(peer, s)) get().markInboxThreadReadTo(peer, msg.id);
|
|
12746
12950
|
return;
|
|
12747
12951
|
}
|
|
12748
12952
|
if (event === "message.queued" || event === "message.expired" || event === "message.delivery_updated" || event === "message.reaction_updated") {
|
|
@@ -99607,6 +99811,45 @@ function Button({ className, variant = "default", size = "default", asChild = fa
|
|
|
99607
99811
|
});
|
|
99608
99812
|
}
|
|
99609
99813
|
//#endregion
|
|
99814
|
+
//#region src/components/shared/copy-button.tsx
|
|
99815
|
+
/**
|
|
99816
|
+
* Shared copy-to-clipboard button with a transient "copied" check state.
|
|
99817
|
+
* Consolidates the duplicated clipboard + timeout pattern used across views.
|
|
99818
|
+
*/
|
|
99819
|
+
function CopyButton({ value, label = "Copy", copiedLabel = "Copied", showText = false, size, variant = "ghost", className, iconClassName, disabled, onCopied }) {
|
|
99820
|
+
const [copied, setCopied] = (0, import_react.useState)(false);
|
|
99821
|
+
const timer = (0, import_react.useRef)(null);
|
|
99822
|
+
(0, import_react.useEffect)(() => () => {
|
|
99823
|
+
if (timer.current) clearTimeout(timer.current);
|
|
99824
|
+
}, []);
|
|
99825
|
+
async function copy(e) {
|
|
99826
|
+
e.preventDefault();
|
|
99827
|
+
e.stopPropagation();
|
|
99828
|
+
try {
|
|
99829
|
+
await navigator.clipboard?.writeText(typeof value === "function" ? value() : value);
|
|
99830
|
+
setCopied(true);
|
|
99831
|
+
onCopied?.();
|
|
99832
|
+
if (timer.current) clearTimeout(timer.current);
|
|
99833
|
+
timer.current = setTimeout(() => setCopied(false), 1400);
|
|
99834
|
+
} catch {
|
|
99835
|
+
setCopied(false);
|
|
99836
|
+
}
|
|
99837
|
+
}
|
|
99838
|
+
const resolvedSize = size ?? (showText ? "sm" : "icon-sm");
|
|
99839
|
+
const iconCls = cn$2("h-3.5 w-3.5", iconClassName);
|
|
99840
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
99841
|
+
type: "button",
|
|
99842
|
+
size: resolvedSize,
|
|
99843
|
+
variant,
|
|
99844
|
+
className,
|
|
99845
|
+
disabled,
|
|
99846
|
+
title: copied ? copiedLabel : label,
|
|
99847
|
+
"aria-label": copied ? copiedLabel : label,
|
|
99848
|
+
onClick: (e) => void copy(e),
|
|
99849
|
+
children: [copied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: iconCls }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: iconCls }), showText && (copied ? copiedLabel : label)]
|
|
99850
|
+
});
|
|
99851
|
+
}
|
|
99852
|
+
//#endregion
|
|
99610
99853
|
//#region \0vite/preload-helper.js
|
|
99611
99854
|
var scriptRel, assetsURL, seen, __vitePreload;
|
|
99612
99855
|
var init_preload_helper = __esmMin((() => {
|
|
@@ -108271,25 +108514,14 @@ var CodePreview = (0, import_react.memo)(function CodePreview({ content, path, m
|
|
|
108271
108514
|
const [html, setHtml] = (0, import_react.useState)("");
|
|
108272
108515
|
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
108273
108516
|
const [failed, setFailed] = (0, import_react.useState)(false);
|
|
108274
|
-
const [copied, setCopied] = (0, import_react.useState)(false);
|
|
108275
|
-
async function copyCode() {
|
|
108276
|
-
try {
|
|
108277
|
-
await navigator.clipboard?.writeText(content);
|
|
108278
|
-
setCopied(true);
|
|
108279
|
-
window.setTimeout(() => setCopied(false), 1400);
|
|
108280
|
-
} catch {
|
|
108281
|
-
setCopied(false);
|
|
108282
|
-
}
|
|
108283
|
-
}
|
|
108284
108517
|
function copyButton() {
|
|
108285
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
108286
|
-
|
|
108518
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
108519
|
+
value: content,
|
|
108520
|
+
label: "Copy code",
|
|
108521
|
+
copiedLabel: "Copied code",
|
|
108287
108522
|
size: "icon",
|
|
108288
108523
|
variant: "ghost",
|
|
108289
|
-
className: "absolute right-2 top-2 h-7 w-7 bg-background/80 opacity-0 shadow-sm backdrop-blur transition-opacity hover:bg-muted group-hover/code:opacity-100 focus-visible:opacity-100"
|
|
108290
|
-
onClick: copyCode,
|
|
108291
|
-
title: copied ? "Copied code" : "Copy code",
|
|
108292
|
-
children: copied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "h-3.5 w-3.5" })
|
|
108524
|
+
className: "absolute right-2 top-2 h-7 w-7 bg-background/80 opacity-0 shadow-sm backdrop-blur transition-opacity hover:bg-muted group-hover/code:opacity-100 focus-visible:opacity-100"
|
|
108293
108525
|
});
|
|
108294
108526
|
}
|
|
108295
108527
|
(0, import_react.useEffect)(() => {
|
|
@@ -124600,7 +124832,6 @@ function FileContent({ orchestratorId, selectedPath, line, onReadError }) {
|
|
|
124600
124832
|
const { file, loading, error } = useFileRead(orchestratorId, selectedPath);
|
|
124601
124833
|
const lineRef = (0, import_react.useRef)(null);
|
|
124602
124834
|
const [mode, setMode] = (0, import_react.useState)("raw");
|
|
124603
|
-
const [copiedPath, setCopiedPath] = (0, import_react.useState)(false);
|
|
124604
124835
|
(0, import_react.useEffect)(() => {
|
|
124605
124836
|
onReadError(error);
|
|
124606
124837
|
}, [error, onReadError]);
|
|
@@ -124622,15 +124853,6 @@ function FileContent({ orchestratorId, selectedPath, line, onReadError }) {
|
|
|
124622
124853
|
file?.content,
|
|
124623
124854
|
line
|
|
124624
124855
|
]);
|
|
124625
|
-
async function copyPath(path) {
|
|
124626
|
-
try {
|
|
124627
|
-
await navigator.clipboard?.writeText(path);
|
|
124628
|
-
setCopiedPath(true);
|
|
124629
|
-
window.setTimeout(() => setCopiedPath(false), 1400);
|
|
124630
|
-
} catch {
|
|
124631
|
-
setCopiedPath(false);
|
|
124632
|
-
}
|
|
124633
|
-
}
|
|
124634
124856
|
function selectMode(nextMode, kind) {
|
|
124635
124857
|
setMode(nextMode);
|
|
124636
124858
|
if (kind) writeModePreference(kind, nextMode);
|
|
@@ -124655,14 +124877,13 @@ function FileContent({ orchestratorId, selectedPath, line, onReadError }) {
|
|
|
124655
124877
|
className: "min-w-0 flex-1 truncate font-mono",
|
|
124656
124878
|
children: file.path
|
|
124657
124879
|
}),
|
|
124658
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
124659
|
-
|
|
124880
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
124881
|
+
value: file.path,
|
|
124882
|
+
label: "Copy path",
|
|
124883
|
+
copiedLabel: "Copied path",
|
|
124660
124884
|
size: "icon",
|
|
124661
124885
|
variant: "ghost",
|
|
124662
|
-
className: "h-7 w-7 shrink-0"
|
|
124663
|
-
onClick: () => copyPath(file.path),
|
|
124664
|
-
title: copiedPath ? "Copied path" : "Copy path",
|
|
124665
|
-
children: copiedPath ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "h-3.5 w-3.5" })
|
|
124886
|
+
className: "h-7 w-7 shrink-0"
|
|
124666
124887
|
}),
|
|
124667
124888
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge$1, {
|
|
124668
124889
|
variant: "outline",
|
|
@@ -124719,14 +124940,13 @@ function FileContent({ orchestratorId, selectedPath, line, onReadError }) {
|
|
|
124719
124940
|
className: "min-w-0 flex-1 truncate font-mono",
|
|
124720
124941
|
children: file.path
|
|
124721
124942
|
}),
|
|
124722
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
124723
|
-
|
|
124943
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
124944
|
+
value: file.path,
|
|
124945
|
+
label: "Copy path",
|
|
124946
|
+
copiedLabel: "Copied path",
|
|
124724
124947
|
size: "icon",
|
|
124725
124948
|
variant: "ghost",
|
|
124726
|
-
className: "h-7 w-7 shrink-0"
|
|
124727
|
-
onClick: () => copyPath(file.path),
|
|
124728
|
-
title: copiedPath ? "Copied path" : "Copy path",
|
|
124729
|
-
children: copiedPath ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "h-3.5 w-3.5" })
|
|
124949
|
+
className: "h-7 w-7 shrink-0"
|
|
124730
124950
|
}),
|
|
124731
124951
|
file.truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge$1, {
|
|
124732
124952
|
variant: "secondary",
|
|
@@ -126475,9 +126695,14 @@ function AddReaction({ open, onToggle, onReact }) {
|
|
|
126475
126695
|
})]
|
|
126476
126696
|
});
|
|
126477
126697
|
}
|
|
126698
|
+
function useTtsPlayingKey() {
|
|
126699
|
+
return (0, import_react.useSyncExternalStore)((cb) => voiceTts.subscribe(cb), () => voiceTts.getPlayingKey(), () => null);
|
|
126700
|
+
}
|
|
126478
126701
|
var MessageBubble = (0, import_react.memo)(function MessageBubble({ msg, peer, onOpenReferencedPath, onPreviewReferencedPath, onPreviewReferencedPathEnd }) {
|
|
126479
126702
|
const isOutbound = msg.from === HUMAN_AGENT_ID;
|
|
126480
126703
|
const reactToMessage = useRelayStore((s) => s.reactToMessage);
|
|
126704
|
+
const voiceTtsEnabled = useRelayStore((s) => s.voiceTtsEnabled);
|
|
126705
|
+
const ttsPlayingKey = useTtsPlayingKey();
|
|
126481
126706
|
const peerCwd = useRelayStore((s) => {
|
|
126482
126707
|
const cwd = s.agentsById[peer]?.meta?.cwd;
|
|
126483
126708
|
return typeof cwd === "string" ? cwd : "";
|
|
@@ -126492,6 +126717,13 @@ var MessageBubble = (0, import_react.memo)(function MessageBubble({ msg, peer, o
|
|
|
126492
126717
|
const reactions = groupedReactions(msg);
|
|
126493
126718
|
const receipt = isOutbound ? outboundReceipt(msg, peer) : null;
|
|
126494
126719
|
const ReceiptIcon = receipt?.icon;
|
|
126720
|
+
const isTtsPlaying = ttsPlayingKey === String(msg.id);
|
|
126721
|
+
const showPlayButton = voiceTtsEnabled && !isOutbound && !isReactionEvent(msg) && body.trim().length > 0;
|
|
126722
|
+
function togglePlay(e) {
|
|
126723
|
+
e.stopPropagation();
|
|
126724
|
+
if (isTtsPlaying) voiceTts.bargeIn();
|
|
126725
|
+
else voiceTts.speak(msg.body, String(msg.id));
|
|
126726
|
+
}
|
|
126495
126727
|
(0, import_react.useEffect)(() => {
|
|
126496
126728
|
if (!showQuickReact) return;
|
|
126497
126729
|
function dismiss(e) {
|
|
@@ -126543,11 +126775,19 @@ var MessageBubble = (0, import_react.memo)(function MessageBubble({ msg, peer, o
|
|
|
126543
126775
|
setShowQuickReact((v) => !v);
|
|
126544
126776
|
}
|
|
126545
126777
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
126778
|
+
"data-msg-id": msg.id,
|
|
126546
126779
|
className: cn$2("group/msg flex mb-3", isOutbound ? "justify-end" : "justify-start"),
|
|
126547
126780
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
126548
126781
|
ref: bubbleRef,
|
|
126549
126782
|
className: "relative max-w-[85%] md:max-w-[75%]",
|
|
126550
126783
|
children: [
|
|
126784
|
+
showPlayButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
126785
|
+
type: "button",
|
|
126786
|
+
title: isTtsPlaying ? "Stop playback" : "Play aloud",
|
|
126787
|
+
onClick: togglePlay,
|
|
126788
|
+
className: cn$2("absolute -top-2 -right-2 z-20 inline-flex h-6 w-6 items-center justify-center rounded-full border bg-popover shadow-sm transition", isTtsPlaying ? "border-primary/50 text-primary opacity-100" : "border-border text-muted-foreground opacity-70 hover:bg-muted hover:text-foreground hover:opacity-100 md:opacity-0 md:group-hover/msg:opacity-100"),
|
|
126789
|
+
children: isTtsPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleStop, { className: "h-3.5 w-3.5 animate-pulse" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Volume2, { className: "h-3.5 w-3.5" })
|
|
126790
|
+
}),
|
|
126551
126791
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
126552
126792
|
className: cn$2("rounded-2xl px-3.5 py-2 text-sm select-text", !isOutbound && !isReactionEvent(msg) && "cursor-pointer", isOutbound ? "bg-primary text-primary-foreground rounded-br-sm" : "bg-card ring-1 ring-foreground/10 rounded-bl-sm"),
|
|
126553
126793
|
onPointerDown: handleBubblePointerDown,
|
|
@@ -126778,6 +127018,109 @@ function sameActivityTurn(a, b) {
|
|
|
126778
127018
|
if (at && bt) return at === bt;
|
|
126779
127019
|
return !at && !bt;
|
|
126780
127020
|
}
|
|
127021
|
+
function StickyPromptBanner({ scrollRef, timeline }) {
|
|
127022
|
+
const [hidden, setHidden] = (0, import_react.useState)(true);
|
|
127023
|
+
const [expanded, setExpanded] = (0, import_react.useState)(false);
|
|
127024
|
+
const [collapsed, setCollapsed] = (0, import_react.useState)(false);
|
|
127025
|
+
const [promptAbove, setPromptAbove] = (0, import_react.useState)(true);
|
|
127026
|
+
const lastOutbound = (0, import_react.useMemo)(() => {
|
|
127027
|
+
for (let i = timeline.length - 1; i >= 0; i--) {
|
|
127028
|
+
const entry = timeline[i];
|
|
127029
|
+
if (!entry || entry.type !== "message") continue;
|
|
127030
|
+
if (entry.msg.from === "user") return {
|
|
127031
|
+
id: entry.msg.id,
|
|
127032
|
+
body: messageBody(entry.msg)
|
|
127033
|
+
};
|
|
127034
|
+
}
|
|
127035
|
+
return null;
|
|
127036
|
+
}, [timeline]);
|
|
127037
|
+
(0, import_react.useEffect)(() => {
|
|
127038
|
+
const scrollEl = scrollRef.current;
|
|
127039
|
+
if (!lastOutbound || !scrollEl) {
|
|
127040
|
+
setHidden(true);
|
|
127041
|
+
return;
|
|
127042
|
+
}
|
|
127043
|
+
const target = scrollEl.querySelector(`[data-msg-id="${lastOutbound.id}"]`);
|
|
127044
|
+
if (!target) {
|
|
127045
|
+
setHidden(true);
|
|
127046
|
+
return;
|
|
127047
|
+
}
|
|
127048
|
+
const observer = new IntersectionObserver(([e]) => {
|
|
127049
|
+
if (!e) return;
|
|
127050
|
+
setHidden(e.isIntersecting);
|
|
127051
|
+
if (!e.isIntersecting) {
|
|
127052
|
+
const rootTop = e.rootBounds?.top ?? scrollEl.getBoundingClientRect().top;
|
|
127053
|
+
setPromptAbove(e.boundingClientRect.top < rootTop);
|
|
127054
|
+
}
|
|
127055
|
+
}, {
|
|
127056
|
+
root: scrollEl,
|
|
127057
|
+
threshold: 0
|
|
127058
|
+
});
|
|
127059
|
+
observer.observe(target);
|
|
127060
|
+
const onScroll = () => {
|
|
127061
|
+
const r = target.getBoundingClientRect();
|
|
127062
|
+
const sr = scrollEl.getBoundingClientRect();
|
|
127063
|
+
setPromptAbove(r.top < sr.top);
|
|
127064
|
+
};
|
|
127065
|
+
scrollEl.addEventListener("scroll", onScroll, { passive: true });
|
|
127066
|
+
return () => {
|
|
127067
|
+
observer.disconnect();
|
|
127068
|
+
scrollEl.removeEventListener("scroll", onScroll);
|
|
127069
|
+
};
|
|
127070
|
+
}, [lastOutbound?.id, scrollRef]);
|
|
127071
|
+
(0, import_react.useEffect)(() => {
|
|
127072
|
+
setExpanded(false);
|
|
127073
|
+
setCollapsed(false);
|
|
127074
|
+
}, [lastOutbound?.id]);
|
|
127075
|
+
if (hidden || !lastOutbound || !lastOutbound.body.trim()) return null;
|
|
127076
|
+
function scrollToPrompt() {
|
|
127077
|
+
const scrollEl = scrollRef.current;
|
|
127078
|
+
if (!scrollEl || !lastOutbound) return;
|
|
127079
|
+
scrollEl.querySelector(`[data-msg-id="${lastOutbound.id}"]`)?.scrollIntoView({
|
|
127080
|
+
behavior: "smooth",
|
|
127081
|
+
block: "center"
|
|
127082
|
+
});
|
|
127083
|
+
}
|
|
127084
|
+
const arrow = promptAbove ? "↑" : "↓";
|
|
127085
|
+
if (collapsed) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
127086
|
+
className: "sticky top-0 z-10 -mx-3 md:-mx-4 -mt-3 md:-mt-4 bg-background",
|
|
127087
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
127088
|
+
type: "button",
|
|
127089
|
+
className: "w-full flex items-center justify-center gap-1.5 py-1 text-[11px] text-primary/60 hover:text-primary border-b border-primary/10 transition-colors",
|
|
127090
|
+
onClick: () => setCollapsed(false),
|
|
127091
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Your prompt" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { className: "w-3 h-3" })]
|
|
127092
|
+
})
|
|
127093
|
+
});
|
|
127094
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
127095
|
+
className: "sticky top-0 z-10 -mx-3 md:-mx-4 -mt-3 md:-mt-4 px-2 md:px-3 pt-2 pb-1.5 bg-background border-b border-primary/10",
|
|
127096
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
127097
|
+
className: "flex items-center gap-2 text-[11px] text-muted-foreground mb-0.5",
|
|
127098
|
+
children: [
|
|
127099
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
127100
|
+
className: "font-medium text-primary/70",
|
|
127101
|
+
children: "Your prompt"
|
|
127102
|
+
}),
|
|
127103
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
127104
|
+
type: "button",
|
|
127105
|
+
className: "ml-auto hover:text-foreground transition-colors",
|
|
127106
|
+
onClick: scrollToPrompt,
|
|
127107
|
+
children: [arrow, " scroll to original"]
|
|
127108
|
+
}),
|
|
127109
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
127110
|
+
type: "button",
|
|
127111
|
+
className: "hover:text-foreground transition-colors",
|
|
127112
|
+
onClick: () => setCollapsed(true),
|
|
127113
|
+
title: "Minimize",
|
|
127114
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronUp, { className: "w-3 h-3" })
|
|
127115
|
+
})
|
|
127116
|
+
]
|
|
127117
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
127118
|
+
className: cn$2("text-sm text-foreground leading-snug cursor-pointer", !expanded && "line-clamp-2"),
|
|
127119
|
+
onClick: () => setExpanded((e) => !e),
|
|
127120
|
+
children: lastOutbound.body
|
|
127121
|
+
})]
|
|
127122
|
+
});
|
|
127123
|
+
}
|
|
126781
127124
|
function ChatPanel({ threads, onBack, showBackButton }) {
|
|
126782
127125
|
const selectedInboxThread = useRelayStore((s) => s.selectedInboxThread);
|
|
126783
127126
|
const agentsById = useRelayStore((s) => s.agentsById);
|
|
@@ -127232,6 +127575,11 @@ function ChatPanel({ threads, onBack, showBackButton }) {
|
|
|
127232
127575
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
127233
127576
|
className: "flex items-center gap-0.5 md:gap-1 shrink-0",
|
|
127234
127577
|
children: agent && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
127578
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
127579
|
+
value: agent.id,
|
|
127580
|
+
label: "Copy agent ID",
|
|
127581
|
+
size: "icon-sm"
|
|
127582
|
+
}),
|
|
127235
127583
|
voiceTts.available && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
127236
127584
|
variant: "ghost",
|
|
127237
127585
|
size: "icon-sm",
|
|
@@ -127438,6 +127786,10 @@ function ChatPanel({ threads, onBack, showBackButton }) {
|
|
|
127438
127786
|
children: "No messages yet"
|
|
127439
127787
|
})]
|
|
127440
127788
|
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
127789
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StickyPromptBanner, {
|
|
127790
|
+
scrollRef: pinnedScroll.ref,
|
|
127791
|
+
timeline
|
|
127792
|
+
}),
|
|
127441
127793
|
timeline.map((entry) => {
|
|
127442
127794
|
if (entry.type === "message") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageBubble, {
|
|
127443
127795
|
msg: entry.msg,
|
|
@@ -127872,6 +128224,13 @@ function AgentCard({ agent }) {
|
|
|
127872
128224
|
className: "flex gap-1 mt-2.5 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 transition-opacity",
|
|
127873
128225
|
onClick: (e) => e.stopPropagation(),
|
|
127874
128226
|
children: [
|
|
128227
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
128228
|
+
value: agent.id,
|
|
128229
|
+
label: "Copy agent ID",
|
|
128230
|
+
size: "icon",
|
|
128231
|
+
className: "h-7 w-7",
|
|
128232
|
+
iconClassName: "w-3 h-3"
|
|
128233
|
+
}),
|
|
127875
128234
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
127876
128235
|
size: "icon",
|
|
127877
128236
|
variant: "ghost",
|
|
@@ -129071,9 +129430,6 @@ function WorkspaceActions({ workspace, expanded, onToggleDetails }) {
|
|
|
129071
129430
|
const gitState = useRelayStore((s) => s.workspaceGitState[workspace.id]);
|
|
129072
129431
|
const landed = !!gitState && gitState.available !== false && gitState.landed === true;
|
|
129073
129432
|
const mergeable = workspace.mode === "isolated" && Boolean(workspace.worktreePath) && MERGEABLE_STATUSES.has(workspace.status) && !landed;
|
|
129074
|
-
async function copyPath() {
|
|
129075
|
-
await navigator.clipboard?.writeText(openPath);
|
|
129076
|
-
}
|
|
129077
129433
|
async function merge() {
|
|
129078
129434
|
await fetchWorkspaceMergePreview(workspace.id);
|
|
129079
129435
|
await workspaceAction(workspace.id, "merge");
|
|
@@ -129096,13 +129452,13 @@ function WorkspaceActions({ workspace, expanded, onToggleDetails }) {
|
|
|
129096
129452
|
onClick: () => void openFilesAt({ path: openPath }),
|
|
129097
129453
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderOpen, { className: "h-3.5 w-3.5" })
|
|
129098
129454
|
}),
|
|
129099
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
129455
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
129456
|
+
value: openPath,
|
|
129457
|
+
label: "Copy path",
|
|
129458
|
+
copiedLabel: "Copied path",
|
|
129100
129459
|
size: "icon-sm",
|
|
129101
129460
|
variant: "ghost",
|
|
129102
|
-
|
|
129103
|
-
disabled: !openPath,
|
|
129104
|
-
onClick: () => void copyPath(),
|
|
129105
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "h-3.5 w-3.5" })
|
|
129461
|
+
disabled: !openPath
|
|
129106
129462
|
}),
|
|
129107
129463
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
129108
129464
|
size: "icon-sm",
|
|
@@ -129872,7 +130228,7 @@ function ChannelCard({ channel }) {
|
|
|
129872
130228
|
}
|
|
129873
130229
|
function ChannelsView() {
|
|
129874
130230
|
const channels = useRelayStore((s) => s.channels);
|
|
129875
|
-
const readyCount = channels.filter(
|
|
130231
|
+
const readyCount = channels.filter(channelIsReady).length;
|
|
129876
130232
|
const errorCount = channels.filter((c) => c.targetHealth?.status === "error").length;
|
|
129877
130233
|
const warningCount = channels.filter((c) => c.targetHealth?.status === "warning").length;
|
|
129878
130234
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -130382,10 +130738,6 @@ function SecurityView() {
|
|
|
130382
130738
|
await api("POST", `/tokens/${encodeURIComponent(token.jti)}/revoke`);
|
|
130383
130739
|
await refresh();
|
|
130384
130740
|
}
|
|
130385
|
-
async function copy(value) {
|
|
130386
|
-
await navigator.clipboard?.writeText(value);
|
|
130387
|
-
setStatus("Copied");
|
|
130388
|
-
}
|
|
130389
130741
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
130390
130742
|
className: "space-y-4",
|
|
130391
130743
|
children: [
|
|
@@ -130615,11 +130967,12 @@ function SecurityView() {
|
|
|
130615
130967
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
130616
130968
|
className: "text-xs text-muted-foreground",
|
|
130617
130969
|
children: "New token"
|
|
130618
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
130619
|
-
|
|
130970
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
130971
|
+
value: issuedToken,
|
|
130972
|
+
label: "Copy token",
|
|
130620
130973
|
size: "sm",
|
|
130621
|
-
|
|
130622
|
-
|
|
130974
|
+
variant: "ghost",
|
|
130975
|
+
onCopied: () => setStatus("Copied")
|
|
130623
130976
|
})]
|
|
130624
130977
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
|
|
130625
130978
|
className: "block max-h-24 overflow-auto break-all text-xs",
|
|
@@ -153311,8 +153664,11 @@ function MaintenanceView() {
|
|
|
153311
153664
|
})]
|
|
153312
153665
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollArea, {
|
|
153313
153666
|
className: "h-[calc(100dvh-10rem)]",
|
|
153314
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153315
|
-
className: "
|
|
153667
|
+
children: jobs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153668
|
+
className: "rounded-md border border-border px-3 py-12 text-center text-sm text-muted-foreground",
|
|
153669
|
+
children: "No maintenance jobs registered"
|
|
153670
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153671
|
+
className: "hidden overflow-x-auto rounded-md border border-border md:block",
|
|
153316
153672
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
|
|
153317
153673
|
className: "w-full min-w-[980px] text-sm",
|
|
153318
153674
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", {
|
|
@@ -153347,22 +153703,117 @@ function MaintenanceView() {
|
|
|
153347
153703
|
children: "Action"
|
|
153348
153704
|
})
|
|
153349
153705
|
] })
|
|
153350
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: jobs.
|
|
153351
|
-
colSpan: 7,
|
|
153352
|
-
className: "px-3 py-12 text-center text-sm text-muted-foreground",
|
|
153353
|
-
children: "No maintenance jobs registered"
|
|
153354
|
-
}) }) : jobs.map((job) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MaintenanceRow, {
|
|
153706
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: jobs.map((job) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MaintenanceRow, {
|
|
153355
153707
|
job,
|
|
153356
153708
|
now,
|
|
153357
153709
|
onRun: () => void runMaintenanceJob(job.id)
|
|
153358
153710
|
}, job.id)) })]
|
|
153359
153711
|
})
|
|
153360
|
-
})
|
|
153712
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153713
|
+
className: "space-y-3 md:hidden",
|
|
153714
|
+
children: jobs.map((job) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MaintenanceCard, {
|
|
153715
|
+
job,
|
|
153716
|
+
now,
|
|
153717
|
+
onRun: () => void runMaintenanceJob(job.id)
|
|
153718
|
+
}, job.id))
|
|
153719
|
+
})] })
|
|
153361
153720
|
})]
|
|
153362
153721
|
});
|
|
153363
153722
|
}
|
|
153723
|
+
function jobStatus(job) {
|
|
153724
|
+
return job.running ? "running" : job.enabled ? job.lastStatus : "disabled";
|
|
153725
|
+
}
|
|
153726
|
+
function StatusBadge({ status }) {
|
|
153727
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge$1, {
|
|
153728
|
+
variant: "outline",
|
|
153729
|
+
className: cn$2("border", STATUS_CLASS[status] || STATUS_CLASS.idle),
|
|
153730
|
+
children: [statusIcon(status), status]
|
|
153731
|
+
});
|
|
153732
|
+
}
|
|
153733
|
+
function MaintenanceCard({ job, now, onRun }) {
|
|
153734
|
+
const status = jobStatus(job);
|
|
153735
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153736
|
+
className: "rounded-md border border-border p-3",
|
|
153737
|
+
children: [
|
|
153738
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153739
|
+
className: "flex items-start justify-between gap-2",
|
|
153740
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153741
|
+
className: "min-w-0",
|
|
153742
|
+
children: [
|
|
153743
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153744
|
+
className: "font-medium",
|
|
153745
|
+
children: job.title
|
|
153746
|
+
}),
|
|
153747
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153748
|
+
className: "mt-0.5 text-xs text-muted-foreground",
|
|
153749
|
+
children: job.description
|
|
153750
|
+
}),
|
|
153751
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153752
|
+
className: "mt-1 font-mono text-[11px] text-muted-foreground/80",
|
|
153753
|
+
children: job.id
|
|
153754
|
+
})
|
|
153755
|
+
]
|
|
153756
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBadge, { status })]
|
|
153757
|
+
}),
|
|
153758
|
+
job.consecutiveFailures > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153759
|
+
className: "mt-2 text-xs text-red-400",
|
|
153760
|
+
children: [
|
|
153761
|
+
job.consecutiveFailures,
|
|
153762
|
+
" failure",
|
|
153763
|
+
job.consecutiveFailures === 1 ? "" : "s"
|
|
153764
|
+
]
|
|
153765
|
+
}),
|
|
153766
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", {
|
|
153767
|
+
className: "mt-3 grid grid-cols-3 gap-2 text-xs",
|
|
153768
|
+
children: [
|
|
153769
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
|
|
153770
|
+
className: "text-muted-foreground",
|
|
153771
|
+
children: "Last run"
|
|
153772
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: job.lastRunAt ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
153773
|
+
title: fmtTime$1(job.lastRunAt),
|
|
153774
|
+
children: timeAgo(now, job.lastRunAt)
|
|
153775
|
+
}) : "never" })] }),
|
|
153776
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
|
|
153777
|
+
className: "text-muted-foreground",
|
|
153778
|
+
children: "Next run"
|
|
153779
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: job.nextRunAt ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
153780
|
+
title: fmtTime$1(job.nextRunAt),
|
|
153781
|
+
children: nextRunText(now, job.nextRunAt)
|
|
153782
|
+
}) : "-" })] }),
|
|
153783
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
|
|
153784
|
+
className: "text-muted-foreground",
|
|
153785
|
+
children: "Duration"
|
|
153786
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: job.lastDurationMs !== void 0 ? `${job.lastDurationMs}ms` : "-" })] })
|
|
153787
|
+
]
|
|
153788
|
+
}),
|
|
153789
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153790
|
+
className: "mt-3",
|
|
153791
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153792
|
+
className: "text-xs text-muted-foreground",
|
|
153793
|
+
children: "Result"
|
|
153794
|
+
}), job.lastError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153795
|
+
className: "mt-0.5 text-xs text-red-400 line-clamp-3",
|
|
153796
|
+
children: job.lastError
|
|
153797
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153798
|
+
className: "mt-0.5 font-mono text-[11px] text-muted-foreground line-clamp-3",
|
|
153799
|
+
children: resultSummary(job)
|
|
153800
|
+
})]
|
|
153801
|
+
}),
|
|
153802
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
153803
|
+
className: "mt-3 flex justify-end",
|
|
153804
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
153805
|
+
size: "sm",
|
|
153806
|
+
variant: "outline",
|
|
153807
|
+
disabled: !job.enabled || job.running,
|
|
153808
|
+
onClick: onRun,
|
|
153809
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Play, { className: "h-3.5 w-3.5" }), " Run"]
|
|
153810
|
+
})
|
|
153811
|
+
})
|
|
153812
|
+
]
|
|
153813
|
+
});
|
|
153814
|
+
}
|
|
153364
153815
|
function MaintenanceRow({ job, now, onRun }) {
|
|
153365
|
-
const status = job
|
|
153816
|
+
const status = jobStatus(job);
|
|
153366
153817
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
|
|
153367
153818
|
className: "border-t border-border align-top",
|
|
153368
153819
|
children: [
|
|
@@ -153385,11 +153836,7 @@ function MaintenanceRow({ job, now, onRun }) {
|
|
|
153385
153836
|
}),
|
|
153386
153837
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", {
|
|
153387
153838
|
className: "px-3 py-3",
|
|
153388
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
153389
|
-
variant: "outline",
|
|
153390
|
-
className: cn$2("border", STATUS_CLASS[status] || STATUS_CLASS.idle),
|
|
153391
|
-
children: [statusIcon(status), status]
|
|
153392
|
-
}), job.consecutiveFailures > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153839
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBadge, { status }), job.consecutiveFailures > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153393
153840
|
className: "mt-1 text-xs text-red-400",
|
|
153394
153841
|
children: [
|
|
153395
153842
|
job.consecutiveFailures,
|
|
@@ -154071,7 +154518,6 @@ function AgentDiagnostics({ agent, orchestrators }) {
|
|
|
154071
154518
|
const now = useNow();
|
|
154072
154519
|
const [policyHealth, setPolicyHealth] = (0, import_react.useState)(null);
|
|
154073
154520
|
const [agentEvents, setAgentEvents] = (0, import_react.useState)([]);
|
|
154074
|
-
const [copied, setCopied] = (0, import_react.useState)(false);
|
|
154075
154521
|
const [expandedSections, setExpandedSections] = (0, import_react.useState)({
|
|
154076
154522
|
spawn: true,
|
|
154077
154523
|
workspace: true,
|
|
@@ -154115,7 +154561,7 @@ function AgentDiagnostics({ agent, orchestrators }) {
|
|
|
154115
154561
|
[key]: !s[key]
|
|
154116
154562
|
}));
|
|
154117
154563
|
}
|
|
154118
|
-
|
|
154564
|
+
function buildDiagnosticBundle() {
|
|
154119
154565
|
const policy = policyHealth?.policy;
|
|
154120
154566
|
const state = policyHealth?.state;
|
|
154121
154567
|
const lines = [
|
|
@@ -154192,11 +154638,7 @@ function AgentDiagnostics({ agent, orchestrators }) {
|
|
|
154192
154638
|
}
|
|
154193
154639
|
const contracts = agent.meta?.contracts;
|
|
154194
154640
|
if (contracts) for (const [k, v] of Object.entries(contracts)) lines.push(`Contract ${k}: ${v}`);
|
|
154195
|
-
|
|
154196
|
-
await navigator.clipboard.writeText(lines.join("\n"));
|
|
154197
|
-
setCopied(true);
|
|
154198
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
154199
|
-
} catch {}
|
|
154641
|
+
return lines.join("\n");
|
|
154200
154642
|
}
|
|
154201
154643
|
const policy = policyHealth?.policy;
|
|
154202
154644
|
const state = policyHealth?.state;
|
|
@@ -154208,12 +154650,14 @@ function AgentDiagnostics({ agent, orchestrators }) {
|
|
|
154208
154650
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", {
|
|
154209
154651
|
className: "text-sm font-medium flex items-center gap-1.5",
|
|
154210
154652
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Stethoscope, { className: "w-3.5 h-3.5 text-muted-foreground" }), "Diagnostics"]
|
|
154211
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.
|
|
154653
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
154654
|
+
value: buildDiagnosticBundle,
|
|
154655
|
+
label: "Copy Bundle",
|
|
154656
|
+
showText: true,
|
|
154212
154657
|
size: "sm",
|
|
154213
154658
|
variant: "outline",
|
|
154214
154659
|
className: "h-7 text-xs gap-1",
|
|
154215
|
-
|
|
154216
|
-
children: [copied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "w-3 h-3" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "w-3 h-3" }), copied ? "Copied" : "Copy Bundle"]
|
|
154660
|
+
iconClassName: "w-3 h-3"
|
|
154217
154661
|
})]
|
|
154218
154662
|
}),
|
|
154219
154663
|
policy && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapsibleSection, {
|
|
@@ -155406,13 +155850,21 @@ function AgentDetailDrawer() {
|
|
|
155406
155850
|
className: "space-y-1 text-sm",
|
|
155407
155851
|
children: [
|
|
155408
155852
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
155409
|
-
className: "flex justify-between gap-2 min-w-0",
|
|
155853
|
+
className: "flex items-center justify-between gap-2 min-w-0",
|
|
155410
155854
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
155411
155855
|
className: "text-muted-foreground shrink-0",
|
|
155412
155856
|
children: "ID"
|
|
155413
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.
|
|
155414
|
-
className: "
|
|
155415
|
-
children:
|
|
155857
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
155858
|
+
className: "flex min-w-0 items-center gap-1",
|
|
155859
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
155860
|
+
className: "font-mono text-xs truncate",
|
|
155861
|
+
children: agent.id
|
|
155862
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
155863
|
+
value: agent.id,
|
|
155864
|
+
label: "Copy agent ID",
|
|
155865
|
+
size: "icon-xs",
|
|
155866
|
+
className: "shrink-0 text-muted-foreground"
|
|
155867
|
+
})]
|
|
155416
155868
|
})]
|
|
155417
155869
|
}),
|
|
155418
155870
|
runtimePackage && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -156996,11 +157448,6 @@ function OrchestratorInstallModal() {
|
|
|
156996
157448
|
setLoading(false);
|
|
156997
157449
|
}
|
|
156998
157450
|
}
|
|
156999
|
-
async function copy() {
|
|
157000
|
-
if (!command) return;
|
|
157001
|
-
await navigator.clipboard.writeText(command);
|
|
157002
|
-
showNotification("Install command copied");
|
|
157003
|
-
}
|
|
157004
157451
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog, {
|
|
157005
157452
|
open,
|
|
157006
157453
|
onOpenChange: (o) => !o && set({ orchestratorInstallOpen: false }),
|
|
@@ -157069,11 +157516,14 @@ function OrchestratorInstallModal() {
|
|
|
157069
157516
|
onClick: () => set({ orchestratorInstallOpen: false }),
|
|
157070
157517
|
children: "Close"
|
|
157071
157518
|
}),
|
|
157072
|
-
command && /* @__PURE__ */ (0, import_jsx_runtime.
|
|
157519
|
+
command && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
157520
|
+
value: command,
|
|
157521
|
+
label: "Copy",
|
|
157522
|
+
showText: true,
|
|
157523
|
+
size: "default",
|
|
157073
157524
|
variant: "outline",
|
|
157074
157525
|
className: "gap-1",
|
|
157075
|
-
|
|
157076
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Copy, { className: "w-3.5 h-3.5" }), "Copy"]
|
|
157526
|
+
onCopied: () => showNotification("Install command copied")
|
|
157077
157527
|
}),
|
|
157078
157528
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
157079
157529
|
className: "gap-1",
|
|
@@ -157589,6 +158039,29 @@ function AgentProfileModal() {
|
|
|
157589
158039
|
...partial
|
|
157590
158040
|
} });
|
|
157591
158041
|
}
|
|
158042
|
+
function updateCodexToolOutputTokenLimit(value) {
|
|
158043
|
+
const providerOptions = profile.providerOptions || {};
|
|
158044
|
+
const codex = { ...providerOptions.codex && typeof providerOptions.codex === "object" && !Array.isArray(providerOptions.codex) ? providerOptions.codex : {} };
|
|
158045
|
+
if (!value.trim()) delete codex.toolOutputTokenLimit;
|
|
158046
|
+
else {
|
|
158047
|
+
const limit = Number(value);
|
|
158048
|
+
if (!Number.isSafeInteger(limit)) return;
|
|
158049
|
+
codex.toolOutputTokenLimit = limit;
|
|
158050
|
+
}
|
|
158051
|
+
updateProfileModal({ providerOptions: {
|
|
158052
|
+
...providerOptions,
|
|
158053
|
+
codex
|
|
158054
|
+
} });
|
|
158055
|
+
}
|
|
158056
|
+
function updateMaxSpawnedAgents(value) {
|
|
158057
|
+
if (!value.trim()) {
|
|
158058
|
+
updateProfileModal({ maxSpawnedAgents: void 0 });
|
|
158059
|
+
return;
|
|
158060
|
+
}
|
|
158061
|
+
const n = Number(value);
|
|
158062
|
+
if (!Number.isInteger(n) || n < 0) return;
|
|
158063
|
+
updateProfileModal({ maxSpawnedAgents: Math.min(n, 100) });
|
|
158064
|
+
}
|
|
157592
158065
|
function selectBase(base) {
|
|
157593
158066
|
const isHost = base === "host";
|
|
157594
158067
|
updateProfileModal({
|
|
@@ -157615,6 +158088,8 @@ function AgentProfileModal() {
|
|
|
157615
158088
|
}
|
|
157616
158089
|
});
|
|
157617
158090
|
}
|
|
158091
|
+
const codexOptions = profile.providerOptions?.codex && typeof profile.providerOptions.codex === "object" && !Array.isArray(profile.providerOptions.codex) ? profile.providerOptions.codex : {};
|
|
158092
|
+
const codexToolOutputTokenLimit = Number.isSafeInteger(codexOptions.toolOutputTokenLimit) ? String(codexOptions.toolOutputTokenLimit) : "";
|
|
157618
158093
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog, {
|
|
157619
158094
|
open,
|
|
157620
158095
|
onOpenChange: (o) => !o && closeProfileModal(),
|
|
@@ -157752,6 +158227,27 @@ function AgentProfileModal() {
|
|
|
157752
158227
|
})] })]
|
|
157753
158228
|
})
|
|
157754
158229
|
}),
|
|
158230
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
|
|
158231
|
+
title: "Spawning",
|
|
158232
|
+
defaultOpen: false,
|
|
158233
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
158234
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { children: "Spawn Quota" }),
|
|
158235
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
158236
|
+
type: "number",
|
|
158237
|
+
min: 0,
|
|
158238
|
+
max: 100,
|
|
158239
|
+
step: 1,
|
|
158240
|
+
value: profile.maxSpawnedAgents === void 0 ? "" : String(profile.maxSpawnedAgents),
|
|
158241
|
+
onChange: (e) => updateMaxSpawnedAgents(e.target.value),
|
|
158242
|
+
placeholder: "0",
|
|
158243
|
+
disabled: readOnly
|
|
158244
|
+
}),
|
|
158245
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
158246
|
+
className: "mt-1 text-xs text-muted-foreground",
|
|
158247
|
+
children: "Max concurrent live child agents this profile may spawn. Empty or 0 = cannot spawn (default). Children never inherit it (no grandchildren)."
|
|
158248
|
+
})
|
|
158249
|
+
] })
|
|
158250
|
+
}),
|
|
157755
158251
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
|
|
157756
158252
|
title: "Environment Variables",
|
|
157757
158253
|
defaultOpen: false,
|
|
@@ -157761,10 +158257,18 @@ function AgentProfileModal() {
|
|
|
157761
158257
|
disabled: readOnly
|
|
157762
158258
|
})
|
|
157763
158259
|
}),
|
|
157764
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
158260
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Section, {
|
|
157765
158261
|
title: "Provider Options",
|
|
157766
158262
|
defaultOpen: false,
|
|
157767
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
158263
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { children: "Codex Tool Output Limit" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
158264
|
+
type: "number",
|
|
158265
|
+
min: 1e3,
|
|
158266
|
+
max: 2e5,
|
|
158267
|
+
value: codexToolOutputTokenLimit,
|
|
158268
|
+
onChange: (e) => updateCodexToolOutputTokenLimit(e.target.value),
|
|
158269
|
+
placeholder: "12000",
|
|
158270
|
+
disabled: readOnly
|
|
158271
|
+
})] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
|
|
157768
158272
|
value: JSON.stringify(profile.providerOptions || {}, null, 2),
|
|
157769
158273
|
onChange: (e) => {
|
|
157770
158274
|
try {
|
|
@@ -157775,7 +158279,7 @@ function AgentProfileModal() {
|
|
|
157775
158279
|
disabled: readOnly,
|
|
157776
158280
|
className: "font-mono text-xs",
|
|
157777
158281
|
placeholder: "{}"
|
|
157778
|
-
})
|
|
158282
|
+
})]
|
|
157779
158283
|
})
|
|
157780
158284
|
]
|
|
157781
158285
|
}),
|
|
@@ -158607,6 +159111,10 @@ if ("serviceWorker" in navigator) {
|
|
|
158607
159111
|
inset-block: calc(var(--spacing) * 0);
|
|
158608
159112
|
}
|
|
158609
159113
|
|
|
159114
|
+
.-top-2 {
|
|
159115
|
+
top: calc(var(--spacing) * -2);
|
|
159116
|
+
}
|
|
159117
|
+
|
|
158610
159118
|
.top-0 {
|
|
158611
159119
|
top: calc(var(--spacing) * 0);
|
|
158612
159120
|
}
|
|
@@ -158635,6 +159143,10 @@ if ("serviceWorker" in navigator) {
|
|
|
158635
159143
|
right: calc(var(--spacing) * -.5);
|
|
158636
159144
|
}
|
|
158637
159145
|
|
|
159146
|
+
.-right-2 {
|
|
159147
|
+
right: calc(var(--spacing) * -2);
|
|
159148
|
+
}
|
|
159149
|
+
|
|
158638
159150
|
.right-2 {
|
|
158639
159151
|
right: calc(var(--spacing) * 2);
|
|
158640
159152
|
}
|
|
@@ -158769,6 +159281,10 @@ if ("serviceWorker" in navigator) {
|
|
|
158769
159281
|
margin-inline: calc(var(--spacing) * -1);
|
|
158770
159282
|
}
|
|
158771
159283
|
|
|
159284
|
+
.-mx-3 {
|
|
159285
|
+
margin-inline: calc(var(--spacing) * -3);
|
|
159286
|
+
}
|
|
159287
|
+
|
|
158772
159288
|
.-mx-4 {
|
|
158773
159289
|
margin-inline: calc(var(--spacing) * -4);
|
|
158774
159290
|
}
|
|
@@ -158793,6 +159309,10 @@ if ("serviceWorker" in navigator) {
|
|
|
158793
159309
|
margin-top: calc(var(--spacing) * -1);
|
|
158794
159310
|
}
|
|
158795
159311
|
|
|
159312
|
+
.-mt-3 {
|
|
159313
|
+
margin-top: calc(var(--spacing) * -3);
|
|
159314
|
+
}
|
|
159315
|
+
|
|
158796
159316
|
.mt-0\.5 {
|
|
158797
159317
|
margin-top: calc(var(--spacing) * .5);
|
|
158798
159318
|
}
|
|
@@ -158931,6 +159451,10 @@ if ("serviceWorker" in navigator) {
|
|
|
158931
159451
|
display: inline-flex;
|
|
158932
159452
|
}
|
|
158933
159453
|
|
|
159454
|
+
.table {
|
|
159455
|
+
display: table;
|
|
159456
|
+
}
|
|
159457
|
+
|
|
158934
159458
|
.field-sizing-content {
|
|
158935
159459
|
field-sizing: content;
|
|
158936
159460
|
}
|
|
@@ -160183,6 +160707,16 @@ if ("serviceWorker" in navigator) {
|
|
|
160183
160707
|
}
|
|
160184
160708
|
}
|
|
160185
160709
|
|
|
160710
|
+
.border-primary\/10 {
|
|
160711
|
+
border-color: var(--primary);
|
|
160712
|
+
}
|
|
160713
|
+
|
|
160714
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
160715
|
+
.border-primary\/10 {
|
|
160716
|
+
border-color: color-mix(in oklab, var(--primary) 10%, transparent);
|
|
160717
|
+
}
|
|
160718
|
+
}
|
|
160719
|
+
|
|
160186
160720
|
.border-primary\/25 {
|
|
160187
160721
|
border-color: var(--primary);
|
|
160188
160722
|
}
|
|
@@ -161223,6 +161757,10 @@ if ("serviceWorker" in navigator) {
|
|
|
161223
161757
|
padding-bottom: calc(var(--spacing) * 1);
|
|
161224
161758
|
}
|
|
161225
161759
|
|
|
161760
|
+
.pb-1\.5 {
|
|
161761
|
+
padding-bottom: calc(var(--spacing) * 1.5);
|
|
161762
|
+
}
|
|
161763
|
+
|
|
161226
161764
|
.pb-2 {
|
|
161227
161765
|
padding-bottom: calc(var(--spacing) * 2);
|
|
161228
161766
|
}
|
|
@@ -162440,6 +162978,10 @@ if ("serviceWorker" in navigator) {
|
|
|
162440
162978
|
.hover\:opacity-80:hover {
|
|
162441
162979
|
opacity: .8;
|
|
162442
162980
|
}
|
|
162981
|
+
|
|
162982
|
+
.hover\:opacity-100:hover {
|
|
162983
|
+
opacity: 1;
|
|
162984
|
+
}
|
|
162443
162985
|
}
|
|
162444
162986
|
|
|
162445
162987
|
.focus\:bg-accent:focus {
|
|
@@ -163007,6 +163549,14 @@ if ("serviceWorker" in navigator) {
|
|
|
163007
163549
|
margin: calc(var(--spacing) * -6);
|
|
163008
163550
|
}
|
|
163009
163551
|
|
|
163552
|
+
.md\:-mx-4 {
|
|
163553
|
+
margin-inline: calc(var(--spacing) * -4);
|
|
163554
|
+
}
|
|
163555
|
+
|
|
163556
|
+
.md\:-mt-4 {
|
|
163557
|
+
margin-top: calc(var(--spacing) * -4);
|
|
163558
|
+
}
|
|
163559
|
+
|
|
163010
163560
|
.md\:block {
|
|
163011
163561
|
display: block;
|
|
163012
163562
|
}
|
|
@@ -163094,6 +163644,10 @@ if ("serviceWorker" in navigator) {
|
|
|
163094
163644
|
padding: calc(var(--spacing) * 6);
|
|
163095
163645
|
}
|
|
163096
163646
|
|
|
163647
|
+
.md\:px-3 {
|
|
163648
|
+
padding-inline: calc(var(--spacing) * 3);
|
|
163649
|
+
}
|
|
163650
|
+
|
|
163097
163651
|
.md\:px-4 {
|
|
163098
163652
|
padding-inline: calc(var(--spacing) * 4);
|
|
163099
163653
|
}
|
|
@@ -163115,6 +163669,10 @@ if ("serviceWorker" in navigator) {
|
|
|
163115
163669
|
text-wrap: pretty;
|
|
163116
163670
|
}
|
|
163117
163671
|
|
|
163672
|
+
.md\:opacity-0 {
|
|
163673
|
+
opacity: 0;
|
|
163674
|
+
}
|
|
163675
|
+
|
|
163118
163676
|
@media (hover: hover) {
|
|
163119
163677
|
.md\:group-hover\/msg\:pointer-events-auto:is(:where(.group\/msg):hover *) {
|
|
163120
163678
|
pointer-events: auto;
|