mixdog 0.9.66 → 0.9.68

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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +3 -0
  3. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -0
  4. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +20 -2
  5. package/src/runtime/channels/lib/config.mjs +13 -5
  6. package/src/runtime/channels/lib/owned-runtime.mjs +65 -5
  7. package/src/runtime/channels/lib/scheduler.mjs +7 -15
  8. package/src/runtime/channels/lib/status-snapshot.mjs +6 -30
  9. package/src/runtime/channels/lib/tool-dispatch.mjs +6 -1
  10. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +203 -0
  11. package/src/runtime/channels/lib/webhook.mjs +46 -246
  12. package/src/runtime/channels/lib/worker-main.mjs +45 -92
  13. package/src/runtime/shared/automation-attachments.mjs +72 -0
  14. package/src/runtime/shared/automation-workflow.mjs +41 -0
  15. package/src/runtime/shared/config.mjs +0 -9
  16. package/src/runtime/shared/schedule-session-run.mjs +10 -1
  17. package/src/runtime/shared/schedules-db.mjs +18 -3
  18. package/src/runtime/shared/time-format.mjs +56 -0
  19. package/src/runtime/shared/tool-card-model.mjs +740 -0
  20. package/src/runtime/shared/webhook-session-run.mjs +57 -0
  21. package/src/runtime/shared/webhooks-db.mjs +19 -3
  22. package/src/session-runtime/channel-config-api.mjs +13 -1
  23. package/src/session-runtime/lifecycle-api.mjs +12 -0
  24. package/src/session-runtime/prewarm.mjs +13 -7
  25. package/src/session-runtime/runtime-core.mjs +127 -22
  26. package/src/session-runtime/session-text.mjs +6 -0
  27. package/src/session-runtime/settings-api.mjs +0 -12
  28. package/src/standalone/channel-admin.mjs +71 -22
  29. package/src/standalone/channel-daemon-client.mjs +5 -1
  30. package/src/standalone/channel-daemon-transport.mjs +112 -20
  31. package/src/standalone/channel-daemon.mjs +6 -4
  32. package/src/tui/App.jsx +2 -47
  33. package/src/tui/app/channel-pickers.mjs +8 -173
  34. package/src/tui/app/doctor.mjs +0 -1
  35. package/src/tui/app/slash-commands.mjs +1 -3
  36. package/src/tui/app/slash-dispatch.mjs +3 -3
  37. package/src/tui/components/ToolExecution.jsx +47 -196
  38. package/src/tui/components/tool-execution/surface-detail.mjs +33 -394
  39. package/src/tui/components/tool-execution/text-format.mjs +27 -104
  40. package/src/tui/dist/index.mjs +340 -346
  41. package/src/tui/engine/live-share.mjs +9 -0
  42. package/src/tui/engine/session-api-ext.mjs +32 -16
  43. package/src/tui/engine.mjs +14 -1
  44. package/src/tui/time-format.mjs +4 -51
  45. package/src/runtime/channels/lib/webhook/ngrok.mjs +0 -181
