viveworker 0.8.10 → 0.9.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.
@@ -837,6 +837,7 @@ async function installMoltbookScout({ cliOptions, suppressOutput = false }) {
837
837
  // Use the auto script which runs scout (no LLM), then drafts via harness, then proposes.
838
838
  const envVars = [
839
839
  `SCOUT_HARNESS=${harness.kind}`,
840
+ harness.bin ? `SCOUT_HARNESS_BIN=${shellQuote(harness.bin)}` : "",
840
841
  submoltsFlag ? `SCOUT_FLAGS="${submoltsFlag.trim()}${maxDailyFlag}"` : maxDailyFlag ? `SCOUT_FLAGS="${maxDailyFlag.trim()}"` : "",
841
842
  ].filter(Boolean).join(" ");
842
843
  inner =
@@ -2778,6 +2779,10 @@ function escapeXml(value) {
2778
2779
  .replace(/"/gu, """);
2779
2780
  }
2780
2781
 
2782
+ function shellQuote(value) {
2783
+ return `'${String(value ?? "").replace(/'/gu, "'\\''")}'`;
2784
+ }
2785
+
2781
2786
  async function printPairingInfo(locale, config, { sectioned = false } = {}) {
2782
2787
  const baseUrl = String(config.NATIVE_APPROVAL_SERVER_PUBLIC_BASE_URL || "").trim();
2783
2788
  const pairCode = String(config.PAIRING_CODE || "").trim();
package/web/app.css CHANGED
@@ -391,6 +391,11 @@ pre code {
391
391
  color: #d8e7f1;
392
392
  }
393
393
 
394
+ .status-pill--in_progress {
395
+ background: rgba(83, 154, 214, 0.14);
396
+ color: #cfeaff;
397
+ }
398
+
394
399
  .count-chip {
395
400
  min-width: 2.2rem;
396
401
  background: rgba(255, 255, 255, 0.08);
@@ -2352,6 +2357,99 @@ pre code {
2352
2357
  inset 0 1px 0 rgba(255, 255, 255, 0.04);
2353
2358
  }
2354
2359
 
2360
+ .work-health {
2361
+ display: flex;
2362
+ align-items: flex-start;
2363
+ gap: 0.7rem;
2364
+ padding: 0.78rem 0;
2365
+ border-top: 1px solid rgba(230, 176, 84, 0.24);
2366
+ border-bottom: 1px solid rgba(230, 176, 84, 0.24);
2367
+ color: #f5dfb2;
2368
+ }
2369
+
2370
+ .work-health__icon,
2371
+ .work-caught-up__icon,
2372
+ .work-section__more-icon {
2373
+ width: 1rem;
2374
+ height: 1rem;
2375
+ display: inline-flex;
2376
+ flex: 0 0 auto;
2377
+ }
2378
+
2379
+ .work-health__copy {
2380
+ min-width: 0;
2381
+ display: grid;
2382
+ gap: 0.22rem;
2383
+ font-size: 0.84rem;
2384
+ line-height: 1.4;
2385
+ }
2386
+
2387
+ .work-caught-up {
2388
+ display: flex;
2389
+ align-items: center;
2390
+ gap: 0.55rem;
2391
+ color: #bfe9d6;
2392
+ font-size: 0.88rem;
2393
+ font-weight: 700;
2394
+ }
2395
+
2396
+ .work-sections {
2397
+ display: grid;
2398
+ gap: 1.25rem;
2399
+ }
2400
+
2401
+ .work-section {
2402
+ min-width: 0;
2403
+ display: grid;
2404
+ gap: 0.65rem;
2405
+ }
2406
+
2407
+ .work-section__header {
2408
+ display: flex;
2409
+ align-items: center;
2410
+ justify-content: space-between;
2411
+ gap: 0.75rem;
2412
+ min-width: 0;
2413
+ }
2414
+
2415
+ .work-section__title {
2416
+ margin: 0;
2417
+ color: var(--text);
2418
+ font-size: 0.9rem;
2419
+ line-height: 1.3;
2420
+ }
2421
+
2422
+ .work-section__count {
2423
+ min-width: 1.45rem;
2424
+ height: 1.45rem;
2425
+ display: inline-flex;
2426
+ align-items: center;
2427
+ justify-content: center;
2428
+ padding: 0 0.38rem;
2429
+ border-radius: var(--radius-pill);
2430
+ background: rgba(255, 255, 255, 0.08);
2431
+ color: var(--muted);
2432
+ font-size: 0.74rem;
2433
+ font-weight: 800;
2434
+ }
2435
+
2436
+ .work-section__more {
2437
+ width: 100%;
2438
+ min-height: 2.5rem;
2439
+ display: flex;
2440
+ align-items: center;
2441
+ justify-content: space-between;
2442
+ gap: 0.65rem;
2443
+ padding: 0.4rem 0;
2444
+ border: 0;
2445
+ border-top: 1px solid var(--line);
2446
+ background: transparent;
2447
+ color: #cfe2ef;
2448
+ font-size: 0.84rem;
2449
+ font-weight: 700;
2450
+ text-align: left;
2451
+ }
2452
+
2355
2453
  .timeline-thread-filter__row {
2356
2454
  display: grid;
2357
2455
  grid-template-columns: minmax(0, 1fr) auto;
@@ -5500,6 +5598,14 @@ button[aria-busy="true"]:not(.is-loading) {
5500
5598
  color: #81c784;
5501
5599
  background: rgba(129, 199, 132, 0.12);
5502
5600
  }
5601
+ .provider-badge--mcp {
5602
+ color: #d6c287;
5603
+ background: rgba(214, 194, 135, 0.1);
5604
+ }
5605
+ .provider-badge--viveworker {
5606
+ color: #9bc4ce;
5607
+ background: rgba(155, 196, 206, 0.1);
5608
+ }
5503
5609
 
5504
5610
  .provider-filter {
5505
5611
  width: 100%;
package/web/app.js CHANGED
@@ -15,7 +15,7 @@ const INITIAL_DETECTED_LOCALE = detectBrowserLocale();
15
15
  const TIMELINE_MESSAGE_KINDS = new Set(["user_message", "assistant_commentary", "assistant_final"]);
16
16
  const TIMELINE_OPERATIONAL_KINDS = new Set(["approval", "plan", "plan_ready", "choice"]);
17
17
  const EXTERNAL_TARGET_TABS = new Set(["inbox", "timeline", "diff"]);
18
- const EXTERNAL_TARGET_INBOX_SUBTABS = new Set(["pending", "completed"]);
18
+ const EXTERNAL_TARGET_INBOX_SUBTABS = new Set(["today", "pending", "completed"]);
19
19
  const THREAD_FILTER_INTERACTION_DEFER_MS = 8000;
20
20
  const SCROLLABLE_CONTENT_INTERACTION_DEFER_MS = 8000;
21
21
  const MAX_COMPLETION_REPLY_IMAGE_COUNT = 4;
@@ -27,7 +27,7 @@ const REMOTE_PAIRING_STATE_SCHEMA_VERSION = 2;
27
27
  const REMOTE_PAIRING_STATE_LEGACY_SCHEMA_VERSION = 1;
28
28
  const BOOT_SPLASH_SLOW_HINT_MS = 10000;
29
29
  const BOOT_SPLASH_REMOTE_SWITCHING_MIN_MS = 650;
30
- const BOOTSTRAP_REMOTE_TIMEOUT_MS = 12_000;
30
+ const BOOTSTRAP_REMOTE_TIMEOUT_MS = 50_000;
31
31
  const REMOTE_PAIRING_TOKEN_REFRESH_MS = 30 * 24 * 60 * 60 * 1000;
32
32
  const BOOT_TRACE_MAX_EVENTS = 90;
33
33
  const BOOT_TRACE_MAX_VALUE_LENGTH = 120;
@@ -52,6 +52,7 @@ const CLIENT_EVENT_REPORT_TIMEOUT_MS = 1_800;
52
52
  const TIMELINE_LIVE_REFRESH_TIMEOUT_MS = 2_000;
53
53
  const TIMELINE_LIVE_RETRY_MS = 5_000;
54
54
  const HAZBASE_ACTION_TIMEOUT_MS = 30_000;
55
+ const TODAY_COMPLETED_ITEM_LIMIT = 6;
55
56
  const timelineImageObjectUrlCache = new Map();
56
57
 
57
58
  function wait(ms) {
@@ -92,7 +93,7 @@ const state = {
92
93
  currentDetailLoading: false,
93
94
  detailLoadingItem: null,
94
95
  detailOpen: false,
95
- inboxSubtab: "pending",
96
+ inboxSubtab: "today",
96
97
  timelineThreadFilter: "all",
97
98
  timelineKindFilter: "all",
98
99
  providerFilter: "all",
@@ -317,6 +318,7 @@ function flushBootTrace(reason, extra = {}) {
317
318
  totalMs: Math.max(0, Math.round(bootTraceNow() - bootTraceStartPerfMs)),
318
319
  remoteRouteSeen: bootSplashRemoteRouteSeen,
319
320
  finalStage: bootSplashStatusStage,
321
+ origin: window.location.origin,
320
322
  userAgent: navigator.userAgent || "",
321
323
  events: bootTraceEvents,
322
324
  ...extra,
@@ -502,7 +504,6 @@ function handleBootRoutingStatus(event) {
502
504
  case "lan-checking":
503
505
  setBootSplashStatus("boot.status.checkingLan", BOOT_SPLASH_STAGE.checking);
504
506
  break;
505
- case "lan-failed":
506
507
  case "remote-switching":
507
508
  showBootRemoteSwitchingStatus();
508
509
  break;
@@ -980,7 +981,7 @@ async function refreshSession() {
980
981
  }
981
982
 
982
983
  // One-shot boot fetch: hits `/api/bootstrap` which bundles session,
983
- // inbox (pending + completed), timeline, and devices into a single
984
+ // inbox work-item buckets, timeline, and devices into a single
984
985
  // HTTPS round-trip. Saves 3 additional TLS handshakes versus calling
985
986
  // the four endpoints in parallel, which is the dominant boot cost on
986
987
  // iOS PWAs where connection reuse is aggressive. Leaves the diff and
@@ -990,6 +991,11 @@ async function refreshBootstrap() {
990
991
  const bootstrap = await apiGet("/api/bootstrap", {
991
992
  timeoutMs: BOOTSTRAP_REMOTE_TIMEOUT_MS,
992
993
  preferRelayError: true,
994
+ raceLanRecoveryWithRelay: true,
995
+ confirmLanReachabilityBeforeRelay: true,
996
+ retryLanAfterReachabilityProbe: true,
997
+ lanReachabilityProbeTimeoutMs: 1_500,
998
+ lanRecoveryTimeoutMs: 7_000,
993
999
  });
994
1000
  recordBootTraceEvent("bootstrap-response", { url: "/api/bootstrap" });
995
1001
  state.session = bootstrap?.session || null;
@@ -1007,8 +1013,10 @@ async function refreshBootstrap() {
1007
1013
  const previousDiff = Array.isArray(state.inbox?.diff) ? state.inbox.diff : [];
1008
1014
  state.inbox = {
1009
1015
  pending: Array.isArray(fastInbox.pending) ? fastInbox.pending : [],
1016
+ inProgress: Array.isArray(fastInbox.inProgress) ? fastInbox.inProgress : [],
1010
1017
  completed: Array.isArray(fastInbox.completed) ? fastInbox.completed : [],
1011
1018
  diff: previousDiff,
1019
+ work: fastInbox?.work && typeof fastInbox.work === "object" ? fastInbox.work : null,
1012
1020
  };
1013
1021
  syncDiffThreadFilter();
1014
1022
  syncCompletedThreadFilter();
@@ -1279,7 +1287,7 @@ async function getClientPushState() {
1279
1287
 
1280
1288
  async function refreshInbox(opts = {}) {
1281
1289
  const fast = await apiGet("/api/inbox", opts);
1282
- // `/api/inbox` now returns only `{ pending, completed }`. The `diff`
1290
+ // `/api/inbox` returns fast work-item buckets without diff data. The `diff`
1283
1291
  // half lives at `/api/inbox/diff` because it spawns `git` subprocesses
1284
1292
  // server-side and was blocking first paint of the completed/pending
1285
1293
  // lists. Preserve whatever diff entries we already have in memory so
@@ -1287,8 +1295,10 @@ async function refreshInbox(opts = {}) {
1287
1295
  const previousDiff = Array.isArray(state.inbox?.diff) ? state.inbox.diff : [];
1288
1296
  state.inbox = {
1289
1297
  pending: Array.isArray(fast?.pending) ? fast.pending : [],
1298
+ inProgress: Array.isArray(fast?.inProgress) ? fast.inProgress : [],
1290
1299
  completed: Array.isArray(fast?.completed) ? fast.completed : [],
1291
1300
  diff: previousDiff,
1301
+ work: fast?.work && typeof fast.work === "object" ? fast.work : null,
1292
1302
  };
1293
1303
  syncDiffThreadFilter();
1294
1304
  syncCompletedThreadFilter();
@@ -1298,11 +1308,13 @@ async function refreshInbox(opts = {}) {
1298
1308
  async function refreshInboxDiff() {
1299
1309
  try {
1300
1310
  const response = await apiGet("/api/inbox/diff");
1301
- const previous = state.inbox || { pending: [], completed: [] };
1311
+ const previous = state.inbox || { pending: [], inProgress: [], completed: [] };
1302
1312
  state.inbox = {
1303
1313
  pending: Array.isArray(previous.pending) ? previous.pending : [],
1314
+ inProgress: Array.isArray(previous.inProgress) ? previous.inProgress : [],
1304
1315
  completed: Array.isArray(previous.completed) ? previous.completed : [],
1305
1316
  diff: Array.isArray(response?.diff) ? response.diff : [],
1317
+ work: previous.work && typeof previous.work === "object" ? previous.work : null,
1306
1318
  };
1307
1319
  syncDiffThreadFilter();
1308
1320
  syncInboxSubtab();
@@ -1685,6 +1697,7 @@ function allInboxEntries() {
1685
1697
  }
1686
1698
  return [
1687
1699
  ...(Array.isArray(state.inbox.pending) ? state.inbox.pending.map((item) => ({ item, status: "pending" })) : []),
1700
+ ...(Array.isArray(state.inbox.inProgress) ? state.inbox.inProgress.map((item) => ({ item, status: "in_progress" })) : []),
1688
1701
  ...(Array.isArray(state.inbox.diff) ? state.inbox.diff.map((item) => ({ item, status: "diff" })) : []),
1689
1702
  ...(Array.isArray(state.inbox.completed) ? state.inbox.completed.map((item) => ({ item, status: "completed" })) : []),
1690
1703
  ];
@@ -1780,6 +1793,9 @@ function listInboxEntries() {
1780
1793
  if (!state.inbox) {
1781
1794
  return [];
1782
1795
  }
1796
+ if (state.inboxSubtab === "today") {
1797
+ return todayInboxEntries();
1798
+ }
1783
1799
  if (state.inboxSubtab === "completed") {
1784
1800
  return filteredCompletedEntries().map((item) => ({ item, status: "completed" }));
1785
1801
  }
@@ -1788,6 +1804,35 @@ function listInboxEntries() {
1788
1804
  .map((item) => ({ item, status: "pending" }));
1789
1805
  }
1790
1806
 
1807
+ function isTodayTimestamp(value, now = new Date()) {
1808
+ const timestamp = Number(value) || 0;
1809
+ if (!timestamp) {
1810
+ return false;
1811
+ }
1812
+ const date = new Date(timestamp);
1813
+ return date.getFullYear() === now.getFullYear() &&
1814
+ date.getMonth() === now.getMonth() &&
1815
+ date.getDate() === now.getDate();
1816
+ }
1817
+
1818
+ function todayInboxGroups() {
1819
+ const pending = (Array.isArray(state.inbox?.pending) ? state.inbox.pending : [])
1820
+ .filter((item) => entryMatchesProviderFilter(item))
1821
+ .map((item) => ({ item, status: "pending" }));
1822
+ const inProgress = (Array.isArray(state.inbox?.inProgress) ? state.inbox.inProgress : [])
1823
+ .filter((item) => entryMatchesProviderFilter(item))
1824
+ .map((item) => ({ item, status: "in_progress" }));
1825
+ const completed = (Array.isArray(state.inbox?.completed) ? state.inbox.completed : [])
1826
+ .filter((item) => entryMatchesProviderFilter(item) && isTodayTimestamp(item?.createdAtMs))
1827
+ .map((item) => ({ item, status: "completed" }));
1828
+ return { pending, inProgress, completed };
1829
+ }
1830
+
1831
+ function todayInboxEntries() {
1832
+ const groups = todayInboxGroups();
1833
+ return [...groups.pending, ...groups.inProgress, ...groups.completed];
1834
+ }
1835
+
1791
1836
  function normalizeProviderClient(value) {
1792
1837
  const normalized = String(value || "").toLowerCase();
1793
1838
  if (normalized === "claude") return "claude";
@@ -2069,11 +2114,12 @@ function syncCompletedThreadFilter() {
2069
2114
  }
2070
2115
 
2071
2116
  function syncInboxSubtab() {
2072
- if (state.inboxSubtab === "completed") {
2073
- state.inboxSubtab = "completed";
2074
- return;
2075
- }
2076
- state.inboxSubtab = "pending";
2117
+ state.inboxSubtab = normalizeInboxSubtab(state.inboxSubtab);
2118
+ }
2119
+
2120
+ function normalizeInboxSubtab(value, fallback = "today") {
2121
+ const normalized = normalizeClientText(value || "");
2122
+ return EXTERNAL_TARGET_INBOX_SUBTABS.has(normalized) ? normalized : fallback;
2077
2123
  }
2078
2124
 
2079
2125
  function renderPair() {
@@ -3305,11 +3351,13 @@ function renderInboxPanel({ entries, desktop }) {
3305
3351
  const subtabControls = renderInboxSubtabs();
3306
3352
  const providerFilterHtml = renderProviderFilter();
3307
3353
  const threadFilterHtml = state.inboxSubtab === "completed" ? renderCompletedThreadDropdown() : "";
3308
- const bodyHtml = entries.length
3309
- ? `<div class="card-list ${desktop ? "card-list--desktop" : ""}">
3310
- ${entries.map((entry) => renderItemCard(entry, "inbox", desktop)).join("")}
3311
- </div>`
3312
- : renderInboxEmptyState();
3354
+ const bodyHtml = state.inboxSubtab === "today"
3355
+ ? renderTodayInboxBody({ desktop })
3356
+ : entries.length
3357
+ ? `<div class="card-list ${desktop ? "card-list--desktop" : ""}">
3358
+ ${entries.map((entry) => renderItemCard(entry, "inbox", desktop)).join("")}
3359
+ </div>`
3360
+ : renderInboxEmptyState();
3313
3361
 
3314
3362
  if (!desktop) {
3315
3363
  return `
@@ -3378,17 +3426,94 @@ function renderInboxSubtabs() {
3378
3426
 
3379
3427
  function inboxSubtabOptions() {
3380
3428
  return [
3429
+ { id: "today", label: L("inbox.subtab.today") },
3381
3430
  { id: "pending", label: L("inbox.subtab.pending") },
3382
3431
  { id: "completed", label: L("inbox.subtab.completed") },
3383
3432
  ];
3384
3433
  }
3385
3434
 
3435
+ function renderTodayInboxBody({ desktop }) {
3436
+ const groups = todayInboxGroups();
3437
+ const visibleCompleted = groups.completed.slice(0, TODAY_COMPLETED_ITEM_LIMIT);
3438
+ const hiddenCompletedCount = Math.max(0, groups.completed.length - visibleCompleted.length);
3439
+ const healthNotice = renderWorkHealthNotice();
3440
+ const hasAnyItems = groups.pending.length > 0 || groups.inProgress.length > 0 || groups.completed.length > 0;
3441
+ if (!hasAnyItems) {
3442
+ return `${healthNotice}${renderInboxEmptyState()}`;
3443
+ }
3444
+
3445
+ return `
3446
+ ${healthNotice}
3447
+ ${groups.pending.length === 0 ? `
3448
+ <div class="work-caught-up" role="status">
3449
+ <span class="work-caught-up__icon" aria-hidden="true">${renderIcon("completed")}</span>
3450
+ <span>${escapeHtml(L("work.today.caughtUp"))}</span>
3451
+ </div>
3452
+ ` : ""}
3453
+ <div class="work-sections">
3454
+ ${renderTodayWorkSection("needs-action", L("work.section.needsAction"), groups.pending, desktop)}
3455
+ ${renderTodayWorkSection("in-progress", L("work.section.inProgress"), groups.inProgress, desktop)}
3456
+ ${renderTodayWorkSection("completed", L("work.section.completedToday"), visibleCompleted, desktop, {
3457
+ hiddenCount: hiddenCompletedCount,
3458
+ })}
3459
+ </div>
3460
+ `;
3461
+ }
3462
+
3463
+ function renderTodayWorkSection(id, label, entries, desktop, { hiddenCount = 0 } = {}) {
3464
+ if (!entries.length) {
3465
+ return "";
3466
+ }
3467
+ return `
3468
+ <section class="work-section work-section--${escapeHtml(id)}" aria-labelledby="work-section-${escapeHtml(id)}">
3469
+ <div class="work-section__header">
3470
+ <h3 class="work-section__title" id="work-section-${escapeHtml(id)}">${escapeHtml(label)}</h3>
3471
+ <span class="work-section__count">${entries.length + hiddenCount}</span>
3472
+ </div>
3473
+ <div class="card-list ${desktop ? "card-list--desktop" : ""}">
3474
+ ${entries.map((entry) => renderItemCard(entry, "inbox", desktop)).join("")}
3475
+ </div>
3476
+ ${hiddenCount > 0 ? `
3477
+ <button class="work-section__more" type="button" data-inbox-subtab="completed">
3478
+ <span>${escapeHtml(L("work.today.viewAllCompleted", { count: hiddenCount }))}</span>
3479
+ <span class="work-section__more-icon" aria-hidden="true">${renderIcon("chevron-right")}</span>
3480
+ </button>
3481
+ ` : ""}
3482
+ </section>
3483
+ `;
3484
+ }
3485
+
3486
+ function renderWorkHealthNotice() {
3487
+ const attention = Array.isArray(state.inbox?.work?.health?.attention)
3488
+ ? state.inbox.work.health.attention
3489
+ : [];
3490
+ if (!attention.length) {
3491
+ return "";
3492
+ }
3493
+ const messages = attention.map((issue) => {
3494
+ if (issue?.code === "connection-required") {
3495
+ return L("work.health.connectionRequired", { provider: providerDisplayName(issue.source) });
3496
+ }
3497
+ return L("work.health.degraded");
3498
+ });
3499
+ return `
3500
+ <div class="work-health" role="status">
3501
+ <span class="work-health__icon" aria-hidden="true">${renderIcon("lock")}</span>
3502
+ <div class="work-health__copy">
3503
+ <strong>${escapeHtml(L("work.health.title"))}</strong>
3504
+ ${messages.map((message) => `<span>${escapeHtml(message)}</span>`).join("")}
3505
+ </div>
3506
+ </div>
3507
+ `;
3508
+ }
3509
+
3386
3510
  function renderInboxEmptyState() {
3387
3511
  const isCompletedView = state.inboxSubtab === "completed";
3512
+ const isTodayView = state.inboxSubtab === "today";
3388
3513
  return `
3389
3514
  <div class="empty-state">
3390
- <p class="empty-state__title">${escapeHtml(L(isCompletedView ? "inbox.subtab.completed" : "inbox.subtab.pending"))}</p>
3391
- <p class="muted">${escapeHtml(L(isCompletedView ? "empty.completed" : "empty.pending"))}</p>
3515
+ <p class="empty-state__title">${escapeHtml(L(isCompletedView ? "inbox.subtab.completed" : isTodayView ? "work.today.emptyTitle" : "inbox.subtab.pending"))}</p>
3516
+ <p class="muted">${escapeHtml(L(isCompletedView ? "empty.completed" : isTodayView ? "work.today.emptyCopy" : "empty.pending"))}</p>
3392
3517
  </div>
3393
3518
  `;
3394
3519
  }
@@ -3404,6 +3529,12 @@ function providerBadgeMeta(provider) {
3404
3529
  if (normalized === "a2a") {
3405
3530
  return { id: "a2a", label: "A2A", glyph: "A" };
3406
3531
  }
3532
+ if (normalized === "mcp") {
3533
+ return { id: "mcp", label: "MCP", glyph: "M" };
3534
+ }
3535
+ if (normalized === "viveworker") {
3536
+ return { id: "viveworker", label: L("common.appName"), glyph: "V" };
3537
+ }
3407
3538
  return { id: "codex", label: L("common.codex"), glyph: "X" };
3408
3539
  }
3409
3540
 
@@ -3449,7 +3580,7 @@ function renderProviderFilter() {
3449
3580
  `;
3450
3581
  }
3451
3582
 
3452
- const COMPLETED_CARD_KINDS = new Set(["assistant_final", "approval", "moltbook_reply", "moltbook_draft", "a2a_task_result", "thread_share"]);
3583
+ const COMPLETED_CARD_KINDS = new Set(["completion", "assistant_final", "approval", "moltbook_reply", "moltbook_draft", "a2a_task_result", "thread_share"]);
3453
3584
 
3454
3585
  function renderItemCard(entry, sourceTab, desktop) {
3455
3586
  if (entry.status === "completed" && COMPLETED_CARD_KINDS.has(entry.item.kind)) {
@@ -3457,9 +3588,13 @@ function renderItemCard(entry, sourceTab, desktop) {
3457
3588
  }
3458
3589
  const kindInfo = kindMeta(entry.item.kind, entry.item);
3459
3590
  const cardTitle = cardTitleForEntry(entry);
3460
- const statusText = entry.status === "completed" ? L("common.completed") : L("common.actionNeeded");
3591
+ const statusText = entry.status === "completed"
3592
+ ? L("common.completed")
3593
+ : entry.status === "in_progress"
3594
+ ? L("common.inProgress")
3595
+ : L("common.actionNeeded");
3461
3596
  const intentText = itemIntentText(entry.item.kind, entry.status, entry.item.provider);
3462
- const showCompletedTimestamp = entry.status === "completed" && sourceTab === "completed";
3597
+ const showCompletedTimestamp = entry.status === "completed";
3463
3598
  const timestampLabel = showCompletedTimestamp ? formatTimelineTimestamp(entry.item.createdAtMs) : "";
3464
3599
  return `
3465
3600
  <button
@@ -3467,6 +3602,7 @@ function renderItemCard(entry, sourceTab, desktop) {
3467
3602
  data-open-item-kind="${escapeHtml(entry.item.kind)}"
3468
3603
  data-open-item-token="${escapeHtml(entry.item.token)}"
3469
3604
  data-source-tab="${escapeHtml(sourceTab)}"
3605
+ data-source-subtab="${escapeHtml(sourceTab === "inbox" ? state.inboxSubtab : "")}"
3470
3606
  >
3471
3607
  <div class="item-card__header">
3472
3608
  <div class="item-card__meta">
@@ -3491,7 +3627,7 @@ function renderItemCard(entry, sourceTab, desktop) {
3491
3627
  </p>
3492
3628
  <p class="item-card__summary">${escapeHtml(entry.item.summary || fallbackSummaryForKind(entry.item.kind, entry.status, entry.item.provider))}</p>
3493
3629
  ${
3494
- !desktop && sourceTab === "inbox"
3630
+ !desktop && sourceTab === "inbox" && state.inboxSubtab !== "today"
3495
3631
  ? `<p class="item-card__status-note">${escapeHtml(statusText)}</p>`
3496
3632
  : ""
3497
3633
  }
@@ -3543,7 +3679,7 @@ function renderCompletedCompletionCard(entry, sourceTab) {
3543
3679
  data-open-item-kind="${escapeHtml(item.kind)}"
3544
3680
  data-open-item-token="${escapeHtml(item.token)}"
3545
3681
  data-source-tab="${escapeHtml(sourceTab)}"
3546
- data-source-subtab="completed"
3682
+ data-source-subtab="${escapeHtml(sourceTab === "inbox" ? state.inboxSubtab : "completed")}"
3547
3683
  >
3548
3684
  <div class="item-card__header">
3549
3685
  <div class="item-card__meta">
@@ -9237,7 +9373,7 @@ function bindShellInteractions() {
9237
9373
 
9238
9374
  for (const button of document.querySelectorAll("[data-inbox-subtab]")) {
9239
9375
  button.addEventListener("click", async () => {
9240
- const nextSubtab = button.dataset.inboxSubtab === "completed" ? "completed" : "pending";
9376
+ const nextSubtab = normalizeInboxSubtab(button.dataset.inboxSubtab);
9241
9377
  if (nextSubtab === state.inboxSubtab) {
9242
9378
  return;
9243
9379
  }
@@ -11094,7 +11230,7 @@ function bindPartialListSurfaceInteractions(listSurface) {
11094
11230
 
11095
11231
  const subtabButton = target?.closest("[data-inbox-subtab]");
11096
11232
  if (subtabButton) {
11097
- const nextSubtab = subtabButton.dataset.inboxSubtab === "completed" ? "completed" : "pending";
11233
+ const nextSubtab = normalizeInboxSubtab(subtabButton.dataset.inboxSubtab);
11098
11234
  if (nextSubtab === state.inboxSubtab) {
11099
11235
  return;
11100
11236
  }
@@ -11347,8 +11483,12 @@ function tabForItemKind(kind, fallback) {
11347
11483
  }
11348
11484
 
11349
11485
  function inboxSubtabForItemKind(kind, sourceSubtab = "") {
11350
- if (normalizeClientText(sourceSubtab || "") === "completed") {
11351
- return "completed";
11486
+ const normalizedSourceSubtab = normalizeClientText(sourceSubtab || "");
11487
+ if (EXTERNAL_TARGET_INBOX_SUBTABS.has(normalizedSourceSubtab)) {
11488
+ return normalizedSourceSubtab;
11489
+ }
11490
+ if (kind === "activity_status") {
11491
+ return "today";
11352
11492
  }
11353
11493
  const completedKinds = new Set(["completion", "assistant_final", "plan_ready", "moltbook_reply", "thread_share", "a2a_task_result"]);
11354
11494
  return completedKinds.has(kind) ? "completed" : "pending";
@@ -11428,6 +11568,9 @@ function renderTypePillContent(kindInfo) {
11428
11568
 
11429
11569
  function itemIntentText(kind, status = "pending", provider) {
11430
11570
  const vars = { provider: providerDisplayName(provider) };
11571
+ if (status === "in_progress" || kind === "activity_status") {
11572
+ return L("intent.inProgress", vars);
11573
+ }
11431
11574
  if (kind === "diff_thread") {
11432
11575
  return L("intent.diffThread");
11433
11576
  }
@@ -11461,6 +11604,12 @@ function itemIntentText(kind, status = "pending", provider) {
11461
11604
  return L("intent.choice", vars);
11462
11605
  case "completion":
11463
11606
  return L("intent.completed");
11607
+ case "moltbook_draft":
11608
+ return L("intent.draftReview");
11609
+ case "a2a_task":
11610
+ return L("intent.delegationReview");
11611
+ case "thread_share":
11612
+ return L("intent.handoffReview");
11464
11613
  default:
11465
11614
  return L("summary.default");
11466
11615
  }
@@ -11468,6 +11617,9 @@ function itemIntentText(kind, status = "pending", provider) {
11468
11617
 
11469
11618
  function detailIntentText(detail) {
11470
11619
  const provider = detail?.provider;
11620
+ if (detail.kind === "activity_status" || (detail.kind === "a2a_task" && ["working", "input-required"].includes(normalizeClientText(detail.taskStatus)))) {
11621
+ return itemIntentText(detail.kind, "in_progress", provider);
11622
+ }
11471
11623
  if (detail.kind === "diff_thread") {
11472
11624
  return itemIntentText(detail.kind, "diff", provider);
11473
11625
  }
@@ -11542,6 +11694,9 @@ function detailDisplayTitle(detail) {
11542
11694
 
11543
11695
  function fallbackSummaryForKind(kind, status, provider) {
11544
11696
  const vars = { provider: providerDisplayName(provider) };
11697
+ if (status === "in_progress" || kind === "activity_status") {
11698
+ return L("summary.inProgress", vars);
11699
+ }
11545
11700
  if (status === "completed") {
11546
11701
  return L("summary.completed");
11547
11702
  }
package/web/build-id.js CHANGED
@@ -1 +1 @@
1
- export const APP_BUILD_ID = "20260525-wallet-network-count";
1
+ export const APP_BUILD_ID = "20260713-lan-recovery-v2";