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.
- package/README.md +3 -2
- package/package.json +1 -1
- package/scripts/lib/immutable-json-patch.mjs +142 -0
- package/scripts/lib/ipc-frame-worker.mjs +30 -0
- package/scripts/lib/ipc-message-projector.mjs +221 -0
- package/scripts/lib/length-prefixed-frame-decoder.mjs +128 -0
- package/scripts/lib/remote-pairing/bridge-relay-client.mjs +24 -0
- package/scripts/lib/remote-pairing/orchestrator.mjs +10 -0
- package/scripts/lib/work-items.mjs +179 -0
- package/scripts/moltbook-api.mjs +27 -0
- package/scripts/moltbook-cli.mjs +12 -4
- package/scripts/moltbook-scout-auto.sh +105 -37
- package/scripts/viveworker-bridge.mjs +472 -206
- package/scripts/viveworker.mjs +5 -0
- package/web/app.css +106 -0
- package/web/app.js +183 -28
- package/web/build-id.js +1 -1
- package/web/i18n.js +44 -10
- package/web/remote-pairing/api-router.js +356 -11
- package/web/remote-pairing/transport.js +7 -1
package/scripts/viveworker.mjs
CHANGED
|
@@ -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 =
|
|
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: "
|
|
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
|
|
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`
|
|
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
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
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 =
|
|
3309
|
-
?
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
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"
|
|
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"
|
|
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
|
|
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
|
|
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
|
-
|
|
11351
|
-
|
|
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 = "
|
|
1
|
+
export const APP_BUILD_ID = "20260713-lan-recovery-v2";
|