@@ -1246,7 +1246,6 @@ __export(config_exports, {
1246
1246
  getOpenAIUsageSessionKey: () => getOpenAIUsageSessionKey,
1247
1247
  getOpenCodeGoAuthCookie: () => getOpenCodeGoAuthCookie,
1248
1248
  getTelegramToken: () => getTelegramToken,
1249
- getWebhookAuthtoken: () => getWebhookAuthtoken,
1250
1249
  hasStoredSecret: () => hasStoredSecret,
1251
1250
  invalidateConfigReadCache: () => invalidateConfigReadCache,
1252
1251
  invalidateSecretReadCache: () => invalidateSecretReadCache,
@@ -1563,9 +1562,6 @@ function getDiscordToken() {
1563
1562
  function getTelegramToken() {
1564
1563
  return _readSecret(SECRET_ACCOUNTS.telegramToken);
1565
1564
  }
1566
- function getWebhookAuthtoken() {
1567
- return _readSecret(SECRET_ACCOUNTS.webhookAuth);
1568
- }
1569
1565
  function getOpenAIUsageSessionKey() {
1570
1566
  return process.env.OPENAI_USAGE_SESSION_KEY || process.env.OPENAI_DASHBOARD_SESSION_KEY || process.env.OPENAI_SESSION_KEY || process.env.MIXDOG_OPENAI_USAGE_SESSION_KEY || _readSecret(SECRET_ACCOUNTS.openaiUsageSessionKey);
1571
1567
  }
@@ -1631,7 +1627,6 @@ var init_config = __esm({
1631
1627
  SECRET_ACCOUNTS = Object.freeze({
1632
1628
  discordToken: "discord.token",
1633
1629
  telegramToken: "telegram.token",
1634
- webhookAuth: "webhook.authtoken",
1635
1630
  agentApiKey: (provider) => `agent.${provider}.apiKey`,
1636
1631
  openaiUsageSessionKey: "agent.openai.usageSessionKey",
1637
1632
  opencodeGoAuthCookie: "agent.opencode-go.authCookie"
@@ -5844,7 +5839,7 @@ var SPINNER_VERBS = [
5844
5839
  ];
5845
5840
  var SPINNER_FRAMES = ["\u25C7", "\u25C6", "\u25C8", "\u25C6", "\u25C7"];
5846
5841
 
5847
- // src/tui/time-format.mjs
5842
+ // src/runtime/shared/time-format.mjs
5848
5843
  function formatDuration(ms, options = {}) {
5849
5844
  if (!Number.isFinite(Number(ms))) return "";
5850
5845
  const value = Math.max(0, Number(ms) || 0);
@@ -10098,10 +10093,8 @@ var SLASH_COMMANDS = [
10098
10093
  { name: "plugins", usage: "/plugins", description: "Manage local plugin integrations" },
10099
10094
  { name: "hooks", usage: "/hooks", description: "Manage before-tool hook rules and events" },
10100
10095
  { name: "providers", usage: "/providers", description: "Manage auth, API keys, OAuth, and local endpoints" },
10101
- { name: "channels", usage: "/channels", description: "Manage Discord, channels, schedules, webhooks" },
10096
+ { name: "channels", usage: "/channels", description: "Manage Discord, Telegram, and voice" },
10102
10097
  { name: "remote", usage: "/remote", description: "Claim remote for this session (takes over from any other session)" },
10103
- { name: "schedules", usage: "/schedules", description: "Manage schedules" },
10104
- { name: "webhooks", usage: "/webhooks", description: "Manage inbound webhooks" },
10105
10098
  { name: "settings", usage: "/setting", aliases: ["setting", "config"], aliasUsage: ["settings", "config"], showAliasUsage: false, description: "Open runtime settings" },
10106
10099
  { name: "profile", usage: "/profile", description: "Set your title and response language" },
10107
10100
  { name: "update", usage: "/update", description: "Check version and update mixdog" },
@@ -17349,159 +17342,6 @@ function createChannelPickers({
17349
17342
  setContextPanel(null);
17350
17343
  setChannelPrompt(prompt);
17351
17344
  };
17352
- const channelRemoteEnabled = store.isRemoteEnabled?.() === true;
17353
- if (focus === "schedules") {
17354
- const schedules = setup.schedules || [];
17355
- const items2 = [
17356
- ...schedules.length ? schedules.map((schedule) => {
17357
- const enabled = schedule.enabled !== false;
17358
- return {
17359
- value: `schedule:${schedule.name}`,
17360
- label: schedule.name,
17361
- marker: enabled ? "\u25CF" : "\u25CB",
17362
- markerColor: channelRemoteEnabled ? enabled ? theme.success : theme.inactive : theme.inactive,
17363
- description: `${schedule.time || "(no cron)"} \xB7 ${schedule.route}${schedule.model ? ` \xB7 ${schedule.model}` : ""}${channelRemoteEnabled ? "" : " \xB7 channel off"}`,
17364
- _action: "schedule-toggle",
17365
- _name: schedule.name,
17366
- _enabled: enabled
17367
- };
17368
- }) : [{
17369
- value: "empty",
17370
- label: "No schedules",
17371
- description: "no schedules configured",
17372
- _action: "noop"
17373
- }]
17374
- ];
17375
- const toggleSchedule = async (item) => {
17376
- if (item._action !== "schedule-toggle") return;
17377
- if (!channelRemoteEnabled) {
17378
- store.pushNotice("enable channel first", "warn");
17379
- return;
17380
- }
17381
- try {
17382
- await store.setScheduleEnabled?.(item._name, !item._enabled);
17383
- void openChannelSetupPicker("schedules", { highlightValue: `schedule:${item._name}` });
17384
- } catch (e) {
17385
- store.pushNotice(`schedule toggle failed: ${e?.message || e}`, "error");
17386
- }
17387
- };
17388
- setPicker({
17389
- title: "Schedules",
17390
- description: channelRemoteEnabled ? "Enable or disable cron schedules." : "Enable channel to toggle schedules.",
17391
- initialIndex: Math.max(0, items2.findIndex((entry) => entry.value === options.highlightValue)),
17392
- items: items2,
17393
- onSelect: (_value, item) => toggleSchedule(item),
17394
- onLeft: (item) => toggleSchedule(item),
17395
- onRight: (item) => toggleSchedule(item),
17396
- onCancel: () => {
17397
- setPicker(null);
17398
- }
17399
- });
17400
- return;
17401
- }
17402
- if (focus === "webhook-endpoint") {
17403
- const returnTo = typeof options.returnTo === "function" ? options.returnTo : () => setPicker(null);
17404
- const domain = setup.webhook?.ngrokDomain || setup.webhook?.domain || "";
17405
- const items2 = [
17406
- {
17407
- value: "endpoint-domain",
17408
- label: "ngrok domain",
17409
- description: domain ? domain : "Not set \xB7 Enter ngrok domain",
17410
- _action: "endpoint-domain"
17411
- },
17412
- {
17413
- value: "endpoint-authtoken",
17414
- label: "authtoken",
17415
- description: setup.webhook?.authenticated === true ? "Set" : "Not set \xB7 Enter authtoken",
17416
- _action: "endpoint-authtoken"
17417
- }
17418
- ];
17419
- setPicker({
17420
- title: "Webhook endpoint",
17421
- description: "ngrok domain and authtoken. Toggle individual webhooks in /webhooks.",
17422
- help: "\u2191/\u2193 Select \xB7 Enter Edit \xB7 Esc Back",
17423
- indexMode: "always",
17424
- labelWidth: 18,
17425
- items: items2,
17426
- onSelect: (_value, item) => {
17427
- try {
17428
- if (item._action === "endpoint-domain") {
17429
- openChannelPrompt({
17430
- kind: "webhook-domain",
17431
- label: "ngrok domain",
17432
- hint: "Paste the reserved ngrok domain (e.g. my-app.ngrok-free.app).",
17433
- afterSave: () => void openChannelSetupPicker("webhook-endpoint", options)
17434
- });
17435
- return;
17436
- }
17437
- if (item._action === "endpoint-authtoken") {
17438
- openChannelPrompt({
17439
- kind: "webhook-token",
17440
- label: "Webhook/ngrok authtoken",
17441
- hint: "Paste the webhook/ngrok authtoken. It is stored in the OS keychain.",
17442
- afterSave: () => void openChannelSetupPicker("webhook-endpoint", options)
17443
- });
17444
- }
17445
- } catch (e) {
17446
- store.pushNotice(`webhook endpoint failed: ${e?.message || e}`, "error");
17447
- }
17448
- },
17449
- onCancel: () => {
17450
- setPicker(null);
17451
- returnTo();
17452
- }
17453
- });
17454
- return;
17455
- }
17456
- if (focus === "webhooks") {
17457
- const hooks = setup.webhooks || [];
17458
- const items2 = [
17459
- ...hooks.length ? hooks.map((hook) => {
17460
- const enabled = hook.enabled !== false;
17461
- return {
17462
- value: `webhook:${hook.name}`,
17463
- label: hook.name,
17464
- marker: enabled ? "\u25CF" : "\u25CB",
17465
- markerColor: channelRemoteEnabled ? enabled ? theme.success : theme.inactive : theme.inactive,
17466
- description: `${hook.parser || "github"} \xB7 ${hook.route} \xB7 secret:${hook.secretSet ? "set" : "missing"}${channelRemoteEnabled ? "" : " \xB7 channel off"}`,
17467
- _action: "webhook-toggle",
17468
- _name: hook.name,
17469
- _enabled: enabled
17470
- };
17471
- }) : [{
17472
- value: "empty",
17473
- label: "No webhooks",
17474
- description: "no webhook endpoints configured",
17475
- _action: "noop"
17476
- }]
17477
- ];
17478
- const toggleWebhook = async (item) => {
17479
- if (item._action !== "webhook-toggle") return;
17480
- if (!channelRemoteEnabled) {
17481
- store.pushNotice("enable channel first", "warn");
17482
- return;
17483
- }
17484
- try {
17485
- await store.setWebhookEnabled?.(item._name, !item._enabled);
17486
- void openChannelSetupPicker("webhooks", { highlightValue: `webhook:${item._name}` });
17487
- } catch (e) {
17488
- store.pushNotice(`webhook toggle failed: ${e?.message || e}`, "error");
17489
- }
17490
- };
17491
- setPicker({
17492
- title: "Webhooks",
17493
- description: channelRemoteEnabled ? "Enable or disable inbound webhook endpoints." : "Enable channel to toggle webhooks.",
17494
- initialIndex: Math.max(0, items2.findIndex((entry) => entry.value === options.highlightValue)),
17495
- items: items2,
17496
- onSelect: (_value, item) => toggleWebhook(item),
17497
- onLeft: (item) => toggleWebhook(item),
17498
- onRight: (item) => toggleWebhook(item),
17499
- onCancel: () => {
17500
- setPicker(null);
17501
- }
17502
- });
17503
- return;
17504
- }
17505
17345
  const worker = store.getChannelWorkerStatus?.();
17506
17346
  const activeBackend = setup.backend === "telegram" ? "telegram" : "discord";
17507
17347
  const backendLabel = activeBackend === "telegram" ? "Telegram" : "Discord";
@@ -17575,20 +17415,10 @@ function createChannelPickers({
17575
17415
  {
17576
17416
  value: "webhook-endpoint",
17577
17417
  label: "Webhook endpoint",
17578
- meta: (() => {
17579
- const hasDomain = Boolean(setup.webhook?.ngrokDomain || setup.webhook?.domain);
17580
- const hasAuth = setup.webhook?.authenticated === true;
17581
- return hasDomain && hasAuth ? "On" : "Off";
17582
- })(),
17583
- description: (() => {
17584
- const hasDomain = Boolean(setup.webhook?.ngrokDomain || setup.webhook?.domain);
17585
- const hasAuth = setup.webhook?.authenticated === true;
17586
- const needs = [
17587
- ...hasDomain ? [] : ["domain"],
17588
- ...hasAuth ? [] : ["authtoken"]
17589
- ];
17590
- return needs.length ? `Needs ${needs.join(" + ")}` : "ngrok domain and authtoken set";
17591
- })(),
17418
+ // Relay tunnel: public exposure is automatic, so the endpoint is
17419
+ // "On" whenever the webhook server itself is enabled.
17420
+ meta: setup.webhook?.enabled === false ? "Off" : "On",
17421
+ description: setup.webhook?.publicUrl ? setup.webhook.publicUrl : "Mixdog relay tunnel \u2014 URL assigned on first run",
17592
17422
  _action: "webhook-endpoint"
17593
17423
  }
17594
17424
  ];
@@ -19609,10 +19439,8 @@ function createSlashDispatch({
19609
19439
  void openChannelSetupPicker("all");
19610
19440
  return true;
19611
19441
  case "schedules":
19612
- void openChannelSetupPicker("schedules");
19613
- return true;
19614
19442
  case "webhooks":
19615
- void openChannelSetupPicker("webhooks");
19443
+ store.pushNotice("Schedules and webhooks are managed in the Mixdog desktop app", "info");
19616
19444
  return true;
19617
19445
  case "auth":
19618
19446
  store.pushNotice("/auth moved to /providers", "info");
@@ -20400,7 +20228,7 @@ import React16 from "react";
20400
20228
  import { Box as Box14, Text as Text16 } from "../../../vendor/ink/build/index.js";
20401
20229
  import stringWidth9 from "string-width";
20402
20230
 
20403
- // src/tui/components/tool-execution/text-format.mjs
20231
+ // src/runtime/shared/tool-card-model.mjs
20404
20232
  import stripAnsi6 from "strip-ansi";
20405
20233
 
20406
20234
  // src/runtime/shared/tool-status.mjs
@@ -20424,7 +20252,7 @@ function toolResultTerminalStatus(text) {
20424
20252
  return normalizeToolTerminalStatus(inline);
20425
20253
  }
20426
20254
 
20427
- // src/tui/components/tool-execution/text-format.mjs
20255
+ // src/runtime/shared/tool-card-model.mjs
20428
20256
  var MIN_RESULT_LINE_CHARS = 24;
20429
20257
  var RESULT_LINE_HARD_MAX = 80;
20430
20258
  var SUMMARY_MAX_CHARS = 48;
@@ -20448,47 +20276,9 @@ function normalizeCountMap(value = {}) {
20448
20276
  }
20449
20277
  return out;
20450
20278
  }
20451
- function deltaColor(token) {
20452
- return String(token || "").startsWith("+") ? theme.success : theme.error;
20453
- }
20454
- function deltaTextParts(text) {
20455
- const value = String(text ?? "");
20456
- const parts = [];
20457
- const re = /(^|[\s([,{·])([+-]\s*\d+)(?=\s+Lines?\b)/gi;
20458
- let last = 0;
20459
- let match;
20460
- while (match = re.exec(value)) {
20461
- const prefix = match[1] || "";
20462
- const token = (match[2] || "").replace(/\s+/g, "");
20463
- const tokenStart = match.index + prefix.length;
20464
- if (match.index > last) parts.push({ text: value.slice(last, match.index) });
20465
- if (prefix) parts.push({ text: prefix });
20466
- if (token) parts.push({ text: token, color: deltaColor(token) });
20467
- last = tokenStart + (match[2] || "").length;
20468
- }
20469
- if (last < value.length) parts.push({ text: value.slice(last) });
20470
- return parts;
20471
- }
20472
20279
  function plural(count, singular, pluralText = `${singular}s`) {
20473
20280
  return count === 1 ? singular : pluralText;
20474
20281
  }
20475
- function fitResultLine(line, columns) {
20476
- const max = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
20477
- const text = safeInlineText(line);
20478
- return displayWidth(text) > max ? truncateToWidth(text, max) : text;
20479
- }
20480
- function truncateToWidth(text, maxWidth) {
20481
- const str = safeInlineText(text);
20482
- if (maxWidth < 1) return "";
20483
- if (displayWidth(str) <= maxWidth) return str;
20484
- const chars = Array.from(str);
20485
- let out = "";
20486
- for (const ch of chars) {
20487
- if (displayWidth(out + ch + "\u2026") > maxWidth) break;
20488
- out += ch;
20489
- }
20490
- return `${out}\u2026`;
20491
- }
20492
20282
  function shellResultStatus(value) {
20493
20283
  const match = String(value || "").match(/(?:^|\b)status:\s*(running|pending|queued|completed|failed|cancelled|canceled)\b/im);
20494
20284
  return match ? String(match[1] || "").toLowerCase() : "";
@@ -20524,8 +20314,6 @@ function shellResultElapsed(value) {
20524
20314
  const elapsedMs = Number(match[1]);
20525
20315
  return Number.isFinite(elapsedMs) && elapsedMs >= 1e3 ? formatElapsed(elapsedMs) : "";
20526
20316
  }
20527
-
20528
- // src/tui/components/tool-execution/surface-detail.mjs
20529
20317
  function isShellTool(normalizedName, label = "") {
20530
20318
  const n = String(normalizedName || "").toLowerCase();
20531
20319
  const l = String(label || "").toLowerCase();
@@ -20857,6 +20645,248 @@ function clampFailureCount(errorCount, groupCount, isError) {
20857
20645
  if (Number.isFinite(explicit)) return Math.max(0, Math.min(groupCount, Math.floor(explicit)));
20858
20646
  return isError ? groupCount : 0;
20859
20647
  }
20648
+ function clipPlain(text, maxChars) {
20649
+ const value = safeInlineText(text);
20650
+ const max = Math.max(1, Number(maxChars) || 1);
20651
+ if (value.length <= max) return value;
20652
+ return `${value.slice(0, Math.max(0, max - 1))}\u2026`;
20653
+ }
20654
+ function deriveToolCardModel(input = {}, options = {}) {
20655
+ const {
20656
+ name = "",
20657
+ args = {},
20658
+ result = null,
20659
+ rawResult = null,
20660
+ isError = false,
20661
+ errorCount,
20662
+ callErrorCount,
20663
+ exitErrorCount,
20664
+ count = 1,
20665
+ completedCount,
20666
+ startedAt = 0,
20667
+ completedAt = 0,
20668
+ aggregate = false,
20669
+ categories = {},
20670
+ doneCategories = null,
20671
+ headerFinalized = true
20672
+ } = input;
20673
+ const nowMs = Number(input.nowMs || Date.now());
20674
+ const truncate2 = typeof options.truncate === "function" ? options.truncate : clipPlain;
20675
+ const maxResultChars = Math.max(
20676
+ MIN_RESULT_LINE_CHARS,
20677
+ Math.min(RESULT_LINE_HARD_MAX, Number(options.maxResultChars ?? RESULT_LINE_HARD_MAX))
20678
+ );
20679
+ const groupCount = Math.max(1, Number(count || 1));
20680
+ const doneCount = Math.max(0, Math.min(groupCount, Number(completedCount ?? (result == null ? 0 : groupCount))));
20681
+ const rt = result == null ? null : String(result).replace(/\s+$/, "");
20682
+ const rawRt = rawResult == null ? null : String(rawResult).replace(/\s+$/, "");
20683
+ const pending = doneCount < groupCount;
20684
+ const headerPending = pending || headerFinalized === false;
20685
+ const hasResult = result != null && Boolean(String(rt || "").trim());
20686
+ const hasRawResult = rawResult != null && Boolean(String(rawRt || "").trim());
20687
+ const startedAtMs = Number(startedAt || 0);
20688
+ const completedAtMs = Number(completedAt || 0);
20689
+ const elapsedMs = startedAtMs ? Math.max(0, (pending ? nowMs : completedAtMs || nowMs) - startedAtMs) : 0;
20690
+ const elapsed = elapsedMs >= 1e3 ? formatElapsed(elapsedMs) : "";
20691
+ const failedCount = clampFailureCount(errorCount, groupCount, isError);
20692
+ const callFailedCount = clampFailureCount(callErrorCount, groupCount, false);
20693
+ const exitFailedCount = clampFailureCount(exitErrorCount, groupCount, false);
20694
+ if (aggregate) {
20695
+ const displayCategories = normalizeCountMap(categories || {});
20696
+ const normalizedDone = doneCategories ? normalizeCountMap(doneCategories) : displayCategories;
20697
+ const hasDoneCounts = Object.values(normalizedDone || {}).some(
20698
+ (v) => (v && typeof v === "object" ? Number(v.count || 0) : Number(v || 0)) > 0
20699
+ );
20700
+ const displayDone = hasDoneCounts ? normalizedDone : displayCategories;
20701
+ const headerOrder = Array.isArray(args?.categoryOrder) ? args.categoryOrder : null;
20702
+ const labelText2 = safeInlineText(formatAggregateHeader(
20703
+ (headerPending ? displayCategories : displayDone) || {},
20704
+ { pending: headerPending, order: headerOrder }
20705
+ ));
20706
+ const detailText = hasResult ? safeInlineText(rt) : "";
20707
+ const terminalStatus2 = pending ? "running" : resultTerminalStatus(rt) || (isError || failedCount > 0 ? "failed" : "completed");
20708
+ return {
20709
+ aggregate: true,
20710
+ pending,
20711
+ headerPending,
20712
+ groupCount,
20713
+ doneCount,
20714
+ elapsed,
20715
+ failedCount,
20716
+ callFailedCount,
20717
+ exitFailedCount,
20718
+ terminalStatus: terminalStatus2,
20719
+ labelText: labelText2,
20720
+ summaryText: "",
20721
+ headerFailureText: "",
20722
+ detailLine: detailText || (pending ? "Running" : "Finished"),
20723
+ detailIsPlaceholder: !detailText,
20724
+ hasResult,
20725
+ hasRawResult,
20726
+ displayedResultBodyText: rt || "",
20727
+ firstResultLine: detailText,
20728
+ totalLines: detailText ? 1 : 0,
20729
+ resultSummary: detailText || null,
20730
+ toolArgPath: "",
20731
+ normalizedName: "",
20732
+ label: "",
20733
+ parsedArgs: args,
20734
+ isShellSurface: false,
20735
+ isSkillSurface: false,
20736
+ isAgentSurfaceCard: false,
20737
+ isAgentResponse: false,
20738
+ isBackgroundResponse: false,
20739
+ isBackgroundMetadataResult: false,
20740
+ backgroundMeta: null
20741
+ };
20742
+ }
20743
+ const { label, summary, normalizedName, args: parsedArgs } = formatToolSurface(name, args);
20744
+ const isShellSurface = isShellTool(normalizedName, label);
20745
+ const isSkillSurface = SKILL_SURFACE_NAMES2.has(String(normalizedName || "").toLowerCase());
20746
+ const backgroundMeta = !pending && isBackgroundTaskTool(normalizedName) ? resolveBackgroundTaskMeta(parsedArgs, rt || "") : null;
20747
+ const backgroundError = backgroundMeta?.error || parsedArgs?.error || "";
20748
+ const errorOnlyResult = Boolean(rt) && isBackgroundErrorOnlyBody(rt, backgroundError);
20749
+ const backgroundResultText = backgroundMeta?.hasResponse ? backgroundMeta.body : "";
20750
+ const displayedResultText = backgroundResultText || (errorOnlyResult ? "" : rt || "");
20751
+ const hasDisplayResult = Boolean(String(displayedResultText || "").trim());
20752
+ const displayedResultBodyText = stripLeadingStatusMarkerFromText(displayedResultText);
20753
+ const hasDisplayBody = Boolean(String(displayedResultBodyText || "").trim());
20754
+ const lines = displayedResultBodyText ? displayedResultBodyText.split("\n") : [];
20755
+ const totalLines = lines.length;
20756
+ const resultSummary = !pending && hasDisplayBody ? summarizeToolResult(name, args, displayedResultBodyText, isError) : null;
20757
+ const firstResultLine = hasDisplayResult ? String(lines[0] ?? "") : "";
20758
+ const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, exitFailedCount, isError, result: displayedResultText }) : "";
20759
+ const shellElapsed = isShellSurface ? shellResultElapsed(displayedResultText) || elapsed : "";
20760
+ const backgroundElapsed = backgroundMeta ? backgroundTaskElapsed(backgroundMeta, elapsed) : isBackgroundTaskTool(normalizedName) ? backgroundTaskElapsed(parsedArgs, elapsed) : "";
20761
+ const toolArgPath = parsedArgs?.path ?? parsedArgs?.file_path ?? parsedArgs?.file ?? "";
20762
+ const imageDetail = normalizedName === "view_image" && toolArgPath && !isError ? String(toolArgPath) : "";
20763
+ const isBackgroundResult = !pending && isBackgroundTaskTool(normalizedName) && Boolean(backgroundMeta);
20764
+ const isBackgroundResponse = isBackgroundResult && (backgroundMeta?.hasResponse || isBackgroundTaskResponseArgs(normalizedName, parsedArgs));
20765
+ const isBackgroundMetadataResult = isBackgroundResult && !isBackgroundResponse && Boolean(backgroundMeta);
20766
+ const backgroundMetadataFailureLabel = isBackgroundMetadataResult ? backgroundTaskFailureDetail(backgroundMeta, parsedArgs) : "";
20767
+ const backgroundMetadataHeaderFailure = Boolean(backgroundMetadataFailureLabel) && !hasDisplayResult ? backgroundMetadataFailureLabel : "";
20768
+ const agentHeaderFailure = !pending && isAgentTool(normalizedName) && isError && parsedArgs?.error && !hasDisplayResult ? backgroundTaskFailureStatusLabel(parsedArgs?.status, parsedArgs?.error, { surface: "agent" }) : "";
20769
+ const headerFailureText = backgroundMetadataHeaderFailure || agentHeaderFailure || "";
20770
+ const agentCompletionDetail = !pending && isAgentTool(normalizedName) && !agentHeaderFailure ? agentTerminalDetail(parsedArgs?.status, isError, elapsed, parsedArgs?.error) : "";
20771
+ const agentDetail = !pending && isAgentTool(normalizedName) && !hasDisplayResult ? agentCompletionDetail : "";
20772
+ const genericDetail = !pending && !isShellSurface && !agentDetail && !imageDetail && !resultSummary ? genericCompletedDetail({ normalizedName, label, hasResult, firstResultLine, isError }) : "";
20773
+ const terminalStatus = pending ? "running" : shellStatus || normalizeTerminalStatus(backgroundMeta?.status) || normalizeTerminalStatus(parsedArgs?.status) || resultTerminalStatus(displayedResultText) || (isError || failedCount > 0 ? "failed" : "completed");
20774
+ const backgroundMetadataDetail = isBackgroundMetadataResult && !backgroundMetadataHeaderFailure ? backgroundTaskDetail(backgroundMeta, backgroundElapsed, parsedArgs) : "";
20775
+ const backgroundResponseDetail = isBackgroundResponse && resultSummary ? prefixElapsed(resultSummary, backgroundElapsed) : resultSummary;
20776
+ const syncElapsedDetail = !isBackgroundResponse && shouldPrefixSyncElapsed(normalizedName, label) ? prefixElapsed(backgroundResponseDetail, elapsed) : backgroundResponseDetail;
20777
+ const nonShellDetail = backgroundMetadataDetail || (/^(Cancelled|Failed|Finished)$/i.test(resultSummary || "") && agentCompletionDetail ? agentCompletionDetail : syncElapsedDetail) || agentDetail || imageDetail || genericDetail;
20778
+ const pendingDetailPlaceholder = pending && !isSkillSurface ? elapsed ? `Running \xB7 ${elapsed}` : "Running" : "";
20779
+ const shellCollapsedSummary = isShellSurface && !pending && hasDisplayResult ? resultSummary || truncate2(firstResultLine, Math.min(120, maxResultChars)) : resultSummary;
20780
+ const collapsedDetail = pending ? pendingDetailPlaceholder : isShellSurface ? prefixElapsed(mergeTerminalDetail(shellStatus, shellCollapsedSummary), shellElapsed) : mergeTerminalDetail(terminalStatus, nonShellDetail);
20781
+ const isAgentResult = !isBackgroundResult && !pending && isAgentTool(normalizedName) && hasDisplayResult;
20782
+ const isAgentResponse = isAgentResult && hasAgentResponseResult(rt);
20783
+ const isAgentSurfaceCard = isAgentTool(normalizedName);
20784
+ const agentSurfaceBriefRaw = isAgentSurfaceCard ? summarizeAgentSurfaceBrief(name, parsedArgs, displayedResultText || "", { isError, isResponse: isAgentResponse }) : "";
20785
+ const agentSurfaceBrief = agentSurfaceBriefRaw ? truncate2(agentSurfaceBriefRaw, Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars)) : "";
20786
+ let detailLine = collapsedDetail;
20787
+ if (isSkillSurface) {
20788
+ detailLine = isError && collapsedDetail ? collapsedDetail : "";
20789
+ } else if (isBackgroundMetadataResult && backgroundMetadataHeaderFailure) {
20790
+ detailLine = "";
20791
+ } else if (isAgentSurfaceCard) {
20792
+ const agentDetailFallback = collapsedDetail || (pending ? pendingDetailPlaceholder || "Running" : "Finished");
20793
+ const agentDetailLine = agentSurfaceBrief || truncate2(String(agentDetailFallback), Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars));
20794
+ const agentFailureText = /\b(Cancelled|Canceled|Failed)\b/i.test(agentSurfaceBrief || collapsedDetail || "");
20795
+ const keepAgentDetail = (isError || agentFailureText) && !(agentHeaderFailure && !agentSurfaceBrief);
20796
+ detailLine = keepAgentDetail ? agentDetailLine : "";
20797
+ }
20798
+ let labelText;
20799
+ if (isAgentResponse) labelText = agentResponseTitle(parsedArgs, groupCount);
20800
+ else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);
20801
+ else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
20802
+ else if (isShellSurface) labelText = shellHeader(shellStatus, groupCount);
20803
+ else labelText = (isAgentTool(normalizedName) ? agentActionTitle(parsedArgs) : "") || formatToolActionHeader(name, args, { pending: headerPending, count: groupCount });
20804
+ labelText = safeInlineText(labelText);
20805
+ const toolSearchSummary = !pending && normalizedName === "load_tool" && hasResult ? toolSearchLoadedSummary(displayedResultText) : "";
20806
+ const rawSummaryText = safeInlineText(isAgentResponse || isBackgroundResponse ? "" : toolSearchSummary || (isAgentTool(normalizedName) ? agentActionSummary(parsedArgs, summary) : summary));
20807
+ const summaryIsHeaderCount = rawSummaryText && /^\d+\s+\S+$/.test(rawSummaryText) && labelText.endsWith(rawSummaryText);
20808
+ const summaryText = summaryIsHeaderCount ? "" : rawSummaryText;
20809
+ return {
20810
+ aggregate: false,
20811
+ pending,
20812
+ headerPending,
20813
+ groupCount,
20814
+ doneCount,
20815
+ elapsed,
20816
+ failedCount,
20817
+ callFailedCount,
20818
+ exitFailedCount,
20819
+ terminalStatus,
20820
+ labelText,
20821
+ summaryText,
20822
+ headerFailureText,
20823
+ detailLine,
20824
+ detailIsPlaceholder: Boolean(pendingDetailPlaceholder) && detailLine === pendingDetailPlaceholder,
20825
+ hasResult,
20826
+ hasRawResult,
20827
+ hasDisplayResult,
20828
+ hasDisplayBody,
20829
+ displayedResultBodyText,
20830
+ firstResultLine,
20831
+ totalLines,
20832
+ resultSummary,
20833
+ shellCollapsedSummary,
20834
+ agentSurfaceBrief,
20835
+ toolArgPath: String(toolArgPath || ""),
20836
+ normalizedName,
20837
+ label,
20838
+ parsedArgs,
20839
+ isShellSurface,
20840
+ isSkillSurface,
20841
+ isAgentSurfaceCard,
20842
+ isAgentResponse,
20843
+ isBackgroundResponse,
20844
+ isBackgroundMetadataResult,
20845
+ backgroundMeta
20846
+ };
20847
+ }
20848
+
20849
+ // src/tui/components/tool-execution/text-format.mjs
20850
+ function deltaColor(token) {
20851
+ return String(token || "").startsWith("+") ? theme.success : theme.error;
20852
+ }
20853
+ function deltaTextParts(text) {
20854
+ const value = String(text ?? "");
20855
+ const parts = [];
20856
+ const re = /(^|[\s([,{·])([+-]\s*\d+)(?=\s+Lines?\b)/gi;
20857
+ let last = 0;
20858
+ let match;
20859
+ while (match = re.exec(value)) {
20860
+ const prefix = match[1] || "";
20861
+ const token = (match[2] || "").replace(/\s+/g, "");
20862
+ const tokenStart = match.index + prefix.length;
20863
+ if (match.index > last) parts.push({ text: value.slice(last, match.index) });
20864
+ if (prefix) parts.push({ text: prefix });
20865
+ if (token) parts.push({ text: token, color: deltaColor(token) });
20866
+ last = tokenStart + (match[2] || "").length;
20867
+ }
20868
+ if (last < value.length) parts.push({ text: value.slice(last) });
20869
+ return parts;
20870
+ }
20871
+ function fitResultLine(line, columns) {
20872
+ const max = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
20873
+ const text = safeInlineText(line);
20874
+ return displayWidth(text) > max ? truncateToWidth(text, max) : text;
20875
+ }
20876
+ function truncateToWidth(text, maxWidth) {
20877
+ const str = safeInlineText(text);
20878
+ if (maxWidth < 1) return "";
20879
+ if (displayWidth(str) <= maxWidth) return str;
20880
+ const chars = Array.from(str);
20881
+ let out = "";
20882
+ for (const ch of chars) {
20883
+ if (displayWidth(out + ch + "\u2026") > maxWidth) break;
20884
+ out += ch;
20885
+ }
20886
+ return `${out}\u2026`;
20887
+ }
20888
+
20889
+ // src/tui/components/tool-execution/surface-detail.mjs
20860
20890
  function toolStatusColor({ pending, groupCount, callFailedCount = 0, exitFailedCount = 0, terminalStatus = "" }) {
20861
20891
  if (pending) return theme.text;
20862
20892
  const status = normalizeTerminalStatus(terminalStatus);
@@ -20895,9 +20925,6 @@ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
20895
20925
  var TOOL_BLINK_MS = 500;
20896
20926
  var TOOL_PENDING_SHOW_DELAY_MS = 1e3;
20897
20927
  var TOOL_ANIM_TICK_MS = TOOL_BLINK_MS;
20898
- function statusCopy(name, label, count, doneCount, pending, isError, args = {}) {
20899
- return formatToolActionHeader(name, args, { pending, count });
20900
- }
20901
20928
  function ToolExecution({ name, args, result, rawResult, isError, errorCount, callErrorCount, exitErrorCount, expanded, columns = 80, attached = false, count = 1, completedCount = 0, startedAt = 0, completedAt = 0, aggregate = false, categories = {}, doneCategories = null, headerFinalized = true, deferredDisplayReady = false, agentResponseAggregate = false }) {
20902
20929
  const rowWidth = Math.max(1, Number(columns || 80));
20903
20930
  const groupCount = Math.max(1, Number(count || 1));
@@ -20984,87 +21011,60 @@ function ToolExecution({ name, args, result, rawResult, isError, errorCount, cal
20984
21011
  )
20985
21012
  ] });
20986
21013
  }
20987
- const { label, summary, normalizedName, args: parsedArgs } = formatToolSurface(name, args);
20988
- const isShellSurface = isShellTool(normalizedName, label);
20989
- const isSkillSurface = SKILL_SURFACE_NAMES2.has(String(normalizedName || "").toLowerCase());
20990
- const backgroundMeta = !pending && isBackgroundTaskTool(normalizedName) ? resolveBackgroundTaskMeta(parsedArgs, rt || "") : null;
20991
- const backgroundError = backgroundMeta?.error || parsedArgs?.error || "";
20992
- const errorOnlyResult = Boolean(rt) && isBackgroundErrorOnlyBody(rt, backgroundError);
20993
- const backgroundResultText = backgroundMeta?.hasResponse ? backgroundMeta.body : "";
20994
- const displayedResultText = backgroundResultText || (errorOnlyResult ? "" : rt || "");
20995
- const hasDisplayResult = Boolean(String(displayedResultText || "").trim());
20996
- const displayedResultBodyText = stripLeadingStatusMarkerFromText(displayedResultText);
20997
- const hasDisplayBody = Boolean(String(displayedResultBodyText || "").trim());
20998
- const lines = displayedResultBodyText ? displayedResultBodyText.split("\n") : [];
20999
- const totalLines = lines.length;
21000
- const resultSummary = !pending && hasDisplayBody ? summarizeToolResult(name, args, displayedResultBodyText, isError) : null;
21001
21014
  const maxResultChars = Math.min(RESULT_LINE_HARD_MAX, Math.max(MIN_RESULT_LINE_CHARS, Number(columns || 80) - 7));
21015
+ const model = deriveToolCardModel({
21016
+ name,
21017
+ args,
21018
+ result,
21019
+ rawResult,
21020
+ isError,
21021
+ errorCount,
21022
+ callErrorCount,
21023
+ exitErrorCount,
21024
+ count: displayGroupCount,
21025
+ completedCount: doneCount,
21026
+ startedAt,
21027
+ completedAt,
21028
+ headerFinalized,
21029
+ nowMs
21030
+ }, { truncate: truncateToWidth, maxResultChars });
21031
+ const {
21032
+ labelText,
21033
+ summaryText,
21034
+ headerFailureText: headerFailureStatus,
21035
+ detailLine: collapsedDetailLine,
21036
+ detailIsPlaceholder,
21037
+ terminalStatus,
21038
+ normalizedName,
21039
+ isShellSurface,
21040
+ isAgentSurfaceCard,
21041
+ isAgentResponse,
21042
+ isBackgroundMetadataResult,
21043
+ hasDisplayResult,
21044
+ hasDisplayBody,
21045
+ displayedResultBodyText,
21046
+ firstResultLine,
21047
+ totalLines,
21048
+ resultSummary,
21049
+ shellCollapsedSummary,
21050
+ toolArgPath
21051
+ } = model;
21052
+ const lines = displayedResultBodyText ? displayedResultBodyText.split("\n") : [];
21002
21053
  const resultColor = theme.text;
21003
- const firstResultLine = hasDisplayResult ? String(lines[0] ?? "") : "";
21004
21054
  const firstResultLineClipped = hasDisplayBody && stringWidth9(firstResultLine) > maxResultChars;
21005
21055
  const hasHiddenDetail = !pending && hasDisplayBody && (totalLines > 1 || firstResultLineClipped || Boolean(resultSummary));
21006
- const shellStatus = isShellSurface ? shellDisplayStatus({ pending, failedCount, exitFailedCount, isError, result: displayedResultText }) : "";
21007
- const shellElapsed = isShellSurface ? shellResultElapsed(displayedResultText) || elapsed : "";
21008
- const backgroundElapsed = backgroundMeta ? backgroundTaskElapsed(backgroundMeta, elapsed) : isBackgroundTaskTool(normalizedName) ? backgroundTaskElapsed(parsedArgs, elapsed) : "";
21009
- const toolArgPath = parsedArgs?.path ?? parsedArgs?.file_path ?? parsedArgs?.file ?? "";
21010
- const imageDetail = normalizedName === "view_image" && toolArgPath && !isError ? String(toolArgPath) : "";
21011
- const isBackgroundResult = !pending && isBackgroundTaskTool(normalizedName) && Boolean(backgroundMeta);
21012
- const isBackgroundResponse = isBackgroundResult && (backgroundMeta?.hasResponse || isBackgroundTaskResponseArgs(normalizedName, parsedArgs));
21013
- const isBackgroundMetadataResult = isBackgroundResult && !isBackgroundResponse && Boolean(backgroundMeta);
21014
- const backgroundMetadataFailureLabel = isBackgroundMetadataResult ? backgroundTaskFailureDetail(backgroundMeta, parsedArgs) : "";
21015
- const backgroundMetadataHeaderFailure = Boolean(backgroundMetadataFailureLabel) && !hasDisplayResult ? backgroundMetadataFailureLabel : "";
21016
- const agentHeaderFailure = !pending && isAgentTool(normalizedName) && isError && parsedArgs?.error && !hasDisplayResult ? backgroundTaskFailureStatusLabel(parsedArgs?.status, parsedArgs?.error, { surface: "agent" }) : "";
21017
- const headerFailureStatus = backgroundMetadataHeaderFailure || agentHeaderFailure || "";
21018
- const agentCompletionDetail = !pending && isAgentTool(normalizedName) && !agentHeaderFailure ? agentTerminalDetail(parsedArgs?.status, isError, elapsed, parsedArgs?.error) : "";
21019
- const agentDetail = !pending && isAgentTool(normalizedName) && !hasDisplayResult ? agentCompletionDetail : "";
21020
- const genericDetail = !pending && !isShellSurface && !agentDetail && !imageDetail && !resultSummary ? genericCompletedDetail({ normalizedName, label, hasResult, firstResultLine, isError }) : "";
21021
- const terminalStatus = pending ? "running" : shellStatus || normalizeTerminalStatus(backgroundMeta?.status) || normalizeTerminalStatus(parsedArgs?.status) || resultTerminalStatus(displayedResultText) || (isError || failedCount > 0 ? "failed" : "completed");
21022
- const backgroundMetadataDetail = isBackgroundMetadataResult && !backgroundMetadataHeaderFailure ? backgroundTaskDetail(backgroundMeta, backgroundElapsed, parsedArgs) : "";
21023
- const backgroundResponseDetail = isBackgroundResponse && resultSummary ? prefixElapsed(resultSummary, backgroundElapsed) : resultSummary;
21024
- const syncElapsedDetail = !isBackgroundResponse && shouldPrefixSyncElapsed(normalizedName, label) ? prefixElapsed(backgroundResponseDetail, elapsed) : backgroundResponseDetail;
21025
- const nonShellDetail = backgroundMetadataDetail || (/^(Cancelled|Failed|Finished)$/i.test(resultSummary || "") && agentCompletionDetail ? agentCompletionDetail : syncElapsedDetail) || agentDetail || imageDetail || genericDetail;
21026
- const pendingDetailPlaceholder = pending && !isSkillSurface ? elapsed ? `Running \xB7 ${elapsed}` : "Running" : "";
21027
- const shellCollapsedSummary = isShellSurface && !pending && hasDisplayResult ? resultSummary || truncateToWidth(firstResultLine, Math.min(120, maxResultChars)) : resultSummary;
21028
- const collapsedDetail = pending ? pendingDetailPlaceholder : isShellSurface ? prefixElapsed(mergeTerminalDetail(shellStatus, shellCollapsedSummary), shellElapsed) : mergeTerminalDetail(terminalStatus, nonShellDetail);
21029
21056
  const backgroundMetadataExpandable = isBackgroundMetadataResult && hasRawResult && !pending;
21030
21057
  const showRawResult = expanded && (hasDisplayBody || hasRawResult) && (!isBackgroundMetadataResult || hasRawResult);
21031
- const detailLines = showRawResult ? agentResponseAggregate && hasRawResult ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : hasDisplayBody ? lines : rawRt ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : [] : collapsedDetail ? [collapsedDetail] : [];
21032
- const isPendingPlaceholderDetail = !showRawResult && Boolean(pendingDetailPlaceholder);
21058
+ const detailLines = showRawResult ? agentResponseAggregate && hasRawResult ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : hasDisplayBody ? lines : rawRt ? stripLeadingStatusMarkerLines(rawRt.split("\n")) : [] : collapsedDetailLine ? [collapsedDetailLine] : [];
21059
+ const isPendingPlaceholderDetail = !showRawResult && detailIsPlaceholder;
21033
21060
  const detailColor = isPendingPlaceholderDetail ? theme.subtle : theme.text;
21034
- const isAgentResult = !isBackgroundResult && !pending && isAgentTool(normalizedName) && hasDisplayResult;
21035
- const isAgentResponse = isAgentResult && hasAgentResponseResult(rt);
21036
- const isAgentSurfaceCard = isAgentTool(normalizedName);
21037
- const agentSurfaceBriefRaw = isAgentSurfaceCard && !showRawResult ? summarizeAgentSurfaceBrief(name, parsedArgs, displayedResultText || "", { isError, isResponse: isAgentResponse }) : "";
21038
- const agentSurfaceBrief = agentSurfaceBriefRaw ? truncateToWidth(agentSurfaceBriefRaw, Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars)) : "";
21039
- let visibleDetailLines = detailLines;
21040
- if (isSkillSurface && !showRawResult) {
21041
- visibleDetailLines = isError && collapsedDetail ? [collapsedDetail] : [];
21042
- } else if (isBackgroundMetadataResult && backgroundMetadataHeaderFailure && !showRawResult) {
21043
- visibleDetailLines = [];
21044
- } else if (isAgentSurfaceCard && !showRawResult) {
21045
- const agentDetailFallback = collapsedDetail || (pending ? pendingDetailPlaceholder || "Running" : "Finished");
21046
- const agentDetailLine = agentSurfaceBrief || truncateToWidth(String(agentDetailFallback), Math.min(AGENT_SURFACE_BRIEF_MAX, maxResultChars));
21047
- const agentFailureText = /\b(Cancelled|Canceled|Failed)\b/i.test(agentSurfaceBrief || collapsedDetail || "");
21048
- const keepAgentDetail = (isError || agentFailureText) && !(agentHeaderFailure && !agentSurfaceBrief);
21049
- visibleDetailLines = keepAgentDetail ? [agentDetailLine] : [];
21050
- }
21061
+ const visibleDetailLines = detailLines;
21051
21062
  const finalStatusColor = toolStatusColor({ pending, groupCount, callFailedCount, exitFailedCount, terminalStatus });
21052
21063
  const dotColor = finalStatusColor;
21053
21064
  const markerGlyph = isAgentResponse ? AGENT_RESPONSE_MARKER : isAgentSurfaceCard ? AGENT_CALL_MARKER : TURN_MARKER;
21054
21065
  const isDirectionalMarker = isAgentResponse || isAgentSurfaceCard;
21055
21066
  const markerText = isDirectionalMarker ? `${markerGlyph} ` : markerGlyph;
21056
21067
  const dotText = pending && !blinkOn ? " " : markerText;
21057
- let labelText;
21058
- if (isAgentResponse) labelText = agentResponseTitle(parsedArgs, displayGroupCount);
21059
- else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);
21060
- else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
21061
- else if (isShellSurface) labelText = shellHeader(shellStatus, displayGroupCount);
21062
- else labelText = (isAgentTool(normalizedName) ? agentActionTitle(parsedArgs) : "") || statusCopy(name, label, displayGroupCount, doneCount, headerPending, isError, parsedArgs);
21063
- labelText = safeInlineText(labelText);
21064
- const toolSearchSummary = !pending && normalizedName === "load_tool" && hasResult ? toolSearchLoadedSummary(displayedResultText) : "";
21065
- const rawSummaryText = safeInlineText(isAgentResponse || isBackgroundResponse ? "" : toolSearchSummary || (isAgentTool(normalizedName) ? agentActionSummary(parsedArgs, summary) : summary));
21066
- const summaryIsHeaderCount = rawSummaryText && /^\d+\s+\S+$/.test(rawSummaryText) && labelText.endsWith(rawSummaryText);
21067
- const summaryText = summaryIsHeaderCount ? "" : rawSummaryText;
21068
21068
  const agentHasExpandableBody = isAgentSurfaceCard && !pending && hasResult && (isAgentResponse || totalLines > 1 || firstResultLineClipped);
21069
21069
  const shellHasExpandableBody = isShellSurface && !pending && hasDisplayResult && hasDisplayBody && (totalLines > 1 || firstResultLineClipped || Boolean(shellCollapsedSummary && shellCollapsedSummary !== firstResultLine));
21070
21070
  const showHeaderExpandHint = (isShellSurface ? shellHasExpandableBody : isAgentSurfaceCard ? agentHasExpandableBody : hasHiddenDetail || backgroundMetadataExpandable) && normalizedName !== "load_tool";
@@ -22681,19 +22681,6 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
22681
22681
  resumeAfterChannelPrompt(channelPrompt);
22682
22682
  return true;
22683
22683
  }
22684
- if (channelPrompt.kind === "webhook-token") {
22685
- if (!commandText) return false;
22686
- store.saveWebhookAuthtoken(commandText);
22687
- resumeAfterChannelPrompt(channelPrompt);
22688
- return true;
22689
- }
22690
- if (channelPrompt.kind === "webhook-domain") {
22691
- if (!commandText) return false;
22692
- Promise.resolve(store.setWebhookConfig?.({ ngrokDomain: commandText })).then(() => resumeAfterChannelPrompt(channelPrompt)).catch((e) => {
22693
- store.pushNotice(`webhook config update failed: ${e?.message || e}`, "error");
22694
- });
22695
- return true;
22696
- }
22697
22684
  const parts = commandText.split("|").map((part) => part.trim());
22698
22685
  if (channelPrompt.kind === "channel-add") {
22699
22686
  const isPipe = parts.length > 1;
@@ -22706,29 +22693,6 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
22706
22693
  });
22707
22694
  return true;
22708
22695
  }
22709
- if (channelPrompt.kind === "schedule-add") {
22710
- const [name, time, instructions, channel, model] = parts;
22711
- Promise.resolve(store.saveSchedule({ name, time, instructions, channel, model })).then(() => {
22712
- setChannelPrompt(null);
22713
- void openChannelSetupPicker("schedules");
22714
- }).catch((e) => {
22715
- store.pushNotice(`schedule save failed: ${e?.message || e}`, "error");
22716
- });
22717
- return true;
22718
- }
22719
- if (channelPrompt.kind === "webhook-add") {
22720
- const [name, instructions, channel, model, parser] = parts;
22721
- Promise.resolve(store.saveWebhook({ name, instructions, channel, model, parser })).then((result) => {
22722
- if (result?.secret) {
22723
- store.pushNotice(`webhook secret for ${result.name}: ${result.secret}`, "info");
22724
- }
22725
- setChannelPrompt(null);
22726
- void openChannelSetupPicker("webhooks");
22727
- }).catch((e) => {
22728
- store.pushNotice(`webhook save failed: ${e?.message || e}`, "error");
22729
- });
22730
- return true;
22731
- }
22732
22696
  } catch (e) {
22733
22697
  store.pushNotice(`channels update failed: ${e?.message || e}`, "error");
22734
22698
  return false;
@@ -23976,6 +23940,12 @@ var SYNTHETIC_SESSION_TEXT_PATTERNS = Object.freeze([
23976
23940
  /^\[mixdog-runtime\]/i,
23977
23941
  /^\[(?:truncated|request interrupted by user)\]$/i,
23978
23942
  /^a previous model worked on this task and produced the compacted handoff summary below\b/i,
23943
+ // Compact/auto-clear re-seed handoff variants: these lead the FIRST user
23944
+ // message of every post-compaction session, so titling from them painted
23945
+ // "Re-attached after compaction…" rows in Recent (user report). Skipping
23946
+ // them titles the session from its first REAL user message instead.
23947
+ /^re-attached after compaction\b/i,
23948
+ /^reference files:\s/i,
23979
23949
  /^the async (?:agent|shell) task\b/i
23980
23950
  ]);
23981
23951
  var LATE_TOOL_ANNOUNCEMENT_SENTINEL = "connected after this session started";
@@ -28148,15 +28118,30 @@ function createEngineApiB(bag) {
28148
28118
  set({ commandBusy: false });
28149
28119
  }
28150
28120
  },
28151
- // Toggle Discord remote mode for this session. Flips the runtime's
28152
- // remoteEnabled flag (booting/stopping the channel worker) and returns the
28153
- // NEW enabled getState() so the caller can render an ON/OFF notice.
28121
+ // Toggle channel remote mode for the CURRENT session (session-scoped,
28122
+ // user decision). Off on claims for this session; on + this session
28123
+ // owns off; on + ANOTHER session owns move the relay seat here
28124
+ // (last-wins) without restarting the worker.
28154
28125
  toggleRemote: () => {
28155
28126
  const enabled = runtime.isRemoteEnabled?.() === true;
28156
- if (enabled) runtime.stopRemote?.();
28127
+ const owner = String(runtime.getRemoteSessionId?.() || "");
28128
+ const currentId = String(getState().sessionId || "");
28129
+ const workerRunning = runtime.getChannelWorkerStatus?.()?.running === true;
28130
+ if (enabled && !workerRunning) {
28131
+ runtime.startRemote?.();
28132
+ const next2 = runtime.isRemoteEnabled?.() === true;
28133
+ set({ remoteEnabled: next2, remoteSessionId: runtime.getRemoteSessionId?.() || null });
28134
+ return next2;
28135
+ }
28136
+ if (enabled && owner && currentId && owner !== currentId) {
28137
+ runtime.claimRemoteForCurrentSession?.();
28138
+ set({ remoteEnabled: true, remoteSessionId: runtime.getRemoteSessionId?.() || null });
28139
+ return true;
28140
+ }
28141
+ if (enabled) runtime.releaseRemote?.();
28157
28142
  else runtime.startRemote?.();
28158
28143
  const next = runtime.isRemoteEnabled?.() === true;
28159
- set({ remoteEnabled: next });
28144
+ set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
28160
28145
  return next;
28161
28146
  },
28162
28147
  // Force-claim remote for this session (single-holder, last-wins). Always
@@ -28166,9 +28151,16 @@ function createEngineApiB(bag) {
28166
28151
  claimRemote: () => {
28167
28152
  runtime.startRemote?.();
28168
28153
  const next = runtime.isRemoteEnabled?.() === true;
28169
- set({ remoteEnabled: next });
28154
+ set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
28170
28155
  return next;
28171
28156
  },
28157
+ // Idempotent desired-state OFF for desktop controls. Unlike toggleRemote,
28158
+ // a delayed/replayed request can never turn remote back on.
28159
+ releaseRemote: () => {
28160
+ runtime.releaseRemote?.();
28161
+ set({ remoteEnabled: false, remoteSessionId: null });
28162
+ return false;
28163
+ },
28172
28164
  isRemoteEnabled: () => runtime.isRemoteEnabled?.() === true,
28173
28165
  getVoiceStatus: () => getVoiceStatus(),
28174
28166
  // Desktop push-to-talk dictation: accept a recorded audio payload
@@ -28375,16 +28367,6 @@ function createEngineApiB(bag) {
28375
28367
  pushNotice("telegram token forgotten", "info");
28376
28368
  return result;
28377
28369
  },
28378
- saveWebhookAuthtoken: (token) => {
28379
- const result = runtime.saveWebhookAuthtoken(token);
28380
- pushNotice("webhook/ngrok authtoken saved", "info");
28381
- return result;
28382
- },
28383
- forgetWebhookAuthtoken: () => {
28384
- const result = runtime.forgetWebhookAuthtoken();
28385
- pushNotice("webhook/ngrok authtoken forgotten", "info");
28386
- return result;
28387
- },
28388
28370
  setChannel: async (entry) => {
28389
28371
  const result = await runtime.setChannel(entry);
28390
28372
  pushNotice("channel saved", "info");
@@ -28801,7 +28783,6 @@ async function buildDoctorReport(runtime = {}, getState = () => ({})) {
28801
28783
  const tokens = [];
28802
28784
  if (setup.discord?.authenticated) tokens.push("discord");
28803
28785
  if (setup.telegram?.authenticated) tokens.push("telegram");
28804
- if (setup.webhook?.authenticated) tokens.push("webhook");
28805
28786
  const running = worker.running === true;
28806
28787
  const detail = `enabled \xB7 worker ${running ? "running" : "stopped"} \xB7 tokens: ${tokens.length ? tokens.join(", ") : "none"}`;
28807
28788
  row(running ? "ok" : "warn", detail);
@@ -29929,6 +29910,7 @@ function createLiveShare({
29929
29910
  const stopClient = () => {
29930
29911
  const closing = client;
29931
29912
  const closingId = clientId;
29913
+ const wasUp = clientUp;
29932
29914
  client = null;
29933
29915
  clientId = "";
29934
29916
  clientUp = false;
@@ -29940,6 +29922,7 @@ function createLiveShare({
29940
29922
  } catch {
29941
29923
  }
29942
29924
  }
29925
+ if (wasUp) clearMirroredLiveState();
29943
29926
  };
29944
29927
  const startClient = (id) => {
29945
29928
  let socket;
@@ -31042,12 +31025,23 @@ async function createEngineSession({
31042
31025
  if (typeof runtime.onRemoteStateChange === "function") {
31043
31026
  lifecycle.unsubscribeRemoteState = runtime.onRemoteStateChange(({ enabled, reason }) => {
31044
31027
  if (flags.disposed) return;
31045
- set({ remoteEnabled: enabled === true });
31028
+ set({
31029
+ remoteEnabled: enabled === true,
31030
+ remoteSessionId: runtime.getRemoteSessionId?.() || null
31031
+ });
31046
31032
  if (reason === "superseded") {
31047
31033
  pushNotice("Remote mode OFF \u2014 another session took over remote.", "warn");
31034
+ } else if (reason === "claim-failed") {
31035
+ pushNotice("Remote mode could not start. Check the channel connection and try again.", "error");
31036
+ } else if (reason === "release-failed") {
31037
+ pushNotice("Remote mode could not be stopped cleanly. The shared state will reconcile automatically.", "error");
31048
31038
  }
31049
31039
  });
31050
31040
  }
31041
+ set({
31042
+ remoteEnabled: runtime.isRemoteEnabled?.() === true,
31043
+ remoteSessionId: runtime.getRemoteSessionId?.() || null
31044
+ });
31051
31045
  const { patchToolCardResult, flushToolResults } = createToolCardResults({
31052
31046
  getState: () => state,
31053
31047
  set,